Aaron Yodaiken
Aaron Yodaiken

Reputation: 19561

arguments into instance methods in ruby

So, I'd like to be able to make a call

x = MyClass.new('good morning', 'good afternoon', 'good evening', 'good night',
            ['hello', 'goodbye'])

that would add methods to the class whose values are the values of the arguments. So now:

p x.methods #> [m_greeting, a_greeting, e_greeting, n_greeting,
                         r_greeting, ...]

And

p x.m_greeting #> "good morning"
p x.r_greeting #> ['hello', 'goodbye']

I realize that this is sort of what instance variables are to do (and that if I wanted them immutable I could make them frozen constants) but, for reasons beyond my control, I need to make methods instead.

Thanks!

BTW: I tried

def initialize(*args)
  i = 0
  %w[m_a, m_b, m_c].each do |a|
    self.class.send(:define_method, a.to_s, Proc.new { args[i] })
    i+=1
  end
end

But that ended up giving every method the value of the last argument.

Upvotes: 0

Views: 345

Answers (3)

Wayne Conrad
Wayne Conrad

Reputation: 108239

You can do what you want, like so:

class Foo

  def initialize(*args)
    methods = %w[m_greeting a_greeting e_greeting n_greeting r_greeting]
    raise ArgumentError unless args.size == methods.size
    args.zip(methods).each do |arg, method|
      self.class.instance_eval do
        define_method method do
          arg
        end
      end
    end
  end

end

foo = Foo.new(1, 2, 3, 4, 5)
p foo.m_greeting  # => 1
p foo.a_greeting  # => 2
p foo.e_greeting  # => 3
p foo.n_greeting  # => 4
p foo.r_greeting  # => 5

But this may not be the droid you're looking for: More than a few positional arguments can make code difficult to read. You might consider using OpenStruct. You'll have to write almost no code, and the constructor calls will be easier to read:

require 'ostruct'

class Foo < OpenStruct
end

foo = Foo.new(:m_greeting=>1,
              :a_greeting=>2,
              :e_greeting=>3,
              :n_greeting=>4,
              :r_greeting=>5)
p foo.m_greeting  # => 1
p foo.a_greeting  # => 2
p foo.e_greeting  # => 3
p foo.n_greeting  # => 4
p foo.r_greeting  # => 5

Don't sweat mutability. If you feel the need to write code to protect yourself from mistakes, consider writing unit tests instead. Then the code can be unfettered with sundry checks and protections.

Upvotes: 2

Jim Schubert
Jim Schubert

Reputation: 20367

Your last loop would send the last argument to redefine the method for each of your m_a, m_b, m_c Try looping over the args and sending to the indexed method.

e.g.

def initialize(*args)
  methods = %w[m_a m_b m_c]
  args.each_with_index {|item,index|
    self.class.send(:define_method, methods[index], lambda { item })
  }
end

each_with_index comes from the Enumerable module: http://ruby-doc.org/core/classes/Enumerable.html#M003137

Upvotes: 1

Jakub Hampl
Jakub Hampl

Reputation: 40573

I guess this solves the problem:

def initialize(*args)
  @args = args
  %w[m_a m_b m_c].each_with_index do |a, i|
    eval "def #{a}; @args[#{i}]; end"
  end
end

Upvotes: 2

Related Questions