rubymetaprogrammingruby-module

What is provided to `self.included` (typically named `base`) in ruby?


I'm trying to understand ruby metaprogramming a bit better and while I have workarounds for what I want to do, I would like to make it as clean as possible.

Simply put I would like to check if the class that includes a module has defined a given method:

irb(main):058:1* module A
irb(main):059:2*   def self.included(base)
irb(main):060:2*     puts base.singleton_method(:singleton_test)
irb(main):061:1*   end
irb(main):062:0> end
:included
irb(main):063:1* class B
irb(main):064:1*   include A
irb(main):065:2*   def self.singleton_test
irb(main):066:2*     puts "hi"
irb(main):067:1*   end
irb(main):068:0> end

I would expect this to output something like the following:

#<Method: B.singleton_test() (irb):95>

However, it does not:

(irb):60:in `singleton_method': undefined singleton method `singleton_test' for `B' (NameError)

    puts base.singleton_method(:singleton_test)
             ^^^^^^^^^^^^^^^^^

I suspect it's because the singleton methods are not bound to base yet when the included hook is called.

How would I go about checking for singleton_test being defined in this pattern?

PS Maybe also worth noting this seems to happen with any method definitions on base at the time Module A is included, singleton or instance.


Solution

  • It's a simple ordering issue.

    Ruby is (mostly) evaluated left-to-right top-to-bottom.

    class B
      include A
    
      def self.singleton_test
        puts('hi')
      end
    end
    

    As you can see, the include comes before the method definition. You need to swap the two around:

    class B
      def self.singleton_test
        puts('hi')
      end
    
      include A
      # #<Method: B.singleton_test() ./test.rb:2>
    end