Skip to content

Instantly share code, notes, and snippets.

@blatyo

blatyo/README.md Secret

Created October 10, 2011 18:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save blatyo/b4b2f9cd174656a4334c to your computer and use it in GitHub Desktop.
Save blatyo/b4b2f9cd174656a4334c to your computer and use it in GitHub Desktop.
Multiple blocks to one method

BlockCapture

BlockCapture is designed with a couple ideas in mind.

  1. It should be able to capture multiple blocks.
  2. You're program should be able to specify the types of blocks it wants to receive and have the ability to call them at the appropriate time.
  3. When a user specifies a block that the library author has not specified, they are notified of their error.
  4. BlockCapture is an interface for the user of the library and the implementor of the library and should have error messaging to reflect those two situations. (block_given? error vs non-block_given? error)
  5. Blocks can be optionally provided, so you should be able to check if a block has been provided.
  6. method_missing and capture are the only methods defined to not limit the names for methods the library author can use for capturing blocks.
  7. BlockCapture has state and behavior, and thus should be encapsulated as an object rather than as a module to be mixed in.
class BlockCapture
class CaptureError < StandardError; end
def capture(&block)
instance_eval(&block)
end
def method_missing(method, *args)
@captures ||= {}
captures = self.class.captures
name = captures.detect{|capture| capture == method || :"#{capture}?" == method}
question = name && method == :"#{name}?"
if name && question # success?
!!@captures[name]
elsif name && block_given? # success{ ... }
@captures[name] = Proc.new
elsif name # success
@captures[name]
elsif block_given? # non_captured_method{ ... }
raise CaptureError, "Unexpected callback #{name}. Valid callbacks are #{captures.inspect}"
else # non_captured_method
raise CaptureError, "This class does not capture #{name}."
end
end
class << self
attr_reader :captures
def capture(name)
@captures ||= []
(@captures << name).uniq!
end
end
end
class LOLConnector
attr_reader :callbacks
def initialize(url)
@callbacks = CallbackCapture.new
callbacks.capture(&Proc.new) if block_given?
end
def connect(explode = false)
raise if explode
callbacks.success.call if callbacks.success?
rescue
callbacks.failure.call if callbacks.failure?
ensure
callbacks.complete.call if callbacks.complete?
end
class CallbackCapture < BlockCapture
capture :success
capture :failure
capture :complete
end
end
connector = LOLConnector.new("github.com") do
success{ puts "success!" }
failure{ puts "failure :("}
complete{ puts "complete o_0"}
end
connector.connect
# success!
# complete o_0
connector.connect(true)
# failure :(
# complete o_0
@JEG2
Copy link

JEG2 commented Oct 17, 2011

This solution is pretty similar to my own idea and it works well. Seems like a little metaprogramming to get rid of the need to manually create the capture object could help though.

@jeremyevans
Copy link

I'm not sure this can be used generically very easily, it looks like you need a non-trivial custom class per method that accepts multiple blocks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment