I'm trying to programmatically fetch a constant (e.g. a class), but want to only look at the constants defined in a specific namespace. However, const_get will bubble up to higher namespaces while searching for the constant. For instance:
module Foo
class Bar
end
end
class Quux
end
If you then ask Foo to return the constant "Bar", it'll return the correct class.
Foo.const_get('Bar')
#=> Foo::Bar
However, if you ask it for "Quux", it'll bubble up it's search path and find the top-level Quux:
Foo.const_get('Quux')
#=> Quux
Is there any way to make it only search in the module that const_get is called on?
Module#const_get says:
Checks for a constant with the given name in mod If inherit is set, the lookup will also search the ancestors (and Object if mod is a Module.)
The value of the constant is returned if a definition is found, otherwise a NameError is raised.
You can then do as below:
module Foo
class Bar
end
end
class Quux
end
Foo.const_get('Quux',false) rescue NameError
# >> NameError
Foo.const_get('Bar',false) rescue NameError
# >> Foo::Bar
Related
I have a simple module that defines a constant and makes it private:
module Foo
Bar = "Bar"
private_constant :Bar
end
I can include it in a class like this, and it works as expected:
class User
include Foo
def self.test
Bar
end
end
puts User.test
# => Bar
begin
User::Bar
rescue => exception
puts "#{exception} as expected"
# => private constant Foo::Bar referenced as expected
end
(let's call it "typical class" definition)
Then I tried the Class.new approach, but this failed miserably:
X = Class.new do
include Foo
def self.test
Bar # Line 28 pointed in the stack trace
end
end
begin
X::Bar
rescue => exception
puts "#{exception}"
# => private constant Foo::Bar
end
puts X.test
# test.rb:28:in `test': uninitialized constant Bar (NameError)
# from test.rb:28:in `<main>'
Why? I always though class Something and Something = Class.new are equivalent. What's the actual difference?
Then I had a strike of inspiration, and recalled there's alternative way to define class methods, which actually worked:
X = Class.new do
class << self
include Foo
def test
Bar
end
end
end
begin
X::Bar
rescue => exception
puts "#{exception}"
# => uninitialized constant X::Bar
end
puts X.test
# Bar
Again - why this one work, and why the exception is now different: private constant Foo::Bar vs uninitialized constant X::Bar?
It seems like those 3 ways of initializing classes differ in a nuanced way.
does exactly what I want: Bar is accessible internally, and accessing it gives exception about referencing private constant.
second gives "ok" exception, but has no access to Bar itself
third has access, but now gives slightly different exception
What is exactly going on in here?
This is one of the biggest gotchas in Ruby: constant definition scope is partially syntactic, that is, it depends on how the code around it is structured.
module Foo
Bar = "Bar"
end
Bar is inside a module definition, so it is defined in that module.
class << self
include Foo
end
Bar gets included inside a class definition, so it is defined in that class.
Class.new do
include Foo
end
There is no enclosing class or module (this is a normal method call with a block), so the constant is defined at top level.
As for your third error, I believe that is because the constant got defined in the singleton class (that's what class << self is) versus the class itself. They are two separate class objects.
I made a class with a constant & a method:
class A
FOO = 'hello'
def bar
puts FOO
end
end
A.new.bar
=> 'hello'
And everything works as expected. However when I do this:
A.class_eval do
def bar
puts FOO
end
end
A.new.bar
NameError: uninitialized constant FOO
Weirdness... To get around this I'm doing:
A.class_eval do
def bar
puts self.class::FOO
end
end
Any good explanation on why this is so?
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 – 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!
In a tutorial, there are examples querying constants using const_get:
class This
end
p Module.const_get('This').new
and
A_CONSTANT = 42
p Module.const_get('A_CONSTANT')
I can't see any module definition. Why is Module used? Why isn't const_get('This') or self.const_get('This') enough? The class where all this is written in is Object, ancestor of BasicObject.
Does the fact that This is a constant mean that it holds the class definition inside of it? Like as it just could hold the number 42? Or to be clearer, is it the same as:
This = class
end
like an unnamed method so I can write p This and get the class definition?
Some nice knowledge you have acquired. But I don't know why are you mentioning metaprogramming in your title, when your question is all about constants. Metaprogramming is something else.
In any case, #const_get is an instance method of Module class, so it won't work at the top level, where the implicit receiver is of Object class. When you write
class Foo; end
Constant Foo gets added to Object:
Object::Foo #=> Foo
Object::Bar #=> error (we didn't define constant Bar)
This constant is available also in other classes, such as
Module::Foo #=> Foo, but with a warning
Array::Foo #=> same Foo with the same warning
Fixnum::Foo #=> ditto
In other words, your Foo defined at toplevel is visible from almost every module. But accessing toplevel constants like this is frowned upon, as this is often not what the programmer intended. You can indeed see that the constant is defined on Object:
Object.constants( false ).include? :Foo #=> true
and not on eg. Module:
Module.constants( false ).include? :Foo #=> false
However, if you use #const_get, the warning message is suppressed and you may come to think that Module::Foo actually exists:
Module.const_get( :Foo ) #=> Foo
It doesn't.
Background:
ruby thinks I'm referencing a top-level constant even when I specify the full namespace
How do I refer to a submodule's "full path" in ruby?
Here's the problem, distilled down to a minimal example:
# bar.rb
class Bar
end
# foo/bar.rb
module Foo::Bar
end
# foo.rb
class Foo
include Foo::Bar
end
# runner.rb
require 'bar'
require 'foo'
➔ ruby runner.rb
./foo.rb:2: warning: toplevel constant Bar referenced by Foo::Bar
./foo.rb:2:in `include': wrong argument type Class (expected Module) (TypeError)
from ./foo.rb:2
from runner.rb:2:in `require'
from runner.rb:2
Excellent; your code sample is very clarifying. What you have there is a garden-variety circular dependency, obscured by the peculiarities of Ruby's scope-resolution operator.
When you run the Ruby code require 'foo', ruby finds foo.rb and executes it, and then finds foo/bar.rb and executes that. So when Ruby encounters your Foo class and executes include Foo::Bar, it looks for a constant named Bar in the class Foo, because that's what Foo::Bar denotes. When it fails to find one, it searches other enclosing scopes for constants named Bar, and eventually finds it at the top level. But that Bar is a class, and so can't be included.
Even if you could persuade require to run foo/bar.rb before foo.rb, it wouldn't help; module Foo::Bar means "find the constant Foo, and if it's a class or a module, start defining a module within it called Bar". Foo won't have been created yet, so the require will still fail.
Renaming Foo::Bar to Foo::UserBar won't help either, since the name clash isn't ultimately at fault.
So what can you do? At a high level, you have to break the cycle somehow. Simplest is to define Foo in two parts, like so:
# bar.rb
class Bar
A = 4
end
# foo.rb
class Foo
# Stuff that doesn't depend on Foo::Bar goes here.
end
# foo/bar.rb
module Foo::Bar
A = 5
end
class Foo # Yep, we re-open class Foo inside foo/bar.rb
include Bar # Note that you don't need Foo:: as we automatically search Foo first.
end
Bar::A # => 4
Foo::Bar::A # => 5
Hope this helps.
Here is a more minimal example to demonstrate this behavior:
class Bar; end
class Foo
include Foo::Bar
end
Output:
warning: toplevel constant Bar referenced by Foo::Bar
TypeError: wrong argument type Class (expected Module)
And here is even more minimal:
Bar = 0
class Foo; end
Foo::Bar
Output:
warning: toplevel constant Bar referenced by Foo::Bar
The explanation is simple, there is no bug: there is no Bar in Foo, and Foo::Bar is not yet defined. For Foo::Bar to be defined, Foo has to be defined first. The following code works fine:
class Bar; end
class Foo
module ::Foo::Bar; end
include Foo::Bar
end
However, there is something that is unexpected to me. The following two blocks behave differently:
Bar = 0
class Foo; end
Foo::Bar
produces a warning:
warning: toplevel constant Bar referenced by Foo::Bar
but
Bar = 0
module Foo; end
Foo::Bar
produces an error:
uninitialized constant Foo::Bar (NameError)
Here's another fun example:
module SomeName
class Client
end
end
module Integrations::SomeName::Importer
def perform
...
client = ::SomeName::Client.new(...)
...
end
end
That produces:
block in load_missing_constant': uninitialized constant Integrations::SomeName::Importer::SomeName (NameError)
Ruby (2.3.4) just goes to the first occurrence of "SomeName" it can find, not to the top-level.
A way to get around it is to either use better nesting of modules/classes(!!), or to use Kernel.const_get('SomeName')
According to the documentation mod.const_get(sym) "Returns the value of the named constant in mod."
I also know that const_get by default may look up the inheritance chain of the receiver. So the following works:
class A; HELLO = :hello; end
class B < A; end
B.const_get(:HELLO) #=> :hello
I also know that classes in Ruby subclass Object, so that you can use const_get to look up 'global' constants even though the receiver is a normal class:
class C; end
C.const_get(:Array) #=> Array
However, and this is where i'm confused -- modules do not subclass Object. So why can I still look up 'global' constants from a module using const_get? Why does the following work?
module M; end
M.const_get(:Array) #=> Array
If the documentation is correct - const_get simply looks up the constant defined under the receiver or its superclasses. But in the code immediately above, Object is not a superclass of M, so why is it possible to look up Array ?
Thanks
You are correct to be confused... The doc didn't state that Ruby makes a special case for lookup of constants in Modules and has been modified to state this explicitly. If the constant has not been found in the normal hierarchy, Ruby restarts the lookup from Object, as can be found in the source.
Constant lookup by itself can be bit confusing. Take the following example:
module M
Foo = :bar
module N
# Accessing Foo here is fine:
p Foo # => bar
end
end
module M::N
# Accessing Foo here isn't
p Foo # => uninitialized constant M::N::Foo
end
p M::N.const_get :Foo # => uninitialized constant M::N::Foo
In both places, though, accessing Object level constants like Array is fine (thank god!). What's going on is that Ruby maintains a list of "opened Module definitions". If a constant has an explicit scope, say LookHereOnly::Foo, then only LookHereOnly and its included modules will be searched. If no scope is specified (like Foo in the example above), Ruby will look through the opened module definitions to find the constant Foo: M::N, then M and finally Object. The topmost opened module definition is always Object.
So M::N.const_get :Foo is equivalent to accessing Foo when the opened classes are only M::N and Object, like in the last part of my example.
I hope I got this right, coz I'm still confused by constant lookups myself :-)
I came up with the following script to load name spaced constants:
def load_constant(name)
parts = name.split('::')
klass = Module.const_get(parts.shift)
klass = klass.const_get(parts.shift) until parts.empty?
klass
end
As long as we are not checking for errors you can:
def load_constant(name)
name.split('::').inject(Module) do |mod_path, mod_to_find|
mod_path.const_get(mod_to_find)
end
end