A Guide to Method ChainingBy Thiago Jackiw another, especially if you have used ActiveRecord. Remember doing something like this
Model.where(…).order(…)? Yep, that is method chaining. In this article you will learn how to apply the same features to your own code.
This article will be divided into two simple parts:
- Introduction to method chaining
- Method chaining at the class level
The first part will cover the basics of method chaining, with step-by-step examples; and the second part will explain how to do method chaining at the class level, be it via explicit method declaration of class methods or by using and extending an additional module.
Are you ready to embark into this awesome journey? Let’s go!
Introduction to Method Chaining
For the introductory part we are going to be designing a class that will contain a few methods that can be eventually chained together, and one method that outputs a message. Let us start with our basic class structure:
class Person def name(value) end def age(value) end def introduce end end
Nothing much here but this will serve as the base for this example. As you can see, it contains three methods:
name(), which will take an input value;
age(), which also takes an input value;
introduce, it will not take an input value. With this basic information at hand, let us implement the next step.
We are now going to store the input values for
name() and
age() in instance variables so that we can access them later down the road:
# ... def name(value) @name = value end def age(value) @age = value end # ...
Great, with this addition to our methods, let us try method chaining and see what happens:
> person = Person.new # => #<Person:0x007fb18ba29cb8> > person.name('Baz') # => "Baz" > person.name('Baz').age(21) NoMethodError: undefined method `age' for "Baz":String
As expected, it does not work. The reason being is because the methods are just returning a value – a string in this case. We need to modify our methods once again, and this time, we are going to be returning something else:
self.
By returning
self we are allowing instance methods to be chained together, just like that! Easy huh?! Okay, it is time to modify our methods again:
# ... def name(value) @name = value self end def age(value) @age = value self end # ...
Fantastic, what will happen if we try to chain our method calls?
> person = Person.new # => #<Person:0x007ff202829e38> > person.name('Baz') # => #<Person:0x007ff202829e38 @ > person.name('Baz').age(21) # => #<Person:0x007ff202829e38 @name="Baz", @age=21>
It worked! Congratulations, you have just implemented method chaining in Ruby! Now, we need to modify our
introduce method and have it do something:
# ... def introduce puts "Hello, my name is #{@name}, and I am #{@age} years old." end # ...
With all of our code in place, what do you think will happen when we call
introduce in our method chain?
> person = Person.new # => #<Person:0x007fd079085ba0> > person.name('Baz').age(21).introduce # => Hello, my name is Baz and I am 21 years old.
Yep, it worked as expected. Notice that the
introduce method needs to be at the end of the chain because it does not return an instance of
self.
Here is the complete code for this example, which I have also contributed to Wikipedia under the Method chaining article:
class Person def name(value) @name = value self end def age(value) @age = value self end def introduce puts "Hello, my name is #{@name} and I am #{@age} years old." end end
As an exercise for the reader, I encourage you to modify the
introduce method and make it output the text based on the data available. For example, if only
@name is available, you would print the sentence without the age part. If only
@age is available, you would print the sentence without the name part.
Method Chaining at the Class Level
We have just learned how to chain our method calls when invoking methods on an instance of a class. But what about chaining the methods at the class level, similar to
Model.where(…).order(…)? It is pretty much the same process, except that our method declaration will be at the class level instead of the instance level.
Let us go ahead and do just that, let us design a class that contains class methods that can be chained together. The basic structure for this example will be something like this:
class Speaker class << self def say(what) @say = what self end def drink(what) @drink = what self end def output "The speaker drinks #{@drink} and says #{@say}" end end end
Pretty simple, right? Notice that all of these three methods are class methods. I could have declared them individually with
def self.say()… but for multiple declaration of class methods I feel that it is more concise to have a single
class << self declaration and declare all of the class methods inside of it.
Great, let us play with it now:
> Speaker.say('hello').drink('water').output # => The speaker drinks water and says hello
And there you have it, worked like a charm.
Another way to accomplish this without having to explicitly declare the class methods would be to use a module and
extend it in our class. By doing this we expose the methods in the module as class methods in our
Speaker class:
module SpeakerClassMethods def say(what) @say = what self end def drink(what) @drink = what self end def output "The speaker drinks #{@drink} and says #{@say}" end end class Speaker extend SpeakerClassMethods end
And it should work exactly as having explicitly declared the class methods:
> Speaker.say('hello').drink('water').output # => The speaker drinks water and says hello
Great, it worked. As another exercise for the reader, I encourage you to modify the
output method and make it output the text based on the data available, similar to the previously suggested exercise for the reader.
Conclusion
This article has demonstrated how simple and easy it is to incorporate method chaining in Ruby, be it at the instance level or the class level. It also showed the different ways that we can accomplish the same results by using different techniques when adding class-level method chaining. And I hope that it has provided you with enough information for you to be able to apply the same techniques to your own code.