
June 30, 2026
In the previous article, we explored how Ruby simplifies many creational and structural design patterns.
But some of the most interesting patterns aren’t about creating or organizing objects.
They’re about how objects collaborate.
Behavioral patterns define how responsibilities are shared, how communication flows through an application, and how behavior changes over time.
Modern Ruby and Rails applications use these ideas everywhere—even if we don’t always recognize them.
Let’s explore the most common ones.
Strategy Pattern
The Problem
An application may need multiple algorithms that solve the same problem.
For example:
- Different payment providers
- Different pricing strategies
- Different authentication mechanisms
Instead of filling methods with endless conditionals:
if payment_method == :paypal ...elsif payment_method == :stripe ...
We encapsulate each strategy.
class PaypalPayment def call(amount) puts "Processing PayPal payment" endendclass StripePayment def call(amount) puts "Processing Stripe payment" endendclass Checkout def initialize(strategy) @strategy = strategy end def complete(amount) @strategy.call(amount) endend
Thanks to duck typing, Ruby doesn’t require interfaces.
If an object responds to call, it’s a valid strategy.
Simple.
Flexible.
Testable.
Command Pattern
The Problem
Sometimes we want to represent an action as an object.
Ruby developers encounter this constantly.
Service Objects.
Background Jobs.
Rake Tasks.
Every one of these is essentially a Command.
class SendInvoice def call(order) InvoiceMailer.deliver(order) endend
Usage:
SendInvoice.new.call(order)
Libraries like Active Job embrace this pattern by turning work into standalone objects that can be executed later.
Observer Pattern
The Problem
One event should trigger multiple reactions.
Instead of tightly coupling everything together:
Order Created │ ├── Send Email ├── Update Analytics ├── Notify Warehouse └── Create Audit Log
Each observer handles one responsibility.
Rails encourages this through callbacks, notifications, and event-driven architectures.
The result is loosely coupled code that’s easier to extend.
State Pattern
Sometimes an object’s behavior depends on its current state.
Consider an order.
order.payorder.shiporder.cancel
Not every action is valid all the time.
Instead of giant conditional statements:
if status == "paid"...
Ruby applications often use small state objects or state machine libraries to represent transitions cleanly.
The behavior lives with the state itself.
Null Object Pattern
One of Ruby’s most elegant patterns.
Instead of writing:
if current_user current_user.nameend
We return an object that behaves like a user.
class GuestUser def name "Guest" endend
Now the application simply calls:
current_user.name
No special cases.
No scattered nil checks.
Cleaner code.
Rails Is Full of Design Patterns
Even if you’ve never consciously implemented a GoF pattern, you’ve almost certainly used them.
Service Objects
Commands.
CreateInvoice.new.call(order)
Query Objects
Encapsulate database queries.
ActiveCustomersQuery.new.call
Instead of filling models with dozens of scopes, complex querying becomes reusable and testable.
Policy Objects
Popularized by authorization libraries.
Instead of:
if user.admin?
Permissions become dedicated objects.
Each policy owns one responsibility.
Presenter / ViewModel
Views shouldn’t contain business logic.
Presenters prepare data for rendering while models remain focused on the domain.
Value Objects
Ruby makes immutable objects easy.
Money.
Coordinates.
Email addresses.
Currencies.
These objects represent concepts rather than database rows.
Ruby’s Greatest Design Pattern
After exploring dozens of classic patterns, one lesson becomes clear.
Ruby doesn’t replace good software design.
It removes unnecessary ceremony.
Instead of writing more classes…
…we write smaller ones.
Instead of inheritance…
…we compose objects.
Instead of interfaces…
…we rely on behavior.
Instead of frameworks forcing architecture…
…Ruby lets architecture emerge naturally.
That philosophy is why Ruby code often feels expressive without becoming verbose.
The patterns remain.
Only the implementation changes.
Final Thoughts
Design patterns were never meant to be copied verbatim.
They were created to give developers a common vocabulary for solving recurring problems.
Ruby preserves that vocabulary while encouraging simpler, more expressive solutions.
Understanding design patterns helps us recognize architectural problems.
Understanding Ruby helps us solve them elegantly.
The best Ruby code isn’t the code that uses the most patterns.
It’s the code where the right pattern disappears behind a clean, readable API.
And perhaps that’s the most Ruby idea of all.
