Project Ramon

A learning journey from a Ruby noob perspective

Ruby Refactoring: Replace Temporary Variable with Chain

Hello!

Today I’ll be continuing my review of a book I’m reading called Refactoring Ruby Edition.

Replace Temp with Chain

This is fairly new to me, but there are times when we can improve the ease and speed with which our code can be restored after failure by using method chaining instead of a temporary variable. Lets look at an example that closely resembles whats in the book.

class MovieQue
 
  def movies
    @movies ||= []
  end
 
  def add_movie(title)
    movies << title
  end
end
 
rental = MovieQue.new
rental.add_movie("Shawshank Redemption")
rental.add_movie("Flight of the Navigator")
rental.add_movie("Happy Gilmore")

Now lets take a peak at how to perform this refactor:

  • Step1: Return self from methods that you want to allow chaining from
  • Step2: Test (Make sure the target piece(s) of code is/are performing as before)
  • Step3: Remove the local variable and chain the method calls
  • Step4: Test Again

The Refactor

Following the guidance in the book, our first step is to define a method which creates the MovieQue instance and adds a movie’s title.

class MovieQue
  def MovieQue.with_movie(title)
    rental = MovieQue.new
    rental.movies << title
    rental
  end
end

rental = MovieQue.with_movie("Shawshank Redemtion")
rental.with_movie("Wag the Dog")
rental.with_movie("Bill and Ted's Excellent Adventure")
rental.with_movie("Barbarians at the Gate")

Our next step is to change the method that adds our titles to return self so it can be chained.

class MovieQue
  .
  .
  .
  def add_movie(title)
    movies << title
    self
  end
end

rental = MovieQue.with_movie("Shawshank Redemption").add_movie("Superman IV").add_movie("Mad Max Beyond Thunderdome")

Lastly, the authors instruct us to rename the add_movie method to something that reads more fluently, such as “and”.

class MovieQue
  def self.with_movie(title)
    rental = self.new
    rental.movies << title
    rental
  end

  def movies
    @movies ||= []
  end

  def and(title)
    movies << title
    self
  end
end

rental = MovieQue.with_movie("Saturn 2").and("Saturn 3").and("Blade Runner")

In Conclusion

I hope this has been as informative for you as it has been for myself. I welcome any of you reading this post that have had some experience using this refactoring pattern to leave a comment. Feel free to also share when you find this pattern to be best used and maybe some cases where it may not make the best use of your time.

Stay tuned…

Advertisements

Categories: Ruby, Uncategorized

Tags: ,

7 replies

  1. Hey Ramon, nice refactoring. I would preserve the original “add_movie” method, and make an alias to “and” – which reads nicely in the chaining context.

    “add_movie” expresses the intent more clearly when you read the class code alone. When you read the chaining, “and” is better.

    alias and add_movie # OR
    alias_method :and, :add_movie

    I would advise you to always return “self” on methods which modify internal state, like “add_movie”. Plus your object gets a fluent, chainable interface; it’s even better when you have other methods like “add_movie”, which modify internal state.

    Note that if you didn’t return “self”, your method would return the internal array, and any code outside the object could call any other method your array responds to. It would return a reference to the array your custom class internally hides, so it could do anything with your array and break your encapsulation (which is what object oriented programming doesn’t like). That’s why returning self (or nil, when appropriate) is almost always what you’ll want to do. Take a look at the Command / Query separation pattern.

    • Hello Thiago,

      Thanks for the insights! Returning self on methods that modify my class’ internal state made a lot of sense, especially after reading some about the Command / Query separation pattern.

      • I’m glad it was useful. Yeah… you don’t need to refactor your own code, if you remember to return self on “Command” methods. That refactor would be implicit.

        With your first code sample you could do something nasty like rental.add_movie(‘Movie’).delete(‘Other’).nasty_array_method. You should use delegation (or define your own customized method) to explicitly tell which methods from the array you want the client code to use. Forwardable (Ruby standard library) is nice for that.

      • Actually I was wrong about Forwardable… if you need to return self you won’t make good use of it 🙂

      • Ok cool, I’ll look more into this. Thanks again!

  2. Love the posts Ramon, keep them coming. I definitely need to pick up this book to improve my skill.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s