Include vs Extend in Ruby

Tháng Năm 20, 2009

Source: http://railstips.org/2009/5/15/include-verse-extend-in-ruby

Now that we know the difference between an instance method and a class method, let’s cover the difference between include and extend in regards to modules. Include is for adding methods to an instance of a class and extend is for adding class methods. Let’s take a look at a small example.

module Foo
  def foo
    puts 'heyyyyoooo!'
  end
end

class Bar
  include Foo
end

Bar.new.foo # heyyyyoooo!
Bar.foo # NoMethodError: undefined method ‘foo’ for Bar:Class

class Baz
  extend Foo
end

Baz.foo # heyyyyoooo!
Baz.new.foo # NoMethodError: undefined method ‘foo’ for #<Baz:0x1e708>

As you can see, include makes the foo method available to an instance of a class and extend makes the foo method available to the class itself.

Include Example

If you want to see more examples of using include to share methods among models, you can read my article on how I added simple permissions to an app. The permissions module in that article is then included in a few models thus sharing the methods in it. That is all I’ll say here, so if you want to see more check out that article.

Extend Example

I’ve also got a simple example of using extend that I’ve plucked from the Twitter gem. Basically, Twitter supports two methods for authentication—httpauth and oauth. In order to share the maximum amount of code when using these two different authentication methods, I use a lot of delegation. Basically, the Twitter::Base class takes an instance of a “client”. A client is an instance of either Twitter::HTTPAuth or Twitter::OAuth.

Anytime a request is made from the Twitter::Base object, either get or post is called on the client. The Twitter::HTTPAuth client defines the get and post methods, but the Twitter::OAuth client does not. Twitter::OAuth is just a thin wrapper around the OAuth gem and the OAuth gem actually provides get and post methods on the access token, which automatically handles passing the OAuth information around with each request.

The implementation looks something like this (full file on github):

module Twitter
  class OAuth
    extend Forwardable
    def_delegators :access_token, :get, :post

    # a bunch of code removed for clarity
  end
end

Rather than define get and post, I simply delegate the get and post instance methods to the access token, which already has them defined. I do that by extending the Forwardable module onto the Twitter::OAuth class and then using the def_delegators class method that it provides. This may not be the most clear example, but it was the first that came to mind so I hope it is understandable.

A Common Idiom

Even though include is for adding instance methods, a common idiom you’ll see in Ruby is to use include to append both class and instance methods. The reason for this is that include has a self.included hook you can use to modify the class that is including a module and, to my knowledge, extend does not have a hook. It’s highly debatable, but often used so I figured I would mention it. Let’s look at an example.

module Foo
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def bar
      puts 'class method'
    end
  end

  def foo
    puts 'instance method'
  end
end

class Baz
  include Foo
end

Baz.bar # class method
Baz.new.foo # instance method
Baz.foo # NoMethodError: undefined method ‘foo’ for Baz:Class
Baz.new.bar # NoMethodError: undefined method ‘bar’ for #<Baz:0x1e3d4>

There are a ton of projects that use this idiom, including Rails, DataMapper, HTTParty, and HappyMapper. For example, when you use HTTParty, you do something like this.

class FetchyMcfetcherson
  include HTTParty
end

FetchyMcfetcherson.get('http://foobar.com')

When you add the include to your class, HTTParty appends class methods, such as get, post, put, delete, base_uri, default_options and format. I think this idiom is what causes a lot of confusion in the include verse extend understanding. Because you are using include it seems like the HTTParty methods would be added to an instance of the FetchyMcfetcherson class, but they are actually added to the class itself.

Conclusion

Hope this helps those struggling with include verse extend. Use include for instance methods and extend for class methods. Also, it is sometimes ok to use include to add both instance and class methods. Both are really handy and allow for a great amount of code reuse. They also allow you to avoid deep inheritance, and instead just modularize code and include it where needed, which is much more the ruby way.

Source: http://railstips.org/2008/12/30/move-it-to-the-model-and-use-tiny-methods

I’m updating an application that I first wrote two years ago and last updated one year ago. It has been amazing to see how much I have learned in the past two years. One of the requirements of the application is for the users to update their information each year. Just last year, I had code like this. Before you look at the code below, I want to warn you that it is not for the faint of heart. Never, ever do this.

