Backend Integration with Ruby on Rails
In this chapter we will have a look into solving common problems when combining Angular.js with the Ruby on Rails frameworks. The examples used in this chapter are based on an example application to manage a list of contacts.
Consuming REST APIs
Problem
You wish to consume a JSON REST API implemented in your Rails application.
Solution
Using the $resource service is a great start and can be tweaked to feel more natural to a Rails developer by configuring the methods in accordance with the Rails actions.
1 app.factory("Contact", function($resource) {
2 return $resource("/api/contacts/:id", { id: "@id" },
3 {
4 'create': { method: 'POST' },
5 'index': { method: 'GET', isArray: true },
6 'show': { method: 'GET', isArray: false },
7 'update': { method: 'PUT' },
8 'destroy': { method: 'DELETE' }
9 }
10 );
11 });
We can now fetch a list of contacts using Contact.index() and a single contact with Contact.show(id). These actions can be directly mapped to the ContactsController actions in your Rails backend.
1 class ContactsController < ApplicationController
2 respond_to :json
3
4 def index
5 @contacts = Contact.all
6 respond_with @contacts
7 end
8
9 def show
10 @contact = Contact.find(params[:id])
11 respond_with @contact
12 end
13
14 ...
15 end
The Rails action controller uses a Contact ActiveRecord model with the usual contact attributes like firstname, lastname, age, etc. By specifying respond_to :json the controller only responds to the JSON resource format and we can use respond_with to automatically transform the Contact model to a JSON response.
The route definition uses the Rails default resource routing and an api scope to separate the API requests from other requests.
1 Contacts::Application.routes.draw do
2 scope "api" do
3 resources :contacts
4 end
5 end
This will generate paths like for example api/contacts and api/contacts/:id for the HTTP GET method.
You can find the complete example on github.
Discussion
If you want to get up to speed with Ruby on Rails, I suggest that you look into the Rails Guides which will help you understand how all the pieces fit together.
Rails Security using Authenticity Token
The example code above works nicely until we use the HTTP methods POST, PUT and DELETE with the resource. As a security mechanism, Rails expects an authenticity token to prevent a CSRF (Cross Site Request Forgery) attack. We need to submit an additional HTTP header X-CSRF-Token with the token. It is conveniently rendered in the HTML meta tag csrf-token by Rails. Using jQuery we can fetch that meta tag definition and configure the $httpProvider appropriately.
1 var app = angular.module("Contacts", ["ngResource"]);
2 app.config(function($httpProvider) {
3 $httpProvider.defaults.headers.common['X-CSRF-Token'] =
4 $('meta[name=csrf-token]').attr('content');
5 });
Rails JSON response format
If you are using a Rails version prior 3.1, you’ll notice that the JSON response will use a contact namespace for the model attributes which breaks your Angular.js code. To disable this behavior you can configure your Rails app accordingly.
1 ActiveRecord::Base.include_root_in_json = false
There are still inconsistencies between the Ruby and Javascript world. For example, in Ruby we use underscored attribute names (display_name) whereas in Javascript we use camelCase (displayName).
There is a custom $resource implementation angularjs-rails-resource available to streamline consuming Rails resources. It uses transformers and inceptors to rename the attribute fields and handles the root wrapping behavior for you.
Implementing Client-Side Routing
Problem
You wish to use client-side routing in conjunction with a Ruby on Rails backend.
Solution
Every request to the backend should initially render the complete page in order to load our Angular app. Only then will the client-side rendering take over. Let us first have a look at the route definition for this “catch all” route.
1 Contacts::Application.routes.draw do
2 root :to => "layouts#index"
3 match "*path" => "layouts#index"
4 end
It uses Route Globbing to match all URLs and defines a root URL. Both will be handled by a layout controller with the sole purpose of rendering the initial layout.
1 class LayoutsController < ApplicationController
2 def index
3 render "layouts/application"
4 end
5 end
The actual layout template defines our ng-view directive and resides in app/views/layouts/application.html - nothing new here. So let’s skip ahead to the Angular route definition in app.js.erb.
1 var app = angular.module("Contacts", ["ngResource"]);
2
3 app.config(function($routeProvider, $locationProvider) {
4 $locationProvider.html5Mode(true);
5 $routeProvider
6 .when("/contacts",
7 { templateUrl: "<%= asset_path('contacts/index.html') %> ",
8 controller: "ContactsIndexCtrl" })
9 .when("/contacts/new",
10 { templateUrl: "<%= asset_path('contacts/edit.html') %> ",
11 controller: "ContactsEditCtrl" })
12 .when("/contacts/:id",
13 { templateUrl: "<%= asset_path('contacts/show.html') %> ",
14 controller: "ContactsShowCtrl" })
15 .when("/contacts/:id/edit",
16 { templateUrl: "<%= asset_path('contacts/edit.html') %> ",
17 controller: "ContactsEditCtrl" })
18 .otherwise({ redirectTo: "/contacts" });
19 });
We set the $locationProvider to use the HTML5 mode and define our client-side routes for listing, showing, editing and creating new contacts.
You can find the complete example on github.
Discussion
Let us have a look into the route definition again. First of all the filename ends with erb, since it uses ERB tags in the javascript file, courtesy of the Rails Asset Pipeline. The asset_path method is used to retrieve the URL to the HTML partials since it will change depending on the environment. On production the filename contains an MD5 checksum and the actual ERB output will change from /assets/contacts/index.html to /assets/contacts/index-7ce113b9081a20d93a4a86e1aacce05f.html. If your Rails app is configured to use an asset host, the path will in fact be absolute.
Validating Forms Server-Side
Problem
You wish to validate forms using a server-side REST API provided by Rails.
Solution
Rails already provides model validation support out of the box for us. Let us start with the Contact ActiveRecord model.
1 class Contact < ActiveRecord::Base
2 attr_accessible :age, :firstname, :lastname
3
4 validates :age, :numericality => {
5 :only_integer => true, :less_than_or_equal_to => 50 }
6 end
It defines a validation on the age attribute. It must be an integer and less or equal to 50 years.
In the ContactsController we can use that to make sure the REST API returns proper error messages. As an example let us look into the create action.
1 class ContactsController < ApplicationController
2 respond_to :json
3
4 def create
5 @contact = Contact.new(params[:contact])
6 if @contact.save
7 render json: @contact, status: :created, location: @contact
8 else
9 render json: @contact.errors, status: :unprocessable_entity
10 end
11 end
12
13 end
On success it will render the contact model using a JSON presentation and on failure it will return all validation errors transformed to JSON. Let us have a look at an example JSON response:
1 { "age": ["must be less than or equal to 50"] }
It is a hash with an entry for each attribute with validation errors. The value is an array of Strings since there might be multiple errors at the same time.
Let us move on to the client-side of our application. The Angular.js contact $resource calls the create function and passes the failure callback function.
1 Contact.create($scope.contact, success, failure);
2
3 function failure(response) {
4 _.each(response.data, function(errors, key) {
5 _.each(errors, function(e) {
6 $scope.form[key].$dirty = true;
7 $scope.form[key].$setValidity(e, false);
8 });
9 });
10 }
Note that ActiveRecord attributes can have multiple validations defined. That is why the failure function iterates through each validation entry and each error and uses $setValidity and $dirty to mark the form fields as invalid.
Now we are ready to show some feedback to our users using the same approach discussed already in the forms chapter.
1 <div class="control-group" ng-class="errorClass('age')">
2 <label class="control-label" for="age">Age</label>
3 <div class="controls">
4 <input ng-model="contact.age" type="text" name="age"
5 placeholder="Age" required>
6 <span class="help-block"
7 ng-show="form.age.$invalid && form.age.$dirty">
8 {{errorMessage('age')}}
9 </span>
10 </div>
11 </div>
The errorClass function adds the error CSS class if the form field is invalid and dirty. This will render the label, input field and the help block with a red color.
1 $scope.errorClass = function(name) {
2 var s = $scope.form[name];
3 return s.$invalid && s.$dirty ? "error" : "";
4 };
The errorMessage will print a more detailed error message and is defined in the same controller.
1 $scope.errorMessage = function(name) {
2 result = [];
3 _.each($scope.form[name].$error, function(key, value) {
4 result.push(value);
5 });
6 return result.join(", ");
7 };
It iterates over each error message and creates a comma separated String out of it.
You can find the complete example on github.
Discussion
Finally, the errorMessage handling is of course pretty primitive. A user would expect a localized failure message instead of this technical presentation. The Rails Internationalization Guide describes how to translate validation error messages in Rails and might prove helpful to further use that in your client-side code.