How to represent arrays within ember-data models?

26,044

Solution 1

Well... It was a little bit difficult but mixing all answers in this post I made it work.

Firstly, you should create a transform for the new type "array":

DS.ArrayTransform = DS.Transform.extend({
  deserialize: function(serialized) {
    return (Ember.typeOf(serialized) == "array")
        ? serialized 
        : [];
  },

  serialize: function(deserialized) {
    var type = Ember.typeOf(deserialized);
    if (type == 'array') {
        return deserialized
    } else if (type == 'string') {
        return deserialized.split(',').map(function(item) {
            return jQuery.trim(item);
        });
    } else {
        return [];
    }
  }
});

App.register("transform:array", DS.ArrayTransform);

Now, in your model, just use it as another attr:

App.myModel = Ember.Model.extend({
    name : DS.attr('string'),
    cont : DS.attr('array')
}

And we are done. Remember, when adding elements to the array, to use pushObject.

In a controller:

this.get('model.cont').pushObject('new Item');

I hope this helps someone.

Solution 2

I use a raw transform, which looks like this in ember-data revision 11:

DS.RESTAdapter.registerTransform('raw', {
    deserialize: function(serialized) {
        return serialized;
    },  
    serialize: function(deserialized) {
        return deserialized;
    }   
});

Then, within a model, I do this:

App.MyModel = Ember.Model.extend({
    anArray: DS.attr('raw')
});

and can use anArray like a regular array anywhere.

Solution 3

Here is an example of creating a custom array type in Ember-Data (version 10):

DS.JSONTransforms.array =

  # If the outgoing json is already a valid javascript array
  # then pass it through untouched. In all other cases, replace it
  # with an empty array.  This means null or undefined values
  # automatically become empty arrays when serializing this type.

  serialize: (jsonData)->
    if Em.typeOf(jsonData) is 'array' then jsonData else []


  # If the incoming data is a javascript array, pass it through.
  # If it is a string, then coerce it into an array by splitting
  # it on commas and trimming whitespace on each element.
  # Otherwise pass back an empty array.  This has the effect of
  # turning all other data types (including nulls and undefined
  # values) into empty arrays.

  deserialize: (externalData)->
    switch Em.typeOf(externalData)
      when 'array'  then return externalData
      when 'string' then return externalData.split(',').map((item)-> jQuery.trim(item))
      else               return []

Now you can use the custom type in a model attribute:

App.CalenderWeek = DS.Model.extend
  selected_days = DS.attr('array')

And now when you fetch a record with:

App.CalendarWeek.find(1)

both of these incoming json records will deserialize correctly into an Array:

{ selected_days: ['Monday', 'Tuesday', 'Saturday'] }

or

{ selected_days: 'Monday, Tuesday, Saturday' }

Solution 4

In Ember Data 1.0.0 Beta, one has been given the ability to "register" his or her custom transform "subclass". I'd prefer to refer to it as an extended DS.Transform object.

DS.ArrayTransform = DS.Transform.extend({
    deserialize: function(deserialized) {
        // ...
        return deserialized;
    },

    serialize: function(serialized) {
        // ...
        return serialized;
    }
});

App.register('transform:array', DS.ArrayTransform);

Solution 5

If you absolutely need a custom data structure to be exchanged with your server, you can enrich DS.attr.transforms and declare a new array codec, for example.

See source code for existing attribute codecs implementation. It is a good place to start adding your own.

Share:
26,044
dechov
Author by

dechov

Updated on September 12, 2020

Comments

  • dechov
    dechov over 3 years

    Is it necessary to use DS.hasMany pointing to a DS.Model when a model contains an array? Even if the array elements are not really models (no IDs or endpoints of their own)? Is there a better way?

    I am using DS.hasMany, but my extended DS.RESTAdapter is throwing me a 404 trying to access the model, even though I'm never calling find on it, and hasMany is called with { embedded: true }. I am seeing this error for the first time (apparently in connection with this model, since it goes away without it):

    Uncaught Error: assertion failed: Emptying a view in the inBuffer state is not allowed and should not happen under normal circumstances. Most likely there is a bug in your application. This may be due to excessive property change notifications. ember-latest.js:43
    

    What does this mean and what might be causing it?

    Here's the stack trace:

    Ember.assert ember-latest.js:43
    Ember.View.states.inBuffer.empty ember-latest.js:13644
    Ember.View.Ember.Object.extend.invokeForState ember-latest.js:12257
    Ember.CollectionView.Ember.ContainerView.extend.arrayWillChange ember-latest.js:14477
    invokeAction ember-latest.js:3193
    iterateSet ember-latest.js:3175
    sendEvent ember-latest.js:3323
    Ember.Array.Ember.Mixin.create.arrayContentWillChange ember-latest.js:6963
    Ember.ArrayProxy.Ember.Object.extend.arrangedContentArrayWillChange ember-latest.js:9281
    Ember.ArrayProxy.Ember.Object.extend._arrangedContentWillChange ember-latest.js:9235
    invokeAction ember-latest.js:3193
    iterateSet ember-latest.js:3175
    sendEvent ember-latest.js:3323
    notifyObservers ember-latest.js:1872
    Ember.notifyBeforeObservers ember-latest.js:2016
    propertyWillChange ember-latest.js:2594
    iterDeps ember-latest.js:2077
    dependentKeysWillChange ember-latest.js:2092
    propertyWillChange ember-latest.js:2592
    set ember-latest.js:1416
    DS.Model.Ember.Object.extend.dataDidChange ember-data-latest.js:3145
    Map.forEach ember-latest.js:1273
    OrderedSet.forEach ember-latest.js:1145
    Map.forEach ember-latest.js:1271
    DS.Model.Ember.Object.extend.dataDidChange ember-data-latest.js:3128
    invokeAction ember-latest.js:3193
    iterateSet ember-latest.js:3175
    sendEvent ember-latest.js:3323
    notifyObservers ember-latest.js:1872
    Ember.notifyObservers ember-latest.js:1999
    propertyDidChange ember-latest.js:2632
    Ember.Observable.Ember.Mixin.create.propertyDidChange ember-latest.js:7917
    Ember.Observable.Ember.Mixin.create.notifyPropertyChange ember-latest.js:7930
    didChangeData ember-data-latest.js:2053
    Ember.StateManager.Ember.State.extend.sendRecursively ember-latest.js:15446
    Ember.StateManager.Ember.State.extend.send ember-latest.js:15431
    DS.Model.Ember.Object.extend.send ember-data-latest.js:3058
    DS.Store.Ember.Object.extend.load ember-data-latest.js:1737
    DS.Store.Ember.Object.extend.loadMany ember-data-latest.js:1763
    embeddedFindRecord ember-data-latest.js:3434
    hasAssociation ember-data-latest.js:3459
    ComputedPropertyPrototype.get ember-latest.js:2968
    get ember-latest.js:1362
    getPath ember-latest.js:1484
    get ember-latest.js:1355
    getWithGlobals ember-latest.js:4041
    Binding.connect ember-latest.js:4140
    connectBindings ember-latest.js:4600
    finishPartial ember-latest.js:4610
    Class ember-latest.js:8315
    Ember.Mixin.create.create ember-latest.js:8457
    Ember.View.Ember.Object.extend.createChildView ember-latest.js:13179
    Ember.View.states.inBuffer.appendChild ember-latest.js:13622
    Ember.View.Ember.Object.extend.invokeForState ember-latest.js:12239
    Ember.View.Ember.Object.extend.appendChild ember-latest.js:13058
    EmberHandlebars.ViewHelper.Ember.Object.create.helper ember-latest.js:18687
    (anonymous function) ember-latest.js:18844
    (anonymous function) ember-latest.js:19043
    (anonymous function) ember-latest.js:19208
    (anonymous function)
    (anonymous function) handlebars-1.0.0.beta.6.js:1512
    Ember.View.Ember.Object.extend.render ember-latest.js:12223
    Ember.View.Ember.Object.extend.renderToBuffer ember-latest.js:12872
    Ember.View.states.inBuffer.appendChild ember-latest.js:13625
    Ember.View.Ember.Object.extend.invokeForState ember-latest.js:12239
    Ember.View.Ember.Object.extend.appendChild ember-latest.js:13058
    EmberHandlebars.ViewHelper.Ember.Object.create.helper ember-latest.js:18687
    (anonymous function) ember-latest.js:18844
    program2
    (anonymous function) handlebars-1.0.0.beta.6.js:1529
    Ember.View.Ember.Object.extend.render ember-latest.js:12223
    Ember._HandlebarsBoundView.Ember._MetamorphView.extend.render ember-latest.js:18075
    Ember.wrap.newFunc ember-latest.js:949
    Ember.View.Ember.Object.extend.renderToBuffer ember-latest.js:12872
    Ember.View.states.inBuffer.appendChild ember-latest.js:13625
    Ember.View.Ember.Object.extend.invokeForState ember-latest.js:12239
    Ember.View.Ember.Object.extend.appendChild ember-latest.js:13058
    bind ember-latest.js:18129
    (anonymous function) ember-latest.js:18199
    (anonymous function) ember-latest.js:18271
    program1
    (anonymous function) handlebars-1.0.0.beta.6.js:1529
    Ember.View.Ember.Object.extend.render ember-latest.js:12223
    Ember.View.Ember.Object.extend.renderToBuffer ember-latest.js:12872
    Ember.ContainerView.Ember.View.extend.render ember-latest.js:14078
    Ember.View.Ember.Object.extend.forEachChildView ember-latest.js:12486
    Ember.ContainerView.Ember.View.extend.render ember-latest.js:14077
    Ember.wrap.newFunc ember-latest.js:949
    Ember.View.Ember.Object.extend.renderToBuffer ember-latest.js:12872
    Ember.View.states.inBuffer.appendChild ember-latest.js:13625
    Ember.View.Ember.Object.extend.invokeForState ember-latest.js:12239
    Ember.View.Ember.Object.extend.appendChild ember-latest.js:13058
    EmberHandlebars.ViewHelper.Ember.Object.create.helper ember-latest.js:18687
    (anonymous function) ember-latest.js:18844
    (anonymous function) ember-latest.js:19043
    (anonymous function) ember-latest.js:19208
    (anonymous function)
    (anonymous function) handlebars-1.0.0.beta.6.js:1512
    Ember.View.Ember.Object.extend.render ember-latest.js:12223
    Ember.View.Ember.Object.extend.renderToBuffer ember-latest.js:12872
    Ember.ContainerView.Ember.View.extend.render ember-latest.js:14078
    Ember.View.Ember.Object.extend.forEachChildView ember-latest.js:12486
    Ember.ContainerView.Ember.View.extend.render ember-latest.js:14077
    Ember.wrap.newFunc ember-latest.js:949
    Ember.View.Ember.Object.extend.renderToBuffer ember-latest.js:12872
    Ember.View.states.inBuffer.appendChild ember-latest.js:13625
    Ember.View.Ember.Object.extend.invokeForState ember-latest.js:12257
    Ember.View.Ember.Object.extend.appendChild ember-latest.js:13058
    EmberHandlebars.ViewHelper.Ember.Object.create.helper ember-latest.js:18687
    (anonymous function) ember-latest.js:18844
    (anonymous function) ember-latest.js:19624
    (anonymous function) ember-latest.js:18167
    (anonymous function)
    (anonymous function) handlebars-1.0.0.beta.6.js:1512
    Ember.View.Ember.Object.extend.render ember-latest.js:12223
    Ember.View.Ember.Object.extend.renderToBuffer ember-latest.js:12872
    Ember.View.Ember.Object.extend.createElement ember-latest.js:12669
    Ember.View.states.preRender.insertElement ember-latest.js:13558
    Ember.View.Ember.Object.extend.invokeForState ember-latest.js:12257
    invoke ember-latest.js:3428
    iter ember-latest.js:3475
    RunLoop.flush ember-latest.js:3531
    RunLoop.end ember-latest.js:3447
    Ember.run.end ember-latest.js:3639
    autorun ember-latest.js:3705
    

    Thanks for any help.

    Update: This fiddle works (with example from docs), but how could those objects be represented if the tags aren't real models (i.e. don't have IDs)?

  • Braedon Wooding
    Braedon Wooding about 11 years
    This doesn't seem to work with EM 11. Shreyans submission does though.
  • Mike Aski
    Mike Aski about 11 years
    Indeed a good implementation. I've got the same in my apps... :-)
  • pooja upadhyay
    pooja upadhyay about 11 years
    If using this solution to render your data in a partial - look at the workaround here -> github.com/emberjs/ember.js/issues/1990#issuecomment-1385576‌​7
  • Pascal Zajac
    Pascal Zajac over 10 years
    This worked perfectly, thanks for the example and explanation.
  • Damon Bauer
    Damon Bauer over 10 years
    This worked perfect for my needs (a simple array in a FIXTURE), with an {{#each}} to display the array values. Thanks!
  • J. Mitchell
    J. Mitchell over 10 years
    This doesn't set the isDirty flag on the model when I change selected_days. Is this a known issue?
  • Patrick Fisher
    Patrick Fisher about 10 years
    This is the method shown in the documentation, so I'd call it the "Ember way": emberjs.com/api/data/classes/DS.Transform.html
  • aceofspades
    aceofspades about 10 years
    This syntax appears to be deprecated, see @PatrickFisher's link
  • BJ McDuck
    BJ McDuck almost 10 years
    Why not set this as the answer?
  • nruth
    nruth almost 9 years
    An advantage is this lets you set a DS.attr defaultValue, where as DS.attr(null, defalutValue: []) doesn't work. I don't see a deprecation notice? Which version / where on the page?
  • Gustavo Alexandre
    Gustavo Alexandre over 8 years
    Could you pl. let us know in which file this code needs to be added.
  • rxgx
    rxgx over 8 years
    @RajaNagendraKumar Add it to the file where you have access to DS variable. Do you have that as an import or global variable?
  • iOS dev
    iOS dev about 7 years
    @J.Mitchell were you able to fix the isDirty issue. Even, I cannot see the model getting updated with the values changed in the handlebars
  • iOS dev
    iOS dev about 7 years
    hey @BrandonJMcKay, how to edit the array and save it back to the payload. I dont see any bindings when editing the existing array.
  • BJ McDuck
    BJ McDuck about 7 years
    @iOSdev, umm... why not ask @Bommox? I didn't provide any information here, but I think you might be looking for a serializer, or an adapter. A transform modifies the data incoming/outgoing, but it's meant for specific data types. Nothing more. If you're using the word payload, I'm thinking you want a serializer, or an adapter.