RL ROLAND LOPEZ
// 3 min read

Design Patterns in Rails

Design Patterns in Rails — When and how to use Singleton, Factory, and other design patterns in Rails. Concrete examples, anti-patterns to avoid, and a rule of thumb for overuse.

Why use design patterns in Rails?

Ruby on Rails, as a framework, encourages convention over configuration, making it easy to follow best practices.

However, despite Rails’ simplifications, complex problems needing elegant solutions will always exist. This is where design patterns come into play.

Design patterns in Rails serve as a standardized approach to common problems, promoting code reusability and maintainability.

Understanding when and how to apply these patterns can significantly optimize your application architecture.

Singleton pattern: a Rails example

The Singleton pattern ensures a class has only one instance and provides a global point of access to it. In Rails, Singleton can be useful for managing a shared resource throughout the application, like a configuration settings object.

Bad practice: misusing global variables

# app/models/global_config.rb
class GlobalConfig
  @@instance = new

  def self.instance
    @@instance
  end

  private_class_method :new
end

This approach, using class variables, risks threading issues and complicates testing due to state persistence between tests.

Preferred Singleton implementation

# app/models/configuration.rb
require 'singleton'

class Configuration
  include Singleton

  attr_reader :api_key, :api_secret

  def initialize
    @api_key = ENV['API_KEY']
    @api_secret = ENV['API_SECRET']
  end
end

Using Ruby’s Singleton module, we sidestep potential pitfalls by ensuring thread safety and making unit tests easier to manage.

Same object ID every time:

Configuration.instance # => #<Configuration:0x00007ffca20fd998>
Configuration.instance # => #<Configuration:0x00007ffca20fd998>
Configuration.instance # => #<Configuration:0x00007ffca20fd998>

Factory pattern: managing object creation in Rails

The Factory pattern provides a way to encapsulate the instantiation of objects. This pattern is ideal when dealing with a system that must manage, maintain, or manipulate collections of objects that share common characteristics.

Example: different user types

Suppose your application handles different types of users, each with unique behaviors and permissions.

# app/models/user_factory.rb
class UserFactory
  def self.create(user_type, attributes)
    case user_type
    when :admin
      AdminUser.new(attributes)
    when :guest
      GuestUser.new(attributes)
    else
      StandardUser.new(attributes)
    end
  end
end

This Factory abstracts the instantiation logic and promotes a cleaner codebase by encapsulating creation details.

The code above could also be refactored using metaprogramming:

# app/models/user_factory.rb
class UserFactory
  def self.create(user_type, attributes)
    if user_type
      klass = "#{user_type.humanize}User".constantize
      klass.new(attributes)
    else
      StandardUser.new(attributes)
    end
  end
end

PS: this pattern is used with STI. In that case your status column would be validated by an enum and you would have one model per enum value, making the code above developer-friendly. Metaprogramming can be a trap sometimes.

Overusing design patterns

While design patterns provide robust solutions, they come with the risk of overuse. Implementing a pattern where it is not needed can overcomplicate the application, making it harder to understand and maintain. Always evaluate if a pattern adds value to your solution or if a simpler approach could be enough.

Conclusion

As a rule of thumb, here are some overkill solutions that require second thought:

  • Has and belongs to many tables
  • Multi-database connections
  • Include AWS services
  • Metaprogramming
  • Use dry.rb
  • STI
Roland Lopez
Written by
Roland Lopez

Technical co-founder specialized in SaaS, DevOps, AI agents, and data platforms. Building and scaling with Ruby on Rails, n8n, and fast feedback loops.

Built by Agent Skynet See the agency