Beginning Ember.js on Rails: Part 2

This is part of a series of posts on Ember.js. Please read Warming up to Ember.js as well as the Ember.js home page if you're new to Ember.js.
As promised in Part 1 of this series, this post will show how the example app uses our ultra-simple Ember REST library to query a resource's REST interface to display a list of records.
Server-side Rails
Our Rails code is close to the minimum needed to serve up a RESTful JSON
interface. It was originally generated with rails g scaffold
and then
simplified further.
Model
The Rails model for contacts is quite basic:
1 2 3 4 |
class Contact < ActiveRecord::Base validates :first_name, :presence => true validates :last_name, :presence => true end |
Controller
The controller is a simplified version of the scaffolding:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
class ContactsController < ApplicationController # GET /contacts # GET /contacts.json def index @contacts = Contact.all respond_to do |format| format.html # index.html.erb format.json { render json: @contacts } end end # GET /contacts/1.json def show @contact = Contact.find(params[:id]) respond_to do |format| format.json { render json: @contact } end end # POST /contacts.json def create @contact = Contact.new(params[:contact]) respond_to do |format| if @contact.save format.json { render json: @contact, status: :created, location: @contact } else format.json { render json: @contact.errors, status: :unprocessable_entity } end end end # PUT /contacts/1.json def update @contact = Contact.find(params[:id]) respond_to do |format| if @contact.update_attributes(params[:contact]) format.json { render json: nil, status: :ok } else format.json { render json: @contact.errors, status: :unprocessable_entity } end end end # DELETE /contacts/1.json def destroy @contact = Contact.find(params[:id]) @contact.destroy respond_to do |format| format.json { render json: nil, status: :ok } end end end |
There are a couple interesting aspects to this controller code. First of all,
the only action that responds to an html request is index
. I've trimmed the
others to respond only with json.
Also note that the update
and destroy
actions respond to success with:
1 |
render json: nil, status: :ok |
... instead of the scaffolded default (head :ok
), which jQuery.ajax()
interprets as an error.
Routes
As you can see, we're routing root requests to our contacts controller:
1 2 3 4 |
EmberRestExample::Application.routes.draw do root :to => 'contacts#index' resources :contacts end |
Let's move to the client-side before we get back to our server-side views...
Client-side Ember
We're now going to take a quick look at the client-side Ember model, view and
controller code needed to display a list of contacts. This code is all organized
within app/assets/javascripts/app/
, as discussed in
part 1
of this series.
If you're following along in the example code, note that I'm leaving out a lot of the code having to do with editing records for the sake of simplicity.
Application
As reviewed in the first part of this series, the application object can be quite simple:
1 |
App = Ember.Application.create(); |
This object will contain all of the models, controllers and views for the application.
Model
Let's move along to the model:
1 2 3 4 5 6 7 |
App.Contact = Ember.Resource.extend({ resourceUrl: '/contacts', fullName: Ember.computed(function() { return this.get('first_name') + ' ' + this.get('last_name'); }).property('first_name', 'last_name') }); |
This is an extension of Ember.Resource
, which is defined in ember-rest.js
as
an extension of Ember.Object
. The resourceUrl
is the base url of the
resource.
Also of interest in the model is the computed property fullName
, which returns
a concatenation of first_name
and last_name
. It defines these properties as
dependencies with property('first_name', 'last_name')
. If either of those
dependencies is updated, then fullName
will be updated too. Any bindings to
fullName
in views will also be updated.
Controller
Our Ember-based contacts controller is very simple:
1 2 3 |
App.contactsController = Ember.ResourceController.create({
resourceType: App.Contact
});
|
Instead of defining a class like App.Contact
, we're creating
App.contactsController
as an object. It's an instance of
Ember.ResourceController
, which is defined in ember-rest.js
as a thin
extension to an Ember.ArrayController
. Its only configured property is
resourceType
, which links it to our model. The controller knows enough to use
the resourceUrl
from the model to query resources (although the resourceUrl
can be overridden at the controller level if needed).
Views
Let's now check out our Ember views.
ListContactsView
renders the listing:
1 2 3 4 5 6 7 8 |
App.ListContactsView = Ember.View.extend({ templateName: 'app/templates/contacts/list', contactsBinding: 'App.contactsController', refreshListing: function() { App.contactsController.findAll(); } }); |
While ShowContactView
renders each row within the listing:
1 2 3 4 5 |
App.ShowContactView = Ember.View.extend({ templateName: 'app/templates/contacts/show', classNames: ['show-contact'], tagName: 'tr' }); |
Each view is associated with a template via templateName
. The rendered
template is contained within an html element that is mapped to its corresponding
view. By default this is a div
, although views can override this with the
tagName
property. This element can be customized further with classNames
.
Also note the contactsBinding
property on App.ListContactsView
, which binds
the controller's data to this view as the property contacts
.
Templates
Let's see how each of these views is templated in Handlebars. First up is our listing:
This template loops through contacts, which are bound to
App.contactsController
, using the each
helper and renders each one using
ShowContactView
. Because ShowContactView
has a tagName
of tr
, a new row
will be inserted for each record. It will also render the show
template:
Notice how the contactBinding
in list.handlebars
is passed through to the
show
view, which can reference the object as contact
in its template.
All together now
Let's pull this all together in a single server-rendered ERB view:
1 2 3 4 5 6 7 8 9 10 11 |
<h1>Contacts</h1> <script type="text/x-handlebars"> {{ view App.ListContactsView }} </script> <script type="text/javascript"> $(function() { App.contactsController.loadAll(<%= @contacts.to_json.html_safe %>); }); </script> |
The first script block is written in Handlebars, as indicated by its type
.
It uses the Ember view
helper to render App.ListContactsView
.
The javascript block is a bit of an optimization. It loads contacts, fetched in
our controller's index
action, into our Ember-based App.contactsController
.
The reason for this is simple: we can improve performance by loading contacts in
the same request as the page. A simpler alternative to calling loadAll()
would
be to find all contacts after jQuery has loaded, like so:
Let's recap what we've done here. We first configured a REST interface to
our data in Rails. Next we configured our Ember controller and model to load
data from that interface via ember-rest.js
. Finally, we hooked up our Ember
views and templates to our controller's data.
Let's also recap what we haven't done here. Thanks to ember-rest.js
, we
haven't needed to write any ajax code to query our REST interface. And thanks to
Ember, we haven't needed to write code to update our views when contacts are
loaded into the controller's array.
Ember will really begin to shine when we see that our views are kept in sync even as contacts are added, updated, and removed. Speaking of which, my next post will cover just that...
Please continue on to Beginning Ember.js on Rails: Part 3, the final part in this series.