Add class methods and instance methods to class by including one Module

Subscribe to receive new articles. No spam. Quality content.

Hi guys. I'm so glad to announce that article about Specification Pattern was picked for RubyWeekly #341. Also article Decoupling From Rails [Part 1] started just awesome discussion on Reddit. It means that we're moving to the right direction. I'm going to finish my series of articles about Decoupling From Rails soon, but today would like to talk about modules. How to add class methods and instance methods by including one module into class.

As Ruby-developers we know that we use modules for 2 main reasons: namespaces and mixins.

When we want to use module as a mixin we can use include or extend. There is also way to prepend module. I covered that in this article.

Let's figure out the difference between include and extend.

If we use include - all module methods will become instance methods of class. See this example:

module Persistence
  def save
    'saving'
  end
end

class User
  include Persistence
end

u = User.new
u.save # => 'saving'

We included Persistence module into User class. It means that all objects of class User will have save method. All methods of Persistence module became instance methods of class User.

Let's check how extend works:

module Persistence
  def find(id)
    "looking for entity with id=#{id}"
  end
end

class User
  extend Persistence
end

User.find(10) # => looking for entity with id=10

Because we used extend - all methods of Persistence module became class methods of class User. So we can call User.find(10) now.

If you think about something like ActiveRecord model, it allows us to have both: instance methods and class methods. For model we can use class methods to find objects: User.all, User.first, User.where(first_name: "John"), etc. At the same time we have instance methods to update or save each particular user:

u = User.last
# instance methods
u.save
u.update(first_name: "Sergii")

What if we want to use Persistence module to achieve the same results? To extend our class by both: instance methods and class methods.

Let's start from the easiest part, from instance methods. To achieve that we can use include as we've seen before:

module Persistence
  def save
    'saving'
  end

  def update
    'updating entity'
  end
end

class User
  include Persistence
end

u = User.new
u.save #=> saving
u.update #=> updating entity

First part is done. We included module and our objects have save and update methods now. Next interesting question is how to add class methods having the same code in User class.

We can not use extend Persistence because in that case save and update methods will be class methods of class User.

To solve this problem we will use module callbacks. Let me show you how callbacks work:

module Persistence
  def self.included(klass)
    "Persistence module was included into #{klass}"
  end
end

class User
  include Persistence
end

It will print "Persistence module was included into User". Every time when any class includes module - Ruby will trigger self.included method on that module. It will also pass class as a param. In our case User class will be in klass argument. There are couple more callbacks available: included, extended, prepended for modules and inherited for classes.

Now we know that we can get an access to class that included our module. We can take advantage of that and extend that class by class methods from the same module. I'll show final implementation and then we will go line-by-line to understand how it works:

module Persistence
  def self.included(klass)
    klass.extend(ClassMethods)
  end

  module ClassMethods
    def all
      puts 'all'
    end

    def find(id)
      puts "looking for entity with id=#{id}"
    end
  end

  def save
    puts 'saving'
  end

  def update
    puts 'updating entity'
  end
end

class User
  include Persistence
end

u = User.new

# instance methods
u.save #=> saving
u.update #=> updating entity

# class methods
User.all #=> all
User.find(1) #=> looking for entity with id=1

Ok, we achieved our goal. By including one module we extended User class by instance methods (#save, #update) and class methods (.all, .find).

Let's go step-by-step:

class User
  include Persistence
end

Class User includes Persistence module which makes save and update methods available for all objects of class User.

To add class methods we've added ClassMethods module inside Persistence module and used callback included to extend class User by ClassMethods module:

module Persistence
  def self.included(klass)
    klass.extend(ClassMethods)
  end

  module ClassMethods
    def all
      puts 'all'
    end

    def find(id)
      puts "looking for entity with id=#{id}"
    end
  end
#...
end

As we know when we extend class by module - all module methods become class methods. That's why module inside Persistence module called ClassMethods. Using callback we extended class User by ClassMethods module which added class methods all and find to User class.

Many gems use this technique to extend classes by instance methods and class methods at the same time. Pretty simple idea:

  1. Add instance methods to module
  2. Add ClassMethods module with methods that going to be class methods
  3. Use included hook to extend class by ClassMethods using klass.extend(ClassMethods)
  4. Include module into class

Thanks for reading. As usually I'll appreciate any feedback and sharing.

Happy coding!

Subscribe to receive new articles. No spam. Quality content.

Comments