Skip to content

live updated dirty tracking (attrs and rels) #140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions addon/models/resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,21 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, {
*/
_attributes: null,

/**
Array of changed attribute keys.

The Actual changed values (previous, changed) are stored on _attributes,
but we can not use that object or it's keys to create computed properties
that respond to changes (i.e. no live isDirty property).
This array is a simple list of attributes that are not in their original
state, and can be used to track the (dirty-)state of the resource.

@protected
@property _changedAttributes
@type Array
*/
_changedAttributes: null,

/**
Hash of relationships that were changed

Expand All @@ -112,6 +127,15 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, {
*/
_relationships: null,

/**
Array of changed relationship keys. Same use as _changedAttributes.

@protected
@property _changedRelationships
@type Array
*/
_changedRelationships: null,

/**
Flag for new instance, e.g. not persisted

Expand Down Expand Up @@ -139,7 +163,7 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, {
*/
_updateRelationshipsData(relation, ids) {
if (!Array.isArray(ids)) {
this._updateToOneRelationshipData(relation, ids);
this._updateToOneRelationshipData(relation, ids);
} else {
let existing = this._existingRelationshipData(relation);
if (!existing.length) {
Expand Down Expand Up @@ -178,7 +202,7 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, {
*/
_replaceRelationshipsData(relation, ids) {
if (!Array.isArray(ids)) {
this._updateToOneRelationshipData(relation, ids);
this._updateToOneRelationshipData(relation, ids);
} else {
let existing = this._existingRelationshipData(relation);
if (!existing.length) {
Expand Down Expand Up @@ -300,6 +324,19 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, {
ref.added.push({type: pluralize(relation), id: id});
}
}

// Track relationship changes
if (!this.get('isNew')) {
if (!this.get('_changedRelationships')) {
this.set('_changedRelationships', Ember.A([]));
}

if (!Ember.isEmpty(ref.added) ||
!Ember.isEmpty(ref.removals) ||
!Ember.isEmpty(ref.changed)) {
this.get('_changedRelationships').pushObject(relation);
}
}
},

/**
Expand Down Expand Up @@ -364,6 +401,18 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, {
ref.removals.pushObject({ type: pluralize(relation), id: id });
}
}

// Track relationship changes.
if (!this.get('isNew')) {
if (!this.get('_changedRelationships')) {
this.set('_changedRelationships', Ember.A([]));
}
if (!Ember.isEmpty(ref.added) ||
!Ember.isEmpty(ref.removals) ||
!Ember.isEmpty(ref.previous)) {
this.get('_changedRelationships').removeObject(relation);
}
}
},

/**
Expand Down Expand Up @@ -455,6 +504,7 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, {
delete this._attributes[attr];
}
}
this.set('_changedAttributes', Ember.A([]));
},

/**
Expand Down Expand Up @@ -499,6 +549,7 @@ const Resource = Ember.Object.extend(ResourceOperationsMixin, {
delete this._relationships[attr];
}
}
this.set('_changedRelationships', Ember.A([]));
},

/**
Expand Down
31 changes: 31 additions & 0 deletions addon/utils/attr.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,49 @@ export default function attr(type = 'any', mutable = true) {

set: function (key, value) {
const lastValue = this.get('attributes.' + key);
// Don't allow set on immutable values.
if (!_mutable) {
return immutableValue(key, value, lastValue);
}
// Don't do anything if same value is set.
if (value === lastValue) { return value; }

// Check value type.
assertType.call(this, key, value);

// Set value.
this.set('attributes.' + key, value);

// Track changes.
// Only on non-isNew resources, which are 'dirty' be default
if (!this.get('isNew')) {
// Initialize tracking object and array for this attribute.
this._attributes[key] = this._attributes[key] || {};
if (!this.get('_changedAttributes')) {
this.set('_changedAttributes', Ember.A([]));
}

// Track change(d key) and store previous/changed value.
// We (Ember.)Copy values to `previous` and `changed` to prevent both
// being a reference to the same object (and thus never showing up on
// computed property 'changedAttributes')
if (this._attributes[key].previous === undefined) {
// Value changed for the first time.
this._attributes[key].previous = Ember.copy(lastValue, true);
this.get('_changedAttributes').pushObject(key);
} else {
// Value changed again.
if (this._attributes[key].previous === value) {
// Value reverted to previous. No longer dirty. Remove from tracking.
this.get('_changedAttributes').removeObject(key);
} else if (this.get('_changedAttributes').indexOf(key) === -1){
// Value changed again, wasn't tracked anymore. Track it.
this.get('_changedAttributes').pushObject(key);
}
}

this._attributes[key].changed = Ember.copy(value, true);

let service = this.get('service');
if (service) {
service.trigger('attributeChanged', this);
Expand Down