Have you ever used Ruby's Object#method?
Object#method
is a little-known, yet very interesting Ruby method. Let’s see how it works and how you can use it in your code.
The basics
Let’s imagine we have some Ruby class, e.g.
class Animal
def speak(sound)
puts "I say #{sound}!"
end
end
Calling the #method
on the instance of this class, would return a Method object. This Method
object acts as a closure in the context of the associated object.
animal = Animal.new
met = animal.method(:speak)
met.class
#=> Method
met.inspect
=> "#<Method: Animal#speak(sound)>"
met.call # we forget to pass the argument
#=> ArgumentError (wrong number of arguments (given 0, expected 1))
met.call('meow')
# I say meow!
# => nil
One thing that’s interesting in Method
objects, is the fact they implement the #to_proc
method. To see how this could be useful, let’s first remind ourselves of a widely-used Ruby shortcut.
The ampersand operator
Consider this common Ruby shortcut:
[1, 2, 3, 4, 5, 6].select { |num| num.even? }
#=> [2, 4, 6]
# commonly simplified with:
[1, 2, 3, 4, 5, 6].select(&:even?)
#=> [2, 4, 6]
The reason this works is two-fold. First, there is the &
(ampersand) operator. When used at the beginning of a method argument, it transforms its operant into a Proc
object (by calling #to_proc
on it), and passes it to the method as if it was a block. Secondly, the Symbol
class implements #to_proc
. The way it’s implemented effectively allows us to send
a given symbol to the provided argument. See Symbol#to_proc
in action below:
is_even = :even?.to_proc
is_even.call(1)
#=> false
is_even.call(2)
#=> true
This proc could also be passed to the #select
method from the previous example. We still need to use the ampersand operator (so that Ruby knows to treat it like a block).
[1, 2, 3, 4, 5, 6].select(&is_even)
#=> [2, 4, 6]
Ampersand with the Method object
Given the fact that the Method object implements the #to_proc
method as well, we can use it much the same way as we did the symbol. Let’s circle back to our previous example to see this in action.
class Animal
def speak(sound)
puts "I say #{sound}!"
end
end
met = Animal.new.method(:speak)
animal_sounds = ['meow', 'woof', 'moo', 'oink', 'hee-haw']
animal_sounds.each(&met)
# I say meow!
# I say woof!
# I say moo!
# I say oink!
# I say hee-haw!
# => ["meow", "woof", "moo", "oink", "hee-haw"]
So when is this useful?
One reason I used #method
combined with the ampersand operator in the past was to get rid of repetitive, one-dimensional blocks. This is especially valuable, when you are chaining multiple methods together. Let’s say we have multiple filters with the same interface (all returning true
or false
) that we want to use on a collection:
class AboveMinThresholdChecker
MIN_THRESHOLD = 200
def self.call(number)
new.call(number)
end
def call(number)
number > MIN_THRESHOLD
end
end
class BelowMaxThresholdChecker
# ...
end
class PrimeNumberChecker
# ...
end
Using explicit blocks, we would then do something like:
numbers = (1..400).to_a
numbers.select { |num| AboveMinThresholdChecker.call(num) }.
select { |num| BelowMaxThresholdChecker.call(num) }.
select { |num| PrimeNumberChecker.call(num) }
With our new knowledge, we can simplify the above in the following way using the #method
:
numbers.select(&AboveMinThresholdChecker.method(:call)).
select(&BelowMaxThresholdChecker.method(:call)).
select(&PrimeNumberChecker.method(:call))
Summary
Have you used #method
in Ruby before? Can you think of other interesting situations it could prove handy? Please share in the comments below!
If your project suffers from long CI builds, you can address this with Knapsack Pro. Be the hero in your team by streamlining your CI process and improving the developer productivity!