Javascript Ember Ruby
Setting up an Ember App with a Rails Backend
Update: June 26, 2014
This tutorial is now out of date, and you should instead look at Vic Ramon's Ember Tutorial for a tutorial introducing Ember with Rails.
Update: November 8th, 2013
Some of the instructions below are out of date, particularly the code in the store.js file. Please look at the Ember Data Rails Example that I have posted to Github for correct, updated code. This app can be viewed live on Heroku.
Today I'm going to show you how to setup an Ember app with a Rails backend. The process is relatively straightforward, but . there are some gotchas that I'd like to help you avoid.
The app will use Haml, CoffeeScript, Emblem, and Ember Data. In this tutorial I'll be starting a sample app called Launch Bay. Launch Bay would essentially be an ultra-lightweight version of Pivotal Tracker. In this first post I'm just going to get my app setup to receive data.
Basic App Setup
Let's hit the ground running with a new app
rails new launchbay -d postgresql
Setup your database and rvm gemset, and make sure to include these in your Gemfile:
gem 'coffee-rails', '~> 4.0.0'
gem 'ember-rails'
gem 'ember-source'
gem 'emblem-rails'
gem 'haml-rails'
Open your configs and add the following:
# environments/production.rb
config.ember.variant = :production
# environments/development.rb
config.ember.variant = :development
# environments/test.rb
config.ember.variant = :development
Backend Setup
Ok, now we've got the basics we need to get our Rails backend up and running. Our app is just going to show a list of stories. For the sake of simplicity these won't be scoped to a particular user or project.
Let's create a stories table.
rails g migration create_stories name:string body:text
rake db:migrate
We'll also need a story model, controller, and serializer.
# app/models/story.rb
class Story < ActiveRecord::Base
end
```
````ruby
# app/serializers/story_serializer.rb
class StorySerializer < ActiveModel::Serializer
attributes :name, :body
end
I'm going to create a versioned api for the controller just in case I want to change things later:
# config/routes.rb
namespace :api do
namespace :v1 do
resources :stories, only: :index
end
end
This controller is going to accept json so that we can interact with our Ember app.
# app/controllers/api/v1/stories_controller.rb
class Api::V1::StoriesController < ApplicationController
respond_to :json
def index
respond_with Story.all
end
private
def story_params
params.require(:story).permit(:name, :body)
end
end
To see if this is all setup properly spin up your server and open a rails console. First let's seed the database:
$ rails console
$ Story.create(name: 'User views a list of stories', body: 'Given I am a user <br /> When I visit the stories index <br /> Then I should see a list of stories')
Now hit the api controller and you should get your one story back as json: http://localhost:3000/api/v1/stories.json
Providing an Outlet for Ember
We need to have a place for our Ember app to actually show up, so let's do that now. I am going to create a generic home controller for now.
# config/routes.rb
root to: 'home#index'
# app/controllers/home_controller.rb
class HomeController < ApplicationController
end
Now give Ember an outlet in the view:
# app/views/home/index.html.haml
%script{ type: 'text/x-handlebars' }
{{ outlet }}
We also need a Rails layout:
# app/views/application.html.haml
!!!
%html(lang="en-US")
%head
%title Launch Bay
= stylesheet_link_tag "application"
= javascript_include_tag "application"
= csrf_meta_tags
%body
= yield
```
## Setting up Ember Internals
First, run the generator provided by ember-rails:
````bash
rails g ember:bootstrap -g --javascript-engine coffee -n App
Now restart your Rails server and hit localhost:3000
. You should see a blank page. Open your developer console and you should see output like this:
DEBUG: -------------------------------
DEBUG: Ember.VERSION : 1.0.0
DEBUG: Handlebars.VERSION : 1.0.0
DEBUG: jQuery.VERSION : 1.10.2
DEBUG: -------------------------------
For more advanced debugging, check out the Ember Inspector for Google Chrome
Setting Up Ember Data
We'll be using Ember Data to handle client-server communication.
The Ember-rails generator created a store.js file. Open it and remove everything, and just add the following:
# app/javascripts/store.js
DS.RESTAdapter.reopen
namespace: 'api/v1'
Ember Models
Now to create the actual story model:
# app/assets/javascripts/models/story.js.coffee
App.Story = DS.Model.extend
name: DS.attr('string')
body: DS.attr('string')
Ember Routes
app/assets/javascripts/router.js
contains our top-level routes.
Open that file, change it to Coffeescript, then add this:
# app/assets/javascripts/router.js.coffee
App.Router.map ()->
@resource 'stories'
Note that resource
is singular, in contrast to Rails.
Ember Template
Create the following file:
# app/assets/javascripts/templates/stories.js.emblem
| Hello world.
Save that, now head over to http://localhost:3000/#/stories
. You should see your message on the page.
There a few things to note.
Use a pipe to tell emblem that "Hello" is text and not an html tag.
We created a file named
stories
in the templates folder. This works right now because the stories route has no subroutes. If you added a subroute to the stories resource, then this template would need to be moved to templates/stories/index.emblem.js.We didn't need to create a stories controller or stories route to make this work. Ember will create those in memory for us if we don't explicitly create them.
Pulling in real data
Ok, we've got text showing up on the page, but we need to actually get data out of our database.To do that we need to bind data to the route, and we do that by creating a stories route.
# app/assets/javascripts/routes/stories.js.coffee
App.StoriesRoute = Ember.Route.extend
model: ->
@get('store').findAll('story')
Open the template back up and add the following:
# app/assets/javascripts/templates/stories.js.emblem
h1 Story Listing
= each story in controller
h2= story.name
| {{{story.body}}}
Open up http://localhost:3000/#/stories
and you should see the story you put in the database.
I'm doing {{{story.body}}}
to prevent the html line breaks from being escaped.
Notice that I am referring to the array of stories in the template as controller
. That's because the data is bound do the Stories controller (which is still in memory).
Wrap Up
That's it for app setup with Rails, Ember, and Ember Data. If you were to continue with this project the next step would be to get project models setup, then nest stories under projects in the Ember router. I hope this was helpful.