Help with recursive method def'n using eval

I would like to solve a problem where a writer of a class can provide an array of fields and those fields are turned into two things.

1. A method called #each_"field_name" that iterates over all of the values in that field.

2. A method called #each_combination that defines a series of nested called to #each_"field_name" and at the inside yields a combination of the parameters.

I have the code to solve #1. That was a pretty straight-forward exercise. See the code below (save it to a file and run it).

module Parameters
  module Base
    def self.create_each(mod, fields)
      fields.each do |field_name|
        code = <<-code
        def each_#{field_name}
          @params['#{field_name}'].each do |value|
            @#{field_name}_current_value = value
            yield(value)
          end
        end
        code

        p code
        mod.class_eval(code)
      end
    end

  end # Base
end # Parameters

if __FILE__ == $0

  class Test
    include Parameters::Base

    def self.fields
      ['dollars', 'activation_time', 'personnel_count']
    end

    Parameters::Base.create_each(self, fields)
    
    def initialize
      @params = {
        'dollars' => [1_000, 5_000, 75_000],
        'activation_time' => ["06:30", "08:00", "10:00"],
        'personnel_count' => [1, 3, 7, 25]
      }
    end
  end
end

I'm stuck on solving #2. I want to nest each call to #each_'field_name' in the order given in the #fields class method. The innermost call should yield each of the block parameters.

Given the details above, I would like to produce a method like this:

def each_combination(&blk)
  each_dollars do |dollars|
    each_activation_time do |activation_time|
      each_personnel_count do |personnel_count|
        yield(dollars, activation_time, personnel_count)
      end
    end
  end
end

Any hints on how I can accomplish #2 here? I tried a few approaches similar to my #create_each metaprogramming, but I can't get the syntax right.

cr

I would like to solve a problem where a writer of a class can provide an array of fields and those fields are turned into two things.

1. A method called #each_"field_name" that iterates over all of the values in that field.

2. A method called #each_combination that defines a series of nested called to #each_"field_name" and at the inside yields a combination of the parameters.

[snip]

Given the details above, I would like to produce a method like this:

def each_combination(&blk)
each_dollars do |dollars|
   each_activation_time do |activation_time|
     each_personnel_count do |personnel_count|
       yield(dollars, activation_time, personnel_count)
     end
   end
end
end

I should have waited a few more minutes. After typing this up, I was hit by inspiration. Here is my solution.

    def self.create_combination(mod, fields)
      processed = []
      string = "def each_combination(&blk)\n"
      
      fields.each_with_index do |field_name, index|
        string << (" " * (index + 1)) + "each_#{field_name} do |#{field_name}|\n"
        processed << field_name
      end
      
      string << (" " * (processed.size + 1)) + "yield(#{processed.join(', ')})\n"
      processed.size.downto(1) { |i| string << (" " * i) + "end\n" }
      puts string
    end

It's a bit messy (I do extra work so that the method is formatted correctly for the call to #puts), but it produces exactly what I need. I hope this is helpful to someone else too.

cr

···

On Dec 3, 2011, at 3:31 PM, Chuck Remes wrote:

And once again I should have waited a few minutes or at least long enough to run my test. Here's a bug fix for the missing "end" to close the method definition (it's the call to #downto).

    def self.create_combination(mod, fields)
      processed = []
      string = "def each_combination(&blk)\n"

      fields.each_with_index do |field_name, index|
        string << (" " * (index + 1)) + "each_#{field_name} do |#{field_name}|\n"
        processed << field_name
      end

      string << (" " * (processed.size + 1)) + "yield(#{processed.join(', ')})\n"
      processed.size.downto(0) { |i| string << (" " * i) + "end\n" }
      puts string

      mod.class_eval(string)
    end

···

On Dec 3, 2011, at 3:44 PM, Chuck Remes wrote:

I should have waited a few more minutes. After typing this up, I was hit by inspiration. Here is my solution.

   def self.create_combination(mod, fields)
     processed = []
     string = "def each_combination(&blk)\n"

     fields.each_with_index do |field_name, index|
       string << (" " * (index + 1)) + "each_#{field_name} do |#{field_name}|\n"
       processed << field_name
     end

     string << (" " * (processed.size + 1)) + "yield(#{processed.join(', ')})\n"
     processed.size.downto(1) { |i| string << (" " * i) + "end\n" }
     puts string
   end

