What's the Variable Scope Within 'Class_Eval' String

class_eval and context of class variables?

I've had to experience some weak moment, as the answer is quite clear & obvious.

From the Module.class_eval documentation:

Evaluates the string or block in the context of mod, except that when a
block is given, constant/class variable lookup is not affected
. This can be
used to add methods to a class. module_eval returns the result of evaluating
its argument.

So if I would need directly access class variables from eval block (ie. without use of class variable getter/setter methods), I'd just pass the code as a string:

MyClass.class_eval <<-EOS
p @@myvar # class variable lookup is working from a string
# output: 123 voila!
EOS

Why can't I access local variable in the class_eval block?

It's so called "scope gate". Local variables go out of scope as soon as definition of method, class or module begins. See this book for more in-depth info: https://pragprog.com/book/ppmetr2/metaprogramming-ruby-2

You can bypass this by using define_method or define_singleton_method (whatever is suitable to you) instead of def syntax (because this would be a method call, not method definition)

Accessing Ruby Class Variables with class_eval and instance_eval

I just asked the same question to Matz during the RubyKaigi party. I was half-drunk, but he was perfectly sober, so you can take this as the definitive answer.

Anton is right - the reason why you cannot access class variables through instance_eval() is "just because". Even class_eval() shares the same issue (Matz himself wasn't totally sure about class_eval() until I told him I'd already tried it). More specifically: scope-wise, class variables are more like constants than instance variables, so switching self (as instance_eval() and class_eval() do) is not going to make any difference when it comes to accessing them.

In general, it might be a good idea to avoid class variables altogether.

Class eval passing the class varibale by reference

It's because you set not the variable you think you do.

class A; end

@class = eval("A")
@class.class_eval do
class_variable_set :@@attr, 100

def self.get_attr
class_variable_get :@@attr
end

def self.set_attr(_x)
class_variable_set :@@attr, _x
end
end

class B
end

@class = eval("B")
@class.class_eval do
class_variable_set :@@attr, 100

def self.get_attr
class_variable_get :@@attr
end
def self.set_attr(_x)
class_variable_set :@@attr, _x
end
end

A.set_attr(103)
B.set_attr(222)
puts A.get_attr
puts B.get_attr

# >> 103
# >> 222

When I run your code, it gives several warnings of "Access to class variable from toplevel". So, apparently, you're setting and reading class variables of main object, not of your classes.

How to use class_eval for multi attributes value

You were using @ah and @attr_name instead of @#{ah} and @#{attr_name} when getting/setting in the methods. This meant that they were always setting and returning the same instance variable, instead of different, dynamically named ones.

class Class
def attr_accessor_with_history(attr_name)
class_eval %{
attr_reader :#{attr_name}, :#{attr_name}_history

def #{attr_name}=(value)
@#{attr_name} = value
@#{attr_name}_history ||= [nil]
@#{attr_name}_history << value
end
}
end
end

I've also generally cleaned up your code a little to make it (I think) clearer and more concise.

Whats the difference between class_eval and class className?

class_eval doesn't really have anything to do with class << className.

A.class_eval do
...
end

is equivalent to

class A
...
end

with a few differences. class_eval uses a block (or a string, but ignoring that for the moment) which means it closes over the containing lexical scope. In other words you can use local variables from the surrounding scope. The common class block introduces a brand new scope. Likewise you can create the block and pass it to many different class_eval's, and the body of the block will be executed in the context of the class you are calling class_eval on.

class << className opens the singleton class of className, allowing you to define class methods.

class << A
def foo
...
end
end

Is the same as

def A.foo
...
end

Note that they are oly class methods if A happens to be a class (almost) all objects in ruby have singleton classes and you can define methods for them using either of those two syntaxes. The advantage of class << obj is mainly if you're defining many singleton methods in one go.

How to understand the difference between class_eval() and instance_eval()?

As the documentation says, class_eval evaluates the string or block in the context of the Module or Class. So the following pieces of code are equivalent:

class String
def lowercase
self.downcase
end
end

String.class_eval do
def lowercase
self.downcase
end
end

In each case, the String class has been reopened and a new method defined. That method is available across all instances of the class, so:

"This Is Confusing".lowercase 
=> "this is confusing"
"The Smiths on Charlie's Bus".lowercase
=> "the smiths on charlie's bus"

class_eval has a number of advantages over simply reopening the class. Firstly, you can easily call it on a variable, and it's clear what your intent is. Another advantage is that it will fail if the class doesn't exist. So the example below will fail as Array is spelt incorrectly. If the class was simply reopened, it would succeed (and a new incorrect Aray class would be defined):

Aray.class_eval do
include MyAmazingArrayExtensions
end

Finally class_eval can take a string, which can be useful if you're doing something a little more nefarious...

instance_eval on the other hand evaluates code against a single object instance:

confusing = "This Is Confusing"
confusing.instance_eval do
def lowercase
self.downcase
end
end

confusing.lowercase
=> "this is confusing"
"The Smiths on Charlie's Bus".lowercase
NoMethodError: undefined method ‘lowercase’ for "The Smiths on Charlie's Bus":String

So with instance_eval, the method is only defined for that single instance of a string.

So why does instance_eval on a Class define class methods?

Just as "This Is Confusing" and "The Smiths on Charlie's Bus" are both String instances, Array, String, Hash and all other classes are themselves instances of Class. You can check this by calling #class on them:

"This Is Confusing".class
=> String

String.class
=> Class

So when we call instance_eval it does the same on a class as it would on any other object. If we use instance_eval to define a method on a class, it will define a method for just that instance of class, not all classes. We might call that method a class method, but it is just an instance method for that particular class.

Trying to understand the usage of class_eval

What rails-settings code does is considered good practice: it only messes with third-party modules when it's explicitely asked to do so. This way you also keep the namespaces tidily separated and all your code remains in your modules.



Related Topics



Leave a reply



Submit