Monkey Patching VS Class_Eval

monkey patching vs class_eval?

With class_eval you can do more dynamic things:

>> met = "hello" #=> "hello"
>> String.class_eval "def #{met} ; 'hello' ; end" #=> nil
>> "foo".hello #=> "hello"

How to monkey patch a ruby class inside a method

Instead of using class Clazz; blabla; end to reopen Clazz and monkey patch it, you can use Module#class_eval, Module#instance_eval and some other meta-programming utilities/methods to do the same trick. And because that block accepted by these methods doesn't create new binding scopes, it is more convenient in meta-programming practice.

def my_method
puts ">> creating orig_instance"
orig_instance = A.new

puts ">> dump orig_instance"
orig_instance.do_something

new_do_something = lambda do
puts "Modified A#do_something"
end

# monkey patch class A so that the modified version of do_something get called
# during initialization of new_instance
A.class_eval do
alias_method :old_do_something, :do_something
define_method :do_something, new_do_something
end

puts ">> creating new_instance"
new_instance = A.new

puts ">> dump before do_something gets restored"
new_instance.do_something
orig_instance.do_something

# add singleton method for the special instance
# so that the instance always calls the modified do_something
new_instance_singleton = class << new_instance; self end
new_instance_singleton.send :define_method, :do_something, new_do_something

# restore the modified do_something
# so that orig_instance and all other instances (except new_instance) have the original definition
A.class_eval do
alias_method :do_something, :old_do_something
end
puts ">> dump for final result"
new_instance.do_something
orig_instance.do_something
end

And the following is the output of my_method call:

>> creating orig_instance
Original A#do_something
>> dump orig_instance
Original A#do_something
>> creating new_instance
Modified A#do_something
>> dump before do_something gets restored
Modified A#do_something
Modified A#do_something
>> dump for final result
Modified A#do_something
Original A#do_something

Open class inside module

The error means that the class MyModule::MyClass does not exist. It's likely it has not been loaded yet.

Before monkey patching it, make sure to explicitly require the library.

require 'my_module/my_class'

(make sure to adjust the path) then you can monkey-patch it.

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.

Why might you call instance_eval (as opposed to class_eval) inside 'initialize'?

First of all, you can't do something like this:

class Observer
def initialize(&block)
class_eval(&block) if block_given?
end
end

Because class_eval isn't defined for an instance of Observer. It is defined in Module (which Class descends from). We'll come back to class_eval later.

The reason to use the above idiom is often to allow block initialization:

x = Observer.new do
add_event(foo)
some_other_instance_method_on_observer
self.some_attribute = something
end

Plus, you can add methods to a given instance of the class:

foo = Observer.new do
def foo
'foo'
end
end

foo.foo # => "foo"

You can accomplish roughly the same thing without instance_eval:

class Foo
def initialize
yield self if block_given?
end
end

foo = Foo.new do |x|
x.add_event(foo)
x.some_other_instance_method_on_observer
x.self.some_attribute = something
end

But that doesn't give you the ability to add methods. If you were to do this:

foo = Foo.new do
def foo
'foo'
end
end

foo.foo # => "foo"

It seems to work, right? But what you've actually done is to add the foo method to everything, because self is set to the "main" object. It's equivalent to simply defining the method outside of the block. They get added as instance methods to Object, so they work on everything.

Now, as promised, a brief return to class_eval. You could do something like this:

class Observer
def initialize(&block)
class.class_eval(&block) if block_given?
end
end

But then you open up the entire class:

x = Observer.new { def foo; 'foo'; end }
x.foo # => "foo"
y = Observer.new
y.foo # => "foo"

This isn't typically what we want to do. Plus, self will be the class, not the instance. This makes it useless for the block initialization as demonstrated above.

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.

What is the difference between class_eval, class_exec, module_eval and module_exec?

I'm going to answer a bit more than your question by including instance_{eval|exec} in your question.

All variations of {instance|module|class}_{eval|exec} change the current context, i.e. the value for self:

class Array
p self # prints "Array"
43.instance_eval{ p self } # prints "43"
end

Now for the differences. The eval versions accepts a string or a block, while the exec versions only accept a block but allow you to pass parameters to it:

def example(&block)
42.instance_exec("Hello", &block)
end
example{|mess| p mess, self } # Prints "Hello" then "42"

The eval version does not allow to pass parameters. It provides self as the first parameter, although I can't think of a use for this.

Finally, module_{eval|exec} is the same as the corresponding class_{eval|exec}, but they are slightly different from instance_{eval|exec} as they change what is the current opened class (i.e. what will be affected by def) in different ways:

String.instance_eval{ def foo; end }
Integer.class_eval { def bar; end }

String.method_defined?(:foo) # => false
String.singleton_methods.include?(:foo) # => true
Integer.method_defined?(:bar) # => true

So obj.instance_{eval|exec} opens the singleton class of obj, while mod.{class|module}_{eval|exec} opens mod itself.

Of course, instance_{eval|exec} are available on any Ruby object (including modules), while {class|module}_* are only available on Module (and thus Classes)

Define instance method of a class after class already defined in ruby

If I understand you correctly, you need to be able to access the commentable variable inside your Thread extension, right?

If so, just change this:

Thread.class_eval do

To this:

Thread.class_exec(commentable) do |commentable|

And it should work.



Related Topics



Leave a reply



Submit