Backbone Model.save doesn't wrap model state to nested object. Is it normal?
Typical scenario for Rails is something like this params[:product]. How could I achieve this?
This was answered previously: Saving nested objects with Rails, backbone.js, and accepts_nested_attributes_for
I would suggest to override toJSON on the backbone model.
toJSON: function(){
json = {car : this.attributes};
return _.extend(json, {engine_attributes: this.get("engine").toJSON());
}
toJSON is called within the sync method just before sending data to the backend.
Related
I am working on a backbone/signalr POC. I have very simple models working and I can create them client side and retrieve them via signalr.
The problem is this:
If I create a client side version of a model with a nested model I can access the attributes like this:
model.attributes.nestedModel.attributes.attributeName
When I retrieve the model from signalr via
model.fetch()
the model comes back but now to access the nested model properties I need to use
model.attributes.nestedModel.attributeName
the attributes level on the nested model is dropped, so this causes template rendering to fail
How do I get around this? Am I doing something wrong? I am new to signalr/backbone.
BTW, I am using the backbone.signalr nuget package.
Thanks.
this is because when you are using fetch(), the server returns only one JSON object with the model attributes and the attributes of the nested models. for example, the server returns:
{
id: "1",
name: "Model",
nestedModel: {
id: "12",
name: "nestedModel"
}
}
backbone is not smart enough to figure out that nestedModel is actually a "model". It treats "nestedModel" as an attribute on Model. (it's just a regular JSON object, not a backbone object)
that's why this:
model.attributes.nestedModel.attributes.attributeName
does not work.
to make it work, you have to instantiate nestedModel as a Backbone Model. so after fetch is done: (assuming your nestedModel is an instance of NestedModel)
model.fetch().done(function() {
model.set('nestedModel', new NestedModel(model.get('nestedModel')));
});
You can make backbone does this for you automatically by overwriting the parse() method.
in your model:
var NestedModel = Backbone.Model.extend({
//your nested model methods
});
var Model = Backbone.Model.extend({
//do other model stuff
parse: function(response) {
response.nestedModel = new NestedModel(response.nestedModel);
return response;
}
});
this should make your statement work.
but usually I'd use
model.get('nestedModel').get('attributeName')
for more info about parse, see here: http://backbonejs.org/#Model-parse
and to apply this pattern in all other models with more flexibility, you probably wanna read this:
http://www.devmynd.com/blog/2013-6-backbone-js-with-a-spine-part-2-models-and-collections
Let say you are defining a Backbone.js Model. From the documentation we have ...
new Model([attributes], [options])
This seems great for passing some default attributes to a model. When passed the model automatically inherits those attributes and their respective values. Nothing to do. Awesome!
On they other hand lets say we have a Collection of that model.
new Backbone.Collection([models], [options])
Okay, cool we can pass some initial models and some options. But, I have no initial models and no options I need to pass so let's continue. I am going to fetch the models from the server.
collection.fetch([options])
Well I don't have any options, but I want to pass some attributes to add to each models as it is fetched. I could do this by passing them as options and then adding them to the attributes hash in the initialize for the model, but this seems messy.
Is their a Backbone.js native way to do this?
You can pass the attributes as options to fetch and over-ride the collection's parse method to extend the passed options (attributes) on the response.
The solution would look like the following:
var Collection = Backbone.Collection.extend({
url:'someUrl',
parse:function(resp,options) {
if (options.attributesToAdd) {
for (var i=0;i<resp.length;i++)
_.extend(resp[i],options.attributesToAdd);
}
return resp;
}
});
Then to add attributes when you call fetch on the collection, you can:
var collection = new Collection();
collection.fetch({
attributesToAdd:{foo:'bar',more:'foobar'}
});
You may have to tweak the code a bit to work with your JSON structure, but hopefully this will get you started in the correct direction.
I have a few models that don't just contain basic data attributes, but they might have one or two attributes that hold another models object.
This has been okay, but now I want to call
myRootModel.toJSON()
and I've noticed that it doesn't call .toJSON on the other models in my model that I'm trying to call toJSON() on.
Is there a way to override backbone model .toJSON to go through all fields, recursively, whether they are basic attributes, sub-models or collections? If not, can I override toJSON in each model / collection?
I'm aware of backbone-relational, but I don't want to go that route - I'm not using fetch/save, instead our API returns responses that I adjust in the models parse function and simply invoke new MyRootModel(data,{parse:true}).
Here's a way you can achieve such a thing (there's maybe another way):
Backbone.Model.prototype.toJSON = function() {
var json = _.clone(this.attributes);
for(var attr in json) {
if((json[attr] instanceof Backbone.Model) || (json[attr] instanceof Backbone.Collection)) {
json[attr] = json[attr].toJSON();
}
}
return json;
};
http://jsfiddle.net/2Asjc/.
Calling JSON.parse(JSON.stringify(model)) parses the model with all the sub-models and sub-collections recursively. Tried on Backbone version 1.2.3.
Is always equivalent use one or the other?
These prints in console same things
class Model extends Backbone.Model
defaults:
some: 'thing'
other: 'item'
model = new Model
console.log model.attributes
console.log model.toJSON()
toJSON() is a standard method that the JavaScript JSON serializer looks for when serializing an object.
In the context of Backbone, if you override toJSON in your model you can change the format of values which get sent to the server when saving. For example you could filter out read only fields like time stamps.
attributes is the JavaScript object containing the model data, that's what gets altered when you use model.set(). Except if you don't use set() to alter values, then you bypass all the events and loose some of the benefits of backbone. So only use attributes directly if you know what you're doing.
I have 2 post collections and a model as follows.
# router file
#posts = new MyApp.Collections.PostsCollection()
#posts.reset options.posts
#followed_posts = new MyApp.Collections.PostsCollection()
#followed_posts.reset options.followed_posts
# Post model file
class MyApp.Models.Post extends Backbone.Model
paramRoot: 'post'
follow_post: ->
# ajax call
console.log "_________Index:#{this.collection.indexOf(this);}"
console.log this.collection
console.log "_________Followed:"
console.log #followed_posts
class MyApp.Collections.PostsCollection extends Backbone.Collection
model: MyApp.Models.Post
url: '/posts_all'
What I am trying to do is when one of the model changed in one collection, I want to update the other model in other collection too.
These collections may or may not hold same models.
So let's say if a model in #posts changed in my Post model, I want to update that model in #followed_posts too. If #followed_posts doesn't have that model, I need to add a duplicate of the model to #followed_posts collection.
I can access the collection that model belongs, but I cannot access the other collection.
Any ideas appreciated, thanks.
If the two collections are antisocial and can't talk directly to each other, which is usually good design, then you'll need an intermediary -- a global event dispatcher. When a model changes, propagate that event to the dispatcher along with a reference to the model. Listen for the event in the other collection and use the model passed to check for existence and respond as needed.
EDIT:
Backbone's documentation mentions this pattern:
For example, to make a handy event dispatcher that can coordinate
events among different areas of your application: var dispatcher =
_.clone(Backbone.Events)
But in fact, this is such a common pattern that the Backbone object itself is extended with Events. So you can just do:
// In your Post model
#on "change", -> Backbone.trigger "post:change", this, #collection
// And then something like this in the collection class definition:
#listenTo Backbone, "post:change", (model, collection) =>
if post = #get model.cid
post.set model.toJSON()
else
#add model
Also, is followed posts a subset of posts? If so, why not put an attribute on the model designating it as followed? Then you could find all followed posts with a simple filter function.
I would strongly suggest that you should consider having a single collection and add some kind of attribute in the model to differentiate between what kind of posts they are.