- Check the validity of a model in a controller
- Render a response with the error messages
- Use HTTP status codes to provide additional context
Now that we've seen how Active Record can be used to validate our data, let's see how we can use that in our controllers to give our user access to the validation errors, so they can fix their typos or other problems with their request.
To get set up, run:
bundle install
rails db:migrate db:seed
rails s
Up until this point, our create
action has looked something like this:
# app/controllers/birds_controller.rb
def create
bird = Bird.create(bird_params)
render json: bird, status: :created
end
Let's add some validation to our Bird
model, so that we don't end up with bad
data:
# app/models/bird.rb
class Bird < ApplicationRecord
validates :name, presence: true, uniqueness: true
end
Now, if we try to create a bird using Postman with bad data, we've got a problem!
{
"species": "Archilochus colubris"
}
Our server still returns a Bird
object, but we can clearly see that it wasn't
saved successfully:
{
"id": null,
"name": null,
"species": "Archilochus colubris",
"created_at": null,
"updated_at": null,
"likes": 0
}
From this process, we can tell:
- Our model validation prevented this bad data from being saved in the database (yay!)
- The response doesn't tell us anything about why the data wasn't saved (boo.)
To provide this additional context, we need to update our controller action to change the response based on whether or not the bird was saved successfully.
def create
bird = Bird.create(bird_params)
if bird.valid?
render json: bird, status: :created
else
render json: { errors: bird.errors }, status: :unprocessable_entity
end
end
Now, we get a different response after sending that same request in Postman:
{
"errors": {
"name": ["can't be blank"]
}
}
From the controller, bird.errors
will give a serializable object with all the
error messages from our Active Record validations.
We also included status code of 422 Unprocessable Entity, indicating this was a bad request.
We can take a similar approach to validation in our update
method, since
validations will also run when a model is updated:
def update
bird = Bird.find_by(id: params[:id])
if bird
bird.update(bird_params)
if bird.valid?
render json: bird
else
render json: { errors: bird.errors }, status: :unprocessable_entity
end
else
render json: { error: "Bird not found" }, status: :not_found
end
end
At this point, our controller actions are getting a bit long! There are a few opportunities for DRY-ing up this code by creating some helper methods. See if you can identify which controller actions reuse the same logic, and extract that logic to helper methods.
With model validations in place, we can help protect our database against bad data. Active Record validations also help provide error messages to indicate why a certain value wasn't considered valid data. We can access the model's validity and error messages in the controller. By sending this data in the response, we'll be able to provide additional context to our clients about what went wrong with their request so they can fix it.