Smashing Boxes | 07.08.15 | Code

Merging Rails and Ember-CLI – Part III


  • Android
  • iOS
  • Web

In this third and final part of my “Merging ember-cli with Rails” blog series, I will talk about how I connected the Ember frontend with the Rails backend so that beer rankings could be persisted in the database.

###Writing the AJAX call.

In Part 2 you may recall the swap controller action I put in my ballot controller. Well, that wasn’t sending anything back to the Rails API. Every time I reloaded the browser, my beers were back in their original order. Let’s fix that.

EmberApp/app/controllers/ballot.js

import Ember from 'ember';

export default Ember.Controller.extend({
sortProperties: ['weight'],

actions: {
swap: function(line_item_id_1, line_item_id_2) {
var li1, li1_weight, li2, li2_weight;

li1 = this.model.get('line_items').findBy('id', line_item_id_1);
li2 = this.model.get('line_items').findBy('id', line_item_id_2);
li1_weight = li1.get('weight');
li2_weight = li2.get('weight');
li1.set('weight', li2_weight);
li2.set('weight', li1_weight);
return Ember.$.ajax({
url: "/api/v1/ballots/" + (this.model.get('id')) + "/swap/" + (li1.get('id')) + "/" +(li2.get('id')), type: 'put'
}).done(function() {}).fail(function() {
li1.rollback();
return li2.rollback();
});
}
}
});

Note the addition of the AJAX call at the end of the swap action in the ballot controller. There is likely a cleaner and more Ember(y) way to do this, and I would love to hear it in the comments below; however, that call got the job done for me and it’s good enough to finish out this drag and drop prototype. I opened my console and was pleased to see an AJAX PUT network request after a drop event. Let’s set up our Rails API endpoint.

###The Coupling

In my head, I know I want the interaction between my Ember and Rails apps to look like this:

---------------- -------------------
| | PUT: /api/v1/ballots/1/swap/1/2 | |
| Ember Frontend | ==============================> | Rails API Backend |
| | | |
---------------- -------------------

That is really all we need. A single endpoint that says “swap the weight of line item 1 and line item 2 on ballot 1.” That way, we keep the data on our backend API in sync with that is going on in the Ember frontend.

So, let’s do a little Rails to get this all set up. We are going to generate a ballot controller, then update the routes to access the request to /api/v1/ballots/:id/swap/:li1_id/:li2_id

$ rails g controller Api::V1::Ballots

config/routes.rb

namespace :api do
namespace :v1 do
resources :ballots do
put '/swap/:li1/:li2', to: 'ballots#swap'
end
end
end

Then make yourself a controller action for swap.

app/controllers/ballots_controller.rb

class Api::V1::BallotsController < ApplicationController
respond_to :json

def swap
ballot = Ballot.find(params[:ballot_id])
ballot.swap(LineItem.find(params[:li1]), LineItem.find(params[:li2]))
if ballot.save
render json: {}
else
render status: 500
end
end
end

Lastly, I added this method to my Ballot model to handle swapping line items.

app/models/ballots.rb

def swap(li1, li2)
li1_weight = li1.weight
li2_weight = li2.weight
li1.weight = li2_weight
li2.weight = li1_weight
li1.save && li2.save
end

So that should work well. Right?

###Dealing with Rails CSRF

That AJAX PUT should have worked but our Rails API is throwing back a crazy error.

ActionController::InvalidAuthenticityToken

What is going on now is that Rails is kicking back our request due to a missing CSRF token. Luckily, there is a CSRF ember npm package we can install that will handle setting the token in all our request headers.

$ npm install --save rails-csrf

Once that is done running, update your app.js to include your new module and your routes/index.js to request the rails CSRF token before page load:

EmberApp/app/app.js

import Ember from 'ember';
import Resolver from 'ember/resolver';
import loadInitializers from 'ember/load-initializers';
import config from './config/environment';

Ember.MODEL_FACTORY_INJECTIONS = true;

var App = Ember.Application.extend({
modulePrefix: config.modulePrefix,
podModulePrefix: config.podModulePrefix,
Resolver: Resolver
});

loadInitializers(App, config.modulePrefix);

//Initialize Rails CSRF
loadInitializers(App, 'rails-csrf');
import { setCsrfUrl } from 'rails-csrf/config';
setCsrfUrl('api/v1/csrf');

export default App;

EmberApp/app/routes/index.js

import Ember from 'ember';

export default Ember.Route.extend({
controllerName: 'ballot',

beforeModel: function() {
return this.csrf.fetchToken();
},

model: function() {
return this.store.find('ballot', 1);
},
setupController: function(controller, model){
controller.set('model', model);
}
});

Note the addition of “beforeModel.” This will request the csrf token from the backend before every page load, but we are not done yet. We need to support this request to /api/v1/csrf in our Rails application.

First, we add this line to our routes.rb file underneath the :api and :v1 namespace declarations.

routes.rb

get :csrf, to: 'csrf#index'

Then we will generate a simple CSRF controller to handle this request.

$ rails g controller Api::V1::Csrf index

csrf_controller.rb

class Api::V1::CsrfController < ApplicationController def index render json: { request_forgery_protection_token => form_authenticity_token }.to_json
end
end

Once that is complete. Restart your rails server then reload your Ember app. Now start dragging and dropping to modify your beer rankings. You should see that, on every drop event, an AJAX request is being sent back to the Rails application and updating the line items weights. Now, when you refresh your browser, your updates to beer rankings will be persisted!

##Conclusion

While it does take a little work to wire up a Rails/Ember application, they play quite well together! Ember-Data’s near perfect compatibility with ActiveModelSerializers makes keeping your models up to date crazy simple. And, with the Rails API becoming a first class citizen with Rails 5, I can see this flow becoming even easier in the coming future. So, if you are not a big fan of integrated frameworks like meteor or volt than using Rails and Ember is a very elegant way to get a lot of work done fast!

##Resources


We are Smashing Boxes. We don’t just build great products, we help build great companies. LET’S CHAT.

Careers at Smashing Boxes


Open Positions

We don’t just make great products, we help build great companies.


Contact Us

Get exclusive access to Smashing Boxes news, case studies, and events.

Sign up now
close ×

Get exclusive access to Smashing Boxes news, case studies, and events. Sign up now!

* indicates required