<%- if current_user.roles.select { |r| r.title.include?('Pastor') }.size > 0 && 
    (current_user.email.blank? || current_user.address.blank? || 
     current_user.city.blank? || current_user.state.blank? || 
     current_user.zip.blank? || updated_at.year != Time.now.year) -%>
  <!-- show the update form -->
<%- else -%>
  <!-- show the list of forms to be completed -->
<%- end -%>

I would not lie to you. It was literally like that. A year later, I can barely tell what I was trying to do here. Imagine someone who didn’t know the application inside and out trying to work with code like that. Yikes! Here is my refactored version, that is far more descriptive.

<%- if current_user.needs_to_update_information? --%>
  <!-- show the update form -->
<%- else -%>
  <!-- show the list of forms to be completed -->
<%- end -%>

The benefit of this implementation is that any programmer can immediately tell what my intent is and that all the logic of determining whether or not the current user needs to update their information is moved to the model, where it belongs and can more easily be tested. So what are the steps that I took to get to that new method? First, I changed the if statement to the one above and then I moved all the logic into the user model like this.

class User
  def needs_to_update_information?
    roles.select { |r| r.title.include?('Pastor') }.size > 0 && 
      (email.blank? || address.blank? || 
         city.blank? || state.blank? || zip.blank? || 
         updated_at.year != Time.zone.now.year)
  end
end

This is only step 1. We are not done by any means. I can assure you that if you leave a method like this in your code, I will verbally attack you if I come across it. Let’s break this method down a bit, starting with the first conditional.

roles.select { |r| r.title.include?('Pastor') }.size > 0

This is horribly nondescript. What is my intent? Only pastors need to update their information and that is what this conditional checks. How about we add a pastor? method and use that in place. Also, I tweaked the condition to use Enumerable’s any? method (as suggested by David).

class User
  def needs_to_update_information?
    pastor? && 
      (email.blank? || address.blank? || 
         city.blank? || state.blank? || zip.blank? || 
         updated_at.year != Time.zone.now.year)
  end

  def pastor?
    roles.any? { |r| r.title.include?('Pastor') }
  end
end

That is much better, but why stop there? The next chunk of code that I see that we can pull out is the address stuff.

address.blank? || city.blank? || state.blank? || zip.blank?

The intent of these conditionals is to make sure that the user’s address is not blank, so I created an address_needs_updated? method.

class User
  def needs_to_update_information?
    pastor? && 
      (email.blank? || address_needs_updated? || 
         updated_at.year != Time.zone.now.year)
  end

  def pastor?
    roles.any? { |r| r.title.include?('Pastor') }
  end

  def address_needs_updated?
    address.blank? || city.blank? || state.blank? || zip.blank?
  end
end

Next up, I addressed updated_at.year != Time.zone.now.year. The intent of this conditional is to see if the user’s record has been updated in the current year. I pulled this out into an updated_this_year? method.

class User
  def needs_to_update_information?
    pastor? && (email.blank? || address_needs_updated? || !updated_this_year?)
  end

  def pastor?
    roles.any? { |r| r.title.include?('Pastor') }
  end

  def address_needs_updated?
    address.blank? || city.blank? || state.blank? || zip.blank?
  end

  def updated_this_year?
    updated_at.year == Time.zone.now.year
  end
end

That is probably about as far as I would go with this. So what are the benefits of all these changes?

The Benefits

  • Moved business logic to the model where it is easier and more fun to test.
  • More descriptive as to the intent of what is happening. Intent is really important. It is far more important for someone to understand why you are doing something than how you are doing something. There are many ways to do something, but there is only one intent.
  • Smaller methods are easier to test than larger ones (and more fun). Testing large methods feels burdensome and often doesn’t provide enough coverage.
  • Did I mention testing? It is easier and more fun to test models than views and small methods than large methods (yes, testing can be fun).

Getting back into this app, I learned that I have learned a lot in the past few years and I hope the error of my ways was beneficial for you too. I am in no way a refactoring expert and haven’t read the book on it, but if you are curious about refactoring, the techniques I used above appear to be Decompose Conditional and Extract Method.