From f69a9ebdccb2c72905ac5ae6794b199ee2433509 Mon Sep 17 00:00:00 2001 From: Eric Kelly Date: Thu, 11 Jun 2015 09:25:53 -0400 Subject: [PATCH] Rename rollback() to rollbackAttributes() for Model and InternalModel --- .../lib/system/model/internal-model.js | 4 +- packages/ember-data/lib/system/model/model.js | 30 +- .../ember-data/lib/system/model/states.js | 4 +- .../integration/records/delete-record-test.js | 2 +- .../relationships/belongs-to-test.js | 12 +- .../relationships/has-many-test.js | 24 +- .../relationships/many-to-many-test.js | 14 +- .../relationships/one-to-many-test.js | 36 +- .../relationships/one-to-one-test.js | 18 +- packages/ember-data/tests/unit/model-test.js | 4 +- .../unit/model/rollback-attributes-test.js | 376 ++++++++++++++++++ .../tests/unit/model/rollback-test.js | 364 +---------------- 12 files changed, 473 insertions(+), 415 deletions(-) create mode 100644 packages/ember-data/tests/unit/model/rollback-attributes-test.js diff --git a/packages/ember-data/lib/system/model/internal-model.js b/packages/ember-data/lib/system/model/internal-model.js index dc6276c879d..61bae6ee90f 100644 --- a/packages/ember-data/lib/system/model/internal-model.js +++ b/packages/ember-data/lib/system/model/internal-model.js @@ -320,7 +320,7 @@ InternalModel.prototype = { } }, - rollback: function() { + rollbackAttributes: function() { var dirtyKeys = Ember.keys(this._attributes); this._attributes = Ember.create(null); @@ -669,7 +669,7 @@ InternalModel.prototype = { Ember Data has 3 buckets for storing the value of an attribute on an internalModel. `_data` holds all of the attributes that have been acknowledged by - a backend via the adapter. When rollback is called on a model all + a backend via the adapter. When rollbackAttributes is called on a model all attributes will revert to the record's state in `_data`. `_attributes` holds any change the user has made to an attribute diff --git a/packages/ember-data/lib/system/model/model.js b/packages/ember-data/lib/system/model/model.js index f85b93320d3..2f04fa8d913 100644 --- a/packages/ember-data/lib/system/model/model.js +++ b/packages/ember-data/lib/system/model/model.js @@ -469,8 +469,8 @@ var Model = Ember.Object.extend(Ember.Evented, { /** Marks the record as deleted but does not save it. You must call `save` afterwards if you want to persist it. You might use this - method if you want to allow the user to still `rollback()` a - delete after it was made. + method if you want to allow the user to still `rollbackAttributes()` + after a delete it was made. Example @@ -486,7 +486,7 @@ var Model = Ember.Object.extend(Ember.Evented, { this.controller.get('model').save(); }, undo: function() { - this.controller.get('model').rollback(); + this.controller.get('model').rollbackAttributes(); } } }); @@ -621,9 +621,31 @@ var Model = Ember.Object.extend(Ember.Evented, { ``` @method rollback + @deprecated Use `addAttributes()` instead */ rollback: function() { - this._internalModel.rollback(); + Ember.deprecate('Using model.rollback() has been deprecated. Use model.rollbackAttributes() to discard any unsaved changes to a model.'); + this.rollbackAttributes(); + }, + + /** + If the model `isDirty` this function will discard any unsaved + changes. If the model `isNew` it will be removed from the store. + + Example + + ```javascript + record.get('name'); // 'Untitled Document' + record.set('name', 'Doc 1'); + record.get('name'); // 'Doc 1' + record.rollbackAttributes(); + record.get('name'); // 'Untitled Document' + ``` + + @method rollbackAttributes + */ + rollbackAttributes: function() { + this._internalModel.rollbackAttributes(); }, /* diff --git a/packages/ember-data/lib/system/model/states.js b/packages/ember-data/lib/system/model/states.js index 36379760528..60b71c0908b 100644 --- a/packages/ember-data/lib/system/model/states.js +++ b/packages/ember-data/lib/system/model/states.js @@ -273,7 +273,7 @@ var DirtyState = { }, rollback: function(internalModel) { - internalModel.rollback(); + internalModel.rollbackAttributes(); internalModel.triggerLater('ready'); } }, @@ -632,7 +632,7 @@ var RootState = { }, rollback: function(internalModel) { - internalModel.rollback(); + internalModel.rollbackAttributes(); internalModel.triggerLater('ready'); }, diff --git a/packages/ember-data/tests/integration/records/delete-record-test.js b/packages/ember-data/tests/integration/records/delete-record-test.js index 66bf4cd4434..2d62d77270f 100644 --- a/packages/ember-data/tests/integration/records/delete-record-test.js +++ b/packages/ember-data/tests/integration/records/delete-record-test.js @@ -62,7 +62,7 @@ test("when deleted records are rolled back, they are still in their previous rec run(function() { jaime.deleteRecord(); - jaime.rollback(); + jaime.rollbackAttributes(); }); equal(all.get('length'), 2, 'record was not removed'); equal(filtered.get('length'), 2, 'record was not removed'); diff --git a/packages/ember-data/tests/integration/relationships/belongs-to-test.js b/packages/ember-data/tests/integration/relationships/belongs-to-test.js index 82619eadb82..9b23e2f5b1d 100644 --- a/packages/ember-data/tests/integration/relationships/belongs-to-test.js +++ b/packages/ember-data/tests/integration/relationships/belongs-to-test.js @@ -581,7 +581,7 @@ test("A sync belongsTo errors out if the record is unlaoded", function() { }, /You looked up the 'user' relationship on a 'message' with id 1 but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async \(`DS.belongsTo\({ async: true }\)`\)/); }); -test("Rollbacking a deleted record restores implicit relationship - async", function () { +test("Rollbacking attributes for a deleted record restores implicit relationship - async", function () { Book.reopen({ author: DS.belongsTo('author', { async: true }) }); @@ -592,14 +592,14 @@ test("Rollbacking a deleted record restores implicit relationship - async", func }); run(function() { author.deleteRecord(); - author.rollback(); + author.rollbackAttributes(); book.get('author').then(function(fetchedAuthor) { - equal(fetchedAuthor, author, 'Book has an author after rollback'); + equal(fetchedAuthor, author, 'Book has an author after rollback attributes'); }); }); }); -test("Rollbacking a deleted record restores implicit relationship - sync", function () { +test("Rollbacking attributes for a deleted record restores implicit relationship - sync", function () { var book, author; run(function() { book = env.store.push('book', { id: 1, name: "Stanley's Amazing Adventures", author: 2 }); @@ -607,9 +607,9 @@ test("Rollbacking a deleted record restores implicit relationship - sync", funct }); run(function() { author.deleteRecord(); - author.rollback(); + author.rollbackAttributes(); }); - equal(book.get('author'), author, 'Book has an author after rollback'); + equal(book.get('author'), author, 'Book has an author after rollback attributes'); }); test("Passing a model as type to belongsTo should not work", function () { diff --git a/packages/ember-data/tests/integration/relationships/has-many-test.js b/packages/ember-data/tests/integration/relationships/has-many-test.js index 2339ed9a438..38ad0ca5d1e 100644 --- a/packages/ember-data/tests/integration/relationships/has-many-test.js +++ b/packages/ember-data/tests/integration/relationships/has-many-test.js @@ -1060,7 +1060,7 @@ test("If reordered hasMany data has been pushed to the store, the many array ref deepEqual(post.get('comments').toArray(), [comment4, comment2, comment3, comment1], 'Updated ordering is correct'); }); -test("Rollbacking a deleted record restores implicit relationship correctly when the hasMany side has been deleted - async", function () { +test("Rollbacking attributes for deleted record restores implicit relationship correctly when the hasMany side has been deleted - async", function () { var book, chapter; run(function() { book = env.store.push('book', { id: 1, title: "Stanley's Amazing Adventures", chapters: [2] }); @@ -1068,16 +1068,16 @@ test("Rollbacking a deleted record restores implicit relationship correctly when }); run(function() { chapter.deleteRecord(); - chapter.rollback(); + chapter.rollbackAttributes(); }); run(function() { book.get('chapters').then(function(fetchedChapters) { - equal(fetchedChapters.objectAt(0), chapter, 'Book has a chapter after rollback'); + equal(fetchedChapters.objectAt(0), chapter, 'Book has a chapter after rollback attributes'); }); }); }); -test("Rollbacking a deleted record restores implicit relationship correctly when the hasMany side has been deleted - sync", function () { +test("Rollbacking attributes for deleted record restores implicit relationship correctly when the hasMany side has been deleted - sync", function () { var book, chapter; run(function() { book = env.store.push('book', { id: 1, title: "Stanley's Amazing Adventures", chapters: [2] }); @@ -1085,14 +1085,14 @@ test("Rollbacking a deleted record restores implicit relationship correctly when }); run(function() { chapter.deleteRecord(); - chapter.rollback(); + chapter.rollbackAttributes(); }); run(function() { - equal(book.get('chapters.firstObject'), chapter, "Book has a chapter after rollback"); + equal(book.get('chapters.firstObject'), chapter, "Book has a chapter after rollback attributes"); }); }); -test("Rollbacking a deleted record restores implicit relationship correctly when the belongsTo side has been deleted - async", function () { +test("Rollbacking attributes for deleted record restores implicit relationship correctly when the belongsTo side has been deleted - async", function () { Page.reopen({ chapter: DS.belongsTo('chapter', { async: true }) }); @@ -1103,16 +1103,16 @@ test("Rollbacking a deleted record restores implicit relationship correctly when }); run(function() { chapter.deleteRecord(); - chapter.rollback(); + chapter.rollbackAttributes(); }); run(function() { page.get('chapter').then(function(fetchedChapter) { - equal(fetchedChapter, chapter, 'Page has a chapter after rollback'); + equal(fetchedChapter, chapter, 'Page has a chapter after rollback attributes'); }); }); }); -test("Rollbacking a deleted record restores implicit relationship correctly when the belongsTo side has been deleted - sync", function () { +test("Rollbacking attributes for deleted record restores implicit relationship correctly when the belongsTo side has been deleted - sync", function () { var chapter, page; run(function() { chapter = env.store.push('chapter', { id: 2, title: 'Sailing the Seven Seas' }); @@ -1120,10 +1120,10 @@ test("Rollbacking a deleted record restores implicit relationship correctly when }); run(function() { chapter.deleteRecord(); - chapter.rollback(); + chapter.rollbackAttributes(); }); run(function() { - equal(page.get('chapter'), chapter, "Page has a chapter after rollback"); + equal(page.get('chapter'), chapter, "Page has a chapter after rollback attributes"); }); }); diff --git a/packages/ember-data/tests/integration/relationships/many-to-many-test.js b/packages/ember-data/tests/integration/relationships/many-to-many-test.js index 21778c3631e..916b19d764a 100644 --- a/packages/ember-data/tests/integration/relationships/many-to-many-test.js +++ b/packages/ember-data/tests/integration/relationships/many-to-many-test.js @@ -204,10 +204,10 @@ test("Deleting a record that has a hasMany relationship removes it from the othe }); /* - Rollback tests + Rollback Attributes tests */ -test("Rollbacking a deleted record that has a ManyToMany relationship works correctly - async", function () { +test("Rollbacking attributes for a deleted record that has a ManyToMany relationship works correctly - async", function () { var user, topic; run(function() { user = store.push('user', { id: 1, name: 'Stanley', topics: [2] }); @@ -215,7 +215,7 @@ test("Rollbacking a deleted record that has a ManyToMany relationship works corr }); run(function() { topic.deleteRecord(); - topic.rollback(); + topic.rollbackAttributes(); }); run(function() { topic.get('users').then(async(function(fetchedUsers) { @@ -235,13 +235,13 @@ test("Deleting a record that has a hasMany relationship removes it from the othe }); run(function() { account.deleteRecord(); - account.rollback(); + account.rollbackAttributes(); }); equal(account.get('users.length'), 1, 'Users are still there'); equal(user.get('accounts.length'), 1, 'Account got rolledback correctly into the user'); }); -test("Rollbacking a created record that has a ManyToMany relationship works correctly - async", function () { +test("Rollbacking attributes for a created record that has a ManyToMany relationship works correctly - async", function () { var user, topic; run(function() { user = store.push('user', { id: 1, name: 'Stanley' }); @@ -250,7 +250,7 @@ test("Rollbacking a created record that has a ManyToMany relationship works corr run(function() { user.get('topics').then(async(function(fetchedTopics) { fetchedTopics.pushObject(topic); - topic.rollback(); + topic.rollbackAttributes(); topic.get('users').then(async(function(fetchedUsers) { equal(fetchedUsers.get('length'), 0, 'Users got removed'); equal(fetchedUsers.objectAt(0), null, "User can't be fetched"); @@ -271,7 +271,7 @@ test("Deleting a record that has a hasMany relationship removes it from the othe }); run(function() { account.get('users').pushObject(user); - user.rollback(); + user.rollbackAttributes(); }); equal(account.get('users.length'), 0, 'Users got removed'); equal(user.get('accounts.length'), undefined, 'Accounts got rolledback correctly'); diff --git a/packages/ember-data/tests/integration/relationships/one-to-many-test.js b/packages/ember-data/tests/integration/relationships/one-to-many-test.js index 82622f61fd5..83ec15159c5 100644 --- a/packages/ember-data/tests/integration/relationships/one-to-many-test.js +++ b/packages/ember-data/tests/integration/relationships/one-to-many-test.js @@ -446,10 +446,10 @@ test("When deleting a record that has a hasMany it is removed from the belongsTo }); /* -Rollback from deleted state +Rollback attributes from deleted state */ -test("Rollbacking a deleted record works correctly when the hasMany side has been deleted - async", function () { +test("Rollbacking attributes of a deleted record works correctly when the hasMany side has been deleted - async", function () { var user, message; run(function() { user = store.push('user', { id: 1, name: 'Stanley', messages: [2] }); @@ -457,7 +457,7 @@ test("Rollbacking a deleted record works correctly when the hasMany side has bee }); run(function() { message.deleteRecord(); - message.rollback(); + message.rollbackAttributes(); }); run(function() { message.get('user').then(function(fetchedUser) { @@ -469,7 +469,7 @@ test("Rollbacking a deleted record works correctly when the hasMany side has bee }); }); -test("Rollbacking a deleted record works correctly when the hasMany side has been deleted - sync", function () { +test("Rollbacking attributes of a deleted record works correctly when the hasMany side has been deleted - sync", function () { var account, user; run(function() { account = store.push('account', { id: 2 , state: 'lonely' }); @@ -477,13 +477,13 @@ test("Rollbacking a deleted record works correctly when the hasMany side has bee }); run(function() { account.deleteRecord(); - account.rollback(); + account.rollbackAttributes(); }); equal(user.get('accounts.length'), 1, "Accounts are rolled back"); equal(account.get('user'), user, 'Account still has the user'); }); -test("Rollbacking a deleted record works correctly when the belongsTo side has been deleted - async", function () { +test("Rollbacking attributes of deleted record works correctly when the belongsTo side has been deleted - async", function () { var user, message; run(function() { user = store.push('user', { id: 1, name: 'Stanley', messages: [2] }); @@ -491,7 +491,7 @@ test("Rollbacking a deleted record works correctly when the belongsTo side has b }); run(function() { user.deleteRecord(); - user.rollback(); + user.rollbackAttributes(); }); run(function() { message.get('user').then(function(fetchedUser) { @@ -503,7 +503,7 @@ test("Rollbacking a deleted record works correctly when the belongsTo side has b }); }); -test("Rollbacking a deleted record works correctly when the belongsTo side has been deleted - sync", function () { +test("Rollbacking attributes of a deleted record works correctly when the belongsTo side has been deleted - sync", function () { var account, user; run(function() { account = store.push('account', { id: 2 , state: 'lonely' }); @@ -511,23 +511,23 @@ test("Rollbacking a deleted record works correctly when the belongsTo side has b }); run(function() { user.deleteRecord(); - user.rollback(); + user.rollbackAttributes(); }); equal(user.get('accounts.length'), 1, "User still has the accounts"); equal(account.get('user'), user, 'Account has the user again'); }); /* -Rollback from created state +Rollback attributes from created state */ -test("Rollbacking a created record works correctly when the hasMany side has been created - async", function () { +test("Rollbacking attributes of a created record works correctly when the hasMany side has been created - async", function () { var user, message; run(function() { user = store.push('user', { id: 1, name: 'Stanley' }); message = store.createRecord('message', { user: user }); }); - run(message, 'rollback'); + run(message, 'rollbackAttributes'); run(function() { message.get('user').then(function(fetchedUser) { equal(fetchedUser, null, 'Message does not have the user anymore'); @@ -539,18 +539,18 @@ test("Rollbacking a created record works correctly when the hasMany side has bee }); }); -test("Rollbacking a created record works correctly when the hasMany side has been created - sync", function () { +test("Rollbacking attributes of a created record works correctly when the hasMany side has been created - sync", function () { var user, account; run(function() { user = store.push('user', { id: 1, name: 'Stanley' }); account = store.createRecord('account', { user: user }); }); - run(account, 'rollback'); + run(account, 'rollbackAttributes'); equal(user.get('accounts.length'), 0, "Accounts are rolled back"); equal(account.get('user'), null, 'Account does not have the user anymore'); }); -test("Rollbacking a created record works correctly when the belongsTo side has been created - async", function () { +test("Rollbacking attributes of a created record works correctly when the belongsTo side has been created - async", function () { var message, user; run(function() { message = store.push('message', { id: 2, title: 'EmberFest was great' }); @@ -559,7 +559,7 @@ test("Rollbacking a created record works correctly when the belongsTo side has b run(function() { user.get('messages').then(function(messages) { messages.pushObject(message); - user.rollback(); + user.rollbackAttributes(); message.get('user').then(function(fetchedUser) { equal(fetchedUser, null, 'Message does not have the user anymore'); }); @@ -571,7 +571,7 @@ test("Rollbacking a created record works correctly when the belongsTo side has b }); }); -test("Rollbacking a created record works correctly when the belongsTo side has been created - sync", function () { +test("Rollbacking attributes of a created record works correctly when the belongsTo side has been created - sync", function () { var account, user; run(function() { account = store.push('account', { id: 2 , state: 'lonely' }); @@ -580,7 +580,7 @@ test("Rollbacking a created record works correctly when the belongsTo side has b run(function() { user.get('accounts').pushObject(account); }); - run(user, 'rollback'); + run(user, 'rollbackAttributes'); equal(user.get('accounts.length'), undefined, "User does not have the account anymore"); equal(account.get('user'), null, 'Account does not have the user anymore'); }); diff --git a/packages/ember-data/tests/integration/relationships/one-to-one-test.js b/packages/ember-data/tests/integration/relationships/one-to-one-test.js index 74648cada5b..57df05c02c0 100644 --- a/packages/ember-data/tests/integration/relationships/one-to-one-test.js +++ b/packages/ember-data/tests/integration/relationships/one-to-one-test.js @@ -347,10 +347,10 @@ test("When deleting a record that has a belongsTo relationship, the record is re }); /* -Rollback tests +Rollback attributes tests */ -test("Rollbacking a deleted record restores the relationship on both sides - async", function () { +test("Rollbacking attributes of deleted record restores the relationship on both sides - async", function () { var stanley, stanleysFriend; run(function() { stanley = store.push('user', { id: 1, name: 'Stanley', bestFriend: 2 }); @@ -360,7 +360,7 @@ test("Rollbacking a deleted record restores the relationship on both sides - asy stanley.deleteRecord(); }); run(function() { - stanley.rollback(); + stanley.rollbackAttributes(); stanleysFriend.get('bestFriend').then(function(fetchedUser) { equal(fetchedUser, stanley, 'Stanley got rollbacked correctly'); }); @@ -370,7 +370,7 @@ test("Rollbacking a deleted record restores the relationship on both sides - asy }); }); -test("Rollbacking a deleted record restores the relationship on both sides - sync", function () { +test("Rollbacking attributes of deleted record restores the relationship on both sides - sync", function () { var job, user; run(function() { job = store.push('job', { id: 2 , isGood: true }); @@ -378,20 +378,20 @@ test("Rollbacking a deleted record restores the relationship on both sides - syn }); run(function() { job.deleteRecord(); - job.rollback(); + job.rollbackAttributes(); }); equal(user.get('job'), job, 'Job got rollbacked correctly'); equal(job.get('user'), user, 'Job still has the user'); }); -test("Rollbacking a created record removes the relationship on both sides - async", function () { +test("Rollbacking attributes of created record removes the relationship on both sides - async", function () { var stanleysFriend, stanley; run(function() { stanleysFriend = store.push('user', { id: 2, name: "Stanley's friend" }); stanley = store.createRecord('user', { bestFriend: stanleysFriend }); }); run(function() { - stanley.rollback(); + stanley.rollbackAttributes(); stanleysFriend.get('bestFriend').then(function(fetchedUser) { equal(fetchedUser, null, 'Stanley got rollbacked correctly'); }); @@ -401,14 +401,14 @@ test("Rollbacking a created record removes the relationship on both sides - asyn }); }); -test("Rollbacking a created record removes the relationship on both sides - sync", function () { +test("Rollbacking attributes of created record removes the relationship on both sides - sync", function () { var user, job; run(function() { user = store.push('user', { id: 1, name: 'Stanley' }); job = store.createRecord('job', { user: user }); }); run(function() { - job.rollback(); + job.rollbackAttributes(); }); equal(user.get('job'), null, 'Job got rollbacked correctly'); equal(job.get('user'), null, 'Job does not have user anymore'); diff --git a/packages/ember-data/tests/unit/model-test.js b/packages/ember-data/tests/unit/model-test.js index 58aa5d8a949..a4dce35c43a 100644 --- a/packages/ember-data/tests/unit/model-test.js +++ b/packages/ember-data/tests/unit/model-test.js @@ -219,9 +219,9 @@ test("changedAttributes() return correct values", function() { deepEqual({ name: [undefined, 'Tomster'], likes: ['JavaScript', 'Ember.js'] }, mascot.changedAttributes(), 'attributes has changed'); run(function() { - mascot.rollback(); + mascot.rollbackAttributes(); }); - deepEqual({}, mascot.changedAttributes(), 'after rollback there are no changes'); + deepEqual({}, mascot.changedAttributes(), 'after rollback attributes there are no changes'); }); test("a DS.Model does not require an attribute type", function() { diff --git a/packages/ember-data/tests/unit/model/rollback-attributes-test.js b/packages/ember-data/tests/unit/model/rollback-attributes-test.js new file mode 100644 index 00000000000..408305f306d --- /dev/null +++ b/packages/ember-data/tests/unit/model/rollback-attributes-test.js @@ -0,0 +1,376 @@ +var env, store, Person, Dog; +var run = Ember.run; + +module("unit/model/rollbackAttributes - model.rollbackAttributes()", { + setup: function() { + Person = DS.Model.extend({ + firstName: DS.attr(), + lastName: DS.attr() + }); + + env = setupStore({ person: Person }); + store = env.store; + } +}); + +test("changes to attributes can be rolled back", function() { + var person; + run(function() { + person = store.push('person', { id: 1, firstName: "Tom", lastName: "Dale" }); + person.set('firstName', "Thomas"); + }); + + equal(person.get('firstName'), "Thomas"); + + run(function() { + person.rollbackAttributes(); + }); + + equal(person.get('firstName'), "Tom"); + equal(person.get('isDirty'), false); +}); + +test("changes to unassigned attributes can be rolled back", function() { + var person; + run(function() { + person = store.push('person', { id: 1, lastName: "Dale" }); + person.set('firstName', "Thomas"); + }); + + equal(person.get('firstName'), "Thomas"); + + run(function() { + person.rollbackAttributes(); + }); + + equal(person.get('firstName'), undefined); + equal(person.get('isDirty'), false); +}); + +test("changes to attributes made after a record is in-flight only rolls back the local changes", function() { + env.adapter.updateRecord = function(store, type, snapshot) { + // Make sure the save is async + return new Ember.RSVP.Promise(function(resolve, reject) { + Ember.run.later(null, resolve, 15); + }); + }; + var person; + + run(function() { + person = store.push('person', { id: 1, firstName: "Tom", lastName: "Dale" }); + person.set('firstName', "Thomas"); + }); + + Ember.run(function() { + var saving = person.save(); + + equal(person.get('firstName'), "Thomas"); + + person.set('lastName', "Dolly"); + + equal(person.get('lastName'), "Dolly"); + + person.rollbackAttributes(); + + equal(person.get('firstName'), "Thomas"); + equal(person.get('lastName'), "Dale"); + equal(person.get('isSaving'), true); + + saving.then(async(function() { + equal(person.get('isDirty'), false, "The person is now clean"); + })); + }); +}); + +test("a record's changes can be made if it fails to save", function() { + env.adapter.updateRecord = function(store, type, snapshot) { + return Ember.RSVP.reject(); + }; + var person; + + run(function() { + person = store.push('person', { id: 1, firstName: "Tom", lastName: "Dale" }); + person.set('firstName', "Thomas"); + }); + + deepEqual(person.changedAttributes(), { firstName: ["Tom", "Thomas"] }); + + run(function() { + person.save().then(null, function() { + equal(person.get('isError'), true); + deepEqual(person.changedAttributes(), { firstName: ["Tom", "Thomas"] }); + + person.rollbackAttributes(); + + equal(person.get('firstName'), "Tom"); + equal(person.get('isError'), false); + deepEqual(person.changedAttributes(), {}); + }); + }); +}); + +test("a deleted record's attributes can be rollbacked if it fails to save, record arrays are updated accordingly", function() { + expect(7); + env.adapter.deleteRecord = function(store, type, snapshot) { + return Ember.RSVP.reject(); + }; + var person, people; + + run(function() { + person = store.push('person', { id: 1, firstName: "Tom", lastName: "Dale" }); + people = store.peekAll('person'); + }); + + run(function() { + person.deleteRecord(); + }); + equal(people.get('length'), 0, "a deleted record does not appear in record array anymore"); + equal(people.objectAt(0), null, "a deleted record does not appear in record array anymore"); + + run(function() { + person.save().then(null, function() { + equal(person.get('isError'), true); + equal(person.get('isDeleted'), true); + run(function() { + person.rollbackAttributes(); + }); + equal(person.get('isDeleted'), false); + equal(person.get('isError'), false); + }).then(function() { + equal(people.get('length'), 1, "the underlying record array is updated accordingly in an asynchronous way"); + }); + }); +}); + +test("new record's attributes can be rollbacked", function() { + var person; + + run(function() { + person = store.createRecord('person', { id: 1 }); + }); + + equal(person.get('isNew'), true, "must be new"); + equal(person.get('isDirty'), true, "must be dirty"); + + Ember.run(person, 'rollbackAttributes'); + + equal(person.get('isNew'), false, "must not be new"); + equal(person.get('isDirty'), false, "must not be dirty"); + equal(person.get('isDeleted'), true, "must be deleted"); +}); + +test("invalid new record's attributes can be rollbacked", function() { + var person; + var adapter = DS.RESTAdapter.extend({ + ajax: function(url, type, hash) { + var adapter = this; + + return new Ember.RSVP.Promise(function(resolve, reject) { + /* If InvalidError is passed back in the reject it will throw the + exception which will bubble up the call stack (crashing the test) + instead of hitting the failure route of the promise. + So wrapping the reject in an Ember.run.next makes it so save + completes without failure and the failure hits the failure route + of the promise instead of crashing the save. */ + Ember.run.next(function() { + reject(adapter.ajaxError({ name: 'is invalid' })); + }); + }); + }, + + ajaxError: function(jqXHR) { + return new DS.InvalidError(jqXHR); + } + }); + + env = setupStore({ person: Person, adapter: adapter }); + + run(function() { + person = env.store.createRecord('person', { id: 1 }); + }); + + equal(person.get('isNew'), true, "must be new"); + equal(person.get('isDirty'), true, "must be dirty"); + + run(function() { + person.save().then(null, async(function() { + equal(person.get('isValid'), false); + person.rollbackAttributes(); + + equal(person.get('isNew'), false, "must not be new"); + equal(person.get('isDirty'), false, "must not be dirty"); + equal(person.get('isDeleted'), true, "must be deleted"); + })); + }); +}); + +test("deleted record's attributes can be rollbacked", function() { + var person, people; + + run(function() { + person = store.push('person', { id: 1 }); + people = store.peekAll('person'); + person.deleteRecord(); + }); + + equal(people.get('length'), 0, "a deleted record does not appear in record array anymore"); + equal(people.objectAt(0), null, "a deleted record does not appear in record array anymore"); + + equal(person.get('isDeleted'), true, "must be deleted"); + + run(function() { + person.rollbackAttributes(); + }); + equal(people.get('length'), 1, "the rollbacked record should appear again in the record array"); + equal(person.get('isDeleted'), false, "must not be deleted"); + equal(person.get('isDirty'), false, "must not be dirty"); +}); + +test("invalid record's attributes can be rollbacked", function() { + Dog = DS.Model.extend({ + name: DS.attr() + }); + + var adapter = DS.RESTAdapter.extend({ + ajax: function(url, type, hash) { + var adapter = this; + + return new Ember.RSVP.Promise(function(resolve, reject) { + /* If InvalidError is passed back in the reject it will throw the + exception which will bubble up the call stack (crashing the test) + instead of hitting the failure route of the promise. + So wrapping the reject in an Ember.run.next makes it so save + completes without failure and the failure hits the failure route + of the promise instead of crashing the save. */ + Ember.run.next(function() { + reject(adapter.ajaxError({ name: 'is invalid' })); + }); + }); + }, + + ajaxError: function(jqXHR) { + return new DS.InvalidError(jqXHR); + } + }); + + env = setupStore({ dog: Dog, adapter: adapter }); + var dog; + run(function() { + dog = env.store.push('dog', { id: 1, name: "Pluto" }); + dog.set('name', "is a dwarf planet"); + }); + + run(function() { + dog.save().then(null, async(function() { + dog.rollbackAttributes(); + + equal(dog.get('name'), "Pluto"); + ok(dog.get('isValid')); + })); + }); +}); + +test("invalid record's attributes rolled back to correct state after set", function() { + Dog = DS.Model.extend({ + name: DS.attr(), + breed: DS.attr() + }); + + var adapter = DS.RESTAdapter.extend({ + ajax: function(url, type, hash) { + var adapter = this; + + return new Ember.RSVP.Promise(function(resolve, reject) { + /* If InvalidError is passed back in the reject it will throw the + exception which will bubble up the call stack (crashing the test) + instead of hitting the failure route of the promise. + So wrapping the reject in an Ember.run.next makes it so save + completes without failure and the failure hits the failure route + of the promise instead of crashing the save. */ + Ember.run.next(function() { + reject(adapter.ajaxError({ name: 'is invalid' })); + }); + }); + }, + + ajaxError: function(jqXHR) { + return new Error(jqXHR); + } + }); + + env = setupStore({ dog: Dog, adapter: adapter }); + var dog; + run(function() { + dog = env.store.push('dog', { id: 1, name: "Pluto", breed: "Disney" }); + dog.set('name', "is a dwarf planet"); + dog.set('breed', 'planet'); + }); + + run(function() { + dog.save().then(null, async(function() { + equal(dog.get('name'), "is a dwarf planet"); + equal(dog.get('breed'), "planet"); + + run(function() { + dog.set('name', 'Seymour Asses'); + }); + + equal(dog.get('name'), "Seymour Asses"); + equal(dog.get('breed'), "planet"); + + run(function() { + dog.rollbackAttributes(); + }); + + equal(dog.get('name'), "Pluto"); + equal(dog.get('breed'), "Disney"); + ok(dog.get('isValid')); + })); + }); +}); + +test("when destroying a record setup the record state to invalid, the record's attributes can be rollbacked", function() { + Dog = DS.Model.extend({ + name: DS.attr() + }); + + var adapter = DS.RESTAdapter.extend({ + ajax: function(url, type, hash) { + var adapter = this; + + return new Ember.RSVP.Promise(function(resolve, reject) { + Ember.run.next(function() { + reject(adapter.ajaxError({ name: 'is invalid' })); + }); + }); + }, + + ajaxError: function(jqXHR) { + return new DS.InvalidError(jqXHR); + } + }); + + env = setupStore({ dog: Dog, adapter: adapter }); + var dog; + run(function() { + dog = env.store.push('dog', { id: 1, name: "Pluto" }); + }); + + run(function() { + dog.destroyRecord().then(null, async(function() { + + + equal(dog.get('isError'), false, "must not be error"); + equal(dog.get('isDeleted'), true, "must be deleted"); + equal(dog.get('isValid'), false, "must not be valid"); + ok(dog.get('errors.length') > 0, "must have errors"); + + dog.rollbackAttributes(); + + equal(dog.get('isError'), false, "must not be error after `rollbackAttributes`"); + equal(dog.get('isDeleted'), false, "must not be deleted after `rollbackAttributes`"); + equal(dog.get('isValid'), true, "must be valid after `rollbackAttributes`"); + ok(dog.get('errors.length') === 0, "must not have errors"); + })); + }); +}); diff --git a/packages/ember-data/tests/unit/model/rollback-test.js b/packages/ember-data/tests/unit/model/rollback-test.js index 0095245e347..8e1b98ec73e 100644 --- a/packages/ember-data/tests/unit/model/rollback-test.js +++ b/packages/ember-data/tests/unit/model/rollback-test.js @@ -1,7 +1,7 @@ -var env, store, Person, Dog; +var env, store, Person; var run = Ember.run; -module("unit/model/rollback - model.rollback()", { +module("unit/model/rollback - model.rollback() - deprecated", { setup: function() { Person = DS.Model.extend({ firstName: DS.attr(), @@ -13,364 +13,24 @@ module("unit/model/rollback - model.rollback()", { } }); -test("changes to attributes can be rolled back", function() { +test("changes to attributes can be rolled back - deprecated", function() { var person; run(function() { - person = store.push('person', { id: 1, firstName: "Tom", lastName: "Dale" }); - person.set('firstName', "Thomas"); + person = store.push('person', { id: 1, firstName: 'Tom', lastName: 'Dale' }); + person.set('firstName', 'Thomas'); }); - equal(person.get('firstName'), "Thomas"); + equal(person.get('firstName'), 'Thomas'); - run(function() { - person.rollback(); - }); - - equal(person.get('firstName'), "Tom"); - equal(person.get('isDirty'), false); -}); - -test("changes to unassigned attributes can be rolled back", function() { - var person; - run(function() { - person = store.push('person', { id: 1, lastName: "Dale" }); - person.set('firstName', "Thomas"); - }); - - equal(person.get('firstName'), "Thomas"); - - run(function() { - person.rollback(); - }); - - equal(person.get('firstName'), undefined); - equal(person.get('isDirty'), false); -}); - -test("changes to attributes made after a record is in-flight only rolls back the local changes", function() { - env.adapter.updateRecord = function(store, type, snapshot) { - // Make sure the save is async - return new Ember.RSVP.Promise(function(resolve, reject) { - Ember.run.later(null, resolve, 15); - }); - }; - var person; - - run(function() { - person = store.push('person', { id: 1, firstName: "Tom", lastName: "Dale" }); - person.set('firstName', "Thomas"); - }); - - Ember.run(function() { - var saving = person.save(); - - equal(person.get('firstName'), "Thomas"); - - person.set('lastName', "Dolly"); - - equal(person.get('lastName'), "Dolly"); - - person.rollback(); - - equal(person.get('firstName'), "Thomas"); - equal(person.get('lastName'), "Dale"); - equal(person.get('isSaving'), true); - - saving.then(async(function() { - equal(person.get('isDirty'), false, "The person is now clean"); - })); - }); -}); - -test("a record's changes can be made if it fails to save", function() { - env.adapter.updateRecord = function(store, type, snapshot) { - return Ember.RSVP.reject(); - }; - var person; - - run(function() { - person = store.push('person', { id: 1, firstName: "Tom", lastName: "Dale" }); - person.set('firstName', "Thomas"); - }); - - deepEqual(person.changedAttributes(), { firstName: ["Tom", "Thomas"] }); - - run(function() { - person.save().then(null, function() { - equal(person.get('isError'), true); - deepEqual(person.changedAttributes(), { firstName: ["Tom", "Thomas"] }); - - person.rollback(); - - equal(person.get('firstName'), "Tom"); - equal(person.get('isError'), false); - deepEqual(person.changedAttributes(), {}); - }); - }); -}); - -test("a deleted record can be rollbacked if it fails to save, record arrays are updated accordingly", function() { - expect(7); - env.adapter.deleteRecord = function(store, type, snapshot) { - return Ember.RSVP.reject(); - }; - var person, people; - - run(function() { - person = store.push('person', { id: 1, firstName: "Tom", lastName: "Dale" }); - people = store.peekAll('person'); - }); - - run(function() { - person.deleteRecord(); - }); - equal(people.get('length'), 0, "a deleted record does not appear in record array anymore"); - equal(people.objectAt(0), null, "a deleted record does not appear in record array anymore"); - - run(function() { - person.save().then(null, function() { - equal(person.get('isError'), true); - equal(person.get('isDeleted'), true); + expectDeprecation( + function() { run(function() { person.rollback(); }); - equal(person.get('isDeleted'), false); - equal(person.get('isError'), false); - }).then(function() { - equal(people.get('length'), 1, "the underlying record array is updated accordingly in an asynchronous way"); - }); - }); -}); - -test("new record can be rollbacked", function() { - var person; - - run(function() { - person = store.createRecord('person', { id: 1 }); - }); - - equal(person.get('isNew'), true, "must be new"); - equal(person.get('isDirty'), true, "must be dirty"); - - Ember.run(person, 'rollback'); - - equal(person.get('isNew'), false, "must not be new"); - equal(person.get('isDirty'), false, "must not be dirty"); - equal(person.get('isDeleted'), true, "must be deleted"); -}); - -test("invalid new record can be rollbacked", function() { - var person; - var adapter = DS.RESTAdapter.extend({ - ajax: function(url, type, hash) { - var adapter = this; - - return new Ember.RSVP.Promise(function(resolve, reject) { - /* If InvalidError is passed back in the reject it will throw the - exception which will bubble up the call stack (crashing the test) - instead of hitting the failure route of the promise. - So wrapping the reject in an Ember.run.next makes it so save - completes without failure and the failure hits the failure route - of the promise instead of crashing the save. */ - Ember.run.next(function() { - reject(adapter.ajaxError({ name: 'is invalid' })); - }); - }); - }, - - ajaxError: function(jqXHR) { - return new DS.InvalidError(jqXHR); - } - }); - - env = setupStore({ person: Person, adapter: adapter }); - - run(function() { - person = env.store.createRecord('person', { id: 1 }); - }); - - equal(person.get('isNew'), true, "must be new"); - equal(person.get('isDirty'), true, "must be dirty"); - - run(function() { - person.save().then(null, async(function() { - equal(person.get('isValid'), false); - person.rollback(); - - equal(person.get('isNew'), false, "must not be new"); - equal(person.get('isDirty'), false, "must not be dirty"); - equal(person.get('isDeleted'), true, "must be deleted"); - })); - }); -}); - -test("deleted record can be rollbacked", function() { - var person, people; - - run(function() { - person = store.push('person', { id: 1 }); - people = store.peekAll('person'); - person.deleteRecord(); - }); - - equal(people.get('length'), 0, "a deleted record does not appear in record array anymore"); - equal(people.objectAt(0), null, "a deleted record does not appear in record array anymore"); - - equal(person.get('isDeleted'), true, "must be deleted"); - - run(function() { - person.rollback(); - }); - equal(people.get('length'), 1, "the rollbacked record should appear again in the record array"); - equal(person.get('isDeleted'), false, "must not be deleted"); - equal(person.get('isDirty'), false, "must not be dirty"); -}); - -test("invalid record can be rollbacked", function() { - Dog = DS.Model.extend({ - name: DS.attr() - }); - - var adapter = DS.RESTAdapter.extend({ - ajax: function(url, type, hash) { - var adapter = this; - - return new Ember.RSVP.Promise(function(resolve, reject) { - /* If InvalidError is passed back in the reject it will throw the - exception which will bubble up the call stack (crashing the test) - instead of hitting the failure route of the promise. - So wrapping the reject in an Ember.run.next makes it so save - completes without failure and the failure hits the failure route - of the promise instead of crashing the save. */ - Ember.run.next(function() { - reject(adapter.ajaxError({ name: 'is invalid' })); - }); - }); - }, - - ajaxError: function(jqXHR) { - return new DS.InvalidError(jqXHR); - } - }); - - env = setupStore({ dog: Dog, adapter: adapter }); - var dog; - run(function() { - dog = env.store.push('dog', { id: 1, name: "Pluto" }); - dog.set('name', "is a dwarf planet"); - }); - - run(function() { - dog.save().then(null, async(function() { - dog.rollback(); - - equal(dog.get('name'), "Pluto"); - ok(dog.get('isValid')); - })); - }); -}); - -test("invalid record is rolled back to correct state after set", function() { - Dog = DS.Model.extend({ - name: DS.attr(), - breed: DS.attr() - }); - - var adapter = DS.RESTAdapter.extend({ - ajax: function(url, type, hash) { - var adapter = this; - - return new Ember.RSVP.Promise(function(resolve, reject) { - /* If InvalidError is passed back in the reject it will throw the - exception which will bubble up the call stack (crashing the test) - instead of hitting the failure route of the promise. - So wrapping the reject in an Ember.run.next makes it so save - completes without failure and the failure hits the failure route - of the promise instead of crashing the save. */ - Ember.run.next(function() { - reject(adapter.ajaxError({ name: 'is invalid' })); - }); - }); }, + 'Using model.rollback() has been deprecated. Use model.rollbackAttributes() to discard any unsaved changes to a model.' + ); - ajaxError: function(jqXHR) { - return new Error(jqXHR); - } - }); - - env = setupStore({ dog: Dog, adapter: adapter }); - var dog; - run(function() { - dog = env.store.push('dog', { id: 1, name: "Pluto", breed: "Disney" }); - dog.set('name', "is a dwarf planet"); - dog.set('breed', 'planet'); - }); - - run(function() { - dog.save().then(null, async(function() { - equal(dog.get('name'), "is a dwarf planet"); - equal(dog.get('breed'), "planet"); - - run(function() { - dog.set('name', 'Seymour Asses'); - }); - - equal(dog.get('name'), "Seymour Asses"); - equal(dog.get('breed'), "planet"); - - run(function() { - dog.rollback(); - }); - - equal(dog.get('name'), "Pluto"); - equal(dog.get('breed'), "Disney"); - ok(dog.get('isValid')); - })); - }); -}); - -test("when destroying a record setup the record state to invalid, the record can be rollbacked", function() { - Dog = DS.Model.extend({ - name: DS.attr() - }); - - var adapter = DS.RESTAdapter.extend({ - ajax: function(url, type, hash) { - var adapter = this; - - return new Ember.RSVP.Promise(function(resolve, reject) { - Ember.run.next(function() { - reject(adapter.ajaxError({ name: 'is invalid' })); - }); - }); - }, - - ajaxError: function(jqXHR) { - return new DS.InvalidError(jqXHR); - } - }); - - env = setupStore({ dog: Dog, adapter: adapter }); - var dog; - run(function() { - dog = env.store.push('dog', { id: 1, name: "Pluto" }); - }); - - run(function() { - dog.destroyRecord().then(null, async(function() { - - - equal(dog.get('isError'), false, "must not be error"); - equal(dog.get('isDeleted'), true, "must be deleted"); - equal(dog.get('isValid'), false, "must not be valid"); - ok(dog.get('errors.length') > 0, "must have errors"); - - dog.rollback(); - - equal(dog.get('isError'), false, "must not be error after `rollback`"); - equal(dog.get('isDeleted'), false, "must not be deleted after `rollback`"); - equal(dog.get('isValid'), true, "must be valid after `rollback`"); - ok(dog.get('errors.length') === 0, "must not have errors"); - })); - }); + equal(person.get('firstName'), 'Tom'); + equal(person.get('isDirty'), false); });