5 Best Practices for Handling Controllers in Ruby on Rails


Catch the Correct Errors

Bad practice: Catching all exceptions indiscriminately.

# app/controllers/posts_controller.rb
def create
  @post.create(post_params)
rescue => e
  logger.error "Unhandled exception: #{e}"
end

This approach will suppress all errors, making debugging difficult and hiding issues that might require immediate attention.

Good practice: Catch specific exceptions relevant to the context.

# app/controllers/payments_controller.rb
def process_payment
  PaymentGateway.charge!(current_user, amount)
rescue PaymentGateway::PaymentFailed => e
  logger.error "Payment failed: #{e}"
  redirect_to :back, alert: "Payment failed: #{e.message}"
end

Specifying the exception type makes the error handling focused and reduces the suppression of unexpected errors.

You can also define a constant variable with all the errors:

# /config/intializers/custom/errors.rb
STRIPE_ERRORS = [
  # All the errors
]

# app/controllers/payments_controller.rb
def process_payment
  PaymentGateway.charge!(current_user, amount)
rescue *STRIPE_ERRORS => e
  logger.error "Payment failed: #{e}"
  redirect_to :back, alert: "Payment failed: #{e.message}"
end

This really helps in terms of developer experience to understand what to expect.


Truly RESTful APIs

Bad practice: Unclear or inexistant status management.

# app/controllers/api/v1/books_controller.rb
def update
  if @book.update(book_params)
    render json: @book
  else
    render json: @book.errors
  end
end

Good practice: Use Rails’ symbolic status codes for clarity.

# app/controllers/api/v1/books_controller.rb
def update
  if @book.update(book_params)
    render json: @book, status: :ok
  else
    render json: @book.errors, status: :unprocessable_entity
  end
end

You are supposed to understand what to do with the response solely by using the status code.

It’s bad practice if you need to parse the body to understand what to do next.


Proper Timeout Settings

Bad practice: Using default or overly long timeouts.

# config/initializers/timeout.rb
Rack::Timeout.timeout = 120  # seconds

Long timeouts can lead to slow application responses under high load or services failures.

Good practice: Set reasonable timeouts based on the expected response times of external services.

# config/initializers/timeout.rb
Rack::Timeout.timeout = 10  # seconds

Shorter, more appropriate timeouts help maintain application performance and better user experiences.


Code to Background

Bad practice: Handling long-running tasks synchronously.

# app/controllers/orders_controller.rb
def create
  @order.process_payment
  @order.ship_order
  render json: @order
end

Processing payments and shipping orders in the request cycle can lead to timeouts and poor user experiences.

Good practice: Use background jobs for heavy lifting.

# app/controllers/orders_controller.rb
def create
  OrderJob.perform_later(@order)
  render json: @order, status: :accepted
end

perform_later method enqueues the job to be performed asynchronously, improving response times and scalability.


Leverage Gems

Bad practice: Writing custom code for functionality that is already well-handled by existing libraries.

# lib/custom_parser.rb
class CustomParser
  def self.parse(html)
    # complex parsing logic
  end
end

Maintaining custom parsing logic can be error-prone and costly in terms of development time.

Good practice: Use a gem like Nokogiri for HTML and XML parsing.

# Gemfile
gem 'nokogiri'
# app/services/parser_service.rb
require 'nokogiri'

class ParserService
  def parse(html)
    Nokogiri::HTML(html)
  end
end

This last point is a bit trivial. In the Ruby on Rails community, we don’t reinvent the wheel and we love our gems, unlike the JS community.

The point that I’m trying to make here is that you should be able to “gemify” sections of your logic to make it simpler to read and maintain.

We’ll keep that for next time!


Related posts

Free Training By Email:
Building a Rails Engine

We will send udpates but we care about your data.
No spams.

Back to homepage