Rails introduce powerful ErrorReporter#unexpected for controlled error reporting

railsDecember 13, 2023Dotby Alkesh Ghorpade

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.

Closing Remark

Could your team use some help with topics like this and others covered by ShakaCode's blog and open source? We specialize in optimizing Rails applications, especially those with advanced JavaScript frontends, like React. We can also help you optimize your CI processes with lower costs and faster, more reliable tests. Scraping web data and lowering infrastructure costs are two other areas of specialization. Feel free to reach out to ShakaCode's CEO, Justin Gordon, at justin@shakacode.com or schedule an appointment to discuss how ShakaCode can help your project!
Are you looking for a software development partner who can
develop modern, high-performance web apps and sites?
See what we've doneArrow right