Skip to content

Commit

Permalink
Merge pull request #3303 from pangratz/reference_unification
Browse files Browse the repository at this point in the history
Implement RFC 57 - Reference Unification
  • Loading branch information
bmac committed Dec 15, 2015
2 parents 9ab3695 + 4a30273 commit 5826230
Show file tree
Hide file tree
Showing 16 changed files with 1,917 additions and 11 deletions.
2 changes: 2 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ entry in `config/features.json`.

## Feature Flags

- `ds-references`

Adds references as described in [RFC 57](https://github.com/emberjs/rfcs/pull/57)
26 changes: 26 additions & 0 deletions addon/-private/system/model/internal-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import {
getOwner
} from 'ember-data/-private/utils';

import {
RecordReference,
BelongsToReference,
HasManyReference
} from "ember-data/-private/system/references";

var Promise = Ember.RSVP.Promise;
var get = Ember.get;
var set = Ember.set;
Expand Down Expand Up @@ -67,6 +73,8 @@ export default function InternalModel(type, id, store, _, data) {
this._relationships = new Relationships(this);
this._recordArrays = undefined;
this.currentState = RootState.empty;
this.recordReference = new RecordReference(store, this);
this.references = {};
this.isReloading = false;
this.isError = false;
this.error = null;
Expand Down Expand Up @@ -594,6 +602,24 @@ InternalModel.prototype = {
return value;
},

referenceFor: function(type, name) {
var reference = this.references[name];

if (!reference) {
var relationship = this._relationships.get(name);

if (type === "belongsTo") {
reference = new BelongsToReference(this.store, this, relationship);
} else if (type === "hasMany") {
reference = new HasManyReference(this.store, this, relationship);
}

this.references[name] = reference;
}

return reference;
},


/**
@method updateRecordArrays
Expand Down
125 changes: 125 additions & 0 deletions addon/-private/system/model/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Ember from 'ember';
import { assert, deprecate } from "ember-data/-private/debug";
import { PromiseObject } from "ember-data/-private/system/promise-proxies";
import Errors from "ember-data/-private/system/model/errors";
import isEnabled from 'ember-data/-private/features';

/**
@module ember-data
Expand Down Expand Up @@ -847,4 +848,128 @@ if (Ember.setOwner) {
});
}

if (isEnabled("ds-references")) {

Model.reopen({

/**
Get the reference for the specified belongsTo relationship.
Example
```javascript
// models/blog.js
export default DS.Model.extend({
user: DS.belongsTo({ async: true })
});
store.push({
type: 'blog',
id: 1,
relationships: {
user: { type: 'user', id: 1 }
}
});
var userRef = blog.belongsTo('user');
// check if the user relationship is loaded
var isLoaded = userRef.value() !== null;
// get the record of the reference (null if not yet available)
var user = userRef.value();
// get the identifier of the reference
if (userRef.remoteType() === "id") {
var id = userRef.id();
} else if (userRef.remoteType() === "link") {
var link = userRef.link();
}
// load user (via store.find or store.findBelongsTo)
userRef.load().then(...)
// or trigger a reload
userRef.reload().then(...)
// provide data for reference
userRef.push({
type: 'user',
id: 1,
attributes: {
username: "@user"
}
}).then(function(user) {
userRef.value() === user;
});
```
@method belongsTo
@param {String} name of the relationship
@return {BelongsToReference} reference for this relationship
*/
belongsTo: function(name) {
return this._internalModel.referenceFor('belongsTo', name);
},

/**
Get the reference for the specified hasMany relationship.
Example
```javascript
// models/blog.js
export default DS.Model.extend({
comments: DS.hasMany({ async: true })
});
store.push({
type: 'blog',
id: 1,
relationships: {
comments: {
data: [
{ type: 'comment', id: 1 },
{ type: 'comment', id: 2 }
]
}
}
});
var commentsRef = blog.hasMany('comments');
// check if the comments are loaded already
var isLoaded = commentsRef.value() !== null;
// get the records of the reference (null if not yet available)
var comments = commentsRef.value();
// get the identifier of the reference
if (commentsRef.remoteType() === "ids") {
var ids = commentsRef.ids();
} else if (commentsRef.remoteType() === "link") {
var link = commentsRef.link();
}
// load comments (via store.findMany or store.findHasMany)
commentsRef.load().then(...)
// or trigger a reload
commentsRef.reload().then(...)
// provide data for reference
commentsRef.push([{ type: 'comment', id: 1 }, { type: 'comment', id: 2 }]).then(function(comments) {
commentsRef.value() === comments;
});
```
@method hasMany
@param {String} name of the relationship
@return {HasManyReference} reference for this relationship
*/
hasMany: function(name) {
return this._internalModel.referenceFor('hasMany', name);
}
});

}

export default Model;
5 changes: 5 additions & 0 deletions addon/-private/system/references.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import RecordReference from './references/record';
import BelongsToReference from './references/belongs-to';
import HasManyReference from './references/has-many';

