Skip to content

Commit

Permalink
Initial implementation of the Snapshot API
Browse files Browse the repository at this point in the history
  • Loading branch information
wecc committed Jan 5, 2015
1 parent 542fd25 commit c293f30
Show file tree
Hide file tree
Showing 5 changed files with 581 additions and 1 deletion.
3 changes: 3 additions & 0 deletions packages/ember-data/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
RootState,
attr
} from "ember-data/system/model";
import Snapshot from "ember-data/system/snapshot";
import {
InvalidError,
Adapter
Expand Down Expand Up @@ -83,6 +84,8 @@ DS.RootState = RootState;
DS.attr = attr;
DS.Errors = Errors;

DS.Snapshot = Snapshot;

DS.Adapter = Adapter;
DS.InvalidError = InvalidError;

Expand Down
9 changes: 9 additions & 0 deletions packages/ember-data/lib/system/model/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { PromiseObject } from "ember-data/system/promise_proxies";
import merge from "ember-data/system/merge";
import JSONSerializer from "ember-data/serializers/json_serializer";
import createRelationshipFor from "ember-data/system/relationships/state/create";
import Snapshot from "ember-data/system/snapshot";

/**
@module ember-data
Expand Down Expand Up @@ -944,6 +945,14 @@ var Model = Ember.Object.extend(Ember.Evented, {
this._notifyProperties(dirtyKeys);
},

/**
@method _createSnapshot
@private
*/
_createSnapshot: function() {
return new Snapshot(this);
},

toStringExtension: function() {
return get(this, 'id');
},
Expand Down
283 changes: 283 additions & 0 deletions packages/ember-data/lib/system/snapshot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
/**
@module ember-data
*/

var get = Ember.get;

/**
@class Snapshot
@namespace DS
@private
*/
function Snapshot(record) {
this._attributes = Ember.create(null);
this._belongsToRelationships = Ember.create(null);
this._hasManyRelationships = Ember.create(null);

this.id = get(record, 'id');

record.eachAttribute(function(keyName) {
this._attributes[keyName] = get(record, keyName);
}, this);

this.record = record;
this.type = record.constructor;
this.typeKey = record.constructor.typeKey;

/**
The following code is here to keep backwards compatibility when accessing
`constructor` directly.
With snapshots you should use `type` instead of `constructor`.
Remove for Ember Data 1.0.
*/
if (Ember.platform.hasPropertyAccessors) {
var callDeprecate = true;

Ember.defineProperty(this, 'constructor', {
get: function() {
/**
Ugly hack since accessing error.stack (done in `Ember.deprecate()`)
causes the internals of Chrome to access the constructor, which then
causes an infinite loop if accessed and calls `Ember.deprecate()`
again.
*/
if (callDeprecate) {
callDeprecate = false;
Ember.deprecate('Usage of `snapshot.constructor` is deprecated, use `snapshot.type` instead.');
callDeprecate = true;
}

return this.type;
}
});
} else {
this.constructor = this.type;
}
}

Snapshot.prototype = {
constructor: Snapshot,

/**
Returns the value of an attribute.
Example
```javascript
var post = store.createRecord('post', { author: 'Tomster', title: 'Ember.js rocks' });
var snapshot = post._createSnapshot();
snapshot.attr('author'); // => 'Tomster'
snapshot.attr('title'); // => 'Ember.js rocks'
```
Note: Values are loaded eagerly and cached when the snapshot is created.
@method attr
@param {string} keyName
@return {Object} The attribute value or undefined
*/
attr: function(keyName) {
if (keyName in this._attributes) {
return this._attributes[keyName];
}
throw new Ember.Error("Model '" + Ember.inspect(this.record) + "' has no attribute named '" + keyName + "' defined.");
},

/**
Returns all attributes and their corresponding values.
Example
```javascript
var post = store.createRecord('post', { author: 'Tomster', title: 'Ember.js rocks' });
var snapshot = post._createSnapshot();
snapshot.attributes(); // => { author: 'Tomster', title: 'Ember.js rocks' }
```
@method attributes
@return {Array} All attributes for the current snapshot
*/
attributes: function() {
return Ember.copy(this._attributes);
},

/**
Returns the current value of a belongsTo relationship.
Example
```javascript
var post = store.createRecord('post', { title: 'Hello World' });
var comment = store.createRecord('comment', { body: 'Lorem ipsum', post: post });
var snapshot = comment._createSnapshot();
snapshot.belongsTo('post'); // => DS.Snapshot of post
```
Calling `belongsTo` will return a new Snapshot as long as there's any
data available, such as an ID. If there's no data available `belongsTo` will
return undefined.
Note: Relationships are loaded lazily and cached upon first access.
@method belongsTo
@param {string} keyName
@return {DS.Snapshot|undefined} A snapshot of a belongsTo relationship or undefined
*/
belongsTo: function(keyName) {
var relationship, snapshot, inverseRecord;

if (!(keyName in this._belongsToRelationships)) {
relationship = this.record._relationships[keyName];

if (!(relationship && relationship.relationshipMeta.kind === 'belongsTo')) {
throw new Ember.Error("Model '" + Ember.inspect(this.record) + "' has no belongsTo relationship named '" + keyName + "' defined.");
}

inverseRecord = get(relationship, 'inverseRecord');
if (inverseRecord) {
snapshot = inverseRecord._createSnapshot();
}

this._belongsToRelationships[keyName] = snapshot;
}

return this._belongsToRelationships[keyName];
},

/**
Returns the current value of a hasMany relationship.
`hasMany` takes an optional hash of options as a second parameter,
currently supported options are:
- `ids`: set to `true` if you only want the IDs of the related items to be
returned.
Example
```javascript
var post = store.createRecord('post', { title: 'Hello World', comments: [2, 3] });
var snapshot = post._createSnapshot();
snapshot.hasMany('comments'); // => [DS.Snapshot, DS.Snapshot]
snapshot.hasMany('comments', { ids: true }); // => [2, 3]
```
Note: Relationships are loaded lazily and cached upon first access.
@method hasMany
@param {string} keyName
@param {Object} [options]
@return {Array} An array of snapshots of a hasMany relationship
*/
hasMany: function(keyName, options) {
var ids = options && options.ids;
var snapshots = [];
var relationship, members, hasMany;

if (!(keyName in this._hasManyRelationships)) {
relationship = this.record._relationships[keyName];

if (!(relationship && relationship.relationshipMeta.kind === 'hasMany')) {
throw new Ember.Error("Model '" + Ember.inspect(this.record) + "' has no hasMany relationship named '" + keyName + "' defined.");
}

members = get(relationship, 'members');
members.forEach(function(member) {
snapshots.push(member._createSnapshot());
});

this._hasManyRelationships[keyName] = snapshots;
}

hasMany = this._hasManyRelationships[keyName];

return ids ? Ember.A(hasMany).mapBy('id') : hasMany;
},

/**
Iterates through all the attributes of the model, calling the passed
function on each attribute.
Example
```javascript
snapshot.eachAttribute(function(name, meta) {
// ...
});
```
@method eachAttribute
@param {Function} callback the callback to execute
@param {Object} [binding] the value to which the callback's `this` should be bound
*/
eachAttribute: function(callback, binding) {
this.record.eachAttribute(callback, binding);
},

/**
Iterates through all the relationships of the model, calling the passed
function on each relationship.
Example
```javascript
snapshot.eachRelationship(function(name, relationship) {
// ...
});
```
@method eachRelationship
@param {Function} callback the callback to execute
@param {Object} [binding] the value to which the callback's `this` should be bound
*/
eachRelationship: function(callback, binding) {
this.record.eachRelationship(callback, binding);
},

/**
@method get
@param {string} keyName
@return {Object} The property value
@deprecated Use [attr](#method_attr), [belongsTo](#method_belongsTo) or [hasMany](#method_hasMany) instead
*/
get: function(keyName) {
Ember.deprecate('Using DS.Snapshot.get() is deprecated. Use .attr(), .belongsTo() or .hasMany() instead.');

if (keyName === 'id') {
return this.id;
}

if (keyName in this._attributes) {
return this.attr(keyName);
}

var relationship = this.record._relationships[keyName];

if (relationship && relationship.relationshipMeta.kind === 'belongsTo') {
return this.belongsTo(keyName);
}
if (relationship && relationship.relationshipMeta.kind === 'hasMany') {
return this.hasMany(keyName);
}

return get(this.record, keyName);
},

/**
@method unknownProperty
@param {string} keyName
@return {Object} The property value
@deprecated Use [attr](#method_attr), [belongsTo](#method_belongsTo) or [hasMany](#method_hasMany) instead
*/
unknownProperty: function(keyName) {
return this.get(keyName);
}
};

export default Snapshot;
3 changes: 2 additions & 1 deletion packages/ember-data/lib/system/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ Store = Ember.Object.extend({
@param {Object} options an options hash
*/
serialize: function(record, options) {
return this.serializerFor(record.constructor.typeKey).serialize(record, options);
var snapshot = record._createSnapshot();
return this.serializerFor(snapshot.typeKey).serialize(snapshot, options);
},

/**
Expand Down
Loading

0 comments on commit c293f30

Please sign in to comment.