Fetch dynamically a defined constant - ruby

According to Ruby const_get's documentation, this method returns value for a constant.
So to reproduce the following behavior:
module A
FOO = 42
module B
def self.call
FOO
end
end
end
A::B.call # => 42
I wrote this code:
A = Module.new
A.const_set(:FOO, 42)
A.const_set(:B, Module.new do
def self.call
const_get(:FOO)
end
end)
But when I call the method, I've got a NameError exception:
A::B.call # => exception
# NameError (uninitialized constant A::B::FOO)
# Did you mean? A::FOO
It looks like FOO and const_get(:FOO) are not exactly the same.
Is there another way to find the FOO constant looking recursively in the parents modules?
Edit:
I also have this trouble with const_get doing directly:
module A
FOO = 42
module B
def self.a_method
const_get(:FOO)
end
end
end
A::B.a_method # => exception
# NameError (uninitialized constant A::B::FOO)
# Did you mean? A::FOO

We can use the method Module::nesting to better understand what is going on here.
module A
FOO = 42
module B
puts "Module.nesting = #{Module.nesting}"
def self.call
FOO
end
end
end
displays
Module.nesting = [A::B, A]
This tells us that, when executing A::B.call, in searching for the value of FOO, Ruby first looks in module A::B, does not find it, then searches in module A where she does find it.
Your third code fragment also creates a nested module A::B:
module A
FOO = 42
module B
puts "Module.nesting = #{Module.nesting}"
def self.a_method
const_get(:FOO)
end
end
end
displays
Module.nesting = [A::B, A]
A::B.a_method executes A::B.const_get(:FOO). The doc for Module#const_get states, "Checks for a constant with the given name in mod." Therefore, if it fails to find the constant in the module that is its receiver it gives up without examining any modules within which the given module is nested.
Your second code fragment is similar to the third in that it fails to find the value of FOO because of the behaviour of const_get.

Related

Why does Ruby not add parent modules to the lexical scope for constant lookup when using double colons?

This question is an extension of this question. The answer helped me understand what was happening, but I am still questioning why.
When defining two classes within a module there are two ways to write it.
Using Module Blocks:
module Foo
class FirstClass
def meth
puts 'HELLO'
end
end
class SecondClass
def meth
FirstClass.new.meth
end
end
end
Foo::SecondClass.new.meth
Using Double Colons:
module Foo; end
class Foo::FirstClass
def meth
puts 'HELLO'
end
end
class Foo::SecondClass
def meth
FirstClass.new.meth
end
end
Foo::SecondClass.new.meth
Both ways work for class definition, but when using double colons you cannot directly lookup FirstClass inside of SecondClass without including FirstClass or writing Foo::FirstClass. This happens because Foo is not a part of the lexical scope of SecondClass when it's defined with double colons, which can be demonstrated by using Module.nesting.
Why is Foo not added to the lexical scope with double colons? In the context of the lower level Ruby source code, why does ruby_cref point only to Foo::SecondClass instead of ruby_cref pointing to SecondClass which then points to Foo?
For Example:
+---------+ +---------+
| nd_next | <-----+ nd_next | <----+ ruby_cref
| nd_clss | | nd_clss |
+----+----+ +----+----+
| |
| |
v v
Foo SecondClass
Let me ask you the reverse question: why would it?
As you found in the last question, module nesting is important. For a quick intuitive example,
module Foo
puts self # executing in Foo context
module Bar
puts self # executing in Foo::Bar context
end
end
It is only modules (and its subclasses, such as Class) that can do this — change the execution context by nesting.
Now, over to your examples. The first snippet is effectively equivalent to:
module Foo
# executing in Foo namespace context
FirstClass = Class.new
meth1 = proc do puts "HELLO" end
FirstClass.define_method(:meth, meth1)
SecondClass = Class.new
meth2 = proc do FirstClass.new.meth end
SecondClass.define_method(:meth, meth2)
SecondClass.new.meth
end
Here, assuming we executed this at the main level, all of the references are relative to ::Foo. When we write FirstClass, it is understood as ::Foo::FirstClass.
The second snippet is effectively equivalent to
# executing in top namespace context
Foo = Module.new
Foo::FirstClass = Class.new
meth1 = proc do puts "HELLO" end
Foo::FirstClass.define_method(:meth, meth1)
Foo::SecondClass = Class.new
meth2 = proc do FirstClass.new.meth end # ERROR
Foo::SecondClass.define_method(:meth, meth2)
Foo::SecondClass.new.meth
Written this way, it might be obvious why the second example does not work. If this was executed in main, then Foo::FirstClass that we defined is understood as ::Foo::FirstClass. The FirstClass mention in the error line is understood as ::FirstClass, which was never defined.
It allows you to explicitly set the modules that are used for constant lookup. Here's an example of a class MyClass defined under a module Foo:
module Foo
A = 'A in Foo'
B = 'B in Foo'
C = 'C in Foo'
end
module Foo
class MyClass
B = 'B in MyClass'
p Module.nesting #=> [Foo::MyClass, Foo]
def self.abc
[A, B, C]
end
end
end
Foo::MyClass.abc
#=> ["A in Foo", "B in MyClass", "C in Foo"]
The constants are resolved the way Module.nesting shows, i.e. A, B, C are searched in Foo::MyClass and then in Foo.
Now for something more unusual. We can add a totally unrelated module Bar::Baz in-between Foo::MyClass and Foo for constant lookup:
module Foo
A = 'A in Foo'
B = 'B in Foo'
C = 'C in Foo'
end
module Bar
module Baz
C = 'C in Bar::Baz'
end
end
module Foo
module ::Bar::Baz
class Foo::MyClass
B = 'B in MyClass'
p Module.nesting #=> [Foo::MyClass, Bar::Baz, Foo]
def self.abc
[A, B, C]
end
end
end
end
Foo::MyClass.abc
#=> ["A in Foo", "B in MyClass", "C in Bar::Baz"]
I don't know if this has any real-world application, but it makes constant lookup extremely flexible. You can precisely select the modules you want to include (or exclude) for constant lookup and their order.

How to work through name collisions in ruby

Two modules Foo and Baa respectively define a method with the same name name, and I did include Foo and include Baa in a particular context.
When I call name, how can I disambiguate whether to call the name method of Foo or Baa?
Only the order of modules inclusion decides which one will get called. Can't have both with the same name - the latter will override the former.
Of course, you can do any tricks, just from the top of my head:
module A
def foo
:foo_from_A
end
end
module B
def foo
:foo_from_B
end
end
class C
def initialize(from)
#from = from
end
def foo
from.instance_method(__method__).bind(self).call
end
private
attr_reader :from
end
C.new(A).foo #=> :a_from_A
C.new(B).foo #=> :a_from_B
But that's no good for real life use cases :)
Technically, there is no name collision because the method foo is redefined.
In the following exemple, A.foo is redefined and is never called
module A
def foo
raise "I'm never called"
end
end
module B
def foo
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_B
If you write A and B module, you can use super to call previous definition of foo. As if it where an inherited method.
module A
def foo
puts :foo_from_A
end
end
module B
def foo
super
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_A
# foo_from_B
There are side effects and I would not use this but this is doing the trick :
module A
def foo
puts :foo_from_A
end
end
module B
def foo
puts :foo_from_B
end
end
class C
def self.include_with_suffix(m, suffix)
m.instance_methods.each do |method_name|
define_method("#{method_name}#{suffix}", m.instance_method(method_name))
end
end
include_with_suffix A, "_from_A"
include_with_suffix B, "_from_B"
end
c= C.new
c.foo_from_A
c.foo_from_B
begin
c.foo
rescue NoMethodError
puts "foo is not defined"
end
# =>
# foo_from_A
# foo_from_B
# foo is not defined
Provided none of the methods of Foo or Baa call name (which seems a reasonable assumption), one can simply create aliases.
module Foo
def name; "Foo#name"; end
end
module Baa
def name; "Baa#name"; end
end
class C
include Foo
alias :foo_name :name
include Baa
alias :baa_name :name
undef_method :name
end
c = C.new
c.foo_name
#=> "Foo#name"
c.baa_name
#=> "Baa#name"
C.instance_methods & [:foo_name, :baa_name, :name]
#=> [:foo_name, :baa_name]
The keyword alias is documented here. One may alternatively use the method #alias_method. See this blog for a comparison of the two.
Module#undef_method is not strictly necessary. It's just to ensure that an exception is raised if name is called.
You should definetely read about method lookups.
Anyway, I would do it this way:
module Foo
def name
:foo
end
end
module Bar
def name
:bar
end
end
class MyClass
include Foo
include Bar
def foo_name
Foo.instance_method(:name).bind(self).call
end
def bar_name
Bar.instance_method(:name).bind(self).call
end
#
# or even like this: obj.name(Foo)
#
def name(mod)
mod.instance_method(:name).bind(self).call
end
end
BTW if you are using Module#instance_method and UnboundMethod#bind you don't really need to include specific module. This code works:
Foo.instance_method(:name).bind('any object (e.g. string)').call

Class-like closures in ruby

