Is it possible to remove a method from a single instance?
class Foo
def a_method
"a method was invoked"
end
end
f1 = Foo.new
puts f1.a_method # => a method was invoked
I can remove a_method from the class an from the already created object with this:
class Foo
remove_method(:a_method)
end
If I invoke a_method from the same object:
puts f1.a_method # => undefined method
If I create another object:
f2 = Foo.new
puts f2.a_method # => undefined method
How can I only remove a method from an specific single object?
Yes, it is possible:
f1.instance_eval('undef :a_method')
Related
In the below code snippet, I monkey patch Foo#bar with a define_method block. After I initialize a new instance of Foo, I overwrite it with a bind call to the parent class bar method, but when I call the method the block defined by the define_method block is called. Why doesn't the bind call change the behavior of the method?
class OriginalFoo
def bar
puts 'in OriginalFoo!'
end
end
class Foo < OriginalFoo
def bar
puts 'in Foo'
end
end
class Foo < OriginalFoo
old_bar = instance_method(:bar)
define_method(:bar) do
puts 'in define_method block'
old_bar.bind(self).call
end
end
foo_instance = Foo.new # => #<Foo:0x00007fe3ff037038>
OriginalFoo.instance_method(:bar).bind(foo_instance) # => #<Method: OriginalFoo#bar>
foo_instance.bar
# >> in define_method block
# >> in Foo
You're misunderstanding how UnboundMethod#bind works. You call Module#instance_method to get an UnboundMethod (i.e. a method without a self):
OriginalFoo.instance_method(:bar)
# #<UnboundMethod: ... >
Then you call UnboundMethod#bind to attach a self to that method, that returns a Method instance:
m = OriginalFoo.instance_method(:bar).bind(foo_instance)
# => #<Method: ...>
But that won't alter the method in foo_instance, all it does is make self your foo_instance when (or if) you said m.call: um.bind(obj) doesn't do anything to obj, it just gives you um as a Method that has obj as its self.
There is a pretty good documentation of the current implementation of refinements in ruby here:
http://ruby-doc.org//core-2.2.0/doc/syntax/refinements_rdoc.html,
but there are some strange corner cases.
First, include module is orthogonal to using module (one include the instance method of module while the other activates the refinement). But there is a trick to include a refinement module itself, see
Better way to turn a ruby class into a module than using refinements?.
def to_module(klass)
Module.new do
#note that we return the refinement module itself here
return refine(klass) {
yield if block_given?
}
end
end
class Base
def foo
"foo"
end
end
class Receiver
include to_module(Base) {
def foo
"refined " + super
end
}
end
Receiver.new.foo #=> "refined foo"
Strangely this refinement module can't be used with using!
m=to_module(Base) {}
m.class #=> Module
using m
#=>TypeError: wrong argument type Class (expected Module)
So using only work on the enclosing module of the refinement modules.
Secondly I wanted to use the above yield trick to be able to pass a Proc to refine (even through it only accepts a block), without resorting to converting the Proc back to source as in
https://www.new-bamboo.co.uk/blog/2014/02/05/refinements-under-the-knife/.
But using yield as in the include example does not work:
def ref_module1(klass)
Module.new do
refine(klass) {
yield
}
end
end
class Receiver1
using ref_module1(Base) {
def foo
"refined " + super
end
}
def bar
Base.new.foo
end
end
Receiver1.new.bar #=> NoMethodError: super: no superclass method `foo'
We see that Receiver1 still use Bar#foo and not the refined method.
Howewer we can use module_eval instead:
def ref_module2(klass,&b)
Module.new do
refine(klass) {
module_eval(&b)
}
end
end
class Receiver2
using ref_module2(Base) {
def foo
"refined " + super
end
}
def bar
Base.new.foo
end
end
Receiver2.new.bar #=> "refined foo"
I don't quite understand why module_eval works here and not the yield method. Inside the refinement block, the 'default_definee' is the refinement module, so module_eval which puts the 'default_definee' to self='the refinement module' should not affect it. And indeed in the 'include' example at the beginning, I get the same result when I use module_eval or a direct yield.
Can anyone explain this behavior?
Context (or binding) is the reason why module_eval works and yield doesn't in your last set of examples. It actually has nothing to do with refinements, as demonstrated below.
Starting with module_eval:
class Foo
def run(&block)
self.class.module_eval(&block)
end
end
foo = Foo.new
foo.run {
def hello
"hello"
end
}
puts foo.hello # => "hello"
puts hello => # '<main>': undefined method 'hello' for main:Object (NameError)
In Foo#run we call module_eval on Foo. This switches the context (self) to be Foo. The result is much like we had simple defined hello inside of class Foo originally.
Now let's take a look at yield:
class Foo
def run
yield
end
end
foo = Foo.new
foo.run {
def hello
"hello"
end
}
puts hello # => "hello"
puts foo.hello # => '<main>': private method 'hello' called for ...
yield simply invokes the block in its original context, which in this example would be <main>. When the block is invoked, the end result is exactly the same as if the method were defined at the top level normally:
class Foo
def run
yield
end
end
foo = Foo.new
def hello
"hello"
end
puts hello # => "hello"
puts foo.hello # => '<main>': private method 'hello' called for ...
You might notice that foo seems to have the hello method in the yield examples. This is a side effect of defining hello as a method at the top level. It turns out that <main> is just an instance of Object, and defining top level methods is really just defining private methods on Object which nearly everything else ends up inheriting. You can see this by opening up irb and running the following:
self # => main
self.class # => Object
def some_method
end
"string".method(:some_method) # => #<Method: String(Object)#some_method>
Now back to your examples.
Here's what happens in the yield example:
def ref_module1(klass)
Module.new do
refine(klass) {
yield
}
end
end
class Receiver1
# like my yield example, this block is going to
# end up being invoked in its original context
using ref_module1(Base) {
def foo
"I'm defined on Receiver1"
end
}
def bar
# calling foo here will simply call the original
# Base#foo method
Base.new.foo
end
end
# as expected, if we call Receiver1#bar
# we get the original Base#foo method
Receiver1.new.bar # => "foo"
# since the block is executed in its original context
# the method gets defined in Receiver1 -- its original context
Receiver1.new.foo # => "I'm defined on Receiver1"
As for module_eval, it works in your examples because it causes the block to be run in the context of the new module, rather than on the Receiver1 class.
I need to disable a class method for some time, then enable it again. How could I do that? I know that I can remove a method:
class Foo
def Foo.bar
puts "bar"
end
end
Foo.bar # => bar
class <<Foo
remove_method :bar
end
Foo.bar # => undefined method `bar' for Foo:Class (NoMethodError)
Now I need Foo.bar again. How could I do that? I tried to save the method in a proc
m = Proc.new { Foo.bar }
and then define it again:
class Foo
define_method(:bar, &m)
end
but I get
NameError: undefined local variable or method 'm' for...
So I flattened the scope
P = Class.new(Foo) do
define_method(:bar, &m)
end
but I get undefined method if I run it
P.bar
Foo.bar
Is it possible to save a method, undefine it, and then define it back?
Instead of keeping a method body in a proc, you should keep methods as methods. Constantly keep it defined under some different name, and switch Foo.bar between alias of it or not.
class Foo
def Foo.temporal_bar
puts "bar"
end
end
Foo.singleton_class.class_eval{alias bar temporal_bar}
Foo.bar # => bar
Foo.singleton_class.class_eval{remove_method bar}
Foo.bar # => Undefined local variable or method `bar' for #<Class:Foo>
Foo.singleton_class.class_eval{alias bar temporal_bar}
Foo.bar # => bar
You can try out instance_eval:
(class << Foo; self; end).instance_eval do
define_method(:bar, &m)
end
Trying to use define_method inside initialize but getting undefined_method define_method. What am I doing wrong?
class C
def initialize(n)
define_method ("#{n}") { puts "some method #{n}" }
end
end
C.new("abc") #=> NoMethodError: undefined method `define_method' for #<C:0x2efae80>
I suspect that you're looking for define_singleton_method:
define_singleton_method(symbol, method) → new_method
define_singleton_method(symbol) { block } → proc
Defines a singleton method in the receiver. The method parameter can be a Proc, a Method or an UnboundMethod object. If a block is specified, it is used as the method body.
If you use define_method on self.class, you'll create the new method as an instance method on the whole class so it will be available as a method on all instances of the class.
You'd use define_singleton_method like this:
class C
def initialize(s)
define_singleton_method(s) { puts "some method #{s}" }
end
end
And then:
a = C.new('a')
b = C.new('b')
a.a # puts 'some method a'
a.b # NoMethodError
b.a # NoMethodError
b.b # puts 'some method b'
If your initialize did:
self.class.send(:define_method,n) { puts "some method #{n}" }
then you'd get:
a.a # puts 'some method a'
a.b # puts 'some method b'
b.a # puts 'some method a'
b.b # puts 'some method b'
and that's probably not what you're looking for. Creating a new instance and having the entire class change as a result is rather odd.
Do as below :
class C
def initialize(n)
self.class.send(:define_method,n) { puts "some method #{n}" }
end
end
ob = C.new("abc")
ob.abc
# >> some method abc
Module#define_method is a private method and also a class method.Your one didn't work,as you tried to call it on the instance of C.You have to call it on C,using #send in your case.
You were almost there. Just point to the class with self.class, don't even need to use :send:
class C
def initialize(n)
self.class.define_method ("#{n}") { puts "some method #{n}" }
end
end
ob = C.new('new_method')
ob2 = C.new('new_method2')
# Here ob and ob2 will have access to new_method and new_method2 methods
You can also use it with :method_missing to teach your class new methods like this:
class Apprentice
def method_missing(new_method)
puts "I don't know this method... let me learn it :)"
self.class.define_method(new_method) do
return "This is a method I already learned from you: #{new_method}"
end
end
end
ap = Apprentice.new
ap.read
=> "I don't know this method... let me learn it :)"
ap.read
=> "This is a method I already learned from you: read"
There is my code using SimpleDelegator
require 'delegate'
class Foo < SimpleDelegator
def meth
p 'new_meth'
end
end
class Bar
def meth
p 'old_meth'
end
def bar_meth
meth
end
end
bar = Bar.new
foo = Foo.new(bar)
foo.meth #=> "new_meth"
foo.bar_meth #=> "old_meth"
Why the last line gives "old_meth"???!!! Thanks!
Delegator saying:
This library provides three different ways to delegate method calls to an object. The easiest to use is SimpleDelegator. Pass an object to the constructor and all methods supported by the object will be delegated. This object can be changed later.
Okay,so now look at the output,which is to the right of the symbol # =>.
require 'delegate'
class Foo < SimpleDelegator
def meth
p 'new_meth'
end
end
class Bar
def meth
p 'old_meth'
end
def bar_meth
self.method(:meth)
end
end
bar = Bar.new # => #<Bar:0x8b31728>
foo = Foo.new(bar)
foo.__getobj__ # => #<Bar:0x8b31728>
foo.bar_meth # => #<Method: Bar#meth>
foo.method(:meth) # => #<Method: Foo#meth>
So when I used the line foo.method(:meth), then output(#<Method: Foo#meth>) confirms that whenever you will call foo.meth,then meth method of the Foo class will be called.But the line foo.bar_meth outputs(#<Method: Bar#meth>) simply saying that inside the method bar_meth,if you call meth method then, Bar#meth will be invoked.
SimpleDelegator saying that:
A concrete implementation of Delegator, this class provides the means to delegate all supported method calls to the object passed into the constructor and even to change the object being delegated to at a later time with #setobj.
Yes,in your case foo object has been set to bar object,by using #__setobj__. The output of the line foo.__getobj__ is showing that.