Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the point of Ruby's method unbinding mechanism?

Method#unbind returns an UnboundMethod reference to the method, which can later be bound to another object using UnboundMethod#bind.

class Foo
  attr_reader :baz

  def initialize(baz)
    @baz = baz
  end
end

class Bar
  def initialize(baz)
    @baz = baz
  end
end

f = Foo.new(:test1)
g = Foo.new(:test2)
h = Bar.new(:test3)
f.method(:baz).unbind.bind(g).call # => :test2
f.method(:baz).unbind.bind(h).call # => TypeError: bind argument must be an instance of Foo

Initially, I thought this is incredibly awesome, because I expected it would work similarly to JavaScript's Function.prototype.call()/Function.prototype.apply(). However, the object to which you want to bind the method must be of the same class.

The only application I can think of is if you unbind a method, lose the original implementation (redefine the method in the original or singleton class) and then rebind and call it.

like image 346
ndnenkov Avatar asked Nov 14 '15 12:11

ndnenkov


2 Answers

I'll summarize the good uses I found so far. Neither uses unbind though.


Firstly, overwriting a method, using the old implementation. Source, thanks to @WandMaker.

Let say you want to do something like this:

class Foo
  alias old_bar bar

  def bar
    old_bar
    some_additional_processing
  end
end

This will work, but will leave behind old_bar, which is not something desirable. Instead, one could do:

class Foo
  old_bar = instance_method(:bar)

  define_method(:bar) do
    old_bar.bind(self).call
    some_additional_processing
  end
end

Secondly, calling another implementation of a method from the hierarchy. Source.

The very practical example given in the post was that often times during debugging, you want to find where a method was defined. Generally speaking you can do that by:

method(:foo).source_location

However, this won't work if the current instance implements a method method, like is the case with ActionDispatch::Request. In that case you can do:

Kernel.instance_method(:method).bind(self).call(:foo).source_location
like image 159
ndnenkov Avatar answered Sep 28 '22 03:09

ndnenkov


Method and UnboundMethod types expect that the bind target must be subclass of the original class where you have referenced the method. However the Method has a #to_proc method implemented and with that, you can get rid off the 'same class type' constraint.

You have to use #send method, as #define_method is private (you cannot call it directly).

class A
  def hoge ; "hoge" ; end
end

class B ; end

hoge = A.new.method(:hoge)

B.send(:define_method, :hoge_in_b, &hoge) #converting to proc

b = B.new
puts b.hoge_in_b
like image 37
karatedog Avatar answered Sep 28 '22 03:09

karatedog