I'm trying to do something like this in Ruby:
class A
def b(c)
Class.new do
def d
c
end
end
end
end
class B < A.b('whoa'); end
#
# What I want:
#
B.new.d # => 'whoa'
#
# What I get:
#
# NameError: undefined local variable or method `d' for B:0xfdfyadayada
Is there any way to do this?
The context is I'm trying to get some code reuse by constructing classes that are mostly similar except for some minor config.
I'm looking for an API similar to Sequel::Model where you can do something like:
class Order < Sequel::Model(:order)
The contents of a method definition using the def keyword are not lexically scoped the way blocks are. In other words you can’t do something like this:
foo = 7
def bar
# Code here inside the method definition can't
# "see" foo, so this doesn't work.
foo
end
However you can use the define_method method to dynamically define a method using a block, and the block will be able to refer to local variables in its outer scope. So in your case you could do this (I’ve also changed def b to def self.b, which I think is what you meant):
class A
def self.b(c)
Class.new do
define_method(:d) do # N.B. the d here is a symbol
c
end
end
end
end
class B < A.b('whoa'); end
B.new.d # => 'whoa'
This would also work if you created other classes:
class C < A.b('dude'); end
C.new.d # => 'dude'
# B still works
B.new.d # => 'whoa'
Assuming that b is a class method (i.e def self.b instead of def b), you could store c in a class variable.
class A
def self.b(c)
Class.new do
##c = c
def d
##c
end
end
end
end
class B < A.b('whoa'); end
puts B.new.d # => whoa
There could be a better solution, I haven't looked at the source code of Sequel. This is just the first thing that came to my mind.

Nesting versus namespace operator

I had believed that
module A
module B
...
end
end
and (provided that module A is defined in advance):
module A::B
...
end
are equivalent, but it turned out not. Given:
module A
C = :foo
end
the two forms behave differently:
module A
module B
puts C
end
end
# => :foo
module A::B
puts C
end
# => NameError: uninitialized constant A::B::C
What is the logic behind this difference? Particularly, why cannot A::B access A::C in the second form (although it can in the first form)?
Update
I found some related posts on the Ruby core:
https://bugs.ruby-lang.org/issues/5148
https://bugs.ruby-lang.org/issues/6810
Module::nesting is a good tool to answer you here. As the doc is saying - Returns the list of Modules nested at the point of call.
Here is the reason why the below one is not working :
module A
C = :foo
end
module A::B
puts C
end
To let this explain I would write something like below :
module A
C = :foo
end
module A::B
$a = Module.nesting
end
$a # => [A::B]
So from the output of $a,it is clear that why the constant C is not accessible.
The reason why the below code does work :
module A
C = :foo
end
module A
module B
puts C
end
end
To let this explain I would write something like below :
module A
C = :foo
end
module A
module B
$a = Module.nesting
end
end
$a # => [A::B, A]
So from the output of $a,it is clear that why the constant C is accessible.
Read here(Everything you ever wanted to know about constant lookup in Ruby) is an wonderful resource for the same I just found.

Why are constants from extended module not available in class methods declared with self.?

I thought there were no differences between methods declared within a class << self block and those declared with a self. prefix, but there are:
module A
VAR = 'some_constant'
end
class B
extend A
class << self
def m1
puts VAR
end
end
def self.m2
puts VAR
end
end
B.m1 # => OK
B.m2 # => uninitialized constant B::VAR
Why are constants of A available in m1 but not in m2?
In Ruby, constant lookup is not the same as method lookup. For method lookup, calling foo is always the same as calling self.foo (assuming it isn't private). Calling a constant FOO is very different from self::FOO or singleton_class::FOO.
Using an unqualified constant (e.g. FOO) will do a lookup in the currently opened modules. A module is opened with module Mod, class Klass, class << obj, or module_eval and variants. When defining m1, these are B, and then B.singleton_class. When defining m2, only B is opened.
module Foo
X = 42
class Bar
def self.hello
X
end
end
end
In this code, Foo::Bar.hello will return 42, even though X is not a constant of Bar, its singleton class or ancestor. Also, if you later add a constant X to Bar, then that value will be returned. Finally, the following definition is not equivalent:
module Foo
X = 42
end
class Foo::Bar
def self.hello
X
end
end
Foo::Bar.hello # => uninitialized constant Foo::Bar::X
Indeed, when hello is defined, only the class Foo::Bar is opened, while in the previous example, both Foo and Foo::Bar where opened.
A last example, to show the difference an explicit scope can have with inheritance:
class Base
X = 42
def self.foo
X
end
def self.bar
self::X
end
end
class Parent < Base
X = :other
end
Parent.foo # => 42
Parent.bar # => :other
In your case, you probably want to include your module, instead of extending it, no?
Otherwise, you could use singleton_class::VAR, your code will work as you expect it.
module A
VAR = 'some_constant'
end
class B
extend A
class << self
def m1
puts singleton_class::VAR # not necessary here, as singleton_class is opened
end
end
def self.m2
puts singleton_class::VAR # necessary here!
end
end
B.m1 # => OK
B.m2 # => OK

Resources