Instance_eval doesn't have access to class in module - ruby

I've came across an issue with instance_eval and module inclusion.
Please take a look at the following code:
module B
class C
def initialize
puts 'This is C'
end
end
def hello
puts 'hello'
end
end
class A
include B
def initialize(&block)
hello
C.new
instance_eval(&block)
end
end
A.new do
hello
C.new
end
When I run this code I get
hello
This is C
hello
-:25:in `block in ': uninitialized constant C (NameError)
from -:19:in `instance_eval'
from -:19:in `initialize'
from -:23:in `new'
from -:23:in `'
I understand that it has to do with bindings and how methods and objects are binded to class. What I cannot understand is how come I have access to C within A, but not when I eval the block. I would expect them to be in the same scope.
Thanks!

In the below code
A.new do
hello
C.new
end
You are are trying to create the object of C, as if it is defined in the scope of the class Object. No, it is not. class C is defined inside the scope of module B. And you need to tell that explicitly as B:C.new.
Above explanation is for the error -:25:in 'block in ': uninitialized constant C (NameError).
What I cannot understand is how come I have access to C within A ?
Module#constants has the answer for you :-
Returns an array of the names of the constants accessible in mod. This includes the names of constants in any included modules (example at start of section), unless the inherit parameter is set to false.
Look at the example :
module M
X = 10
class D; Y = 10 ;end
end
class C
include M
def initialize
p self.class.constants
end
end
C.new
# >> [:X, :D]
Now applying to your example :
module B
class C
def initialize
'This is C'
end
end
def hello
'hello'
end
end
class A
include B
def initialize(&block)
p self.class.constants
C.new # it will work
#instance_eval(&block)
end
end
A.new do
hello
C.new # but this will throw error.
end
# it confirms that C is accessible, within the method.
# That is the same reason you got the output "This is C"
# >> [:C]
Hope that helps.

Related

Fetch dynamically a defined constant

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.

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.

How to create an instance with part of the methods of a class in Ruby?

I have class A with methods X and Y. Now I want to create an instance but only want it to have method X from class A.
How should I do it? Should it be by deleting method Y for the instance when creating it? Your help is appreciated!
You should not do this. You should instead share the problem you're solving and find a better pattern for solving it.
An example for solving this problem a little differently:
class A
def x; end
end
module Foo
def y; end
end
instance_with_y = A.new
instance_with_y.send :include, Foo
instance_with_y.respond_to? :y #=> true
Here is one way to solve the problem :
class X
def a
11
end
def b
12
end
end
ob1 = X.new
ob1.b # => 12
ob1.singleton_class.class_eval { undef b }
ob1.b
# undefined method `b' for #<X:0x9966e60> (NoMethodError)
or, you could write as ( above and below both are same ) :
class << ob1
undef b
end
ob1.b
# undefined method `b' for #<X:0x93a3b54> (NoMethodError)
It's possible to do what you want with ruby, as ruby can be very malleable like that, but there are much better ways. What you want to achieve seems like a really bad idea.
The problem you just described a problem inheritance is designed to solve. So really, you have two classes. Class A and also class B which inherits from class A.
class A
def foo
'foo'
end
end
# B inherits all functionality from A, plus adds it's own
class B < A
def bar
'bar'
end
end
# an instance of A only has the method "foo"
a = A.new
a.foo #=> 'foo'
a.bar #=> NoMethodError undefined method `bar' for #<A:0x007fdf549dee88>
# an instance of B has the methods "foo" and "bar"
b = B.new
b.foo #=> 'foo'
b.bar #=> 'bar'

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.

calling method in nested module included in class

I have the following configuration:
module A
module B
def foo
puts "foo"
end
end
end
class C
include A
end
c = C.new
c.foo
NoMethodError: undefined method `foo' for #<C:0x8765284>
How do I achieve the above?
Thanks.
Module B is "defined" inside of A, it is not "included" in A. This is why you don't get access to the #foo instance method when you include the A module in C. You could do the following:
class C
include A::B
end
C.new.foo
You can use the included callback to have B include when A is included.
module A
def A.included(klass)
klass.include B
end
module B
def foo
puts "foo"
end
end
end
class C
include A
end
and the following would work
c = C.new
c.foo

Resources