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
Using Nokogiri HTML Builder to Create Fragment with Multiple Root Nodes
Sudo Gem Install Pg Won't Work
In Ruby, Should I Use ||= or If Defined? for Memoization
Deleting While Iterating in Ruby
Certificate Verify Failed in "Gem Install Foundation"
How to Get the Current Route in Rails
How to Run Phantomjs on Heroku
Named Parameters in Ruby Structs
Trouble Resizing the Default Image with Paperclip
How to Use Ruby Metaprogramming to Add Callbacks to a Rails Model
Carrierwave - Resizing Images to Fixed Width
How to Turn a Ruby Method into a Block
Ruby on Rails: Yielding Specific Views in a Specific Places in the Layout