Module: Sqrbl::MethodMissingDelegation

Included in:
Group, Step, StepPair
Defined in:
lib/sqrbl/mixins/method_missing_delegation.rb

Overview

When used as directed, Sqrbl will create a graph of objects with bidirectional references to one another. The graph will have the following general shape (where –> indicates a relationship that is generally one-to-many):

Conversion –> Group –> StepPair –> Step

Also, because these various objects are created with blocks that are instance_eval’d, those blocks can define helper methods that will be added to the appropriate objects in the graph.

Taken together, these two features (back-references and instance_eval) let us provide a useful facility for defining helper methods inside a block, and accessing them using scoping rules that work in an intuitive manner. For example, if a Conversion defines a method #foo, which is later called from inside a Step’s block, the Step can catch that call using method_missing and delegate it to its StepPair, which in turn will delegate to its Group, which in turn will delegate to its Conversion.

Confused yet? Here’s a slightly modified version of the example in README.txt:

Sqrbl.conversion "Convert from old widgets to new widgets" do
  def new_widget_insert()
    insert_into("new_widgets", {
      :name     => 'widget_name',
      :part_num => 'CONCAT("X_", part_number)',
      :note     => '"Imported from old_widgets"',
    })
  end

  group "Widgets" do
    step "Create widgets" do
      up do
        action "Migrate old_widgets" {
          "#{new_widget_insert()} FROM old_widgets"
        }
      end

      down do
        action "Drop imported organizational contacts" {
          'DELETE FROM new_widgets WHERE note LIKE "Imported from old_widgets"'
        }
      end
    end
  end
end

Note that the call to new_widget_insert occurs several layers of nesting down from its definition. By using this method_missing delegation strategy, we can effectively hide Sqrbl’s object model from the user and provide something that “just works.”

(Without this strategy, the above example would need to define new_widget_insert as a lambda function that gets invoked using either #call or the square-bracket syntax, but I find that more awkward – hence this wee bit of metaprogramming.)

Class Method Summary collapse

Class Method Details

.included(receiver) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/sqrbl/mixins/method_missing_delegation.rb', line 62

def self.included(receiver)
  receiver.class_eval(<<-EOF, __FILE__, __LINE__)
    @@mm_delegate_accessor = nil

    # Defines the accessor method that instances of this class should use
    # when delegating +method_missing+ calls.
    def self.delegate_method_missing_to(accessor)
      @@mm_delegate_accessor = accessor
    end

    # If +delegate_method_missing_to+ was called on the class,
    # use the accessor defined there to find a delegate and
    # pass the unknown method to it.
    def method_missing(method, *args, &block)
      return super unless defined?(@@mm_delegate_accessor) && !@@mm_delegate_accessor.nil?
      delegate = self.send(@@mm_delegate_accessor)
      delegate.send(method, *args, &block)
    end
  EOF
end