Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Created June 3, 2012 16:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshCheek/2864091 to your computer and use it in GitHub Desktop.
Save JoshCheek/2864091 to your computer and use it in GitHub Desktop.
How to make a proc you can bind (removing the need for instance_exec, which can't handle blocks)
class BindableBlock
def initialize(klass, &block)
klass.__send__ :define_method, method_name, &block
@original_block = block
@instance_method = klass.instance_method method_name
klass.__send__ :remove_method, method_name
end
def method_name
@method_name ||= "bindable_block_#{Time.now.to_i}_#{$$}_#{rand 1000000}"
end
def arg_size
instance_method.arity
end
attr_reader :instance_method, :original_block
def bind(target)
Proc.new do |*args, &block|
# match args to arity
if args.size >= arg_size
args = args.take arg_size
else
args[arg_size-1] = nil
end
instance_method.bind(target).call(*args, &block)
end
end
def call(*args, &block)
original_block.call(*args, &block)
end
def to_proc
method(:call).to_proc
end
end
def bindable_block(klass, &block)
BindableBlock.new(klass, block)
end
describe 'bindable_block' do
let(:default_name) { "Carmen" }
let(:klass) { Struct.new :name }
let(:instance) { klass.new default_name }
it 'can be bound to instances of the target' do
block = BindableBlock.new(klass) { self }
block.bind(instance).call.should equal instance
end
it 'can be rebound' do
block = BindableBlock.new(klass) { name }
block.bind(klass.new 'Josh').call.should == 'Josh'
block.bind(klass.new 'Mei').call.should == 'Mei'
end
it 'can also just be invoked where the hell ever' do
a = 1
block = BindableBlock.new(klass) { |b, &c| a + b + c.call }
block.call(2){3}.should == 6
end
it "doesn't care about arity" do
block = BindableBlock.new(klass) { |a| [a] }.bind(instance)
block.call.should == [nil]
block.call(1).should == [1]
block.call(1, 2).should == [1]
end
it 'can take a block' do
block = BindableBlock.new(klass) { |a, &b| [a, (b&&b.call)] }.bind(instance)
block.call(1).should == [1, nil]
block.call(1){2}.should == [1, 2]
end
it 'can be passed to methods and shit' do
doubler = lambda { |&block| block.call + block.call }
doubler.call(&BindableBlock.new(klass) { 12 }).should == 24
# sadly this part doesn't work, I don't think I can pass
# binder = lambda { |&block| block.bind(instance).call }
# binder.call(&BindableBlock.new(klass) { name }).should == default_name
#
# This was my attempted to_proc implementation
# bindable_block = self
# proc = method(:call).to_proc
# proc.define_singleton_method(:bind) { |target| bindable_block.bind target }
# proc
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment