Question

Inside the body of a class, I'd like to pass a block to a method called with. For the lifetime of the block, I would like a with_value method to be available.

Otherwise, everything inside the block should behave as if it were outside the block.

Here's an example:

class C
  extend M

  with "some value" do
    do_something_complicated
    do_something_complicated
    do_something_complicated
  end
end

We can almost get this with:

module M
  def with(str, &block)
    Object.new.tap do |wrapper|
      wrapper.define_singleton_method :with_value do  # Here's our with_value
        str                                           # method.
      end
    end.instance_eval &block
  end

  def do_something_complicated                        # Push a value onto an
    (@foo ||= []).push with_value                     # array.
  end
end

but there's a problem: since we're evaluating the block passed to with inside the context of a different object, do_something_complicated isn't available.

What's the right way to pull this off?

Was it helpful?

Solution

This will make with_value available only within the block. However, _with_value will be defined within or outside of the block.

module M
  def _with_value
    ...
  end
  def with(str, &block)
    alias with_value _with_value
    block.call
    undef with_value
  end
  ...
end

I cannot tell from the question whether this is a problem. If it is a problem, you need to further describe what you are trying to do.

OTHER TIPS

Basically, the idea is to use method_missing to forward method calls from the dummy class to the calling class. If you also need to access instance variables, you can copy them from the calling class to your dummy class, and then back again after the block returns.

The Ruby gem docile is a very simple implementation of such a system. I suggest you read the source code in that repository (don't worry, it's a very small codebase) for a good example of how DSL methods like the one in your example work.

Here is a way that is closer to your attempt:

module M
  def with(str, &block)
    dup.tap do |wrapper|
      wrapper.define_singleton_method :with_value do
        ...
      end
    end.instance_eval &block
  end
  ...
end

dup will duplicate the class from where with is called as a class method.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top