Motivation
If you’re tired of creating APIs like this:
MyApp.configure do |c|
c.description "My app's Description"
end
and you long to create APIs like this:
MyApp.configure do
description "My app's Description"
end
Then look no further than this gem.
Installation
# gem install binder
Use
In truth, there’s no magic to this. If you want to change the context in which a block gets evaluated, you can use a combination of instance_eval and the fact that you can pass a proc to a method as if it were a block by prefixing it with an “&”:
class Dog
def do_tricks(&block)
instance_eval(&block)
end
def speak
puts "ruff!"
end
def fetch
puts "fetching...."
end
end
Dog.new.do_tricks do
speak
fetch
end
If you’re creating a large API like the Rails 3 router, you may find yourself writing a whole lot of mehods that run the block passed to it in some other context.
To dry up the process of creating these methods, you can use the binder gem:
require 'binder'
class Dog
extend Binder
bind :do_tricks, :self
def speak
puts "ruff!"
end
def fetch
puts "fetching...."
end
end
Dog.new.do_tricks do
speak
fetch
end
First, we passed to bind the name of the method that we wanted to pass our block to; the second argument represents the context in which we want our block evaluated. :self, in this case, represents the instance of the Dog that we’re calling “do_tricks” on. We could have alternatively passed a symbol representing an instance method or an instance variable.
If you wanted to bind class method instead of instance methods, you simply have to extend the singleton class:
require 'binder'
class Dog
class << self
extend Binder
bind :do_tricks, :self
def speak
puts "ruff!"
end
def fetch
puts "fetching...."
end
end
end
Dog.do_tricks do
speak
fetch
end
If you’d rather not have to extend all of your classes with “Binder” everytime you intend to use the “bind” class method, you can require ‘binder/pervasive’ instead. Note, however, that this will pollute your namespace, so if you’ve happened to define a “bind” method in any of your existing classes, you may run into issues.
require 'binder/pervasive'
class Dog
bind :do_tricks, :self
def speak
puts "ruff!"
end
def fetch
puts "fetching...."
end
end
Dog.new.do_tricks do
speak
fetch
end
Proc#bind_to
In addition to the “bind” method, binder also adds a “bind_to” instance method to the Proc class. It allows you to change the context in which the proc is run:
require 'binder'
def speak
"why should i?"
end
class Dog
def speak
"ruff!"
end
end
command = proc { speak }
command.call
# ==> "why should i?"
command.bind_to(Dog.new).call
# ==> "ruff!"
“Tell”
The “Tell” method is essentially short hand for an instance_eval on an object - it simply presents you with a more human readable way of using this language feature:
require 'binder/tell'
class Dog
def speak
puts "ruff!"
end
end
fido = Dog.new
fido.instance_eval do
speak
end
# ==> would print "ruff!"
Tell fido do
speak
end
# or
Tell(fido) { speak }
# or
Tell(fido, :to) { speak }
# ==> would all print "ruff!" - and these are all equivalent to the instance eval above
commands = proc { speak }
fido.instance_eval(&commands)
# ==> would print "ruff"
Tell fido, commands
# ==> would also print "ruff!"