Rails + Backbone.js + Backbone-relational

by admin on March 29, 2012

Playing around with Rails and Backbone.js for the last few weeks has been nothing but fun. Both libraries have features and functionality that almost seem like magic. At the same time, they have their limitations as well. For instance, I’ve really felt the need for a way to express relations within Backbone. This would enable a one-to-one mapping between Rails and Backbone models and would, in general, make things a lot simpler. The Backbone docs quite clearly mention that they will not be adding direct support for nested models/relations, and one has to look elsewhere for it. Searching around will lead you to the Backbone-relational library, which is a Github project and well maintained. The documentation, however, is a bit lacking and there are no demo/tutorials either (the lack of a tutorial is an open bug against the project) And thus, I decided to build a demo app using Rails, Backbone.js and Backbone-relational.

To quickly get started, I used the help of scaffolding and the rails-backbone gem. The rails-backbone gem provides rails like scaffolding to create a basic structure for your backbone app. Although not necessary (and probably overkill for this demo) it is an easy way to get things running quickly, and you also don’t need to worry about structuring your code later. (warning: this gem generates Coffeescript, which may or may not be a good thing) For this demo, I have two models, Posts and Comments. Both have a field to hold some text content. The rails associations are defined as follows – a Post “has_many” Comments and a Comment “belongs_to” a Post. By following the instructions on the rails-backbone documentation, you can generate the code for both models, for both rails as well as backbone. (Scaffolding is not something I’d recommend in general, but is quite handy in this case) At this point of time, you could verify things are working fine by hitting localhost:3000/posts .

To use Backbone-relational, first get the JS file from the Github project, plonk it somewhere in the include path (vendor/assets/javascripts might be a good option) and edit your application.js file so that it gets picked up. Next, we edit the models as shown below. The models extend Backbone.RelationalModel instead of Backbone.model, and you also set the relations option within the model. In this case, the Post model has a “HasMany” relation with Comments, and Comments automatically has a relation of type “HasOne” set for Posts. Since a Post can have many comments related to it, they are stored in a CommentsCollection, which is specified by the CollectionType option.

The Backbone Post model and collection are defined as follows

class RailsBackboneRelational.Models.Post extends Backbone.RelationalModel
  paramRoot: 'post'

  defaults:
    title: null

  relations: [
    type: Backbone.HasMany
    key: 'comments'
    relatedModel: 'RailsBackboneRelational.Models.Comment'
    collectionType: 'RailsBackboneRelational.Collections.CommentsCollection'
    includeInJSON: false
    reverseRelation:
      key: 'post_id',
      includeInJSON: 'id'
  ]

class RailsBackboneRelational.Collections.PostsCollection extends Backbone.Collection
  model: RailsBackboneRelational.Models.Post
  url: '/posts'

The Comment model is defined as follows

class RailsBackboneRelational.Models.Comment extends Backbone.RelationalModel
  paramRoot: 'comment'

  defaults:
    content: null

class RailsBackboneRelational.Collections.CommentsCollection extends Backbone.Collection
  model: RailsBackboneRelational.Models.Comment
  url: '/comments'

You can now refer to the Comments in a Post using regular Backbone methods, for eg. post.get(‘comments’) will return a CommentsCollection object. This collection is nothing but the set of Comments “related” to that particular post. Similarly, you could do the reverse, and access the parent post from a Comment using something like comment.get(‘post’). In this case, the reverse relation key is post_id, and you’d have to do comment.get(‘post_id’) instead. This is slightly misleading, as it returns a Post object, and not just the post_id. However, if you call a toJSON() on the comment model, it will include only the id field of the post object (this is specified by the includeInJSON option) Rails only needs a post_id field to create and associate a new comment with an existing post, and so we can ignore the rest of the attributes. In short, make sure the key and includeInJSON options in reverseRelation correspond to what Rails is expecting. Similarly, calling a toJSON() on the Post model will return only the parent Post object, and all nested comments will be ignored because of the ‘includeInJSON: false’ option. Also, initializing the Posts collection from the Rails view now becomes as easy as changing @posts.to_json.html_safe to @posts.to_json({:include => :comments}).html_safe , and BackboneRelational automatically takes care of the rest by initializing the nested Comment models as well (this is because the createModels option is set to true by default)

The rails-backbone gem also includes a couple of other JS files that are of note, backbone_datalink.js and backbone_rails_sync.js. The first binds change event handlers to all generated views that have forms/input fields, while the second just overrides the default Backbone.Sync function to make it work seamlessly with Rails.

