Design Patterns, the Ruby Way (Part 3): Behavioral Patterns in Real Ruby Applications

Design Patterns, the Ruby Way (Part 3): Behavioral Patterns in Real Ruby Applications
Design Patterns, the Ruby Way (Part 3): Behavioral Patterns in Real Ruby Applications

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.


Tokyo Topographic Map
Built for Ruby on Rails

Build Maps Without
Google APIs

Generate beautiful production-ready maps directly from your Rails backend. Fast rendering, zero external dependencies, full control.

✓ No API fees ✓ Self-hosted ✓ Rails Native ✓ Fast Rendering
Why developers switch
Replace expensive map stacks.

Stop relying on third-party map billing and bloated JS libraries. Render static or dynamic maps directly in Ruby.

Try It Now
Tokyo MapView Demo

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"
end
end
class StripePayment
def call(amount)
puts "Processing Stripe payment"
end
end
class Checkout
def initialize(strategy)
@strategy = strategy
end
def complete(amount)
@strategy.call(amount)
end
end

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)
end
end

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.pay
order.ship
order.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.name
end

We return an object that behaves like a user.

class GuestUser
def name
"Guest"
end
end

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.

Article content

Leave a comment