The collection nested inside firebase collection's model doesn't have add function
Why the default collection attribute is not a collection anymore?
When you fetch, or create a new Daymodel
which I assume looks like this:
{
day: 1,
agenda : [{
title: "New Todo",
completed : false
}, {
title: "other Todo",
completed : false
}]
}
The default agenda
attribute which was a Todocollection
at first gets replaced by a raw array of objects. Backbone doesn't know that agenda
is a collection and won't automagically populates it.
This is what Backbone does with the defaults
at model creation (line 401):
var defaults = _.result(this, 'defaults'); attrs = _.defaults(_.extend({}, defaults, attrs), defaults); this.set(attrs, options);
_.extend({}, defaults, attrs)
puts the defaults
first, but then, they're overwritten by the passed attrs
.
How to use a collection within a model?
Below are three solutions to accomplish this. Use only one of them, or create your own based on the followings.
Easiest and most efficient way is don't.
Keep the Todocollection
out of the Daymodel
model and only create the collection when you need it, like in the hypothetical DayView
:
var DayView = Backbone.View.extend({
initialize: function() {
// create the collection in the view directly
this.agenda = new Todocollection(this.model.get('agenda'));
},
/* ...snip... */
});
Then, when there are changes you want to persist in the model, you just put the collection models back into the Daymodel
:
this.model.set('agenda', this.collection.toJSON());
Put the collection into a property of the model
Instead of an attribute, you could make a function which lazily create the collection and keeps it inside the model as a property, leaving the attributes
hash clean.
var Daymodel = Backbone.Model.extend({
defaults: { day: 1, },
getAgenda: function() {
if (!this.agenda) this.agenda = new Todocollection(this.get('agenda'));
return this.agenda;
}
});
Then, the model controls the collection and it can be shared easily with everything that shares the model already, creating only one collection per instance.
When saving the model, you still need to pass the raw models back into the attributes
hash.
A collection inside the attributes
You can accomplish what you're already trying to do with small changes.
-
Never put objects into the
defaults
...without using a function returning an object instead.
var Daymodel = Backbone.Model.extend({ defaults: function() { return { day: 1, agenda: new Todocollection() }; }, });
Otherwise, the
agenda
collection would be shared between every instances ofDaymodel
as the collection is created only once when creating theDaymodel
class.This also applies to object literals, arrays, functions (why would you put that in the
defaults
anyway?!). -
Ensure it's always a collection.
var Daymodel = Backbone.Model.extend({ defaults: { day: 1, }, initialize: function(attrs, options) { var agenda = this.getAgenda(); if (!(agenda instanceof Todocollection)) { // you probably don't want a 'change' event here, so silent it is. return this.set('agenda', new Todocollection(agenda), { silent: true }); } }, /** * Parse can overwrite attributes, so you must ensure it's a collection * here as well. */ parse: function(response) { if (_.has(response, 'agenda')) { response.agenda = new Todocollection(response.agenda); } return response; }, getAgenda: function() { return this.get('agenda'); }, setAgenda: function(models, options) { return this.getAgenda().set(models, options); }, });
-
Ensure it's serializable.
var Daymodel = Backbone.Model.extend({ /* ...snip... */ toJSON: function(options) { var attrs = Daymodel.__super__.toJSON.apply(this, arguments), agenda = attrs.agenda; if (agenda) { attrs.agenda = agenda.toJSON(options); } return attrs; }, });
This could easily apply if you put the collection in a model property as explained above.
-
Avoid accidentally overriding the
agenda
attribute.This goes alongside with point 2 and that's where it's getting hard as it's easy to overlook, or someone else (or another lib) could do that down the line.
It's possible to override the
save
andset
function to add checks, but it gets overly complex without much gain in the long run.
What's the cons of collection in models?
I talked about avoiding it inside a model completely, or lazily creating it. That's because it can get really slow if you instantiate a lot of models and slower if each models is nested multiple times (models which have a collection of models, which have other collections of models, etc).
When creating it on demand, you only use the machine resources when you need it and only for what's needed. Any model that's not on screen now for example, won't get their collection created.
Out of the box solutions
Maybe it's too much work to get this working correctly, so a complete solution might help and there are a couple.
- Backbone relational
- backbone-nested
- backbone-nested-models
- backbone-deep-model