You can see this demo live at http://rails-backbone-relational.herokuapp.com/posts and the code at https://github.com/rohitnair/rails-backbone-relational You can create, edit and delete posts. You can also create and delete comments related to each post. A majority of my modifications to the scaffold generated code are on the view and template files. The post model also uses the above mentioned backbone_datalink lib to grab updated values from the form, while the comment model simply grabs the value in the input box using a jQuery .val() call.

Resources/Docs:
Backbone.js documentation
Example Backbone.js Todo application with annotated source
rails-backbone gem (github)
Backbone-relational (github)

15 comments

Thanks for this article.

Have you thought about modifying this to save back to the database?

by Keenan on May 22, 2012 at 12:24 am. Reply #

Wish I could have edited that last comment. Tweaking the collections worked great.

Thanks again for the great/simple example. This is the first complete example written for rails that I have found.

by Keenan on May 22, 2012 at 12:35 am. Reply #

Oh, sorry for the confusion. Yes, removing the two lines referring to the localStorage adapter should be enough to save them to the database. Will update the post to clarify that bit.
And thanks, I wrote this up because I couldn’t find a good example myself :)

by admin on May 22, 2012 at 12:46 am. Reply #

Hii,

My post is created through your tutorial when i create a post nd then i again click on add title again empty post is created and also saved in model but when i remove the localstorage adapter then comment is not created.

by Prince on August 3, 2012 at 4:09 am. Reply #

thanks dude, this is helpful.

by Bek on July 20, 2012 at 7:18 pm. Reply #

Hii,

My post is created through your tutorial when i create a post nd then i again click on add title again empty post is created and also saved in model but when i remove the localstorage adapter then comment is not created.

please help me out..

by Prince on August 2, 2012 at 12:01 pm. Reply #

I don’t think I can help without more details. Do you see any JavaScript errors? Or server side errors?

by admin on August 3, 2012 at 4:44 am. Reply #

hi,
Actually when i remove that two lines of local storage adapter in my app then post is created fine and also save in db but there is one problem when i click on add then again post is created and comments are not created and does not save so can u help me out or refer anything because i am new to backbone js.
i want to crate post and comment through backbone js and they also save in db ..

plz help me out

thank you
with Regards

by Prince on August 3, 2012 at 6:10 am. Reply #

Does it actually fetch Post.comments or Comment.all and filter the resulted array? Thats important.

by erundook on September 4, 2012 at 3:15 pm. Reply #

I build an app, like the way you did. But when i’m hitting the destroy function of a comment, in my case a ‘picture’, it’ll destroy the whole post, in my case a ‘album’ model.

Any ideas? I get an error after hitting the destroy button:
Uncaught TypeError: Cannot read property ‘forEach’ of null

The error comes out of underscore.js.

Thanks in advance!

Gr. Ronald.

by Ronald on November 11, 2012 at 10:34 am. Reply #

It’s really tough to say anything without seeing the code. Do you have a link or a jsfiddle or something like that? Does it happen with my example? (I hope not)
The other thing to note is that my example was written months back, not sure if anything changed in these libraries that might have introduced a bug in my example.

by admin on November 11, 2012 at 8:44 pm. Reply #

Hi there,

Thank you for your reaction. The problem is solved. I was also using the rails-backbone gem, which include underscore for you. The version included is 1.4.0. Now I have 1.4.2 and now it’s running great. Dont know exactly where the problem came from, but it is working now.

Thank you for this tutorial!

Greetz, Ronald!

by Ronald on November 11, 2012 at 9:49 pm. Reply #

Thanks a lot, man. Really clear and helpful.

by Gerson on February 13, 2013 at 12:59 am. Reply #

Awsm tutorial, it helped me a ton. It should be on github page of backbone-relational..:)

by Sandy on October 22, 2013 at 2:29 pm. Reply #

Thanks good one! There seems to be a rails integration with backbone-relational-rails

Esp this part makes the whole purpose clear: You can now refer to the Comments in a Post using regular Backbone methods, for eg. post.get(‘comments’) will return a CommentsCollection object. This collection is nothing but the set of Comments “related” to that particular post. Similarly, you could do the reverse, and access the parent post from a Comment using something like comment.get(‘post’). In this case, the reverse relation key is post_id, and you’d have to do comment.get(‘post_id’)

by Sanjay on February 26, 2014 at 10:13 am. Reply #

Leave your comment

Required.

Required. Not published.

If you have one.


Elsewhere