A Guide to Method Chaining

method_chaining

Method chaining is a very handy technique for invoking multiple methods at once. Not only does it improve the readability of the code, but it also reduces the amount of code needed when interacting with a class or an instance of a class.

You have probably already interacted with method chaining in one way or 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 @name="Baz">
> 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.

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • Gerard

    Looks like

    def age(value)
    @name = value
    end

    Should be

    def age(value)
    @age = value
    end

    • http://www.ruprict.net/ Glenn Goodrich

      Nice catch, Gerard! Thanks!