I find writing an issue up often helps me figure out a problem, so it's not
uncommon to post for some help and then figure it out a few minutes later.

Maybe a good lesson in this -- write up the post, but give it a few minutes
before actually sending it.

···

On Saturday, December 3, 2011 4:44:58 PM UTC-5, Chuck Remes wrote:

I should have waited a few more minutes. After typing this up, I was hit
by inspiration. Here is my solution.

Quite true. Perhaps my "thinking out loud" in public has been helpful for someone else too.

I guess the technical term for this is "rubber ducking."

cr

···

On Dec 3, 2011, at 8:49 PM, Intransition wrote:

On Saturday, December 3, 2011 4:44:58 PM UTC-5, Chuck Remes wrote:
I should have waited a few more minutes. After typing this up, I was hit by inspiration. Here is my solution.

I find writing an issue up often helps me figure out a problem, so it's not uncommon to post for some help and then figure it out a few minutes later.

Maybe a good lesson in this -- write up the post, but give it a few minutes before actually sending it.

If it's not important to output the constructed string, you could do it
like this:

module Parameters
  module Base

    def self.create_each(mod, fields)
      fields.each do |field|
        mod.send(:define_method, "each_#{field}") do |&block|
          @params[field].each do |value|
            block.call(value)
          end
        end
      end
    end

    def self.create_combination(mod, fields)
      mod.send(:define_method, :each_combination) do |&block|
        head, *tail = @params.values_at(*fields)
        head.product(*tail, &block)
      end
    end

end # Base
end # Parameters

You could use more tricks to avoid making the caller use create_combination
and create_each, but I'm not sure I know what your aim is.

pete

···

On Sun, Dec 4, 2011 at 9:57 AM, Chuck Remes <cremes.devlist@mac.com> wrote:

On Dec 3, 2011, at 8:49 PM, Intransition wrote:

> On Saturday, December 3, 2011 4:44:58 PM UTC-5, Chuck Remes wrote:
> I should have waited a few more minutes. After typing this up, I was hit
by inspiration. Here is my solution.
>
> I find writing an issue up often helps me figure out a problem, so it's
not uncommon to post for some help and then figure it out a few minutes
later.
>
> Maybe a good lesson in this -- write up the post, but give it a few
minutes before actually sending it.

Quite true. Perhaps my "thinking out loud" in public has been helpful for
someone else too.

I guess the technical term for this is "rubber ducking."

cr

Pete, thanks for figuring out another approach. I prefer building a string and eval'ing it because it's easier (IMHO) to verify that the generated code is correct by examining the string. Using #define_method works quite well too, but when something is wrong with the method definition it's a bit tougher to track down and fix.

cr

···

On Dec 5, 2011, at 4:01 AM, Pete Higgins wrote:

On Sun, Dec 4, 2011 at 9:57 AM, Chuck Remes <cremes.devlist@mac.com> wrote:

On Dec 3, 2011, at 8:49 PM, Intransition wrote:

On Saturday, December 3, 2011 4:44:58 PM UTC-5, Chuck Remes wrote:
I should have waited a few more minutes. After typing this up, I was hit

by inspiration. Here is my solution.

I find writing an issue up often helps me figure out a problem, so it's

not uncommon to post for some help and then figure it out a few minutes
later.

Maybe a good lesson in this -- write up the post, but give it a few

minutes before actually sending it.

Quite true. Perhaps my "thinking out loud" in public has been helpful for
someone else too.

I guess the technical term for this is "rubber ducking."

cr

If it's not important to output the constructed string, you could do it
like this:
[snip alternative approach]

I respectfully disagree, but you should write your code how you like your
code written. In either case, some test cases would go a long way to making
sure you're getting the right behavior.

pete

···

On Mon, Dec 5, 2011 at 10:00 AM, Chuck Remes <cremes.devlist@mac.com> wrote:

Pete, thanks for figuring out another approach. I prefer building a string
and eval'ing it because it's easier (IMHO) to verify that the generated
code is correct by examining the string. Using #define_method works quite
well too, but when something is wrong with the method definition it's a bit
tougher to track down and fix.

cr