Why Doesn't Ruby Find Classes in a Higher Scope When Module Is Specified Using ::

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

  1. outwards in the lexically enclosing module declarations
  2. upwards in the inheritance chain of the current module declaration

So, let's just do what Ruby does:

  1. look outwards in the lexically enclosing module declarations: easy – there are no module declarations
  2. 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 – but Object doesn't have a constant named FOO and neither do its ancestors Kernel and BasicObject.

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:

  1. Your current design exposes bad naming
  2. Why not make Admin a module instead of class?
  3. Prefer using the explicit form instead of nested one - you'll never get confused.
  4. 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):

  1. Use binding_of_caller gem
  2. 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
  3. Use TracePoint API for a pure ruby solution that is amazingly slow
  4. 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)
  5. 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



Leave a reply



Submit