Revisiting rebinding self on lambdas

In my previous post on the topic I determined the best method would be to pass the current self into the lambda:

fun = -> context {context.thing}
fun.call(self)

But in a recent project I revisited the problem and discovered you can do this:

class Deal
  def emit_a_funky(funk)
    #returns a lambda which takes a name argument and calls the lambda
    #passed in with the name argument and self rebound to be an instance of Deal.
    -> name do
      self.instance_exec(name, &funk)
    end
  end

  def doathing(name)
    p "hey #{name} I'm doin a thing"
  end
end

a = -> name do
  doathing(name)
end

b = Deal.new
c = b.emit_a_funky(a)
c.call 'max'
# prints "hey max I'm doin a thing"
c.call 'Hulk Hogan'
# prints "hey Hulk Hogan I'm doin a thing"

The actual use case I had for it was writing an RSpec helper that takes a list of user authentication levels and a block and spits out a seperate example for each user level. The examples evaluate the block in the context of the RSpec example and make an assertion on the block’s return value. It came in handy for DRYing up my specs:

def assert_granted(*args, &blk)
  if args.last.instance_of?(Hash)
    options = args.pop
    options = Hash[options.collect {|option,value| [option.to_sym, value]}]
  else
    options = {:granted => true}
  end

  if args.length == 0
    args = [:user, :fulfillment_admin, :engine_admin, :admin]
  end

  message = options[:granted] ? 'is' : 'is not'
  args.each do |name|
    it "#{message} granted for #{name}" do
      auth_level = User.auth_levels.invert[name.to_sym]
      user = stub_grant_current_user(auth_level)
      assert_grant_error(!options[:granted]) do
        #rebind self for blk
        self.instance_exec(user, &blk)
      end
    end
  end
end

def assert_grant_error(raises = true, &blk)
  assertion = raises ? 'should' : 'should_not'
  blk.send(assertion, raise_error(Grant::Error))
end

There are still instances where passing the current self as an argument to a lambda may be useful, but this makes it unnecessary in cases where you don’t need access to both contexts.

posted 1 year ago on February 22nd, 2011 at 13:35 /