If you are using decorator pattern in your Rails application then you are probably using the Draper gem. It's the most popular solution for decorators pattern these days. I have been using it for years. However, using this gem may be an overkill if everything your application needs it's a simple decoration ability for classes. In such cases, it's better to build your own implementation or use a lightweight gem.

Tell me why

I did a simple comparison a few days ago and I came to the conclusion that a simple decorator implementation can be 3 times faster than Draper and it allocates around 60 - 70% fewer objects. So if you don't need any extra functionalities with your decorators, we can build our own decorator base class in seconds.

Want to receive useful tips, information about new Ruby gems and articles on a daily basis? Make sure you follow me and say hello!

Your own decorator

In the decoration process, it's all about delegating new methods to the given object. Ruby already provides a nice interface for this, implemented as SimpleDelegator class.

Let's build our base class:

class MyOwnDecorator < SimpleDelegator
  def model
    __getobj__
  end
end

and that's it! We used __getobj__ to get the passed object and because in Draper we have access to the passed object via model method then we followed this rule here.

If you have a User model and it implements first_name and last_name methods, you can create a new method called full_name in the following way:

class UserDecorator < MyOwnDecorator
  def full_name
    "#{model.first_name} #{model.last_name}"
  end
end

now you can give it a try:

user = User.new(first_name: 'John', last_name: 'Doe')
decorator = UserDecorator.new(user)
decorator.full_name # => 'John Doe'

Polishing the decorator class

As you can remember, in Draper you can decorate your object in different ways using .decorate method for single object and .decorate_collection for collections. Let's implement them:

class MyOwnDecorator < SimpleDelegator
  def model
    __getobj__
  end

  def self.decorate(obj)
    new(obj)
  end

  def self.decorate_collection(collection)
    collection.map { |element| new(element) }
  end
end

That's was a piece of cake. You can now decorate your objects in a couple different ways:

UserDecorator.decorate(user)
UserDecorator.new(user)
UserDecorator.decorate_collection([user1, user2])

Now we can compare our solution with the Draper gem.

Benchmarks

Let's prepare the code that we will be testing first. We will create two decorator classes that will be operating on the Struct imitating User model in your Rails application.

If you haven't installed the draper gem yet, do this now:

gem install draper

Now let's create both classes:

class UserDecorator < MyOwnDecorator
  def full_name
    "#{model.first_name} #{model.last_name}"
  end
end

class UserDraperDecorator < Draper::Decorator
  def full_name
    "#{model.first_name} #{model.last_name}"
  end
end

and our User object:

user = Stuct.new(:first_name, :last_name).new('John', 'Doe')

and we are ready to begin the tests.

Objects allocation

We will start by checking how many objects are allocated when both decorators are decorating our object. To measure it, install allocation_stats gem:

gem install allocation_stats

Now we can use our IRB and perform the test:

require 'draper'
require 'allocation_stats'

class MyOwnDecorator < SimpleDelegator
  def model
    __getobj__
  end

  def self.decorate(obj)
    new(obj)
  end

  def self.decorate_collection(collection)
    collection.map { |element| new(element) }
  end
end

class UserDecorator < MyOwnDecorator
  def full_name
    "#{model.first_name} #{model.last_name}"
  end
end

class UserDraperDecorator < Draper::Decorator
  def full_name
    "#{model.first_name} #{model.last_name}"
  end
end

user = Struct.new(:first_name, :last_name).new('John', 'Doe')

stats = AllocationStats.trace { UserDecorator.new(user).full_name }
puts stats.allocations.group_by(:class).to_text

stats = AllocationStats.trace { UserDraperDecorator.new(user).full_name }
puts stats.allocations.group_by(:class).to_text

We can now see that for our decorator implementation we create two objects, one for String and second for UserDecorator while Draper created 7 objects: 3 for Array, 2 for Hash, 1 for UserDraperDecorator and String.

Execution time

Don't close your IRB yet and load additional library:

require 'benchmark'

now we will measure execution time for both implementations:

Benchmark.bm 10 do |bench|
  bench.report "Own: " do
    1_000_000.times { UserDecorator.new(user).full_name }
  end

  bench.report "Draper: " do
    1_000_000.times { UserDraperDecorator.new(user).full_name }
  end
end

Our implementation needed 0.870284 while Draper needed 2.134296. We were almost three times faster.

Conclusion

It was a very simple test for very simple usage case so our implementation will be a perfect fit only for basic usage. Draper is still a great choice if you need something more from decorators. But remember to always use any external tool with caution.

If you don't want to create your own class for decorators but still want to use some lightweight solution, check Tuner gem that I have created as it implements exactly the same code presented here.

Want to become a better Rails developer?
Download for free the Introduction Rails patterns book and dive into the world of refactoring and easy-testable Ruby code today.
Join over 1,000 developers already subscribed to my newsletter and download the book. You can unsubscribe anytime:

Subscribe and get the book!