Rails introduce powerful ErrorReporter#unexpected for controlled error reporting
Rails adds ErrorReporter#unexpected to report precondition violations. This feature will raise an error in development and test but only report the error in production.
A precondition violation is a broken promise. Essential conditions for an action aren't met, causing unexpected outcomes. When the preconditions, required truths for successful operation, aren't fulfilled, we encounter precondition violations, causing potential malfunctions.
Before
Let's assume you have a Rails application with the below code:
def update_profile(params)
if params[:age].to_i < 18
Rails.error.report("[Security] Attempting to update profile with age under 18")
# raise error or redirect
else
# Update the user profile
end
end
In this example, a precondition violation occurs if the user tries to update their profile with an age under 18. The expected behaviour is to redirect the user to the profile page on production. In the development or test environment, the code must raise an error. To fix this, you should add an environment-based condition to raise an error.
def update_profile(params)
if params[:age].to_i < 18
Rails.error.report("[Security] Attempting to update profile with age under 18")
raise "Age Limit Error" unless Rails.env.production?
redirect_to profile_path....
else
# Update the user profile
end
end
While this approach works, it could be better. You can create a custom error handler that performs the above code gracefully.
After
Rails added the unexpected
method to the ErrorReporter
class.
The method either reports the given error in production
or
raises it in development
or
test.
You can fix the above example by replacing the
report
with the unexpected
method.
def update_profile(params)
if params[:age].to_i < 18
Rails.error.unexpected("[Security] Attempting to update profile with age under 18")
redirect_to profile_path....
else
# Update the user profile
end
end
On production,
the unexpected
method will execute
and
report the error.
It will redirect the user to the profile_path
.
But the development
or
test environment will raise the error.
A few cases where this feature is applicable are:
1. Checking API availability
def fetch_data_from_api(url)
begin
response = Faraday.get(url)
Rails.error.unexpected("[API] Unexpected response code from API: #{response.status}") unless response.success?
# Parse and return the data
rescue StandardError => e
# Handle other errors
end
end
When fetching data from an API,
if the response is not successful,
it's considered a precondition violation
.
Instead of raising an error
and
stopping the process,
ErrorReporter#unexpected
logs the error silently in production
and
lets the application continue with alternative data sources
or
fallback mechanisms.
2. Asserting internal state
class Order
def validate!
Rails.error.unexpected("[Order] Order total is nil") unless total
Rails.error.unexpected("[Order] Order items are empty") if items.empty?
end
end
In this example,
an Order
object validates its internal state before processing.
If the total is missing
or
there are no items,
these are considered precondition violations
.
Since these are likely internal logic errors,
ErrorReporter#unexpected
silently reports them in production
while raising exceptions in development
and
test environments for debugging purposes.
3. Checking database constraints
class Product < ApplicationRecord
validates_uniqueness_of :sku
end
def create_product(params)
product = Product.create(params)
Rails.error.unexpected("[Database] Duplicate SKU encountered for product creation") unless product.persisted?
# Handle duplicate SKU gracefully in production (e.g., flash message)
# Raise an exception in development/test for debugging
end
This example reports duplicate database constraint violations silently in production while raising an exception for debugging in development and test.
To know more about this feature, please refer to this PR.