Next, we'll update our Rails API to support editing of employee records. Let's start by adding some tests for the update method in spec/controllers/api/employees_controller_spec.rb:
At this point, these tests will fail. We need to create an update method in our API controller, but first we must specify what params are allowed on update (Rails' strong parameters) at app/controllers/api/employees_controller.rb:
Next run the tests to make sure they pass. For a more complex application there would be more tests, but in our example these will suffice.
Add the Edit Front End
For our employee edit functionality we'll define a modal dialog containing a form and a submit button. The Angular-bootstrap package contains a modal dialog tool. We'll add the angular-bootstrap package to our bower.json:
{
"lib":{
"name":"bower-rails generated lib assets",
"dependencies":{
"angular":"v1.2.25",
"restangular":"v1.4.0",
"angular-bootstrap":"v0.11.0"
}
},
"vendor":{
"name":"bower-rails generated vendor assets",
"dependencies":{
}
}
}
And bundle exec rake bower:install
Next, require angular-bootstrap in app/assets/javascripts/application.js:
//= require jquery
//= require jquery_ujs
//= require angular
//= require angular-rails-templates
//= require lodash
//= require restangular
//= require angular-bootstrap
//= require bootstrap-sprockets
//= require angular-app/app
// rest of the file omitted
Finally, we'll add it as a dependency to our angular app in app/assets/javascripts/angular-app/modules/employee.js.coffee.erb:
@employeeApp = angular
.module('app.employeeApp',[
'restangular',
'ui.bootstrap'
'templates'
])
.run(->
console.log'employeeApp running'
)
Now, we need to add a modal controller to our AngularJS app. This controller will handle popping up a modal editable form and submitting it to our Rails API. In app/assets/javascripts/angular-app/controllers/employee/EmployeeListCtrl.js.coffee:
In the above code we're defining a modal controller that receives the employee object we passed in the prior controller, along with function stubs for the save / cancel buttons in the dialog. Next, we need to define the edit form template in app/assets/javascripts/angular-app/templates/employee/edit.html.erb:
Finally, if you run the code now you'll get an error loading the template. We need to tell our angular-rails-templates gem what the base path for our templates is. This is necessary because we are using the rails asset pipeline to combine our javascript files. In config/application.rb:
Next, we can return to our employees index and provide an ng-click attribute to launch the edit form. We'll add this link on the employee name cell in the table in app/views/employees/index.html.erb:
Now, clicking an employee's name will launch a modal form with fields containing that employee's data. However, we still need to hook up the Save Changes button to our Rails API. In app/assets/javascripts/angular-app/controllers/employee/EmployeeEditModalCtrl.js.coffee:
We can simply say put() above because we are using Restangular to manage that object.
One problem with the above code is that while the server is applying validation to the object, the client side AngularJS portion is not. One downside of using client-side MVC is the fact that validation from the server must often be duplicated on the client. The subject of AngularJS form validation could be a blog post on its own. I'll leave that as an exercise for the reader.
Secure the UI
Though the editing functionality now works, notice the page still shows each employee's social security number. In order to mask the SSN and only reveal it in edit form, we can create an AngularJS filter. In app/assets/javascripts/angular-app/filters/employee/empMaskNumber.js.coffee
It's recommended that you name filters with an app prefix to avoid colliding with AngularJS's built-in filters. Let's edit our employee table to make use of the filter in app/views/employees/index.html.erb:
Viewing the employee table again you should see the SSN field being masked with asterisks except the last 4 digits. While this filter is simple and apparently works, when building more complex filters we will need javascript tests.
Setup JavaScript Tests
Next, we'll setup jasmine-rails. Add jasmine-rails to the Gemfile:
group:test,:developmentdo
gem'jasmine-rails'
end
And add angular-mocks to the bower.json:
{
"lib":{
"name":"bower-rails generated lib assets",
"dependencies":{
"angular":"v1.2.25",
"restangular":"v1.4.0",
"angular-bootstrap":"v0.11.0",
"angular-mocks":"v1.3.2"
}
},
"vendor":{
"name":"bower-rails generated vendor assets",
"dependencies":{
}
}
}
Then run:
bundle install
rails generate jasmine_rails:install
bundle exec rake bower:install
Next, we need to setup our spec_helper. In app/assets/javascripts/spec_helper.coffee:
Note the beforeEach line above. As your application grows and more angular apps are added, they must also be added here to be available for testing. The above spec helper also requires SinonJS. Download the file and place it at vendor/assets/javascripts/sinon.js .
Now you should be able to run an empty test suite:
RAILS_ENV=test bundle exec rake spec:javascript
Which should return 0 specs and 0 failures. We are now ready to write our test for our filter defined above. In spec/javascripts/filters/emp_mask_number_spec.js.coffee:
In a real application expect to have more specs than just the above. At this point though, you have everything you need to continue building your application and expanding. You can find the code for this example on Github.
Need to catch up on the Angular with Rails series? Check these out: