Ember.js on Rails

Prepared by Bradley Temple

Why Ember?

  • Clean separation of concerns between the front end interactive behavior and the backend logic
  • Powerful data binding
  • Computed Properties are rad

Angular is clearly more popular, why waste my time?

  • Angular is cool, and solves a similar set of problems, but...
  • Encourages Invalid HTML
  • Has to scan the DOM constantly for ng-* attributes
  • Easy to lose track of various ng-attributes littered throughout the DOM
  • Personal Opinion - Google's killed enough things of theirs that I use to want to invest all that much time in their products

Getting started


# Gemfile
gem 'ember-rails'              # for bootstrapping
gem 'ember-auth-rails'         # if you need authentication
gem 'active_model_serializers' # Helpful in rendering JSON
              

$ bundle install
$ rails g ember:bootstrap
              

A Brief Tour Around the Javascripts Directory

You'll have several new directories in app/assets/javascripts

  • controllers
  • helpers
  • models
  • routes
  • templates
  • views

Client-side MVC Nomenclature

  • Model - Your persitant data objects. This is what gets sent back and forth with the Rails server
  • Controller - Decorates the model with application state
  • Routes - Loads data into the controller and performs other setup actions
  • Templates - Displays the current application state in a given controller to the user.
  • Helpers - Shortcut methods for your tempaltes. Especially useful in integrating existing Javascript libraries
  • Views - Objects responsible for translating primitive events (click) to semantic events (submitComment). Used very rarely in practice

Start with Routes

Routes are the starting point with an Ember app. The url is the first thing you are encouraged to think about.

Router


# app/assets/javascripts/router.js.coffee
App.Router.map ->
  @route "articles", path: '/'
              

The Router serves much the same function as it does in Rails. It takes a given URL and translates it to a route object. In this case we are mapping root to the articles route.

Route Object


# app/assets/javascripts/routes/articles.js.coffee
App.ArticlesRoute = Ember.Route.extend
  model: -> App.Article.find() 
  # with no argument this will send findAll()
            

Setting up Models

You basically have three options:

  1. Use EmberData
  2. Use EmberModel
  3. Plain 'ol Jquery $.ajax

I use EmberModel

  • Ember Data is still Alpha software.
  • EmberModel is a lot simplier and wants to stay simple
  • You can use a REST adapter to do all the basic CRUD stuff with minimal code, or you can bring your own $.Ajax for weird stuff.

install ember-model


$ cd app/assets/javascripts
$ mkdir lib && cd lib
$ wget https://raw.github.com/ebryn/ember-model/master/ember-model.js
              

Don't forget to include it in application.js

Define a Model


# app/assets/javascripts/routes/articles_route.js.coffee
App.Article = Ember.Model.extend
  id: Ember.attr()
  title: Ember.attr()
  body: Ember.attr()
  comments: Ember.hasMany('App.Comment', {key: 'comments', embedded: true})
App.Article.adapter = Ember.RESTAdapter.create()
App.Article.url = "/api/v1/article"     # REST endpoint
App.Article.collectionKey = "article"   # the root JSON key for a collection
App.Article.rootKey = "article"         # the root JSON key for an individual item
              

Back over to Rails - Routing

Something important to remember when building a rich front-end client is that your Rails app is now an app exposed through an API. Therefore, it's a good idea to namespace the routes like so:


namespace :api do
  namespace :v1 do
    resources :articles
    resources :users
  end
end
              

Rails Controller

Cheap and Fast Version

gem "inherited_resources"

Define a base controller


class BaseController < ApplicationController
  inherit_resources
  respond_to :json
  before_filter :default_json

  protected
  def default_json
    request.format = :json if params[:format].nil?
  end
end

            

A note about ember data

If you use EmberData's REST Adapter, sometimes it sends some oddly formatted collection requests. You can get around it like this (inherited resources hack):


class BaseController < ActiveRecord::Base
  # ... code omitted
  def collection
    get_collection_ivar || begin
      c = end_of_association_chain
      coll = c.respond_to?(:scoped) ? c.scoped : c
      coll = params[:ids] ? coll.find(params[:ids]) : coll.all
      set_collection_ivar(coll)
    end
  end
end
            

The Rails Model

Nothing much special here


class Article < ActiveRecord::Base
  attr_accessible :title, :body
  has_many :comments
end
            

ActiveModel::Serializers

You can use this to serialize your JSON response in a way that makes sense to you. In the most basic, you just pass it a list of attribtues:


class ArticleSerializer < ActiveModel::Serializer
  attributes :id, :title, :body
  has_many :comments
end
            

Methods-based serialization

You can also define a method to return something more specific


class ArticleSerializer < ActiveModel::Serializer
  attributes :id, :title, :body, :image_url
  has_many :comments

  def image_url
    object.image(:thumb)
  end
endd
            

The View Layer

We've got a route that sends a request to the rails app, and the rails app now renders JSON back to us. now we just need to display something to the user

The Application Template

This basically works like the application layout in Rails, though since the Ember app is rendering inside the application layout, you don't need to worry about boilerplate html


My Awesome App

{{outlet}}

The Articles Template