Raw Syntax

The stuff programs are made of

How to Set Up Angular With Rails Part 2

Permalink

originally posted on the Intridea blog here: http://www.intridea.com/blog/2014/10/14/how-to-set-up-angular-with-rails-part-2

In Part I of our Angular series, we illustrated the process for setting up an Angular app for Rails.

In Part 2 of this series, we'll be creating a basic HR app. This app will allow us to view employee information through an Angular app (edits and updates to be explained in Part 3 of this series). For more details, see code in Github link at the end of post.

Here's a quick snapshot of what we'll be creating for our HR app:

  • Setting up test frameworks
  • Creating a Rails API
  • Making the Angular frontend talk to the Rails API

Setup RSpec

First, remove the test/ directory. This is left over from our initial app setup in Part 1: How to Set Up Angular with Rails. Then add RSpec and factory_girl_rails to the Gemfile:

group :development, :test do
gem 'rspec-rails', '~> 3.1.0'
end
group :test do
gem 'database_cleaner'
gem 'factory_girl_rails'
end

Next, bundle and install it.

bundle install
rails generate rspec:install

And edit your spec/spec_helper.rb to match:

ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", FILE)
require 'rspec/rails'
require 'rspec/autorun'
require 'database_cleaner'
ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)
RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.infer_spec_type_from_file_location!
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end

Create the Employee Model

Let's create the Employee model with a few fields:

bundle exec rails generate model Employee name:string email:string ssn:string salary:integer
bundle exec rake db:migrate

Next, we'll add some validations in app/models/employee.rb:

class Employee < ActiveRecord::Base
# regex taken from Devise gem
validates_format_of :email, with: /\A[@\s]+@([@\s]+.)+[@\s]+\z/
validates_format_of :ssn, with: /\d{3}-\d{2}-\d{4}/
validates_presence_of :name
validates :salary, numericality: { only_integer: true }
end

Before creating the API interface for Angular, let's create a serializer for our Employee model. This will allow our API to expose only those fields that are necessary to Angular frontend. Add ActiveModel Serializers to the Gemfile

gem 'active_model_serializers'

And bundle install

Create the Employee API

In order to support the Angular frontend, we'll need to create an API controller at app/controllers/api/employees_controller.rb:

class Api::EmployeesController < ApplicationController
respond_to :json
def index
serialized_employees =
ActiveModel::ArraySerializer
.new(Employee.all, each_serializer: EmployeeSerializer)
render json: serialized_employees
end
end

Now we need to define that EmployeeSerializer in app/serializers/employee_serializer.rb:

class EmployeeSerializer < ActiveModel::Serializer
attributes :id, :name, :email, :ssn, :salary
end

And expose this API to the frontend in config/routes.rb:

Rails.application.routes.draw do
get 'example' => 'example#index'
namespace :api do
resources :employees, defaults: { format: :json }
end
end

Finally we'll add some Employee data to db/seeds.rb:

Employee.create(name: "MacGyver", email: "test@example.com", ssn: "555-55-5555", salary: 50000)
Employee.create(name: "Calhoun Tubbs", email: "test2@example.com", ssn: "123-45-6789", salary: 60000)

Seed the database and start the Rails server:

bundle exec rake db:seed
rails s

Then we can open http://localhost:3000/api/employees in the browser which will return:

[
{
"id": 1,
"name": "MacGyver",
"email": "test@example.com",
"ssn": "555-55-5555",
"salary": 50000
},
{
"id": 2,
"name": "Calhoun Tubbs",
"email": "test2@example.com",
"ssn": "123-45-6789",
"salary": 60000
}
]

Test the API

Now that we've verified this action is set-up correctly, let's add a factory at spec/factories/employees.rb:

FactoryGirl.define do
factory :employee do
name 'Test Employee'
email 'test@example.com'
salary 50000
ssn '123-45-6789'
end
end

Next, add a test at spec/controllers/api/employees_controller_spec.rb:

require 'spec_helper'
describe Api::EmployeesController do
before(:each) do
create(:employee, name: 'Calhoun Tubbs')
end
describe '#index' do
it 'should return a json array of users' do
get :index
result = JSON.parse(response.body)
expect(result[0]['name']).to eq('Calhoun Tubbs')
end
end
end

