Ruby | SimpleDelegator Methods

Derek Dyer
4 min readNov 9, 2017

--

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!

--

--