This is a pattern you can use in your Rails projects to keep your controllers lean. It works especially well in Rails API projects, but I think you can find success with it even when using HTML over the wire. I originally found this while searching through the Rails API docs on the Rescuable concern.
The idea is to use the rescue_from
behavior in controllers to render a consistent error response for any controller that works with an active record model, or active model model.
Usually we always write that standard:
def create
@blog_post = BlogPost.new(...)
if @blog_post.save
# render ....
else
render :new
end
end
Instead use the bang version of save!
(or validate!
, in the case of an ActiveModel::Model
), and add the corresponding rescue_from
call:
def create
@blog_post = BlogPost.new(...)
@blog_post.save!
render ....
end
rescue_from ActiveRecord::RecordInvalid, with: :model_errors
rescue_from ActiveModel::ValidationError, with: :model_errors
def model_errors(error)
# Depending on the type of error, the errors will be either on the model/record property
errors = error.respond_to?(:record) ? error.record.errors : error.model.errors
respond_to do |format|
format.json { json: { errors: errors.full_messages }, status: :unprocessable_entity } }
end
end
For API controllers, this helps enforce consistency in response format/status code, in addition to DRYing up your code.
For a new codebase, you could potentially put the code in ApplicationController
, but in a more mature codebase, you might consider using a concern where you can opt controllers into this behavior on a one-by-one basis, confirming you don’t unintentionally cause regressions:
# app/controllers/concerns/model_errors.rb
module ModelErrors
extend ActiveSupport::Concern
included do
rescue_from ActiveRecord::RecordInvalid, with: :model_errors
rescue_from ActiveModel::ValidationError, with: :model_errors
end
def model_errors(error)
# Depending on the type of error, the errors will be either on the model/record property
errors = error.respond_to?(:record) ? error.record.errors : error.model.errors
respond_to do |format|
format.json { json: { errors: errors.full_messages }, status: :unprocessable_entity } }
end
end
end
If you’re interested in a similar approach when sending HTML over the wire, here’s a GitHub Gist with a sample implementation (using a tidbit more logic to get the render calls right):
https://gist.github.com/mgodwin/4db6ec1eaca5d08243bada38bf02360b