7

I'm running into an isssue when I try to create a collection of collections using backbone js. Here is the code :

Models and Collections :

var Track = Backbone.Model.extend({

    defaults : {
      title : ""
    }
})

var TrackCollection = Backbone.Collection.extend({

    model : Track,
})

var Playlist = Backbone.Model.extend({

    defaults : {
        name : "",
        tracks : new TrackCollection,
    }
})

var PlaylistCollection = Backbone.Collection.extend({

    model : Playlist,
})

Creation of the playlist collection :

var playlists = new PlaylistCollection;

// create and push the first playlist
playlists.push({ name : "classic" });
// create and push a track in the playlist just created
playlists.last().get("tracks").push({ title : "fur elise" });

// create and push the second playlist
playlists.push({ name : "c2c" });
// create and push a track in the playlist just created
playlists.last().get("tracks").push({ title : "fuya" });

// display first playlist
console.log(JSON.stringify(playlists.at(0).toJSON()))
// display second playlist
console.log(JSON.stringify(playlists.at(1).toJSON()))

Here is the output :

{"name":"classic","tracks":[{"title":"fur elise"},{"title":"fuya"}]}
{"name":"c2c","tracks":[{"title":"fur elise"},{"title":"fuya"}]}

The problem is, as we can see on the output, the 2 playlists have the 2 tracks "fur elise" and "fuya".

So my question is why ? and what should I do in order to have "fur elise" only in the first playlist named "classic" and "fuya" only in the second playlist named "c2c" ?

Thank you.

Ross
  • 1,313
  • 4
  • 16
  • 24
Kraoz
  • 83
  • 5

1 Answers1

7

I think your problem is your default tracks attribute in PlayList:

var Playlist = Backbone.Model.extend({
    defaults : {
        name : "",
        tracks : new TrackCollection,
    }
});

Backbone will shallow-copy the defaults when creating new instances:

defaults model.defaults or model.defaults()
[...]
Remember that in JavaScript, objects are passed by reference, so if you include an object as a default value, it will be shared among all instances.

The result is that every single PlayList instance that uses the default tracks will be using exactly the same TrackCollection as its tracks attribute and that TrackCollection will be the one referenced in the defaults.

The easiest solution is to use a function for defaults:

var Playlist = Backbone.Model.extend({
    defaults : function() {
        return {
            name : "",
            tracks : new TrackCollection,
        };
    }
});

That way the defaults function will be called when defaults are needed and each time defaults is called you'll get a brand new TrackCollection in the default tracks attribute.

Here's a quick rule of thumb for you:

If you want to put anything other than strings, numbers, or booleans in defaults, use a defaults function instead of a defaults object.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • You're totally right, I thought it was doing a copy of the defaults at the instantiation. Anyways, thanks a lot for your response ! – Kraoz Dec 03 '12 at 00:47
  • this is great info and very pertinent but might it simply be easier and more clear to create a new collection in the initialization method? – Alexander Mills Jul 07 '15 at 06:55
  • 1
    @AlexMills: Maybe. I didn't know enough about their situation to say. This answer is more of a "if you're going to do this, then do it this way" sort of thing. – mu is too short Jul 07 '15 at 17:10
  • thanks Mu, one question for you - I'd like to call into question the thing you said about defaults as an object versus defaults as a function. In one of my models, my default is an object and all my default properties are null values, but that doesn't mean when I create an new instance of that model all the other previously created instances values all become null. How can this coincide with what you said about sharing a single value? – Alexander Mills Jul 07 '15 at 18:42
  • 1
    @AlexMills: `null` is non-mutable, an array or object is not: https://jsfiddle.net/7z0c7f6e/ – mu is too short Jul 07 '15 at 19:08