Nested Models in Backbone.js, how to approach
Solution 1
I have the very same issue while I'm writing my Backbone application. Having to deal with embedded/nested models. I did some tweaks that I thought was a quite elegant solution.
Yes, you can modify the parse method to change a attributes around in the object, but all of that is actually pretty unmaintainable code IMO, and feels more of a hack than a solution.
Here's what I suggest for your example:
First define your Layout Model like so.
var layoutModel = Backbone.Model.extend({});
Then here's your image Model:
var imageModel = Backbone.Model.extend({
model: {
layout: layoutModel,
},
parse: function(response){
for(var key in this.model)
{
var embeddedClass = this.model[key];
var embeddedData = response[key];
response[key] = new embeddedClass(embeddedData, {parse:true});
}
return response;
}
});
Notice that I have not tampered with the model itself, but merely pass back the desired object from the parse method.
This should ensure the structure of the nested model when you're reading from the server. Now, you would notice that saving or setting is actually not handled here because I feel that it makes sense for you to set the nested model explicitly using the proper model.
Like so:
image.set({layout : new Layout({x: 100, y: 100})})
Also take note that you are actually invoking the parse method in your nested model by calling:
new embeddedClass(embeddedData, {parse:true});
You can define as many nested models in the model
field as you need.
Of course, if you want to go as far as saving the nested model in its own table. This wouldn't be sufficient. But in the case of reading and saving the object as a whole, this solution should suffice.
Solution 2
I'm posting this code as an example of Peter Lyon's suggestion to redefine parse. I had the same question and this worked for me (with a Rails backend). This code is written in Coffeescript. I made a few things explicit for people unfamiliar with it.
class AppName.Collections.PostsCollection extends Backbone.Collection
model: AppName.Models.Post
url: '/posts'
...
# parse: redefined to allow for nested models
parse: (response) -> # function definition
# convert each comment attribute into a CommentsCollection
if _.isArray response
_.each response, (obj) ->
obj.comments = new AppName.Collections.CommentsCollection obj.comments
else
response.comments = new AppName.Collections.CommentsCollection response.comments
return response
or, in JS
parse: function(response) {
if (_.isArray(response)) {
return _.each(response, function(obj) {
return obj.comments = new AppName.Collections.CommentsCollection(obj.comments);
});
} else {
response.comments = new AppName.Collections.CommentsCollection(response.comments);
}
return response;
};
Solution 3
Use Backbone.AssociatedModel
from Backbone-associations :
var Layout = Backbone.AssociatedModel.extend({
defaults : {
x : 0,
y : 0
}
});
var Image = Backbone.AssociatedModel.extend({
relations : [
type: Backbone.One,
key : 'layout',
relatedModel : Layout
],
defaults : {
name : '',
layout : null
}
});
Solution 4
I'm not sure Backbone itself has a recommended way to do this. Does the Layout object have its own ID and record in the back end database? If so you can make it its own Model as you have. If not, you can just leave it as a nested document, just make sure you convert it to and from JSON properly in the save
and parse
methods. If you do end up taking an approach like this, I think your A example is more consistent with backbone since set
will properly update attributes
, but again I'm not sure what Backbone does with nested models by default. It's likely you'll need some custom code to handle this.
Solution 5
I'd go with Option B if you want to keep things simple.
Another good option would be to use Backbone-Relational. You'd just define something like:
var Image = Backbone.Model.extend({
relations: [
{
type: Backbone.HasOne,
key: 'layout',
relatedModel: 'Layout'
}
]
});
Comments
-
Ross over 3 years
I've got the following JSON provided from a server. With this, I want to create a model with a nested model. I am unsure of which is the way to achieve this.
//json [{ name : "example", layout : { x : 100, y : 100, } }]
I want these to be converted to two nested backbone models with the following structure:
// structure Image Layout ...
So I define the Layout model like so:
var Layout = Backbone.Model.extend({});
But which of the two (if any) techniques below should I use to define the Image model? A or B below?
A
var Image = Backbone.Model.extend({ initialize: function() { this.set({ 'layout' : new Layout(this.get('layout')) }) } });
or, B
var Image = Backbone.Model.extend({ initialize: function() { this.layout = new Layout( this.get('layout') ); } });
-
Ross almost 13 yearsAh! Sorry, it was missing the
new
operator. I have edited it to fix this mistake. -
Peter Lyons almost 13 yearsOh, then I misinterpreted your question. I'll update my answer.
-
newbie_android_dev about 12 yearsThis is nice.. should be the accepted answer as its far cleaner than the other approaches. Only suggestions I'd have is to capitalize the first letter of your classes that extend Backbone.Model for readability.. i.e. ImageModel and LayoutModel
-
rycfung about 12 years@StephenHandley Thanks for the comment and your suggestion. For the information, I'm actually using this in the context of requireJS. So, to answer to the capitalization matter, the var 'imageModel' is actually returned to requireJS. And the reference to the model would be encapsulated by the following construct:
define(['modelFile'], function(MyModel){... do something with MyModel})
But you're right. I do make it a habit to reference the model by the convention you suggested. -
Edward Anderson almost 12 yearsProps for the example code and suggesting overriding parse. Thanks!
-
rycfung almost 12 years@BobS Sorry, was a typo. Should've been response. I've fixed it, thanks for pointing out.
-
Ricardo Gomes over 11 yearswould be nice to have your answer in real JS
-
ABCD.ca over 11 yearshappy to have the coffeescript version, thanks. For others, try js2coffee.org
-
Ross about 11 years+1 Backbone-Releational seems quite established: own website, 1.6k stars, 200+ forks.
-
jasop almost 11 yearsNice! I recommend adding this to the
Backbone.Model.prototype.parse
function. Then, all your models have to do is to define the submodel object types (in your "model" attribute). -
Chris Clark almost 11 yearsCool! I wound up doing something similar (notably and regrettably after I found this answer) and wrote it up here: blog.untrod.com/2013/08/declarative-approach-to-nesting.html The big difference is that for deeply nested models I declare the whole mapping at once, in the root/parent model, and the code takes it from there and walks down the whole model, hydrating relevant objects into Backbone collections and models. But really a very similar approach.
-
Manuel Hernandez almost 11 yearsIf the question is real JS, an answer should be as well.
-
Nathan Do over 10 yearsElegant solution, this should be added to Backbone itself!
-
Ross over 10 yearsI don't take sugar in my JavaScript : )
-
michaelok over 10 yearsNice solution. There is a similar project out there too: github.com/PaulUithol/Backbone-relational
-
Algy Taylor over 10 yearsWonderful solution; I put the parse function in to a new base class, changed 'model' to 'relatedModels' and then have reused it in practically everything.
-
twiz over 9 yearsThe awesome part is that this works for embedding collections in your model as well.
-
Vivek Kodira almost 9 yearsWould it make sense to clone the object 'response' before modifying it?