Skip to content

Instantly share code, notes, and snippets.

@thorncp
Created October 13, 2011 01:34
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 thorncp/f484239e1b3ff81cfab4 to your computer and use it in GitHub Desktop.
Save thorncp/f484239e1b3ff81cfab4 to your computer and use it in GitHub Desktop.

Multiplex

What is it?

An entry for the Methods taking multiple blocks CodeBrawl.

Oh, you want details

The CodeBrawl description has this to say:

For example, a method might want some code to run if an action succeeds and different code to run if it fails.

Multiplex tackles this problem by having the defining code specify that certain states or outcomes are supported (success and failure in the given example). The calling code can then provide blocks for each of these states, which will be executed when prudent. Let me show you.

Example

class ResourceManager
  extend Multiplex

  def add(name)
    @resources ||= []
    @resources << name
  end

  multiplex :delete, [:success, :failure] do |name|
    if @resources.delete(name)
      success(name)
    else
      failure(name)
    end
  end
end

rm = ResourceManager.new

rm.add :one

rm.delete :one do
  success { |x| puts "deleted #{x}" }
  failure { |x| puts "couldn't delete #{x}" }
end

rm.delete :two do
  success { |x| puts "deleted #{x}" }
  failure { |x| puts "couldn't delete #{x}" }
end

This would output

deleted one
couldn't delete two

Notice how both the defining code and the calling code use the methods success and failure. Multiplex does this by defining each state method to allow the calling code to register a block. It then redefines the methods to execute the blocks. It does for every call to the multiplexed method.

"Isn't that dangerous/hackish/dirty/crazy/wrong?", you might ask. Yep. The focus for this exercise was a clean syntax, and it doesn't get much cleaner than calling genuine methods.

Shortcomings

  • Hackish nature of the implementation
  • The calling code must specify a block for each registered state
  • Performance cost of defining a method for each state, twice, for every call to a multiplexed method
  • If a method exists that has the same name as a state, it will be blown away
class Derp
extend Multiplex
multiplex :doit, [:success, :failure] do |i|
i == 42 ? success : failure
end
end
Derp.new.doit 42 do
success { puts "yay" }
failure { puts "boo" }
end
Derp.new.doit 9001 do
success { puts "yay" }
failure { puts "boo" }
end
module Multiplex
def multiplex(name, states, &block)
# everything happens in the lifespan of each method call
define_method name do |*args, &b|
blocks = {}
# set up a method for every state that's been specified to
# keep track of which block is tied to which state
states.each do |state|
define_singleton_method(state) { |&b| blocks[state] = b }
end
# runs the 'outermost' block, which will register the states
instance_exec(&b)
# redefine the state methods to call registered blocks
states.each do |state|
define_singleton_method(state) { |*args| blocks[state].call(*args) }
end
# finally execute it
instance_exec(*args, &block)
# clean up our methods
states.each do |state|
singleton_class.send :undef_method, state
end
end
end
end
@rogerbraun
Copy link

Surprisingly simple and easy code.

@jeremyevans
Copy link

This isn't very generic as it requires including a module in the class and using special metaprogramming to define the method.

@JEG2
Copy link

JEG2 commented Oct 17, 2011

I think my only real complaint here is not being able to use def. Good solution.

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