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
How to Access a Ruby Module Method
Comparing Times Only, Without Dates
How to Declare a Variable Shared Between Examples in Rspec
How to Get Request.Uri in Model in Rails
Creating an Md5 Hash of a Number, String, Array, or Hash in Ruby
Paginate Multiple Models in Kaminari
What's the Most Efficient Way to Deep Copy an Object in Ruby
How to Dynamically Call Routes Helper in Rails
$ Bundle Exec Rake Db:Reset Command Raising Couldn't Drop Db/Development.Sqlite3
Declaring Instance Variables Iterating Over a Hash!
Rails: How to Show User's "Last Seen At" Time
How to Force Ruby to Show a Full Stack Trace
Ruby Array Each_Slice_With_Index
How to Controller (Start/Kill) a Background Process (Server App) in Ruby