Method binding in Ruby
Today I've Learned postHow to do method binding (or re-binding) in Ruby
…or how to decorate object so that it can be undecorated:
class Account
attr_reader :state
def initialize(state)
@state = state
end
end
module Debit
def transaction(amount)
@state = @state - amount
end
end
module Credit
def transaction(amount)
@state = @state + amount
end
end
account = Account.new(100)
account.state # => 100
puts account.public_methods(false) # => [:state]
debit = Debit.instance_method(:transaction)
credit = Credit.instance_method(:transaction)
# Lets do debit transactions
transaction = debit.bind(account)
transaction.call(3)
transaction.call(3)
account.state # => 94
puts account.public_methods(false) # => [:state]
puts account.public_methods(false) # => [:state]
# Lets do credit transactions
transaction = credit.bind(account)
transaction.call(7)
transaction.call(7)
puts account.state # => 108
puts account.public_methods(false) # => [:state]
Note! this is still experimental feature in Ruby and too fast.
So when you think about it, we manage to decorate the object with a method, called some logic that changed the state and then we were able to work with the original object (without that method)
Note! This is still quite slow feature in Ruby. I personally wouldn’t use it in a production code. But it’s cool that something like this exist.
Unlike include
, extend
, inheritance this is reversable => you can
work with original object without that logic. With include/extend once
you go that way you cannot come back:
module Foo
def foo
123
end
end
a = Object.new
b = a
a.extend(Foo)
a.foo # => 123
b.foo # => 123
Well yeah you could, like with Refinements but that’s not my point. You should be able to access the original (undecorated) object.
And although you can do similar transformations with decorator objects (e.g. like SimpleDecorator
)
the point is that you have to define class where you pass object in order to do
transformation. You are not directly injecting / removing functions from
object (bind/unbind).
Sources
Related articles
UPDATE:
Ruby 2.7 will support bind_call
https://blog.saeloun.com/2019/10/17/ruby-2-7-adds-unboundmethod-bind_call-method.html
Discussion to this article:
Entire blog website and all the articles can be forked from this Github Repo