How to handle object/array attributes


Hi all,

I have a rails model that’s translated to model definition like this:

export default DS.Model.extend({
  name: DS.attr('string'),
  settings: DS.attr(), // in rails, its a JSON object
  tags: DS.attr(), // in rails its again an Array object, which actually models a "set"

To make it easier to work with these attributes, I created special transfroms that converts settings into a special object and tags into a set.

However, when I want to update the attributes in settings, or add/remove items from tags, I always had to copy the original object, then update, and finally replace the original with the newly modified object. This way dirty tracking works. However, is there a way for me to update attributes or add/remove objects in place and still trigger dirty? And if there’s an API to mark things dirty, how would rollback work? Generally are there api where I can implement dirty tracking? Ideally what I would like to is the following

user = User.create();
// user.get('isDirty') === false; user.get('settings.isAwesome') === false;
user.get('settings').set('isAwesome', true);
// user.get('isDirty') === true; user.get('settings.isAwesome') === false;
// user.get('isDirty') === false; user.get('settings.isAwesome') === false;

Any thoughts?

Why there is no DS.attr('array')?

Thinking about this at a high level. I think in order to make this work, here are a few recommendations:

First, provide a special type of attr:

// note below we need a different `attr` is because the associated transform need to be given the model object
// see later sections why
export default DS.Model.extend({
  settings: DS.observedAttr('settings'), // settings is a special type of transform

Second, provide a special type of base transform

// in app/transforms/settings
export default DS.ObservedTransform.extend({
  serialize: ... // as normal
  deserialize: ... // as normal
  serializeOnChange: function(deserialized) {
     * the parent model will call this method when it is notified that this attribute has been changed. 
     * the result from this method will be in the changes array
     return this.serialize(deserialized); // default implementation is just serialize the attribute again
  deserializeOnRollback: function(serialized) {
     * the parent model will call this method when rollback happens, the transform will be able to 'restore'
     * to the original state
     return this.deserialize(serialized); // default implmentation is again the same as on first load

Finally, the DS.ObservedTransform should come with the following:

// in observed-transform
export default DS.Transform.extend({
  notifyChange: function() {
     * The following method on the model, once called, will use the serializeOnChange/deserializeOnChange
     * method to track dirtiness/store changes
  .. some other magic

Now with all the above stuff done, we could do the following in the settings transform

// in app/transforms/settings
let Settings = Ember.Object.extend({
  update: function(key, val) {
    this.set(key, val);

Settings.createWithTransform = function(transform, serialized) {
  let settings = Settings.create(serialized);
  settings.transform = transform;
  return settings;

export default DS.ObservedTransform.extend({
  deserialize: function(serialized) {
    return Settings.createWithTransform(this, serialized);

With all that, user.get('settings').update('isAwesome', true) should have the correct dirty/rollback behavior. Aside from the above could be sugarcoded more, does this work? All we need to do is to

  1. add method notifyObservedAttributeChanged to DS.Model
  2. change dirty tracking behavior

Does this sound doable?


I am still really interested whether the above post works or not.

(I think Ember Data seriously needs some native way to handle arrays and objects. You don’t always want a model for everything.)


I wrote an add on that handles what you are looking for