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.