While the above test is somewhat trivial, this set up is great for verifying more complex logic in real world applications. In addition, the reason for adding an API spec prior to the Angular portion is to avoid playing the guessing game if/when problems arise; waiting to add the Angular framework helps to ease the deciphering process. If your Angular app isn't working correctly, its important to verify that the Rails backend is also behaving correctly -- the problem may not be an Angular issue.

Create the Angular frontend

You may want to take a look back at Part 1: How to Set Up Angular with Rails to review how we setup the AngularJS frontend with Rails. Let's start by defining an app in app/assets/javascripts/angular-app/modules/employee.js.coffee.erb:

@employeeApp = angular
.module('app.employeeApp', [
'restangular'
])
.run(->
console.log 'employeeApp running'
)

Next, we'll define the Employees controller in app/controllers/employees.rb:

bundle exec rails g controller Employees index

Then, we'll add to the beginning of config/routes.rb:

root 'employees#index'

Finally, let's start the Angular app in app/views/employees/index.html.erb:

<div ng-app='app.employeeApp' ng-controller='EmployeeListCtrl'>
</div>

If you run the Rails server and hit http://localhost:3000/ you'll get an error about the controller not being defined yet in the javascript console. Before we create the controller, let's setup a Employee model and service to handle fetching Employee records from the Rails API.

The Employee model is defined at app/assets/javascripts/angular-app/models/employee/Employee.js.coffee:

angular.module('app.employeeApp').factory('Employee',[() ->
Employee = () ->
return new Employee()
])

There is no special behavior here yet. It is only returning the data that comes from the API.

To ease the process of working with APIs in Angular, let's add the restangular library to our project. In bower.json add restangular:

"lib": {
"name": "bower-rails generated lib assets",
"dependencies": {
"angular": "v1.2.25",
"restangular": "v1.4.0"
}
},

Then run bundle exec rake bower:install. Next, we need to add restangular and its dependency lodash to app/assets/javascripts/application.js:

//= require jquery
//= require jquery_ujs
//= require angular
//= require angular-rails-templates
//= require lodash
//= require restangular

Afterwards, we'll create the Employee service at app/assets/javascripts/angular-app/services/employee/EmployeeService.js.coffee:

angular.module('app.employeeApp')
.factory('EmployeeService', [
'Restangular', 'Employee',
(Restangular, Employee)->
model = 'employees'
Restangular.extendModel(model, (obj)->
angular.extend(obj, Employee)
)
list: () -> Restangular.all(model).getList()
])

Finally, let's create the controller at app/assets/javascripts/angular-app/controllers/employee/EmployeeListCtrl.js.coffee:

angular.module('app.employeeApp').controller("EmployeeListCtrl", [
'$scope', 'EmployeeService',
($scope, EmployeeService)->
EmployeeService.list().then((employees) ->
$scope.employees = employees
console.dir employees
)
])

If you run the Rails server and visit http://localhost:3000/ in the javascript console you'll see a print out of the Employee objects previously seeded to the database. Next, let's add to the template code to show our Employee objects. In app/views/employees/index.html.erb:

<div ng-app='app.employeeApp' ng-controller='EmployeeListCtrl'>
<table ng-if="employees">
<thead>
<th>Name</th>
<th>Email</th>
<th>SSN</th>
<th>Salary</th>
</thead>
<tbody>
<tr ng-repeat="employee in employees">
<td>{{employee.name}}</td>
<td>{{employee.email}}</td>
<td>{{employee.ssn}}</td>
<td>{{employee.salary | currency}}</td>
</tr>
</tbody>
</table>
</div>

Open http://localhost:3000 and notice the salary field. We used the currency filter to handle formatting that data. Salary comes back from the API as a plain integer value. Angular has a number of useful filters that are included by default.

The above example is very basic. However, we created database objects, exposed those via an API, tested the API, and created an Angular app to handle the frontend. That's no small task! A few pieces are still missing though. While our Ruby API has tests, our Angular app has no tests at all. Furthermore, our employee management app is view-only.

Github code for HR app here.

In Part 3, we'll setup javascript tests, and add editing functionality to our Angular frontend.

Comments