Question

I'm trying to write a DSL to wrap Mongoid's aggregation pipeline (i.e. Mongo DB).

I've made a module that when included, adds a class method that accepts a block, which it hands to an object that passes requests to Mongoid (via method missing).

So I can do:

class Project
  include MongoidAggregationHelper
end

result = Project.pipeline do
  match dept_id: 1
end

#...works!

"match" is a method on Mongoid's aggregation pipeline, which is intercepted and passed on.

BUT instance variables set outside the block are not available, since its being executed in the context of the proxy class.

dept_id = 1

result = Project.pipeline do
  match dept_id: dept_id
end

#...fails, dept_id not found :(

Any way to pass/redefine external instance variables in with the block?

Below is the trimmed code:

module MongoidAggregationHelper
  def self.included base
    base.extend ClassMethods
  end

  module ClassMethods
    def pipeline &block
      p = Pipeline.new self
      p.instance_eval &block
      return p.execute
    end
  end

  class Pipeline
    attr_accessor :cmds, :mongoid_class
    def initialize klass
      self.mongoid_class = klass
    end

    def method_missing name, opts={}
      #...proxy to mongoid...
    end

    def execute
      #...execute pipeline, return the results...
    end
  end
end
Was it helpful?

Solution

You can do the following: (unlimited amount of arguments, kinda have to use a Hash)

# definition
def pipeline(*args, &block)
  # your logic here
end

# usage
dept_id = 1
result = Project.pipeline(dept_id: dept_id) do
  match dept_id: dept_id
end

Or you can use named arguments, if you know how many arguments you need to execute the DSL:

# definition
def pipeline(dept_id, needed_variable, default_variable = false, &block)
  # your logic here
end

# usage
dept_id = 1
result = Project.pipeline(dept_id, other_variable) do
  match dept_id: dept_id
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top