Home > Net >  Is it possible to override value in a block
Is it possible to override value in a block

Time:01-28

New to Ruby so apologies in advance if this is a dumb question. I have a need to supply a value to a block but would like not to update everywhere called.

Let's say I have the following block:

{ base.call_my_test }

It get's passed to a function where I'd like to wrap it in another block but supply that other blocks value to it such as:

def test(&block)
    MyTest.with_base_object do |base|
        #neither of these work
        block.instance_variable_set(:@base, base) # Doesn't work
        block.define_singleton_method(:hi) do
            return base
        end

        block.call <---- Is it possible to define base here? 
    end
end

In the above example, is it possible to define base on the block without passing as an argument? I tried instance_variable_set as well as define_singleton_method but I can't seem to get anything to be defined as it always says base is undefined.

Not sure if this is even possible but figured I'd ask.

CodePudding user response:

First, blocks in Ruby are non-rigid Proc objects. That means that you can pass extra arguments, and anyone who doesn't expect arguments will silently ignore them.

def test(&block)
  base = Object.new
  block.call(base)
end

test { |x| puts "Works with an object #{x}." }
test { puts "Works without an object as well!" }

So if you control the block where you need base to be defined, then you can simply add the argument. Any other uses of the test function will simply ignore the extra argument, even if they're not declared to take one.

But this is assuming you control any of the blocks at all. It sounds like you have some blocks somewhere out there that you can't change, and you automagically want to inject the name base into their scope. You can change the class instance that a block is being invoked for by passing the block to instance_eval.

Buyer beware: This will completely change self. If the block in question tries to view self or call any other methods on the object (it thinks) it's enclosed in, it will fail.

class HasBase
  attr_reader :base

  def initialize(base)
    @base = base
  end

end

def test(&block)
  base = Object.new
  HasBase.new(base).instance_eval(&block)
end

test { puts "I can see my #{base} from here!" }

Depending on your specific needs, you can start augmenting this to work with whatever your callers need. For instance, you can write method_missing to fall back to the proc's original binding. But that's getting into some deep magic, and you probably don't want to play with that footgun in production for very long.

  •  Tags:  
  • ruby
  • Related