Javascript

Backbone associations with Supermodel.js


Backbone is becoming one of my favorites tools to work with. I use it every chance I get to. However, one of the obstacles I've found by using it is the use of associations in my models.

Coming from a Rails scenario, I want to have my rails associations reflected on my backbone models. An association implies to tie up two or more models in a strong way. Keeping two models tied in backbone is a bit harder and needs to be done manually.

Days ago, when I was attending Backboneconf, one of the speakers (Rebecca Murphey) said that Backbone is like raw stones, and that it is our responsibility to take advantage of that stone and build something with it. In brief, we should extend backbone. In her talk, Rebecca mentioned Supermodel.js, a Backbone model extension with a lot of extra functionality, one of these is model associations.

Next, I'll show you how to create a simple Rails/Backbone app using Supermodel.js.

  1. Create a new Rails app.

    rails new supermodel
    
  2. Create 2 resources (Company and Product), the association will be 1-n.

    rails g resource Company name:string  
    rails g resource Product name:string company_id:integer
    
    #
    # company.rb
    #
    class Company < ActiveRecord::Base
      attr_accessible :name
      has_many :products
    end
    
    #
    # product.rb
    #
    class Product < ActiveRecord::Base
      attr_accessible :company_id, :name
      belongs_to :company
    end
    
    #
    # application_controller.rb
    #
    class ApplicationController < ActionController::Base
      protect_from_forgery
      respond_to :json
    end
    
    #
    # companies_controller.rb
    #
    class CompaniesController < ApplicationController
      before_filter :find_company, except: [:index, :create]
      def index
        respond_with Company.all, include: :products
      end
      def show
        respond_with @company, include: :products
      end
      def create
        @company = Company.new params[:company]
        @company.save
        respond_with @company, include: :products
      end
      def update
        @company.update_attributes params[:company]
        respond_with @company, include: :products
      end
      def destroy
        @company.destroy
        respond_with @company, include: :products
      end
      private
      def find_company
        @company = Company.find params[:id]
      end
    end
    
    #
    # products_controller.rb
    #
    class ProductsController < ApplicationController
      before_filter :find_company
      before_filter :find_product, except: [:index, :create]
      def index
        respond_with @company.products, location: company_products_path(@company)
      end
      def show
        respond_with @product, location: company_products_path(@company)
      end
      def create
        @product = @company.products.new params[:product]
        @product.save
        respond_with @product, location: company_products_path(@company)
      end
      def update
        @product.update_attributes params[:product]
        respond_with @product, location: company_products_path(@company)
      end
      def destroy
        @product.destroy
        respond_with @product, location: company_products_path(@company)
      end
      private
      def find_company
        @company = Company.find params[:company_id]
      end
      def find_product
        @product = @company.products.find params[:id]
      end
    end
    
  3. Create a home controller with and index action (I will use this controller to play with the javascript console).

    rails g controller home index
    
  4. Setup our routes.

    resources :companies do
      resources :products
    end
    
    root :to => 'home#index'
    
  5. Add backbone to our project, I'll use the rails-backbone gem.

    #
    # Gemfile
    #
    
    gem 'rails-backbone'
    bundle install  
    rails g backbone:install
    
  6. Download the Supermodel extension and place it in vendor/assets/javascript, then require it in your application.js file.

    //
    // application.js
    //
    
    //= require jquery
    //= require jquery_ujs
    //= require underscore
    //= require backbone
    //= require supermodel
    //= require backbone_rails_sync
    //= require backbone_datalink
    //= require backbone/supermodel-example
    //= require_tree .
    
  7. Create a company backbone model using Supermodel

    #
    # company.js.coffee
    #
    
    class App.Models.Company extends Supermodel.Model
      urlRoot: '/companies'
    
  8. Create a product model using supermodel, then create the products collection

    #
    # product.js.coffee
    #
    
    class App.Models.Product extends Supermodel.Model
    
    class App.Collections.Products extends Backbone.Collection
      model: App.Models.Product
    
  9. Create a file for our associations

    #
    # associations.js.coffee
    #
    
    App.Models.Product.has().one 'company',
      model: App.Models.Company
      inverse: 'products'
    
    App.Models.Company.has().many 'products',
      collection: App.Collections.Products.extend
        url: ->
        "/companies/#{@owner.id}/products"
      inverse: 'company'
    

    I used a separate file because our models could not be loaded when we are defining our associations, we just require this file after our model files.

    #
    # supermodel.js.coffee
    #
    
    #= require_self
    #= require_tree ./templates
    #= require_tree ./models
    #= require ./associations
    #= require_tree ./views
    #= require_tree ./routers
    
    window.App =
      Models: {}
      Collections: {}
      Routers: {}
      Views: {}
    
  10. And that is all the code, now we just need to start the application and play with the javascript console.

    bundle exec rake db:create
    bunde exec rake db:migrate
    rails server
    

Go to http://localhost:3000/ in your browser.

Now you can try the following lines in the browser's javascript console.

    company = new App.Models.Company({ name: 'foo' })
    company.save()

    company.products().create({ name: 'product 1' })
    company.products().create({ name: 'product 2' })

    company.products().toJSON() 
    // you'll see an array containing the products created above

    company.products().first().company() === company // true :)

Associations is only one of the numerous features of Supermodel.js if you want to know what else you can do with it, read its documentation.

Development
Angular 2 Overview
Beginner
ReactJS: Loops in JSX
Javascript
AngularJS Providers under the hood