< Return to Blog

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.