Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add notDirty mapping option #574

Closed
wants to merge 1 commit into from

4 participants

@ppcano

Adding a notDirty property to a model property via the map API, it
allows the record property value be updated and cause the record does not
become dirty.

    Adapter.map(App.Person, {
      firstName: { notDirty: true }
    });

This feature can be used for example, whenever a client will not
update these model properties to the remote server, although it updates
its local copy to improve the application user experience.

@ppcano ppcano Add notDirty mapping option
Adding a notDirty property to a model property via the map API, it
allows the record property value be updated and cause the record does not
become dirty.

```js
    Adapter.map(App.Person, {
      firstName: { notDirty: true }
    });
```

This feature can be used for example, whenever a client will not
update these model properties to the remote server, although it updates
its local copy to improve the application user experience.
424fc60
@mehulkar

I like this for associations, not sure if I'd need it for any other property though. If it doesn't need to persist to the server, then it doesn't need to be a DS.attr property?

@wagenet
Owner

@mehulkar makes a good point.

@ppcano

@mehulkar, You are right, and in my cases i always use it with the flag excludeSerialize:

Yn.Adapter.map('Yn.User', {
  channels: { notDirty: true, excludeSerialize: true},
  friendsCount: { notDirty: true, excludeSerialize: true}
});

My first implementation was cache whose implementation was the sum of notDirty and excludeSerializer, but i didn't create the PR because i had doubts whether the mapping convention was correct.

Yn.Adapter.map('Yn.User', {
  channels: {cache: true},
  friendsCount: { cache: true}
});

Thoughts?

@mehulkar

How are channels and friendsCount set on app load? It seems like you can do one of these two things:

Yn.User = DS.Model.extend({
  channels: null, // if it's not a DS.attr it won't be sent received from server.
  friendsCount: null, // or however else you set them
})

or use the dirtyRecordsForHasMany hook in the case of channels. friendsCount sounds more like a computed property to me.

YN.User = DS.RESTAdapter.extend({
  dirtyRecordsForHasManyChange: function(dirtySet, child, relationship) {
    if (parent.constructor === YN.User && relationship.hasManyName == "channels") {
      return undefined;
    }  else {
      dirtySet.add(child);
    }
  }
})
@ppcano

@mehulkar, channels and friendCount are retrieved by the server and updated in the client as well, because of the server's update, i thought they must be a DS.attr.

I prefer using the meta.option property than updating dirtyRecordsForHasMany for each model, cause this has been a common pattern in my applicaction, used it in two cases:

  • working with embedded records. We assign the object to relation immediately to improve the UX and create the embedded object individually.
  • caching hasMany length because of this bug.

Now, with the new updates and if both problems can be solved, perhaps the convention is not necessary, I just wanted to point the scenario, anyway this can be implemented through ember-data hooks.

@tomdale
Owner

This feature seems OK, but it seems like the correct semantic is to configure the serializer to not serialize specific properties when saving. This seems identical to the case where has-many relationships are loaded in the parent (as a performance optimization) but are not serialized when returning to the server (unless they are embedded).

@tomdale
Owner

Closing due to inactivity.

@tomdale tomdale closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 2, 2013
  1. @ppcano

    Add notDirty mapping option

    ppcano authored
    Adding a notDirty property to a model property via the map API, it
    allows the record property value be updated and cause the record does not
    become dirty.
    
    ```js
        Adapter.map(App.Person, {
          firstName: { notDirty: true }
        });
    ```
    
    This feature can be used for example, whenever a client will not
    update these model properties to the remote server, although it updates
    its local copy to improve the application user experience.
