Ember on Rails: Validations

In the previous episodes we've built a working Ember.js application to manage our growing collection of Books. It lives inside a Rails application which it uses for its backend.

We're able to list all the books in our vast library, and also add new books to our collection. However, if we try to add a book without giving it a title, the backend gladly accepts it and saves it, which is probably not the optimal behavior.

It is time to add validations to our application.

ActiveRecord validations

On the server side, preventing a Book from being saved when it doesn't have a title is straightforward. Change app/models/book.rb to look like:

class Book < ActiveRecord::Base
  validates :title, :presence => true
end

Now, if you attempt to create a Book without a title, ActiveRecord should complain:

> Book.create!
ActiveRecord::RecordInvalid: Validation failed: Title can't be blank

Nice and effective.

The client side is oblivious

However, things are looking a bit different on the client side. Ember has no knowledge of this validation. Neither does it have any real idea what to do when saving fails.

If we click the "Save" button in the "Add a book" form without entering a title, we get an error in the Javascript console:

Uncaught Error: Assertion Failed: Error: The backend rejected the commit because it was invalid: {title: can't be blank}

Not the worlds most user friendly error - especially not hidden away in the Javascript console - but it hints at a possible improvement; The error message from ActiveRecord is actually present in the client side error: "{title: can't be blank}".

As it turns out, using active_model_serializers Rails responds with a JSON structure containing the validation error messages when saving fails:

$ curl -d 'book[author_name]="J.R.R. Tolkien"' http://0.0.0.0:3000/books
{"errors":{"title":["can't be blank"]}}

Wouldn't it be nice if we could somehow show those nice messages to our users?

Showing errors in the UI

And of course we can do just that - even without adding all that much code.

But first things first, we need somewhere to actually show the errors to our users. In app/assets/javascripts/templates/books/new.handlebars add:

{{#each error in errors.title}}
  <p>{{error.message}}</p>
{{/each}}

where you want the errors for the title attribute to appear. I prefer right below the title input element, but it's entirely up to you.

Promises

Now things are going to stray from what we're used to in Ruby-land. In the create action in BooksNewRoute we try to save our book using book.save(). The save method of Ember models returns a Promise.

Promises isn't something we Rubyists are used to dealing with. Simplified they are objects with a fulfillment-callback and a rejections-callback. In our specific case, the promise returned by save() triggers its fulfillmint-callback when we get a success response from the API. Likewise the rejection callback is triggered when we get a failure response.

Codified this looks like:

newBook.save().then(
  function() {
    // Success
  },
  function() {
    // Failure
  }
);

It really does work

The above is a fairly good sketch for the code we need, so we can update our create action to use it.

When saving is successful we want to transition to our list of books like previously. And when saving fails we want to stay on the form, showing the error message we receive.

In reality this means we don't really want to do anything when the save is rejected; we just need to give the promise something to do so the error doesn't bubble all the way to the user interface. Ember-data handles the rest for us.

The above behavior is accomplished by making the create action in app/assets/javascripts/routes/books_new_route.js look like:

create: function() {
  var newBook = this.get('currentModel');
  var self = this;
  newBook.save().then(
    function() { self.transitionTo('books') },
    function() { }
  );
}

The var self = this trick ensures we have access to the transitionTo method inside our success function (damn Javascript and your ever-changing this).

The first parameter to then is the success handler where we transition to the list of books, and the second parameter is our failure handler: A function that does nothing.

Users, consider yourself informed

Now, if you try to add a book without a title you'll get an actual error message in the user interface. Success!

Serverside validation error rendered inline

And if you add a title and click "Save" the book is saved on the backend and you're shown the list of books. Success x 2!

If you like this tutorial, you might also be interested in my upcoming book, Ember for Rails Developers - a Rails-friendly introduction to Ember.