How to Use Class_Eval

How do I use class_eval?

The short answer is: you probably want to avoid using class_eval like this.

Here's an explanation of your code:

The %{hello} is just another way to write a string literal in Ruby, without having to worry about escaping double or single quotes within the string:

%{hello "world"} == "hello \"world\""  # => true

The val in your code is an argument of the method being defined.

The class_eval is used to define some methods by computing the text one would write to do the definition and then evaluating it. It is not necessary here, BTW. An equivalent code would be:

class Module
def attr_ (*syms)
syms.each do |sym|
define_method "#{sym}=" do |val|
instance_variable_set "@#{sym}", val
end
end
end
end

This is just equivalent to the builtin attr_writer.

Update: There can actually be a significant difference between the two...

The class_eval version is vulnerable if you can't trust the argument syms. For example:

class Foo
attr_ "x; end; puts 'I can execute anything here!'; val=42; begin; val"
end

The class_eval version will print "I can execute anything here" twice, proving it can execute anything. The define_method version won't print anything.

This type of code was pivotal to create major vulnerability for all installed Rails apps.

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.

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.

How to use a hash in a class_eval statement in Ruby

or a NameError occurs because it sees 'history_hash' as an undefined local variable or method

I'd say you can't, because it is a local variable, one that is inaccessible in the context you want it. However, why do you even need it? I'm reasonably sure it's in the "some code I added in an attempt to complete the assignment", and not the original assignment code (which, I assume, expects you to store the history of @bar in @bar_history - or else what is attr_hist_name all about?)

I'm also uncomfortable about string evals; it's generally not necessary, and Ruby can do better, with its powerful metaprogramming facilities. Here's how I'd do it:

class Class
def attr_accessor_with_history(attr_name)
attr_setter_name = :"#{attr_name}="
attr_getter_name = :"#{attr_name}"
attr_hist_name = :"@#{attr_name}_history"
attr_name = :"@#{attr_name}"

self.class_eval do
define_method(attr_getter_name) do
instance_variable_get(attr_name)
end

define_method(attr_setter_name) do |val|
instance_variable_set(attr_name, val)
history = instance_variable_get(attr_hist_name)
instance_variable_set(attr_hist_name, history = []) unless history
history << val
end
end
end
end

class Object
def history(attr_name)
attr_hist_name = :"@#{attr_name}_history"
instance_variable_get(attr_hist_name)
end
end

Finally, as it's monkey-patching base classes, I'd rather use refinements to add it where needed, but that's probably an overkill for an assignment.

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.

Using super with class_eval

I think there are several ways to do what you're wanting to do. One is to open the class and alias the old implementation:

class MyClass
def method1
1
end
end

class MyClass
alias_method :old_method1, :method1
def method1
old_method1 + 1
end
end

MyClass.new.method1
=> 2

This is a form of monkey patching, so probably best to make use of the idiom in moderation. Also, sometimes what is wanted is a separate helper method that holds the common functionality.

EDIT: See Jörg W Mittag's answer for a more comprehensive set of options.

Ruby - Using class_eval to define methods

This was fun!!!

class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s # make sure it's a string
attr_reader attr_name
attr_reader attr_name+"_history"
class_eval %Q"
def #{attr_name}=(value)
if !defined? @#{attr_name}_history
@#{attr_name}_history = [@#{attr_name}]
end
@#{attr_name} = value
@#{attr_name}_history << value
end
"
end
end

class Foo
attr_accessor_with_history :bar
end

class Foo2
attr_accessor_with_history :bar
def initialize()
@bar = 'init'
end
end

f = Foo.new
f.bar = 1
f.bar = nil
f.bar = '2'
f.bar = [1,nil,'2',:three]
f.bar = :three
puts "First bar:", f.bar.inspect, f.bar_history.inspect
puts "Correct?", f.bar_history == [f.class.new.bar, 1, nil, '2', [1,nil,'2',:three], :three] ? "yes" : "no"
old_bar_history = f.bar_history.inspect

f2 = Foo2.new
f2.bar = 'baz'
f2.bar = f2
puts "\nSecond bar:", f2.bar.inspect, f2.bar_history.inspect
puts "Correct?", f2.bar_history == [f2.class.new.bar, 'baz', f2] ? "yes" : "no"

puts "\nIs the old f.bar intact?", f.bar_history.inspect == old_bar_history ? "yes" : "no"

Note that the only reason you need to use strings with class_eval is so that you can refer to the value of attr_name when defining the custom setter. Otherwise one would normally pass a block to class_eval.

When using class_eval in ruby, how to access the constant in the original class?

You must be using ruby 1.8 as your code appears to work as written in 1.9.

In 1.8 the problem seems to be that the constant is bound in the context of where the block is defined (whatever self was when you started to write MyModule::YourModule::HisClass.class_eval). You can delay the constant binding until self has become an instance of MyModule::YourModule::HisClass by using Module.const_get.

MyModule::YourModule::HisClass.class_eval do
def show_his_constant
puts self.class.const_get(:HIS_CONSTANT)
end
end

irb 1.8.7> MyModule::YourModule::HisClass.new.show_his_constant
THIS_IS_A_CONSTANT

How do you temporarily class_eval for a test?

You haven't clarified how you are modifying the class.

The remix library allows you to temporarily include a module and properly uninclude it later.

In general, it's probably safest to just duplicate the class and test the duplicate:

irb(main):001:0> class Foo; end
#=> nil
irb(main):002:0> Foo.dup.class_eval{ @x = 42 }
#=> 42
irb(main):003:0> Foo.class_eval{ @x }
#=> nil


Related Topics



Leave a reply



Submit