Tag Archives: server

Ember JS: Server side

Creating the rails app

In the Ember JS MVC tutorials we created a purely browser based application with no persistence between sessions and hard coded fixtures for data. In this article we will create a server side app to handle data persistence, we will also use it to serve our Ember application. We will use Ruby on Rails. The code is available here in zip form or if you want to fork etc on github try here. Start by creating a new rails application

rails new blog

Wait for the app to be created, go in to the blog directory and remove public/index.html. You can use ember-rails to create the ember client side skeleton code but we will do it by hand using the classes and templates we created during the MVC tutorials. I find it is helps you to understand how the application works if you use as little auto-generated code as possible. However, we will use some rails scaffold to get us started.

rails generate scaffold Post title:string content:text
rails generate scaffold Comment text:string post_id:integer

This creates the controllers, views, model, helper, db migrations etc. Then we can create the database.

rake db:migrate
rake db:create

As we defined in the MVC tutorial a Post can have many Comments so add the relationships to the rails models, in blog/app/models.

add belongs_to :post to comment model
add has_many :comments to post model

In routes we have resource for posts and comments, add a root

root :to => 'welcome#index'

We therefore need something for this route to render. Create a welcome_controller.rb in app/controllers with an index method and a index view for it in app/views/welcome/index.html.erb. The view will be empty, it is just a placeholder for our rails app to boot the root ie /. Ember will be doing all the rendering.

class WelcomeController < ApplicationController
  # GET / 
  def index 
    respond_to do |format| 
      format.html # index.html.erb 
    end 
  end 
end

Add the Ember application

Just like in a rails app we can make our ember application easier to manage by creating an ember app structure in app/assets/javascripts. ember-rails would do this for you.

/controllers 
/templates 
/models 
/views 
/helpers

Using the code from app.js and index.html from part 3 of the tutorial create the following files

controllers/commentsNewController.js

App.CommentsNewController=Ember.ObjectController.extend({
  needs: 'post',
  text: null,
  save: function() {
    var post = this.get('controllers.post.content');
    App.Comment.createRecord({ post: post, text: this.get('text') }); 
    this.get('target').transitionTo('post.index');
   }
});

models/post.js

App.Post=DS.Model.extend({
  comments: DS.hasMany('App.Comment'),
  title: DS.attr('string')
});

models/comment.js

App.Comment = DS.Model.extend({
  post: DS.belongsTo('App.Post'),
  text: DS.attr('string')
});

The templates directory structure mimics the data-template-name from index.html

/templates
/templates/comments
/templates/comments/new.hbs
/templates/post
/templates/post/comment.hbs
/templates/post/index.hbs
/templates/application.hbs
/templates/comments.hbs
/templates/index.hbs
/templates/post.hbs
/templates/posts.hbs

Copy the contents of the script tags in index.html from part 3 of the MVC tutorial to the appropriate template, you do not need the enclosing script tag itself. We also need to add a router.js to the app/assets/javascript directory, use the code from app.js we created in part 3.

App.Router.map(function() {
  this.resource('posts');
  this.resource('post', { path: '/posts/:post_id' }, function() {
    this.resource('comments', function() {
      this.route('new');
    });
  this.route('comment', { path: 'comments/:comment_id'});
  });
});

App.PostsRoute=Ember.Route.extend({
  model: function(){
    return App.Post.find();
  }
});

App.PostIndexRoute=Ember.Route.extend({
  model: function(params) {
    return this.modelFor('post');
  }
});

App.CommentsNewRoute=Ember.Route.extend({
  setupController: function(controller, model) {
    controller.set('text', null);
  }
});

Our store.js no longer uses the fixture adapter but uses the rest adapter. Add store.js to app/assets/javascripts and put the following code in it.

App.Store = DS.Store.extend({
  revision: 12,
  adapter: DS.RESTAdapter.create({
    bulkCommit: false
  })
});

Serve the javascript from rails

