Extending a Ruby module in another module, including the module methods

27,512

Solution 1

If you are the author of module A and will frequently need this, you can re-author A like so:

module A 
  module ClassMethods
    def say_hi
      puts "hi"
    end
  end
  extend ClassMethods
  def self.included( other )
    other.extend( ClassMethods )
  end
end

module B 
  include A
end

A.say_hi #=> "hi"
B.say_hi #=> "hi" 

Solution 2

I don't think there's any simple way to do it.

So here is a complex way:

module B
  class << self
    A.singleton_methods.each do |m|
      define_method m, A.method(m).to_proc
    end
  end
end

You can put it in a helper method like this:

class Module
  def include_module_methods(mod)
    mod.singleton_methods.each do |m|
      (class << self; self; end).send :define_method, m, mod.method(m).to_proc
    end
  end
end

module B
  include_module_methods A
end

Solution 3

Use include_complete

gem install include_complete

module A 
  def self.say_hi
    puts "hi"
  end
end

module B 
  include_complete A
end

B.say_hi #=> "hi"

Solution 4

Johnathan, I am not sure if you're still wondering about this, but there are two different ways to use modules in ruby. A.) you use modules in their self contained form Base::Tree.entity(params) directly in your code, or B.) you use modules as mixins or helper methods.

A. Will allow you to use modules as a Namespace pattern. This is good for larger projects where there is the chance for method name conflicts

module Base
  module Tree
    def self.entity(params={},&block)
      # some great code goes here
    end
  end
end

Now you can use this to create some sort of Tree structure in your code, without having to instantiate a new class for every call to Base::Tree.entity.

Another way to do Namespace-ing is on a class by class basis.

module Session
  module Live
    class Actor
      attr_accessor :type, :uuid, :name, :status
      def initialize(params={},&block)
        # check params, insert init values for vars..etc
        # save your callback as a class variable, and use it sometime later
        @block = block
      end

      def hit_rock_bottom
      end

      def has_hit_rock_bottom?
      end

      ...
    end
 end
 class Actor
   attr_accessor :id,:scope,:callback
   def initialize(params={},&block)
     self.callback = block if block_given?
   end

   def respond
     if self.callback.is_a? Proc
       # do some real crazy things...
     end
   end
 end
end

Now we have the potential for overlap in our classes. We want to know that when we make a create an Actor class that it is the correct class, so this is where namespaces come in handy.

Session::Live::Actor.new(params) do |res|...
Session::Actor.new(params) 

B. Mix-Ins These are your friends. Use them whenever you think you will have to do something more than once in your code.

module Friendly
  module Formatter
    def to_hash(xmlstring)
      #parsing methods
      return hash
    end

    def remove_trailing_whitespace(string,&block)
      # remove trailing white space from that idiot who pasted from textmate
    end
  end
end

Now whenever you need to format an xmlstring as a hash, or remove trailing whitespace in any of your future code, just mix it in.

module Fun
  class Ruby
    include Friendly::Formatter

    attr_accessor :string

    def initialize(params={})
    end

  end
end

Now you can format the string in your class.

fun_ruby = Fun::Ruby.new(params)
fun_ruby.string = "<xml><why><do>most</do><people></people><use>this</use><it>sucks</it></why></xml>"
fun_ruby_hash = fun_ruby.to_hash(fun_ruby.string)

Hope this is a good enough explanation. The points raised above are good examples of ways to extend classes, but with modules, the hard part is when to use the self keyword. It refers to the scope of the object within ruby's object hierarchy. So if you want to use a module as a mix-in, and don't want to declare anything singleton, don't use the self keyword, however if you want to keep state within the object, just use a class and mix-in the modules you want.

Share:
27,512
Jonathan Martin
Author by

Jonathan Martin

Updated on October 15, 2020

Comments

  • Jonathan Martin
    Jonathan Martin over 3 years

    Whenever I try to extend a ruby module, I lose the module methods. Neither include nor extend will do this. Consider the snippet:

    module A 
      def self.say_hi
        puts "hi"
      end
    end
    
    module B 
      include A
    end
    
    B.say_hi  #undefined_method
    

    Whether B Includes or Extends A, say_hi will not be defined.

    Is there any way to accomplish something like this?