7

I'm trying to dynamically define functions that call through to another function that takes an options parameter:

class MyClass
  ["hour", "minute", "second"].each do |interval|
    define_method "get_#{interval}" do |args|
      some_helper(interval, args)
    end
  end
  def some_helper(interval, options={})
    # Do something, with arguments
  end
end

I'd like to be able to call the different methods on MyClass in these two ways (with and without optional arguments):

mc = MyClass.new
mc.get_minute( :first_option => "foo", :second_option => "bar")
mc.get_minute  # This fails with: warning: multiple values for a block parameter (0 for 1)

On the second call to minute, I see this warning:

warning: multiple values for a block parameter (0 for 1)

  1. Is there a way to write the block for the "get_*" method so that this warning won't come up?
  2. Am I abusing define_method?
Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
readonly
  • 343,444
  • 107
  • 203
  • 205

3 Answers3

16

The only change you need to make is to change args to *args. The * indicates that args will contain an array of optional arguments to the block.

Gordon Wilson
  • 26,244
  • 11
  • 57
  • 60
5

Two years later... I don't know if is is a new feature with ruby 1.9.2, or if it was also possible in the past, but this works:

class MyClass
    ["hour", "minute", "second"].each do |interval|
        define_method "get_#{interval}" do |args = {:first_option => "default foo", :second_option => "default  bar"}|
           some_helper(interval, args)
        end
    end
    def some_helper(interval, options={})
        # Do something, with arguments
        p options
    end
end

mc = MyClass.new
mc.get_minute( :first_option => "foo", :second_option => "bar")
mc.get_minute  

Result is:

{:first_option=>"foo", :second_option=>"bar"}
{:first_option=>"default foo", :second_option=>"default  bar"}
knut
  • 27,320
  • 6
  • 84
  • 112
0

I agree with Gordon adding * to your args will make it go away.

Another way of doing this is to use method_missing()

Something like this:

class MyClass

  def method_missing(m, *args)  
    if /get_(.+)/.match(m.to_s)
      some_helper($1, args) 
    else
      raise 'Method not found...'
    end
  end  

  def some_helper(interval, *args)
    puts interval + ' -> ' + args.inspect
  end

end

m = MyClass.new
m.get_minute( :first_option => "foo", :second_option => "bar" )
stjernstrom
  • 99
  • 3
  • 7
  • 1
    I'd rather call 'super' in method missing instead of 'raise' so it will perform a default Ruby action when method is really missing. – Priit Jan 18 '09 at 17:06