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 Dec 24, 2014
1 parent 93231ae commit e7bf3b9
Show file tree
Hide file tree
Showing 5 changed files with 312 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 @@ -26,6 +26,7 @@ import {
RootState,
attr
} from "ember-data/system/model";
import Snapshot from "ember-data/system/snapshot";
import {
InvalidError,
Adapter
Expand Down Expand Up @@ -77,6 +78,8 @@ DS.RootState = RootState;
DS.attr = attr;
DS.Errors = Errors;

DS.Snapshot = Snapshot;

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

Expand Down
5 changes: 5 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 @@ -936,6 +937,10 @@ var Model = Ember.Object.extend(Ember.Evented, {
this._notifyProperties(dirtyKeys);
},

snapshot: function() {
return new Snapshot(this);
},

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

var get = Ember.get;

/**
@class Snapshot
@namespace DS
@private
*/
function Snapshot(record) {
var attributes = Ember.create(null);

attributes['id'] = get(record, 'id');

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

this.record = record;

this.attributes = attributes;
this.belongsToRelationships = Ember.create(null);
this.hasManyRelationships = Ember.create(null);
}

Snapshot.prototype = {
constructor: Snapshot,

/**
@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.");
},

/**
@method belongsTo
@param {string} keyName
@return {DS.Snapshot} A snapshot of a belongsTo relationship
*/
belongsTo: function(keyName) {
if (keyName in this.belongsToRelationships) {
return this.belongsToRelationships[keyName];
}

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

if (relationship && relationship.relationshipMeta.kind === 'belongsTo') {
var inverseRecord = get(relationship, 'inverseRecord');
if (inverseRecord) {
snapshot = inverseRecord.snapshot();
}
return this.belongsToRelationships[keyName] = snapshot;
}

throw new Ember.Error("Model '" + Ember.inspect(this.record) + "' has no belongsTo relationship named '" + keyName + "' defined.");
},

/**
@method hasMany
@param {string} keyName
@return {Array} An array of snapshots of a hasMany relationship
*/
hasMany: function(keyName) {
if (keyName in this.hasManyRelationships) {
return this.hasManyRelationships[keyName];
}

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

if (relationship && relationship.relationshipMeta.kind === 'hasMany') {
var members = get(relationship, 'members');
members.forEach(function(member) {
snapshots.pushObject(member.snapshot());
});
return this.hasManyRelationships[keyName] = snapshots;
}

throw new Ember.Error("Model '" + Ember.inspect(this.record) + "' has no hasMany relationship named '" + keyName + "' defined.");
},

/**
@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);
},

/**
@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.');

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 this.attr(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.snapshot();
return this.serializerFor(record.constructor.typeKey).serialize(snapshot, options);
},

/**
Expand Down
162 changes: 162 additions & 0 deletions packages/ember-data/tests/integration/snapshot_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
var run = Ember.run;
var env, Post, Comment;

module("integration/snapshot - DS.Snapshot", {
setup: function() {
Post = DS.Model.extend({
title: DS.attr(),
comments: DS.hasMany()
});
Comment = DS.Model.extend({
body: DS.attr(),
post: DS.belongsTo()
});

env = setupStore({
post: Post,
comment: Comment
});
},

teardown: function() {
run(function(){
env.store.destroy();
});
}
});

test("record.snapshot() returns a snapshot", function() {
expect(1);

run(function() {
var post = env.store.push('post', { id: 1, title: 'Hello World' });
var snapshot = post.snapshot();

ok(snapshot instanceof DS.Snapshot, 'snapshot is an instance of DS.Snapshot');
});
});

test("snapshot attr() does not change when record changes", function() {
expect(2);

run(function() {
var post = env.store.push('post', { id: 1, title: 'Hello World' });
var snapshot = post.snapshot();

equal(snapshot.attr('title'), 'Hello World', 'snapshot title is correct');
post.set('title', 'Tomster');
equal(snapshot.attr('title'), 'Hello World', 'snapshot title is still correct');
});
});

test("snapshot belongTo() returns undefined if relationship is undefined", function() {
expect(1);

run(function() {
var comment = env.store.push('comment', { id: 1, body: 'This is comment' });
var snapshot = comment.snapshot();
var relationship = snapshot.belongsTo('post');

equal(relationship, undefined, 'relationship is undefined');
});
});

test("snapshot belongTo() returns a snapshot if relationship is set", function() {
expect(3);

run(function() {
var post = env.store.push('post', { id: 1, title: 'Hello World' });
var comment = env.store.push('comment', { id: 2, body: 'This is comment', post: 1 });
var snapshot = comment.snapshot();
var relationship = snapshot.belongsTo('post');

ok(relationship instanceof DS.Snapshot, 'snapshot is an instance of DS.Snapshot')
equal(relationship.attr('id'), 1, 'post id is correct');
equal(relationship.attr('title'), 'Hello World', 'post title is correct');
});
});

test("snapshot hasMany() returns empty array if relationship is undefined", function() {
expect(2);

run(function() {
var post = env.store.push('post', { id: 1, title: 'Hello World' });
var snapshot = post.snapshot();
var relationship = snapshot.hasMany('comments');

ok(relationship instanceof Array, 'relationship is an instance of Array');
equal(relationship.length, 0, 'relationship is empty');
});
});

test("snapshot hasMany() returns array of snapshots if relationship is set", function() {
expect(5);

run(function() {
var comment1 = env.store.push('comment', { id: 1, body: 'This is the first comment' });
var comment2 = env.store.push('comment', { id: 2, body: 'This is the second comment' });
var post = env.store.push('post', { id: 3, title: 'Hello World', comments: [1, 2] });
var snapshot = post.snapshot();
var relationship = snapshot.hasMany('comments');

ok(relationship instanceof Array, 'relationship is an instance of Array');
equal(relationship.length, 2, 'relationship has two items');

var relationship1 = relationship[0];
var relationship2 = relationship[1];

ok(relationship1 instanceof DS.Snapshot, 'relationship item is an instance of DS.Snapshot');

equal(relationship1.attr('id'), 1, 'relationship item id is correct');
equal(relationship1.attr('body'), 'This is the first comment', 'relationship item body is correct');
});
});

test("snapshot get() is deprecated", function() {
expect(1);

run(function() {
var post = env.store.push('post', { id: 1, title: 'Hello World' });
var snapshot = post.snapshot();

expectDeprecation(function() {
snapshot.get('title');
}, 'Using DS.Snapshot.get() is deprecated. Use .attr(), .belongsTo() or .hasMany() instead.')
});
});

test("snapshot get() returns attribute", function() {
expect(1);

run(function() {
var post = env.store.push('post', { id: 1, title: 'Hello World' });
var snapshot = post.snapshot();

equal(snapshot.get('title'), 'Hello World', 'snapshot title is correct');
});
});

test("snapshot get() returns belongsTo", function() {
expect(1);

run(function() {
var comment = env.store.push('comment', { id: 1, body: 'This is a comment', post: 2 });
var snapshot = comment.snapshot();
var relationship = snapshot.belongsTo('post');

ok(relationship instanceof DS.Snapshot, 'relationship is an instance of DS.Snapshot');
});
});

test("snapshot get() returns hasMany", function() {
expect(2);

run(function() {
var post = env.store.push('post', { id: 1, title: 'Hello World', comments: [2, 3] });
var snapshot = post.snapshot();
var relationship = snapshot.hasMany('comments');

ok(relationship instanceof Array, 'relationship is an instance of Array');
equal(relationship.length, 2, 'relationship has two items');
});
});

0 comments on commit e7bf3b9

Please sign in to comment.