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

On Dec 3, 2011, at 3:31 PM, Chuck R. wrote:

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]
    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:44 PM, Chuck R. wrote:

 end

 string << ("  " * (processed.size + 1)) + "yield(#{processed.join(', 

')})\n"

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

end

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 8:49 PM, Intransition wrote:

On Saturday, December 3, 2011 4:44:58 PM UTC-5, Chuck R. 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

On Sun, Dec 4, 2011 at 9:57 AM, Chuck R. [email protected]
wrote:

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:

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 Dec 5, 2011, at 4:01 AM, Pete H. wrote:

later.

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

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 Saturday, December 3, 2011 4:44:58 PM UTC-5, Chuck R. 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.

On Mon, Dec 5, 2011 at 10:00 AM, Chuck R. [email protected]
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

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