This page is out of date. Refresh to see the latest.
View
17 packages/ember-data/lib/system/adapter.js
@@ -442,7 +442,8 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
},
dirtyRecordsForAttributeChange: function(dirtySet, record, attributeName, newValue, oldValue) {
- if (newValue !== oldValue) {
+ var notDirty = get(this, 'serializer').mappingOption(record.constructor, attributeName, 'notDirty');
+ if (!notDirty && newValue !== oldValue) {
// If this record is embedded, add its parent
// to the dirty set.
this.dirtyRecordsForRecordChange(dirtySet, record);
@@ -453,12 +454,18 @@ DS.Adapter = Ember.Object.extend(DS._Mappable, {
dirtySet.add(record);
},
- dirtyRecordsForBelongsToChange: function(dirtySet, child) {
- this.dirtyRecordsForRecordChange(dirtySet, child);
+ dirtyRecordsForBelongsToChange: function(dirtySet, child, attributeName) {
+ var notDirty = get(this, 'serializer').mappingOption(child.constructor, attributeName, 'notDirty');
+ if (!notDirty ) {
+ this.dirtyRecordsForRecordChange(dirtySet, child);
+ }
},
- dirtyRecordsForHasManyChange: function(dirtySet, parent) {
- this.dirtyRecordsForRecordChange(dirtySet, parent);
+ dirtyRecordsForHasManyChange: function(dirtySet, parent, attributeName) {
+ var notDirty = get(this, 'serializer').mappingOption(parent.constructor, attributeName, 'notDirty');
+ if (!notDirty ) {
+ this.dirtyRecordsForRecordChange(dirtySet, parent);
+ }
},
/**
View
4 packages/ember-data/lib/system/relationships/one_to_many_change.js
@@ -408,11 +408,11 @@ DS.RelationshipChange.prototype = {
// of ember-data, and should be done as new infrastructure, not
// a one-off hack. [tomhuda]
if (parentRecord && get(parentRecord, 'isLoaded')) {
- this.store.recordHasManyDidChange(dirtySet, parentRecord, this);
+ this.store.recordHasManyDidChange(dirtySet, parentRecord, hasManyName);
}
if (child) {
- this.store.recordBelongsToDidChange(dirtySet, child, this);
+ this.store.recordBelongsToDidChange(dirtySet, child, belongsToName);
}
dirtySet.forEach(function(record) {
View
93 packages/ember-data/tests/integration/dirtiness_test.js
@@ -13,6 +13,7 @@ module("Attribute Changes and Dirtiness", {
firstName: DS.attr('string')
});
}
+
});
test("By default, if a record's attribute is changed, it becomes dirty", function() {
@@ -169,3 +170,95 @@ test("When adding a newly created record to a hasMany relationship, the parent s
equal(post.get('isDirty'), false, "The record should no longer be dirty");
equal(post.get('isSaving'), false, "The record should no longer be saving");
});
+
+var App;
+
+module("Attribute notDirty", {
+ setup: function() {
+ App = Ember.Namespace.create({name: 'App'});
+
+ App.Person = DS.Model.extend({
+ firstName: DS.attr('string')
+ });
+ App.Post = DS.Model.extend({
+ title: DS.attr('string')
+ });
+ App.Comment = DS.Model.extend({
+ title: DS.attr('string'),
+ post: DS.belongsTo(App.Post)
+ });
+ App.Post.reopen({
+ comments: DS.hasMany(App.Comment)
+ });
+
+ var Adapter = DS.Adapter.extend();
+ Adapter.map(App.Person, {
+ firstName: { notDirty: true }
+ });
+ Adapter.map(App.Comment, {
+ post: { notDirty: true }
+ });
+ Adapter.map(App.Post, {
+ comments: { notDirty: true }
+ });
+
+ store = DS.Store.create({
+ adapter: Adapter.create()
+ });
+ },
+
+ teardown: function() {
+ App.destroy();
+ }
+
+});
+
+test("By default, if a notDirty attribute is changed, the record does not become dirty", function() {
+ store.load(App.Person, { id: 1, firstName: "Yehuda" });
+ var wycats = store.find(App.Person, 1);
+
+ wycats.set('firstName', "Brohuda");
+
+ ok(!wycats.get('isDirty'), "record hasn't become dirty");
+});
+
+test("If a notDirty attribute is changed, the record can be reloaded/updated with new data", function() {
+ store.load(App.Person, { id: 1, firstName: "Yehuda" });
+ var wycats = store.find(App.Person, 1);
+
+ wycats.set('firstName', "Brohuda");
+ equal( wycats.get('firstName'), "Brohuda", "record attribute value get updated");
+
+ store.load(App.Person, { id: 1, firstName: "Tiohuda" });
+ equal( wycats.get('firstName'), "Tiohuda", "record can be reloaded after updating a notDirty attribute");
+});
+
+test("If a notDirty belongsTo property changed, the record does not become dirty", function() {
+ store.load(App.Post, { id: 1, title: "Post 1"});
+ store.load(App.Post, { id: 2, title: "Post 2"});
+ var post1 = store.find(App.Post, 1);
+ var post2 = store.find(App.Post, 2);
+
+ store.load(App.Comment, { id: 1, title: "Comment 1", post: post1 });
+ var comment = store.find(App.Comment, 1);
+
+ ok( !comment.get('isDirty'), "created record is not dirty");
+ comment.set('post', post2);
+
+ ok( !comment.get('isDirty'), "record is not dirty after updating a NotDirty belongsTo property");
+});
+
+test("If a notDirty hasMany changed, the record does not become dirty", function() {
+ store.load(App.Comment, { id: 1, title: "Comment 1"});
+ store.load(App.Comment, { id: 2, title: "Comment 2"});
+ var comment1 = store.find(App.Comment, 1);
+ var comment2 = store.find(App.Comment, 2);
+
+ store.load(App.Post, { id: 1, title: "Post 1", comments: [comment1] });
+ var post = store.find(App.Post, 1);
+
+ ok( !post.get('isDirty'), "created record is not dirty");
+ post.get('comments').pushObject(comment2);
+
+ ok( !post.get('isDirty'), "record is not dirty after updating a NotDirty hasMany property");
+});
Something went wrong with that request. Please try again.