export { RecordReference, BelongsToReference, HasManyReference };
82 changes: 82 additions & 0 deletions addon/-private/system/references/belongs-to.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import Model from 'ember-data/-private/system/model';
import Ember from 'ember';
import Reference from './reference';

import { assertPolymorphicType } from "ember-data/-private/utils";

var BelongsToReference = function(store, parentInternalModel, belongsToRelationship) {
this._super$constructor(store, parentInternalModel);
this.belongsToRelationship = belongsToRelationship;
this.type = belongsToRelationship.relationshipMeta.type;
this.parent = parentInternalModel.recordReference;

// TODO inverse
};

BelongsToReference.prototype = Object.create(Reference.prototype);
BelongsToReference.prototype.constructor = BelongsToReference;
BelongsToReference.prototype._super$constructor = Reference;

BelongsToReference.prototype.remoteType = function() {
if (this.belongsToRelationship.link) {
return "link";
}

return "id";
};

BelongsToReference.prototype.id = function() {
var inverseRecord = this.belongsToRelationship.inverseRecord;
return inverseRecord && inverseRecord.id;
};

BelongsToReference.prototype.link = function() {
return this.belongsToRelationship.link;
};

BelongsToReference.prototype.meta = function() {
return this.belongsToRelationship.meta;
};

BelongsToReference.prototype.push = function(objectOrPromise) {
return Ember.RSVP.resolve(objectOrPromise).then((data) => {
var record;

if (data instanceof Model) {
record = data;
} else {
record = this.store.push(data);
}

assertPolymorphicType(this.internalModel, this.belongsToRelationship.relationshipMeta, record._internalModel);

this.belongsToRelationship.setCanonicalRecord(record._internalModel);

return record;
});
};

BelongsToReference.prototype.value = function() {
var inverseRecord = this.belongsToRelationship.inverseRecord;
return inverseRecord && inverseRecord.record;
};

BelongsToReference.prototype.load = function() {
if (this.remoteType() === "id") {
return this.belongsToRelationship.getRecord();
}

if (this.remoteType() === "link") {
return this.belongsToRelationship.findLink().then((internalModel) => {
return this.value();
});
}
};

BelongsToReference.prototype.reload = function() {
return this.belongsToRelationship.reload().then((internalModel) => {
return this.value();
});
};

export default BelongsToReference;
99 changes: 99 additions & 0 deletions addon/-private/system/references/has-many.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import Ember from 'ember';
import Reference from './reference';

const get = Ember.get;

var HasManyReference = function(store, parentInternalModel, hasManyRelationship) {
this._super$constructor(store, parentInternalModel);
this.hasManyRelationship = hasManyRelationship;
this.type = hasManyRelationship.relationshipMeta.type;
this.parent = parentInternalModel.recordReference;

// TODO inverse
};

HasManyReference.prototype = Object.create(Reference.prototype);
HasManyReference.prototype.constructor = HasManyReference;
HasManyReference.prototype._super$constructor = Reference;

HasManyReference.prototype.remoteType = function() {
if (this.hasManyRelationship.link) {
return "link";
}

return "ids";
};

HasManyReference.prototype.link = function() {
return this.hasManyRelationship.link;
};

HasManyReference.prototype.ids = function() {
var members = this.hasManyRelationship.members;
var ids = members.toArray().map(function(internalModel) {
return internalModel.id;
});

return ids;
};

HasManyReference.prototype.meta = function() {
return this.hasManyRelationship.manyArray.meta;
};

HasManyReference.prototype.push = function(objectOrPromise) {
return Ember.RSVP.resolve(objectOrPromise).then((payload) => {
var array = payload;
if (typeof payload === "object" && payload.data) {
array = payload.data;
}

var internalModels = array.map((obj) => {
var record = this.store.push(obj);
return record._internalModel;
});

// TODO add assertion for polymorphic type

this.hasManyRelationship.computeChanges(internalModels);

return this.hasManyRelationship.manyArray;
});
};

HasManyReference.prototype._isLoaded = function() {
var hasData = get(this.hasManyRelationship, 'hasData');
if (!hasData) {
return false;
}

var members = this.hasManyRelationship.members.toArray();
var isEveryLoaded = members.every(function(internalModel) {
return internalModel.isLoaded() === true;
});

return isEveryLoaded;
};

HasManyReference.prototype.value = function() {
if (this._isLoaded()) {
return this.hasManyRelationship.manyArray;
}

return null;
};

HasManyReference.prototype.load = function() {
if (!this._isLoaded()) {
return this.hasManyRelationship.getRecords();
}

var manyArray = this.hasManyRelationship.manyArray;
return Ember.RSVP.resolve(manyArray);
};

HasManyReference.prototype.reload = function() {
return this.hasManyRelationship.reload();
};

export default HasManyReference;
Loading

0 comments on commit 5826230

Please sign in to comment.