diff --git a/packages/ember-model/lib/belongs_to.js b/packages/ember-model/lib/belongs_to.js index 3aaea8b..59f53fe 100644 --- a/packages/ember-model/lib/belongs_to.js +++ b/packages/ember-model/lib/belongs_to.js @@ -42,9 +42,9 @@ Ember.belongsTo = function(type, options) { var dirtyChanged = function(sender) { if (sender.get('isDirty')) { - self._relationshipBecameDirty(key); + self._relationshipBecameDirty(propertyKey); } else { - self._relationshipBecameClean(key); + self._relationshipBecameClean(propertyKey); } }; diff --git a/packages/ember-model/lib/fixture_adapter.js b/packages/ember-model/lib/fixture_adapter.js index 41b6fae..9587579 100644 --- a/packages/ember-model/lib/fixture_adapter.js +++ b/packages/ember-model/lib/fixture_adapter.js @@ -80,23 +80,38 @@ Ember.FixtureAdapter = Ember.Adapter.extend({ return new Ember.RSVP.Promise(function(resolve, reject) { Ember.run.later(this, function() { + var data; + self._setPrimaryKey(record); - fixtures.push(klass.findFromCacheOrLoad(record.toJSON())); - record.didCreateRecord(); + data = record.toJSON(); + fixtures.push(klass.findFromCacheOrLoad(data)); + self.didCreateRecord(record, data); resolve(record); }, 0); }); }, + didCreateRecord: function(record, data) { + this._loadRecordFromData(record, data); + record.didCreateRecord(); + }, + saveRecord: function(record) { + var self = this, data = record.toJSON(); + return new Ember.RSVP.Promise(function(resolve, reject) { Ember.run.later(this, function() { - record.didSaveRecord(); + self.didSaveRecord(record, data); resolve(record); }, 0); }); }, + didSaveRecord: function(record, data) { + this._loadRecordFromData(record, data); + record.didSaveRecord(); + }, + deleteRecord: function(record) { return new Ember.RSVP.Promise(function(resolve, reject) { Ember.run.later(this, function() { @@ -104,5 +119,14 @@ Ember.FixtureAdapter = Ember.Adapter.extend({ resolve(record); }, 0); }); + }, + + _loadRecordFromData: function(record, data) { + var rootKey = get(record.constructor, 'rootKey'), + primaryKey = get(record.constructor, 'primaryKey'), + dataToLoad = rootKey ? get(data, rootKey) : data; + if (!Ember.isEmpty(dataToLoad)) { + record.load(dataToLoad[primaryKey], dataToLoad); + } } }); diff --git a/packages/ember-model/lib/has_many_array.js b/packages/ember-model/lib/has_many_array.js index 671402b..0e8d80d 100644 --- a/packages/ember-model/lib/has_many_array.js +++ b/packages/ember-model/lib/has_many_array.js @@ -168,16 +168,15 @@ Ember.HasManyArray = Ember.ManyArray.extend({ var klass = get(this, 'modelClass'), content = get(this, 'content'), reference = content.objectAt(idx), - record; + record = reference.record; - if (reference.record) { - record = reference.record; - } else { - record = klass.find(reference.id); + if (record) { + if (! record.container) { + record.container = container; + } + return record; } - - record.container = container; - return record; + return klass._findFetchById(reference.id, false, container); }, toJSON: function() { diff --git a/packages/ember-model/lib/model.js b/packages/ember-model/lib/model.js index 5407d88..9f297df 100644 --- a/packages/ember-model/lib/model.js +++ b/packages/ember-model/lib/model.js @@ -88,7 +88,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { if (!reference) { reference = this.constructor._getOrCreateReferenceForId(id); - reference.record = this; + set(reference, 'record', this); this._reference = reference; } else if (reference.id !== id) { reference.id = id; @@ -165,7 +165,7 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { var record = this.get(key); return record ? record.toJSON() : null; } else { - var primaryKey = get(meta.getType(), 'primaryKey'); + var primaryKey = get(meta.getType(this), 'primaryKey'); return this.get(key + '.' + primaryKey); } }, @@ -176,10 +176,16 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { attributes = this.constructor.getAttributes(), relationships = this.constructor.getRelationships(), properties = attributes ? this.getProperties(attributes) : {}, - rootKey = get(this.constructor, 'rootKey'); + rootKey = get(this.constructor, 'rootKey'), + isNew = get(this, 'isNew'); for (key in properties) { meta = this.constructor.metaForProperty(key); + if (meta.options) { + if (meta.options.save === false || (meta.options.update === false && ! isNew)) { + continue; + } + } if (meta.type && meta.type.serialize) { json[this.dataKey(key)] = meta.type.serialize(properties[key]); } else if (meta.type && Ember.Model.dataTypes[meta.type]) { @@ -195,6 +201,11 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { for(var i = 0; i < relationships.length; i++) { key = relationships[i]; meta = this.constructor.metaForProperty(key); + if (meta.options) { + if (meta.options.save === false || (meta.options.update === false && ! isNew)) { + continue; + } + } relationshipKey = meta.options.key || key; if (meta.kind === 'belongsTo') { @@ -251,7 +262,6 @@ Ember.Model = Ember.Object.extend(Ember.Evented, { set(this, 'isNew', false); - set(this, '_dirtyAttributes', []); this.constructor.addToRecordArrays(this); this.trigger('didCreateRecord'); this.didSaveRecord(); @@ -650,7 +660,9 @@ Ember.Model.reopenClass({ getCachedReferenceRecord: function(id, container){ var ref = this._getReferenceById(id); if(ref && ref.record) { - ref.record.container = container; + if (! ref.record.container) { + ref.record.container = container; + } return ref.record; } return undefined; @@ -770,7 +782,7 @@ Ember.Model.reopenClass({ }, this).forEach(callback); }, - load: function(hashes) { + load: function(hashes, container) { if (Ember.typeOf(hashes) !== 'array') { hashes = [hashes]; } if (!this.sideloadedData) { this.sideloadedData = {}; } @@ -778,7 +790,7 @@ Ember.Model.reopenClass({ for (var i = 0, l = hashes.length; i < l; i++) { var hash = hashes[i], primaryKey = hash[get(this, 'primaryKey')], - record = this.getCachedReferenceRecord(primaryKey); + record = this.getCachedReferenceRecord(primaryKey, container); if (record) { record.load(primaryKey, hash); diff --git a/packages/ember-model/lib/store.js b/packages/ember-model/lib/store.js index 3a04808..71b2934 100644 --- a/packages/ember-model/lib/store.js +++ b/packages/ember-model/lib/store.js @@ -1,4 +1,3 @@ -function NIL() {} Ember.Model.Store = Ember.Object.extend({ container: null, @@ -22,14 +21,24 @@ Ember.Model.Store = Ember.Object.extend({ } }, - createRecord: function(type) { + modelWithAdapterFor: function(type) { var klass = this.modelFor(type); - klass.reopenClass({adapter: this.adapterFor(type)}); - return klass.create({container: this.container}); + if (! klass.adapter) { + klass.reopenClass({adapter: this.adapterFor(type)}); + } + return klass; + }, + + createRecord: function(type, props) { + var klass = this.modelFor(type); + if (! klass.adapter) { + klass.reopenClass({adapter: this.adapterFor(type)}); + } + return klass.create(Ember.merge({container: this.container}, props)); }, find: function(type, id) { - if (arguments.length === 1) { id = NIL; } + if (arguments.length === 1) { id = undefined; } return this._find(type, id, true); }, @@ -40,7 +49,7 @@ Ember.Model.Store = Ember.Object.extend({ klass.reopenClass({adapter: this.adapterFor(type)}); // } - if (id === NIL) { + if (id === undefined) { return klass._findFetchAll(async, this.container); } else if (Ember.isArray(id)) { return klass._findFetchMany(id, async, this.container); @@ -52,6 +61,7 @@ Ember.Model.Store = Ember.Object.extend({ }, _findSync: function(type, id) { + if (arguments.length === 1) { id = undefined; } return this._find(type, id, false); } }); diff --git a/packages/ember-model/tests/adapter/fixture_adapter_test.js b/packages/ember-model/tests/adapter/fixture_adapter_test.js index 2fb0900..c149143 100644 --- a/packages/ember-model/tests/adapter/fixture_adapter_test.js +++ b/packages/ember-model/tests/adapter/fixture_adapter_test.js @@ -82,10 +82,35 @@ test("createRecord", function() { stop(); }); +test("revert", function() { + expect(6); + + FixtureModel.FIXTURES = []; + + var record = FixtureModel.create({name: "Erik"}); + + // Ember.run(record, record.save); + Ember.run(record, record.save).then(function(record) { + start(); + // test revert of a clean record + record.revert(); + equal(record.get('isDirty'), false, "Reverted clean record should not be dirty"); + equal(record.get('id'), "fixture-0", "Value of clean Record #id should not have changed"); + equal(record.get('name'), "Erik", "Value of clean Record #name should not have changed"); + // test revert of a dirty model + record.set('name', 'Peter'); + record.revert(); + equal(record.get('isDirty'), false, "Reverted dirty record should not be dirty"); + equal(record.get('id'), "fixture-0", "Value of dirty Record #id should not have changed"); + equal(record.get('name'), "Erik", "Value of dirty Record #name should have reverted"); + }); + stop(); +}); + test("_generatePrimaryKey", function() { expect(3); - equal(adapter._generatePrimaryKey(), "fixture-0", "Retrun next primary key"); - equal(adapter._generatePrimaryKey(), "fixture-1", "Retrun next primary key"); - equal(adapter._generatePrimaryKey(), "fixture-2", "Retrun next primary key"); + equal(adapter._generatePrimaryKey(), "fixture-0", "Return next primary key"); + equal(adapter._generatePrimaryKey(), "fixture-1", "Return next primary key"); + equal(adapter._generatePrimaryKey(), "fixture-2", "Return next primary key"); }); diff --git a/packages/ember-model/tests/dirty_tracking_test.js b/packages/ember-model/tests/dirty_tracking_test.js index 996f37a..c8fbbd1 100644 --- a/packages/ember-model/tests/dirty_tracking_test.js +++ b/packages/ember-model/tests/dirty_tracking_test.js @@ -581,6 +581,51 @@ test("save parent of embedded belongsTo", function() { }); }); +test("save parent of embedded belongsTo with different named key", function() { + expect(9); + var json = { + id: 1, + name: 'foo', + the_author: { id: 1, name: 'Cory Loken' } + }; + + var Author = Ember.Model.extend({ + id: Ember.attr(), + name: Ember.attr() + }), + Post = Ember.Model.extend({ + id: Ember.attr(), + author: Ember.belongsTo(Author, {key: 'the_author', embedded: true}) + }); + + Post.adapter = Ember.FixtureAdapter.create(); + + var post = Post.create(); + Ember.run(post, post.load, json.id, json); + equal(post.get('isDirty'), false, 'post should be clean initially'); + + post.set('author.name', 'Billy Bob'); + equal(post.get('author.isDirty'), true, 'author should be dirty after being modified'); + equal(post.get('isDirty'), true, 'changes to embedded belongsTo should dirty the parent'); + + stop(); + Ember.run(function() { + post.save().then(function() { + start(); + equal(post.get('author.isDirty'), false, 'the author should be clean after being saved'); + equal(post.get('isDirty'), false, 'the post should be clean after being saved'); + + post.set('author.name', 'John Doe'); + equal(post.get('author.isDirty'), true, 'the author should be dirty again'); + equal(post.get('isDirty'), true, 'the post should be dirty because the author is dirty'); + + post.set('author.name', 'Cory Loken'); // special case: setting back to its original value + equal(post.get('author.isDirty'), true, 'the author should be dirty because it was saved as "Billy Bob"'); + equal(post.get('isDirty'), true, 'the post should be dirty because the author is dirty'); + }); + }); +}); + test("set embedded belongsTo", function() { expect(9); var json = { @@ -701,3 +746,44 @@ test("manipulating the content of objects in a hasMany should dirty the parent", ok(post.get('comments.isDirty') === true, "post.comments should be dirty after changing comment1's content"); ok(post.get('isDirty') === true, "Post should be dirty after changing comment1's content"); }); + +test("after saving new record, the model and it's embedded properties shouldn't be dirty", function() { + expect(5); + + var Comment = Ember.Model.extend({ + id: Ember.attr(), + name: Ember.attr() + }); + + var Post = Ember.Model.extend({ + id: Ember.attr(), + comments: Ember.hasMany(Comment, {key: 'comments', embedded: true}) + }); + + Post.adapter = { + createRecord: function(record) { + ok(true, "createRecord was called"); + var deferred = Ember.Deferred.create(); + deferred.then(function() { + record.didCreateRecord(); + }); + deferred.resolve(record); + return deferred; + } + }; + + var post = Post.create(); + post.get('comments').create({name: 'Comment 1'}); + + ok(post.get('isDirty') === true, 'Post should be dirty initially'); + ok(post.get('comments.isDirty') === true, 'Comments should be dirty initially'); + + stop(); + Ember.run(function() { + post.save().then(function() { + start(); + ok(!post.get('isDirty'), "Post should be clean"); + ok(!post.get('comments.isDirty'), "Comments in post should be clean"); + }); + }); +}); diff --git a/packages/ember-model/tests/model_test.js b/packages/ember-model/tests/model_test.js index ebde290..e181889 100644 --- a/packages/ember-model/tests/model_test.js +++ b/packages/ember-model/tests/model_test.js @@ -493,6 +493,67 @@ test("Model#create() works as expected", function() { stop(); }); +test("attribute 'save' option works as expected", function() { + expect(6); + + var SaveableModel = Model.extend({ + dontSave: Ember.attr('string', {save: false}), + canSave: Ember.attr('string', {save: true}) + }); + + var record = SaveableModel.create({name: 'Yehuda', dontSave: 'BAD', canSave: 'OK'}), + json; + + SaveableModel.adapter = Ember.FixtureAdapter.create(); + SaveableModel.FIXTURES = []; + json = record.toJSON(); + ok(! json.dontSave, "The JSON omits non-saveable attributes on create"); + equal(json.canSave, 'OK', "The JSON includes explicitly savable attributes on create"); + equal(json.name, 'Yehuda', "The JSON includes implicitly savable attributes on create"); + record.save().then(function() { + start(); + record.set('dontSave', 'Bar'); + record.set('canSave', 'OK'); + record.save().then(function() { + start(); + json = record.toJSON(); + ok(! json.dontSave, "The JSON omits non-saveable attributes on update"); + equal(json.canSave, 'OK', "The JSON includes explicitly saveable attributes on update"); + equal(json.name, 'Yehuda', "The JSON includes implicitly saveable attributes on update"); + }); + stop(); + }); + stop(); +}); + +test("attribute 'update' option works as expected", function() { + expect(6); + + var UpdateableModel = Model.extend({ + dontUpdate: Ember.attr('string', {update: false}), + canUpdate: Ember.attr('string', {save: true}) + }); + + UpdateableModel.adapter = Ember.FixtureAdapter.create(); + UpdateableModel.FIXTURES = []; + + var record = UpdateableModel.create({name: 'Yehuda', dontUpdate: 'CREATE', canUpdate: 'OK'}), + json; + + json = record.toJSON(); + equal(json.dontUpdate, 'CREATE', "The JSON includes non-updateable attributes on create."); + equal(json.canUpdate, 'OK', "The JSON includes explicitly updateable attributes on create"); + equal(json.name, 'Yehuda', "The JSON includes implicitly updateable attributes on create"); + record.save().then(function(record2) { + start(); + json = record.toJSON(); + ok(! json.dontUpdate, "The JSON omits non-updateable attributes on update."); + equal(json.canUpdate, 'OK', "The JSON includes explicitly updateable attributes on update"); + equal(json.name, 'Yehuda', "The JSON includes implicitly updateable attributes on update"); + }); + stop(); +}); + test(".getAttributes() returns the model's attributes", function() { var attr = Ember.attr, BaseModel = Ember.Model.extend({ diff --git a/packages/ember-model/tests/store_test.js b/packages/ember-model/tests/store_test.js index db6956c..17ce1a8 100644 --- a/packages/ember-model/tests/store_test.js +++ b/packages/ember-model/tests/store_test.js @@ -1,4 +1,4 @@ -var TestModel, EmbeddedModel, store, container, App; +var TestModel, EmbeddedModel, UUIDModel, store, container, App; module("Ember.Model.Store", { setup: function() { @@ -48,8 +48,21 @@ module("Ember.Model.Store", { }); EmbeddedModel.adapter = Ember.FixtureAdapter.create({}); + var uuid = 1234; + + UUIDModel = Ember.Model.extend({ + init: function() { + this.set('id', uuid++); + return this._super.apply(this, arguments); + }, + token: Ember.attr(), + name: Ember.attr() + }); + EmbeddedModel.adapter = Ember.FixtureAdapter.create({}); + container.register('model:test', TestModel); container.register('model:embedded', EmbeddedModel); + container.register('model:uuid', UUIDModel); container.register('store:main', Ember.Model.Store); } }); @@ -57,6 +70,41 @@ module("Ember.Model.Store", { test("store.createRecord(type) returns a record with a container", function() { var record = Ember.run(store, store.createRecord, 'test'); equal(record.container, container); + equal(record.container, container); +}); + +test("store.createRecord(type) with properties", function() { + expect(2); + var record = Ember.run(store, store.createRecord, 'test', {token: 'c', name: 'Andrew'}); + equal(record.get('token'), 'c'); + equal(record.get('name'), 'Andrew'); +}); + +test("model.load(hashes) returns a existing record with correct container", function() { + var model = store.modelFor('uuid'), + record = Ember.run(store, store.createRecord, 'uuid'); + + equal(model, UUIDModel); + equal(record.container, container); + + ok(record.set('token', 'c')); + + equal(record.get('id'), 1234); + equal(record.get('token'), 'c'); + + model.load({id: 1234, token: 'd', name: 'Andrew'}); + + equal(record.get('id'), 1234); + equal(record.get('token'), 'd'); + equal(record.get('name'), 'Andrew'); + equal(record.get('container'), container); + + model.load({id: 1234, name: 'Peter'}, container); + + equal(record.get('id'), 1234); + equal(record.get('token'), undefined); + equal(record.get('name'), 'Peter'); + equal(record.get('container'), container); }); test("store.find(type) returns a record with hasMany and belongsTo that should all have a container", function() {