Michael de Silva's Blog

Software Engineer. Rubyist and Roboticist.

Michael de Silva's Blog

Software Engineer. Rubyist and Roboticist.

Monsieur Eigenclass to the Rescue!

Today, Monseuir Eigenclass saved the day for me. I had an interesting gist sent to me from a colleague where he was leveraging const_missing; he had a passing spec but noticed some strange behaviour on production where his call to super in const_missing was returning nil.

Eventually this snippet was used to see what the stack trace was like between both scenarios, a case of spotting the 'ugly duckling' as it were.

The first attempt was a varient on Module.module_eval and I noticed that even the Object.module_eval version failed miserably in IRB. That's to Rails' various bindings however, this does work in a Rails environment

Object.module_eval do
  class << self
    def const_missing _const
      puts "HELL YEAH"
      raise _const
    rescue => ex
      puts ex.message
      puts ex.backtrace
      super
    end
  end
end

module AAAA
end

AAAA::TEST

The trace shed some light on this mystery and it was the inclusion of a certain mixin, partials

# bad boy...
    module Cart

      include Plugin::Partials
      set_dynamic_const_missing("Cart")

    end

# trace
exception class/object expected
shipping_costs.rb:20:in `raise'
shipping_costs.rb:20:in `const_missing'
gems/activesupport-3.2.13/lib/active_support/dependencies.rb:514:in `load_missing_constant'
gems/activesupport-3.2.13/lib/active_support/dependencies.rb:192:in `block in const_missing'
gems/activesupport-3.2.13/lib/active_support/dependencies.rb:190:in `each'
gems/activesupport-3.2.13/lib/active_support/dependencies.rb:190:in `const_missing'
partials.rb:40:in `block in set_dynamic_const_missing'
shipping_costs.rb:36:in `const_missing'

This mixin essentially defined a method on the base _klass's Class, which happens to be Module since our base _klass is a named Module, via self.class.send(:define_method, :const_missing) { #... }.

I has a sneaky feeling that this was polluting Module causing every defined Module to define said method, and can be disastrous when it is a method such as const_missing.

It wasn't long before I whipped up a quick test where instead I had him define the method on the base _klass's Eigenclass instead. You'll find a working example below

Since :bonjour is defined on the base _klass's Eigenclass, notice how Baz is unable to call the method — and this is exactly how we needed it.

I highly recommend reading 'Ruby's Eigenclasses Demystified' by Andrea Singh and 'A Complete Ruby Class Diagram' by Banisterfiend.

comments powered by Disqus