Ruby | SimpleDelegator Methods
There are a good number of posts about the OOP ‘decorator’ pattern using SimpleDelegator
in ruby, this is another one 😉. More specifically though, this one is about all two instance methods available in the SimpleDelegator
class: __getobj__
and __setobj__
. I hope after reading this post, you will be a little more comfortable with SimpleDelegator and using its methods.
Context
I don’t know what made me think of this as an example but 7/11 gives out free slurpees on July, 11th. So we’ll work on a couple files that print out results for free a slurpee day.
Basic Decorator Pattern
Let’s start simple. You’re probably already familiar with how to implement a decorator but just in case you’re not, check out the code example below:
The slurpee.rb file with the Slurpee class.
require 'date'
class Slurpee
attr_reader :flavor, :size, :cost
def initialize(flavor, size, cost)
@flavor = flavor
@size = size
@cost = cost
end
end
the slurpee_day_decorator.rb file with our decorator class.
class SlurpeeDayDecorator < SimpleDelegator
def serve
"Serving one #{size} slurpee in a delicious flavor #{flavor} and it will cost you $#{cost}"
end
end
To get our desired return out of the decorator we’ll do this:
slurpee = Slurpee.new('🍉', 'massive', 5.00)
slurpee_day = SlurpeeDayDecorator.new(slurpee)
We’re instantiating the Slurpee class and then passing the slurpee
object into the SlurpeeDayDecorator class as an argument. This is how we implement a decorator using SimpleDelegator.
Now we can do something like: slurpee_day.serve
and it should return “Serving one massive 🍉 slurpee. It will cost you $5.00”
Notice how the Slurpee class’s instance methods, size, flavor, and cost are available within the SlurpeeDayDecorator class.
So that is a simple example of using SimpleDelegator but let’s learn about the __getobj__
and __setobj__
methods.
__getobj__
Returns the current object method calls are being delegated to.
http://ruby-doc.org/stdlib-2.4.1/libdoc/delegate/rdoc/SimpleDelegator.html#method-i-__getobj__
__getobj__
returns the Slurpee object being injected into the SlurpeeDayDecorator. Let’s puts __getobj__
from inside a quick made up instance method in the SlurpeeDayDecorator and see what we get back:
slurpee_day_decorator.rb
...def foo
puts __getobj__
end...
slurpee = Slurpee.new(‘🍉’, ‘massive’, 5.00)
slurpee_day = SlurpeeDayDecorator.new(slurpee).foo
☝️ returns: “#<Slurpee:0x007fff12095dd0>”
See? Just a regular Slurpee object. Let’s do the same thing but also call a Slurpee instance method.
slurpee_decorator.rb
def foo
puts __getobj__.cost
end
slurpee = Slurpee.new(‘🍉’, ‘massive’, 5.00)
slurpee_day = SlurpeeDayDecorator.new(slurpee)
slurpee_day.foo
👆 returns: 5.0. So with adding the __getobj__
method we’re able to call instance methods in the Slurpee class from the SlurpeeDayDecorator instance. BUT wait a minute, what’s the point of __getobj__
if we can already call instance methods in the SlurpeeDayDecorator class? With __getobj__
we can seemingly change but more like overwrite the behavior of our Slurpee instance methods in the context of slurpee day in the SlurpeeDayDecorator class.
Let’s use the __getobj__
method in the SlurpeeDayDecorator
class for real now.
slurpee_decorator.rb
def cost
__getobj__.cost - __getobj__.cost
end
slurpee = Slurpee.new(‘🍉’, ‘massive’, 5.00)
slurpee_day = SlurpeeDayDecorator.new(slurpee)
slurpee_day.cost
👆 returns: 0.0
Just to recap, we created a cost method in the SlurpeeDayDecorator because we wanted to get the cost in the context of the free slurpee day.
Let’s add another one to calculate the days until Free Slurpee Day.
slurpee_day.rb (with a new free_slurpee_day method)
class Slurpee
attr_reader :flavor, :size, :cost
def initialize(flavor, size, cost)
@flavor = flavor
@size = size
@cost = cost
end def free_slurpee_day
today = Date.today
year = today < Date.new(today.year, 8, 21) ? today.year : today.year + 1
Date.new(year, 8, 21)
end
end
slurpee_decorator.rb
def free_slurpee_day
number_of_days = __getobj__.free_slurpee_day — Date.today
“There are #{number_of_days.to_i} days until Free Slurpee Day”
end
👆 returns: “There are 245 days until Free Slurpee Day”
__setobj__
This one is a little more interesting. Using __setobj__
we can change the ‘original’ object decoratin’ in the same instance of the decorator class. I could see a use case for this where if there was a condition, we could change the object based on that condition. We had a Slurpee class, let’s add a… HotDog class.
hot_dog.rb
class HotDog
attr_reader :size, :cost
def initialize(size, cost)
@cost = cost
@size = size
end def free_day
today = Date.today
year = today < Date.new(today.year, 8, 21) ? today.year : today.year + 1
Date.new(year, 8, 21)
end
end
Let’s refactor our decorator file since it will be decorating different objects now. We’ll change the file name, class name, method names, and get the name of the class and format it using our friend __getobj__
.
free_decorator.rb (formerly slurpee_day_decorator.rb)
class FreeDecorator < SimpleDelegator
def free_day
number_of_days = __getobj__.free_day - Date.today
"There are #{number_of_days.to_i} days until Free #{format_constant} Day"
end def serve
"Dispensing one #{size} #{format_constant} in a delicious flavor #{flavor} and it will cost you $#{cost}"
end def cost
__getobj__.cost - __getobj__.cost
end private def format_constant
__getobj__.class.name.gsub(/(?<=[a-z])(?=[A-Z])/, ' ')
end
end
Ok, let’s use __setobj__
and puts the results to see what we get.
hot_dog = HotDog.new(‘jalapeno’, ‘footlong’, 5.00)
slurpee = Slurpee.new(‘🍉’, ‘massive’, 5.00)
free_item = FreeDecorator.new(slurpee)
puts free_item.free_day
free_item.__setobj__(hot_dog)
puts free_item.free_day
👆 returns:
There are 244 days until Free Slurpee Day
There are 285 days until Free Hot Dog Day
Thanks for reading!