It is advisable to use the latest stable version of ember and ember-data. Download ember-latest.js & ember-data-latest.js from http://builds.emberjs.com/ and put in vendor/assets/javascripts Add the handlebars_assets gem to the Gemfile so that we load handlebars.js and get the assets pipeline to compile our handlebars templates. You may have noticed that we append .hbs to them since this is what the gem looks for rather than .handlebars. We also need to load our ember application javascript files, create a blog.js file

//= require ./store
//= require_tree ./models
//= require_tree ./controllers
//= require_tree ./views
//= require_tree ./helpers
//= require_tree ./templates
//= require ./router
//= require_self

Then add blog, handlebars, ember and ember-data to application.js. Also create our ember app instance inside there.

//= require jquery
//= require jquery_ujs
//= require handlebars
//= require ember-latest
//= require ember-data-latest
//= require_self
//= require blog

window.App = Ember.Application.create({
  LOG_TRANSITIONS: true
});

Start a rails server with rails s and go to localhost:3000 in a browser. You should see the Blog index page with a link to ‘Posts’. Open up the debugger to look at the network traffic. Click on the Posts link and you will see a request to localhost:3000/posts. The response is empty but the application transitions to posts.

Add some data and respond with JSON

We will now add some data for the server to send to the browser. Add some db seeds to db/seeds.rb

post = Post.create( :title => 'First post', :content => 'Text for first post' )
Comment.create(:text => 'Post one comment one', :post_id => post.id)
Comment.create(:text => 'Post one comment two', :post_id => post.id)
post = Post.create( :title => 'Second post', :content => 'Text for second post' )
Comment.create(:text => 'Post two comment one', :post_id => post.id)
Comment.create(:text => 'Post two comment two', :post_id => post.id)
post = Post.create( :title => 'Third post', :content => 'Text for third post' )
Comment.create(:text => 'Post three comment one', :post_id => post.id)
Comment.create(:text => 'Post three comment two', :post_id => post.id)

Use rake db:seed to add the posts and comments. We also need to make sure that our rails application serves JSON in the correct format. We need to add the correct JSON serialiser to the post model to include the comments as comment_ids array. Ember needs the JSON response in the format "posts" : [{...},{...}]

def as_json(options={})
  { :id => self.id, :title => self.title, :content => self.content, :comment_ids =>self.comments.collect{|comment| comment.id} }
end

We then need to change the posts controller index method to render json using this method

format.json { render :json => { :posts => @posts.as_json } }

as well as in the show method

format.json { render :json => { :post => @post.as_json } }

We also need to change the JSON response in the comments controller show method to

format.json { render :json => { :comment => @comment } }

so that the json is returned as "comment" : {....}

(see http://jonathanjulian.com/2010/04/rails-to_json-or-as_json/)

When fetching the comments ember will request it as /comments?ids[]=1&ids[]=2 ie as an array of ids, change the comments_controller index method to handle that

if params[:ids]
  @comments = []
  params[:ids].each do |id|
    @comments.push(Comment.find(id))
  end
  else
   @comments = Comment.all
end
respond_to do |format|
  format.html # index.html.erb
  format.json { render :json => { :comments => @comments } }
end

Save new comments to the server

We need to change the CommentsNewController save method so that it sends a POST request to /comments with the new comment in JSON. Change it to:

var post = this.get('controllers.post.content');
var comment = App.Comment.createRecord({ post: post, text: this.get('text') });
comment.get("store").commit(); //this is the new bit, after creating a comment we need to commit it
this.get('target').transitionTo('post.index');

comment.get("store").commit() tells the comment to commit itself.
Go to /posts/1/comments/new, enter a comment and hit save. You will see some network traffic heading to /comments on the server. Refresh /posts/1 and you will see that it has the new comment. You can use the rails console to convince yourself. Comment.all will have the new one at the end. We can use exactly the same principles to create new posts, why not add it to your app?