Angular with Rails, Part II: Building an HR App

Angular

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. In the meantime, review Part 1 and if you have any questions feel free to message us on Twitter.