2
votes

I have a module and would like to mixin some methods as class methods and some as instance methods.

For example:

module Foo
  def self.class_method
  end

  def instance_method
  end
end

class Bar
  include Foo
end

Usage:

Bar.class_method
Bar.new.instance_method

Is it possible to do this in Ruby?

If not, is it possible to define which methods are class methods and which are instance methods within the Bar class?

I don't want the same method defined as both a class and instance method.

3

3 Answers

4
votes

This pattern is very common in Ruby. So common, in fact, that ActiveSupport::Concern abstracts it a bit.

Your typical implementation looks like this:

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

  def instance_method
  end

  module ClassMethods
    def class_method
    end
  end
end

class Bar
  include Foo
end

You can't accomplish this easily as you describe without somehow splitting the included module into multiple pieces, though, unfortunately.

3
votes

You can, but not quite like that. This is a common pattern for including both instance and class methods in one module.

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

  def instance_method
    puts 'instance'
  end

  module ClassMethods
    def class_method
      puts 'class'
    end
  end
end

class Bar
  include Foo
end

bar = Bar.new
Bar.class_method    #=> 'class'
bar.instance_method #=> 'instance'
1
votes

You are close. You probably noticed that the instance method works fine. The problem with the class method is that self => Foo when it's defined, so it does not respond to Bar. If you add the line puts "I'm a module method" in self.class_method, you will find

Foo.class_method => "I'm a module method"

Here's an easy way to accomplish what you want to do:

module Foo_class
  attr_accessor :cat
  def class_method
    puts "I'm a class method" 
  end
end

module Foo_instance
  def instance_method
    puts "I'm an instance method" 
  end
end

class Bar
  extend Foo_class
  include Foo_instance
end

Bar.class_method        #=> I'm a class method
Bar.cat = "meow"
Bar.cat                 #=> "meow"
Bar.new.instance_method #=> I'm an instance method

I added a class instance variable, @cat, and an accessor for it, just to show how easy that is to do.

Object#extend is great, because you can just add instance variables and methods to a module, just as you would do with Object#include to mixin instance variables and methods, and extend mixes them in as class instance variables and class methods. You can also do this:

bar = Bar.new
bar.extend Foo_class

to have the instance variables and methods in Foo_class apply to the instance bar.