Why doesn't Ruby find classes in a higher scope when module is specified using ::?
Is this a bug, or is it just a logical consequence
It's a "quirk". Some consider it a bug.
Parent scopes used for looking up unresolved constants are determined by module nesting. It just so happens that when you use module Top::Foo
, it creates just one level of nesting instead of two. Observe:
module Top
module Foo
class SomeTest
Module.nesting # => [Top::Foo::SomeTest, Top::Foo, Top]
end
end
end
module Top::Foo
class SomeTest
Module.nesting # => [Top::Foo::SomeTest, Top::Foo]
end
end
Ruby metaprogramming: define_method block not maintaining scope
When you reference the class Base::Test
like this, ruby does not take the Base::
as the module context to look up constants. That is the normal behaviour and would also not work, if you would define the moethod directly.
But you could do it in this way:
module Base
Test.instance_eval do
def asdf
puts "Test#asdf called"
Foo.new.info
end
end
end
Why doesn't Ruby find constants defined in the caller's class?
This was a puzzle that I encountered in real production code. I wrote up a detailed explanation of what's going on in this blog post.
Here's the TLDR: Ruby uses a much more complex algorithm for resolving constants than it does for methods. One phase of the constant lookup routine involves looking through the superclass chain for the definition. This phase looks very much like the method lookup routine, deepening the mystery of why methods and constants differ in the way illustrated in the question.
The explanation is that the two superclass chain routines differ in where they start, i.e. which class is the root of the chain.
Method lookup starts with self
's class, where self
is the receiver of the original method call. In the example, sub_class_instance
is the receiver and SubClass
is where the lookup starts. SubClass
implements foo_method
, so all is well.
For constants, Ruby doesn't refer to a receiver, because constant invocations aren't relative to a receiver. Instead, constant superclass lookup begins with the class that's open at the lexical scope where the constant invocation occurs. In the example, the class that's open is MyClass
, so thats where Ruby starts to looks for the constant—and never finds it.
Why does Ruby class_eval remove constants from scope?
Constants are looked up
- outwards in the lexically enclosing module declarations
- upwards in the inheritance chain of the current module declaration
So, let's just do what Ruby does:
- look outwards in the lexically enclosing module declarations: easy – there are no module declarations
- look upwards from the current module declaration: again, there is no current module declaration … or is there? Well, if there is no module declaration, it is implicitly assumed to be
class Object
– butObject
doesn't have a constant namedFOO
and neither do its ancestorsKernel
andBasicObject
.
Ergo: Ruby is right. There is no constant named FOO
within the constant lookup path. It didn't remove FOO
from the scope, it was never in scope to begin with.
[Ruby's reflection API actually gives you access to anything you need: #1 is exactly the same as Module.nesting
and #2 is (almost) the same as Module.nesting.first.ancestors
.]
You might think: wait, isn't module_eval
a module declaration? No, it isn't! It's just a method like any other method taking a block that's just like any other block. It doesn't alter the constant lookup rules in any way.
Note that that's not quite true: after all, e.g. instance_eval
does change method lookup rules, for example, so it would not be inconceivable that module_eval
changes constant lookup rules. And even more confusingly, when called with a String
instead of a block, it actually does change Module.nesting
and thus the constant lookup rules!
Which class get inherited when using nested class
If the question is what is the equivalent of this line:
class Admin::ApplicationController < ApplicationController
Then your second assumption is correct, it is equivalent to:
class Admin
class ApplicationController < ApplicationController
end
end
Few sidenotes though:
- Your current design exposes bad naming
- Why not make
Admin
a module instead of class? - Prefer using the explicit form instead of nested one - you'll never get confused.
- See this thread about some difference in levels of nesting between two forms of class definitions.
Ruby - Automatically pass binding() to method like eval?
Okay, so it turns out there's no easy answer. I've compiled a list here:
Ruby: Binding of Caller Solutions
And it breaks down to the following possibilities (expanded on in the link above):
- Use binding_of_caller gem
- Use a really sneaky pure ruby trick that only works if your method takes no args (involving define_singleton_method and then composing (>>) Object::method(:binding) with your method). See examples at https://bugs.ruby-lang.org/issues/18487
- Use TracePoint API for a pure ruby solution that is amazingly slow
- Call rb_eval_string_protect("binding") inside of a C extension which, until Ruby 3.2.0, will get you all of the binding (but with a corrupted receiver)
- Use my cut down C extension that takes the important bits out of the gem that binding_of_caller depends upon (debug_inspector)
Related Topics
Using Www:Mechanize to Download a File to Disk Without Loading It All in Memory First
How to Remove Blank Elements from an Array
Rails4 Unknown Encoding Name - Cp720
Set Global Default Encoding For Ruby 1.9
How to Make Sinatra Work Over Https/Ssl
Why Isn't Self Always Needed in Ruby/Rails/Activerecord
What Is a Regex to Match a String Not At the End of a Line
How to Format a Date in Ruby to Include "Rd" as in "3Rd"
Why Doesn't Ruby Find Classes in a Higher Scope When Module Is Specified Using ::
How to Use CSS with a Ruby on Rails Application
Putting French (Accented) Characters in Ruby File
The <<- Operator on Ruby, Where Is It Documented