30

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)?

Jorgeblom
  • 3,330
  • 1
  • 23
  • 24
dechov
  • 1,833
  • 1
  • 15
  • 18
  • does simply define a usual array property not work ? – sly7_7 Aug 28 '12 at 22:46
  • I updated with a fiddle. How would I do that? – dechov Aug 29 '12 at 15:07
  • Hum, I think if you want your tags to be persisted, you have to describe them as a DS.Model. If not, then just declare an usual array in the Person model. Finally, perhaps I don't understand what you want... – sly7_7 Aug 29 '12 at 15:14
  • I've created a fiddle with Tag declared as a regular ember object: http://jsfiddle.net/Sly7/JBmuA/2/ – sly7_7 Aug 29 '12 at 15:19
  • The fixtures were an example of what I'd receive from the server. So, a regular ember object would do, but how can I attach it to the model? I do need to load it from the database, but I don't care about manipulating it. Is this possible / is there a best practice here? – dechov Aug 29 '12 at 15:31
  • I don't think it's possible without declaring it as a DS.Model, at least I don't know how to do this. Perhaps you can post an issue in ember-data repo, and luckily you would have an answer there... – sly7_7 Aug 29 '12 at 15:57
  • OK, I found that a recently closed issue ([#322](https://github.com/emberjs/data/issues/322)) references an old discussion ([#53](https://github.com/emberjs/data/issues/53)) that they "haven't forgotten about" -- I'm not sure if it's exactly the same issue or not. – dechov Aug 30 '12 at 19:20

7 Answers7

52

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');
starball
  • 20,030
  • 7
  • 43
  • 238
Jorgeblom
  • 3,330
  • 1
  • 23
  • 24
  • 1
    This worked perfectly, thanks for the example and explanation. – Pascal Zajac Oct 28 '13 at 10:04
  • 2
    Why not set this as the answer? – BJ McDuck Jul 01 '14 at 20:24
  • hey @BrandonJMcKay, how to edit the array and save it back to the payload. I dont see any bindings when editing the existing array. – iOS dev Mar 27 '17 at 17:28
  • @iOSdev, umm... why not ask @Bommox? I didn't provide any information here, but I think you might be looking for a [serializer](https://guides.emberjs.com/v2.12.0/models/customizing-serializers/), or an [adapter](https://guides.emberjs.com/v2.12.0/models/customizing-adapters/). 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. – BJ McDuck Mar 27 '17 at 22:07
29

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.

Shreyans
  • 871
  • 7
  • 20
  • This worked perfect for my needs (a simple array in a FIXTURE), with an {{#each}} to display the array values. Thanks! – Damon Bauer Dec 24 '13 at 11:41
  • This is the method shown in the documentation, so I'd call it the "Ember way": http://emberjs.com/api/data/classes/DS.Transform.html – Patrick Fisher Apr 07 '14 at 21:14
  • This syntax appears to be deprecated, see @PatrickFisher's link – aceofspades Apr 25 '14 at 15:45
  • 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? – nruth Aug 10 '15 at 12:33
20

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' }
Mayank Patel
  • 341
  • 2
  • 3
  • This doesn't set the isDirty flag on the model when I change selected_days. Is this a known issue? – J. Mitchell Dec 29 '13 at 19:30
  • @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 Mar 15 '17 at 21:13
9

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);
rxgx
  • 5,089
  • 2
  • 35
  • 43
4

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.

Mike Aski
  • 9,180
  • 4
  • 46
  • 63
  • Indeed a good implementation. I've got the same in my apps... :-) – Mike Aski Mar 06 '13 at 07:52
  • If using this solution to render your data in a partial - look at the workaround here -> https://github.com/emberjs/ember.js/issues/1990#issuecomment-13855767 – calumbrodie Mar 19 '13 at 01:09
1

Interestingly all the other 4 answers to this question have almost identical deserialize and serialize functions, so you could simplify things down to:

import Em from 'ember'
import DS from 'ember-data'

# Presumably based on these answers: http://stackoverflow.com/questions/12168570/how-to-represent-arrays-within-ember-data-models
# All we need to do is always make sure an array is returned from serialize or deserialize

toArray = (data) ->
  switch Em.typeOf(data)
    when 'array'  then data
    when 'string' then JSON.parse(data)
    else []

export default DS.Transform.extend
  deserialize: toArray
  serialize: toArray

This is using ember-cli-coffees6 for coffeescript with import/export support

Devin Rhode
  • 23,026
  • 8
  • 58
  • 72
0

A bit late to the game here but here's a jFiddle I found that is a simple implementation of declaring a new array codec

http://jsfiddle.net/Nook/ab2Xf/

xiaolingxiao
  • 4,793
  • 5
  • 41
  • 88