You wish to validate forms using a server-side REST API provided by Rails.
Rails already provides model validation support out of the box for us. Let us start with the Contact ActiveRecord model.
class Contact < ActiveRecord::Base
attr_accessible :age, :firstname, :lastname
validates :age, :numericality => {
:only_integer => true, :less_than_or_equal_to => 50 }
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.
class ContactsController < ApplicationController
respond_to :json
def create
@contact = Contact.new(params[:contact])
if @contact.save
render json: @contact, status: :created, location: @contact
else
render json: @contact.errors, status: :unprocessable_entity
end
end
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:
{ "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.
Contact.create($scope.contact, success, failure);
function failure(response) {
_.each(response.data, function(errors, key) {
_.each(errors, function(e) {
$scope.form[key].$dirty = true;
$scope.form[key].$setValidity(e, false);
});
});
}
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.
<div class="control-group" ng-class="errorClass('age')">
<label class="control-label" for="age">Age</label>
<div class="controls">
<input ng-model="contact.age" type="text" name="age"
placeholder="Age" required>
<span class="help-block"
ng-show="form.age.$invalid && form.age.$dirty">
{{errorMessage('age')}}
</span>
</div>
</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.
$scope.errorClass = function(name) {
var s = $scope.form[name];
return s.$invalid && s.$dirty ? "error" : "";
};
The errorMessage
will print a more detailed error message and is defined in the same controller.
$scope.errorMessage = function(name) {
result = [];
_.each($scope.form[name].$error, function(key, value) {
result.push(value);
});
return result.join(", ");
};
It iterates over each error message and creates a comma separated String out of it.
You can find the complete example on github.
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.