Restrictions

Get Version

1.0.2

→ ‘contextr

There are certain cases (i.e. Classes and Methods), that may not be extended with ContextR. These are e.g. some methods in Enumberable, Array and Hash, that are used internally in ContextR. Extending them would simply result in endless stacks.

In custom classes, i.e. classes defined by your code, every method may be extended. There are only 3 exceptions.

First the exceptions. You may never extend

They shall not be redefined, that is why it triggers a warning. ContextR sticks to this convention, that’s why it does not support __id__ and __clone__. It uses it internally, that’s why it does not support __send__.

Accessing self

Then there are other problems, related to the way ContextR is implemented and its archetecture is designed. The additional context-dependent behaviour does not reside in the extended object itself but in the corresponding layer, or to be exact, in an anonymous module, that is constructed for each layer-class-combination. This results in different selfs for base code and layer code. This is in fact not by intention but by accident.

In order to access the original self, the one defined by the object, the message was sent to, there is a workaround. Since we did not want to change the method signature, the receiver is avaible in a block, that is passed to each layer method. yield(:receiver) gives you a pointer to it. Please be aware, that you will be able to access public methods only. Everything else could be easily implemented using instance_eval, although this has some performance implications.

If you would like to see yield(:receiver) in action, have a look at the other examples, e.g. the buttom of the Introduction.

Passing Blocks

As already mentioned, ContextR uses a block to pass parameters to each layered method. This implies, that methods, expecting a block, cannot easily access it.

But this is only partially true. Every method in a layered stack may access it simply by calling yield(:block) or execute it with yield(:block!). Of course, you should test, if a block was given with yield(:block_given?). It is also possible to pass a new block to subsequent calls, but that is not to easy, since it is not possible to pass a block as argument to a block. But again, there is a slightly ugly workaround.

All this may be observed in the following artificial example.

class TheMagi
  include Enumerable

  def casper; "Casper"; end
  def melchior; "Melchior"; end
  def balthasar; "Balthasar"; end

  def name_methods
    %w(casper melchior balthasar)
  end

  def each
    if block_given?
      name_methods.each do |name|
        yield self.send(name)
      end
    else
      raise ArgumentError, "No block given"
    end
  end
end

example do
  the_magi = TheMagi.new
  names = the_magi.inject do |akku, element|
    akku.to_s + " " + element.to_s 
  end

  result_of(names) == "Casper Melchior Balthasar"
end

Okay, now we got a working TheMagi class, providing an Array-like interface. Let’s assume, we need HTML-output under certain circumstances. I know this example is not to useful, but I needed anything to explain some things.

class TheMagi
  in_layer :html_output do
    def each
      if yield(:block_given?)
        yield(:receiver).name_methods.each do |name|
          king = yield(:receiver).send(name)
          yield(:block!, "<strong>#{king}</strong>")
        end
      else
        raise ArgumentError, "No block given"
      end
    end
  end
end

example do
  the_magi = TheMagi.new
  ContextR::with_layer :html_output do
    names = the_magi.inject do |akku, element|
      akku.to_s + " " + element.to_s 
    end

    result_of(names) == "<strong>Casper</strong> " +
                        "<strong>Melchior</strong> " +
                        "<strong>Balthasar</strong>"
  end
end

So this is how you would use a block, that is provided by a user of your method. But you may as well change the block, that will be passed to subsequent layers including the base method.

The each in the :html_output layer could as well be implemented in another way. I will name it :xml_output lacking a better name. I’m sorry, that this looks so ugly. I would be happy, if I would know a nicer way.

class TheMagi
  in_layer :xml_output do
    def each
      old_block = yield(:block)

      new_block = lambda do |element|
        old_block.call("<name>" + element + "</name>")
      end

      yield(:block=, new_block)

      return_value = super

      yield(:block=, old_block)

      return_value
    end
  end
end

In the first step, we store the original block in a local variable. Afterwards, we create the block, i.e. a lambda, that should replace the old block. We may easily access the old one, since we stored it beforehand. This local variable will still be available, when the block is executed.

In the next step, the new_block is registered as parameter for subsequent methods. Then we may call super to pass the control flow to the next layer and the base method. Finally we should restore the old block, since layers, that were execute before, will get control again, after this execution is finished and they might be confused, that the block changed by calling super. This would break the call stack behaviour.

Finally, we need to store the return value and give it back explicitly, otherwise the block would be the result.

If in future, this functionality is more often used, I may think about a way to make this easier. For now, this is the way to go. Sorry.

Oh. Let’s use the code and test its output:

example do
  the_magi = TheMagi.new
  ContextR::with_layer :xml_output do
    names = the_magi.inject do |akku, element|
      akku.to_s + " " + element.to_s 
    end

    result_of(names) == "<name>Casper</name> " +
                        "<name>Melchior</name> " +
                        "<name>Balthasar</name>"
  end
end

28th April 2008
Theme extended from Paul Battley