Why?
To say that Ember has grown over the past year would be an understatement. The Ember community has been exploding with awesome new tools to make developing and testing your Ember applications easier than ever. I am a Rails developer and when I first started working with Ember, the fastest way for me to get up and running was to use ember-rails and seriously tweak my the asset pipeline. It wasn't all bad at first but as ember-cli started to become the de-facto standard for new Ember apps I felt myself getting left in the dust. Once I heard that ember-cli was going to become a first class citizen in Ember v2 I decided to quit dragging my feet and get on board with the new way to build Ember apps.
The App
What I needed to build was an application where users could rank their favorite beers via a drag-and-drop ordering UI. Here is what I came up with:
While not 100% fleshed out from a UX point of view this interface was surprisingly simple to implement. In the past I would of had to write a mountain of javascript just to get all the AJAX calls right. THEN I would go out and find some JQuery plugin, become an expert in it and click around in my element inspector for hours until everything was just right.Not anymore. I only used the following combination of tools:
- Rails
- ember-cli
- HTML5 drag-and-drop
Rails API
The Rails API was crazy simple. I used active_model_serializers and just a handfull of rails-generated models.Generate a new rails app w/o sprockets$ rails new App --skip-sprockets
$ cd App
Update Gemfilesource 'https://rubygems.org'
gem 'rails', '4.1.6'
gem 'sqlite3'
gem 'active_model_serializers'
Bundle Install$ bundle install
I'm not going to dive into the details of the API. If you have any questions then please feel free to post them in the comments bellow. :)Once you have your API built out, you should have the following 2 endpoints ready that your Ember app can talk to.PUT /api/v1/ballots/:ballot_id/swap/:li1/:li2(.:format)
GET /api/v1/ballots/:id(.:format)
All we will be doing is getting a ballot and swapping the line_items on that ballot when we drag a beer over another beet. Super simple!
Ember
Now the good stuff. Assuming you want to follow along, install node/npm and ember-cli on your machine.$ brew install node
$ npm install -g ember-cli
Next we will generate our ember app.$ ember new EmberApp
ember-cli is going to go to work standing up the structure of a very basic ember app. It will also setup package management with bower and asset complication with broccoli. This will allow you to use ES6 module syntax, sass, handlebars, coffeescript, and other transpiles languages that you like.Now, startup your Ember app.$ cd EmberApp
$ ember server
You should see this output:
And you will see the following @ localhost:4200
Now, we will start writing Ember. I like to setup my adapter and generate my models first. For this blog post we will be using ember-data.$ ember generate adapter application
$ ember generate model Ballot
$ ember generate model LineItem
Update your generated EmberApp/app/adapters/application.js. We will need to update our adaptor namespace to /api/v1. Then we need to tell Ember to use the ActiveModelAdapter. Ember, out of the box, knows how to communicate with a Rails app using ActiveRecordSerializers. We will also tell it the namespace of our api.import DS from 'ember-data';
export default DS.ActiveModelAdapter.extend({
namespace: 'api/v1',
});
Now open the generated EmberApp/app/models/line-item.js model to find a lot of ember-cli generated javascript! We used to have to do all this tedious work to wire up our models by hand, but now ember-cli handles all this for us. All we need to do is define the attributes for a line item.import DS from 'ember-data';
export default DS.Model.extend({
beer_name: DS.attr('string'),
weight: DS.attr('number'),
ballot: DS.belongsTo('ballot'),
});
Ballot will be a little bit different. We are going to use a computed property called sorted_line_items to sort the ballot's line-items by weight. This will be important since we want to be able to re-arrange the sorted list of beers. Open EmberApp/app/models/ballot.js and update the js with the following.import DS from 'ember-data';
export default DS.Model.extend({
line_items: DS.hasMany('line_items'),
sorted_line_items: (function(){
return this.get('line_items').sortBy('weight');
}).property('line_items.@each.weight'),
});
Next, generate a ballot controller and a line-item view. These don't need to be modified yet.$ ember generate controller Ballot
$ ember generate view LineItem
Finally, we will create an index route.$ ember generate route index
This will generate an EmberApp/app/routes/index.js route file and index.hbs handlebars template. We'll now go into these files and define what controller and model we will want to use.import Ember from 'ember';
export default Ember.Route.extend({
controllerName: 'ballot',
model: function() {
return this.store.find('ballot', 1);
},
setupController: function(controller, model){
controller.set('model', model);
}
});
Then we will define a simple view at EmberApp/app/templates/index.hbs.<div class="container"></div>
<div class="container"></div>
<ol></ol>
<li style="list-style-type: none;"></li>
<ol>{{#each controller.model.sorted_line_items}}</ol>
<li>{{beer_name}}</li>
{{/each}}
Rails
The Rails side of this application is pretty simple. A single controller, for getting ballots and a basic model structure that looks like this.-------- ---------- ------
| | 1 0..* | | 1 1 | |
| Ballot | <======= |="" lineitem=""> | Beer |</=======>
| | | weight | | name |
-------- ---------- ------
A little rails generate magic should get us pretty far.$ rails g model Ballot
$ rails g model Beer name:string
$ rails g model LineItem ballot:references beer:references weight:integer
Then I just made a tiny ballot in my seeds that we can use to play with for this app.db/seeds.rbBeer.delete_all
Ballot.delete_all
b1 = Beer.create!(name: 'Goose Island IPA')
b2 = Beer.create!(name: 'Dogfish Head 90 Min IPA')
b3 = Beer.create!(name: 'Mother Earth Oatmeal Stout')
b4 = Beer.create!(name: 'Raleigh Brewing Mocha Stout')
Ballot.create!(beers: [b1, b2, b3, b4], user_id: 1)
Lastly, I created a BallotsController and route so that my Ember app has a ballot that it can grab from the Rails app.app/controllers/api/v1/ballots_controller.rbclass Api::V1::BallotsController < ApplicationController
respond_to :json
def show
render json: Ballot.first
end
end
app/serializers/ballot_serializer.rbclass BallotSerializer < ActiveModel::Serializer
embed :ids, include: true
attributes :id
has_many :line_items, serializer: LineItemSerializer
end
config/routes.rbRails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :ballots
end
end
end
Once that is done, initialize a Rails server...$ rails s
...and then initialize your Ember app via ember-cli but you will need to tell ember to talk to localhost:3000 which should be where your rails app is hosted at.$ ember server --proxy http://localhost:3000
Once your ember server is up and running, point your browser to localhost:4200 and you will see this. Dont forget to seed your database!
Resources
In part two, I'll go into detail about how I implemented HTML5 drag-n-drop functionality for updating beer rankings. I will demonstrate how simple it was to build this functionality into my existing, non-dynamic list of beers.