Lazy object pattern in ruby
Serhii Potapov October 05, 2014 #ruby #lazy #object #loadingI few days ago my colleague Arthur Shagall reviewing my code suggested me to use Lazy Object pattern to postpone some calculations during the load time. I hadn't heard about the pattern before and even googling it didn't give my much information. So I have decided to write this article to cover the topic.
Intention
Lazy Object allows you to postpone some calculation until the moment when the actual result of the calculation is used. That may help you to speed up booting of the application.
Implementation
It is pretty simple. We create a proxy object that takes a calculation block as its property and execute it on first method call.
@callable = callable
end
@__target_object__ ||= @callable.call
end
__target_object__.send(method_name, *args, &block)
end
end
Usage example 1
A constant assignment like this:
= Array.new(10) { i** 2}
Could be converted to this one:
= LazyObject.new { Array.new(10) { i** 2} }
So now if you want to use SQUARES
it still behaves like an array:
SQUARES.class # => Array
SQUARES.size # => 10
SQUARES # => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Usage example 2
Let's say you have models State
and Address
in you Rails application.
What you want do is to validate inclusion of address.state
in states.
You can just hardcore the list of states:
= [, , , , , ] # and so on
validates :state, inclusion: { in: STATES }
end
But it does not reflect your changes in DB in any way.
Then you can fetch the values from DB:
= State.all.map(&:code)
It seems to look better, but there are 2 possible pitfalls:
- It increases load time (1 more SQL query)
- It may cause real troubles if
STATES
is initialized beforeState
model is seeded. In this caseSTATES
will be empty.
So that is the situation where Lazy Object is useful:
= LazyObject.new { State.all.map(&:code) }
Ruby gem
If your prefer to have it as a ruby gem, please take a look at rubygems.org/gems/lazy_object.
Thanks for reading!