Ruby Class_Eval Method

Ruby class_eval method

There are two accepted ways:

  1. Use define_method:

    @arr.each do |method|
    self.class.class_eval do
    define_method method do |*arguments|
    puts arguments
    end
    end
    end
  2. Use class_eval with a string argument:

    @arr.each do |method|
    self.class.class_eval <<-EVAL
    def #{method}(*arguments)
    puts arguments
    end
    EVAL
    end

The first option converts a closure to a method, the second option evaluates a string (heredoc) and uses regular method binding. The second option has a very slight performance advantage when invoking the methods. The first option is (arguably) a little more readable.

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.

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.

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.

class_eval and open classes

Benjamin, reopen class will not inform you (but class_eval will raise error) if the existing class does not exist or not loaded.

But if you have test coverage, reopen class should be safe I guess?

See https://stackoverflow.com/a/900508/474597 for more detailed explanation.

Why does instance_eval() define a class method when called on a class?

x.instance_eval changes your context so self evaluates to x.

This allows you to do many things, including defining instance variables and instance methods but only for x.

 x = Object.new
y = Object.new

# define instance variables for x and y
x.instance_eval { @var = 1 }
y.instance_eval { @var = 2 }

# define an instance method for all Objects
class Object
def var
@var
end
end

x.var #=> 1
y.var #=> 2

Ruby lets you define instance methods for an object in a couple places. Normally,
one defines them in a class, and those instance methods are shared among all instances
of that class (like def var above).

However, we can also define an instance method for just a single object:

# here's one way to do it
def x.foo
"foo!"
end
# here's another
x.instance_eval do
# remember, in here self is x, so bar is attached to x.
def bar
"bar!"
end
end

Even though x and y have the same class, they don't share these methods, since they were only defined for x.

x.foo #=> "foo!"
x.bar #=> "bar!"
y.foo #=> raises NoMethodError
y.bar #=> raises NoMethodError

Now in ruby, everything's an object, even classes. Class methods are just instance methods
for that class object.

# we have two ways of creating a class:
class A
end
# the former is just syntatic sugar for the latter
B = Class.new

# we have do ways of defining class methods:

# the first two are the same as for any other object
def A.baz
"baz!"
end
A.instance_eval do
def frog
"frog!"
end
end

# the others are in the class context, which is slightly different
class A
def self.marco
"polo!"
end
# since A == self in here, this is the same as the last one.
def A.red_light
"green light!"
end

# unlike instance_eval, class context is special in that methods that
# aren't attached to a specific object are taken as instance methods for instances
# of the class
def example
"I'm an instance of A, not A itself"
end
end
# class_eval opens up the class context in the same way
A.class_eval do
def self.telegram
"not a land shark"
end
end

Note again, that all these methods are A-specific, B doesn't get access to any of them:

A.baz #=> "baz!"
B.telegram #=> raises NoMethodError

The important thing to take away from here is that
class methods are just instance methods of an object of class Class



Related Topics



Leave a reply



Submit