The subtleties of const_missing


#1

Hello all,

I’ve run into an issue regarding the use of const_missing. This issue
revolves around the fact that these two cases are supposed to behave
differently from each other:

irb(main):001:0> B = 10
irb(main):002:0> module A; B; end
=> 10
irb(main):003:0> A::B
NameError: uninitialized constant A::B
from (irb):3

However it seems impossible to detect which case we are in within the
const_missing method. A more detailed explanation follows:

Suppose we are lazy and avoid typing “require ‘my_module.rb’” by
defining a const_missing that performs that require when MyModule is
first referenced. Now, suppose we are writing my_class, and wish to
include MyModule. So, we put this code in my_class.rb:

class MyClass
include MyModule

end

Our const_missing handler is called, and it loads ‘my_module.rb’,
finds the now present MyModule constant, and returns it. Everything
is good.

However, let’s say we’re slightly misguided and do MyClass::MyModule.
Since this is not defined, our const_missing handler is once again
called. ‘my_module.rb’ is thus loaded again, (usually to no effect,)
and MyModule returned.

This is a pretty clear violation of the semantics of ::. Indeed, Ruby
will produce a warning regarding our misbehaved const_missing. That
said, there does not seem to be a way to behave correctly; from
inside the const_missing handler there is no way to tell which case
the constant is missing in.

One way to ‘fix’ this is to have our const_missing look in it’s
parent modules for the missing constant. If we find it there, we know
this is a case of A::B. However this only works if the missing
constant has been defined in one of the parents. Thus, we can still
load ::B using A::B, but afterwards A::B will fail.

So, sorry for rambling on. The real question is: Can const_missing
detect if we are in a “A::B” case rather than “module A; B; end” ?

Thanks for your time and any replies,
Regards,
Nicholas S.


#2

On Wed, 16 Nov 2005, Nicholas S. wrote:

NameError: uninitialized constant A::B
class MyClass
returned.
afterwards A::B will fail.

So, sorry for rambling on. The real question is: Can const_missing detect if
we are in a “A::B” case rather than “module A; B; end” ?

but why do you need to distinguish?

harp:~ > cat a.rb
class Module
def const_missing c
puts “const_missing…”
const_set c, 42
end
end

module A
p B
end

p A::B

harp:~ > ruby a.rb
const_missing…
42
42

the ‘const_missing’ hook will continue to get call only so long as it
remain
undefined - if your handler merely loads a file but does not actually
define
any const then of course it will continue to fire. what, exactly, does
your
handler do?

kind regards.

-a


#3

On 15-Nov-05, at 11:57 PM, Ara.T.Howard wrote:

does your
handler do?

The loaded file is expected to define the constant; We assume b.rb
contains class or module B (or defines it in any other way). If the
constant isn’t defined by the loaded file then a NameError is raised
as if normal.


#4

On Nov 15, 2005, at 8:36 PM, Nicholas S. wrote:

NameError: uninitialized constant A::B
from (irb):3

However it seems impossible to detect which case we are in within
the const_missing method. A more detailed explanation follows:

Suppose we are lazy and avoid typing “require ‘my_module.rb’” by
defining a const_missing that performs that require when MyModule
is first referenced. Now, suppose we are writing my_class, and wish
to include MyModule. So, we put this code in my_class.rb:

I have found this type of laziness often leads to errors that are
difficult to discover because somewhere else a MyModule was defined
leaving the file I really want to use unrequired.

require ‘my_module’ is really not that hard to type.


#5

Hi,

At Wed, 16 Nov 2005 13:36:46 +0900,
Nicholas S. wrote in [ruby-talk:165996]:

However it seems impossible to detect which case we are in within the
const_missing method. A more detailed explanation follows:

What about autoload?

$ echo ‘B = 10’ > b.rb

$ irb
irb(main):001:0> autoload :B, “b”
=> nil
irb(main):002:0> module A;end
=> nil
irb(main):003:0> A::B
NameError: uninitialized constant A::B
from (irb):3
from :0
irb(main):004:0> module A;B;end
=> 10
irb(main):005:0> A::B
NameError: uninitialized constant A::B
from (irb):5
from :0
irb(main):006:0>


#6

Nicholas S. wrote:

NameError: uninitialized constant A::B
from (irb):3

However it seems impossible to detect which case we are in within the
const_missing method. A more detailed explanation follows:

I’m not sure what you’re up to. You define B in a completely different
scope than you use for lookup. Maybe you can give a more real life
example of what you are actually trying to do.

Kind regards

robert

#7

On Wed, 16 Nov 2005, Nicholas S. wrote:

The loaded file is expected to define the constant; We assume b.rb contains
class or module B (or defines it in any other way). If the constant isn’t
defined by the loaded file then a NameError is raised as if normal.

i understand that. but in your original message you said

However, let’s say we’re slightly misguided and do MyClass::MyModule. Since
this is not defined, our const_missing handler is once again called.
‘my_module.rb’ is thus loaded again, (usually to no effect,) and MyModule
returned.

This is a pretty clear violation of the semantics of ::. Indeed, Ruby will
produce a warning regarding our misbehaved const_missing. That said, there
does not seem to be a way to behave correctly; from inside the const_missing
handler there is no way to tell which case the constant is missing in.

here you will see that wether we reference B from inside M, or as M::B
the
handler is called exactly once.

harp:~ > cat a.rb
class Module
def const_missing c
puts “const_missing called once…”
const_set c, 42
end
end

module A
p B
end

p A::B

harp:~ > ruby a.rb
const_missing called once…
42
42

here is another example which loads a file

harp:~ > cat a.rb
class Module
def const_missing c
puts “const_missing called once…”
autoload = {
:B => ‘b.rb’
}
autoload[c] ? require(autoload[c]) : super
const_set(c, const_get©)
end
end

module A
B
end

A::B

module A; p B; end
p A::B

harp:~ > ruby a.rb
const_missing called once…
42
42

so what exactly are you trying to do in your handler that does not
work? i
understand you problem but, as the second example shows, it can easily
be
solved without distinguishing the two syntax cases because, afaikt, ruby
handles them precisely the same.

hth.

-a


#8

Hi,

Rails does this very thing. You should check out their const_missing and
see if it suffers from the self-same problem.

Devin