Включение аргументов конструктора в переменные экземпляра

Возможный дубликат:
Создание идиоматического объекта в рубине

Есть много случаев, когда у меня есть метод initialize, который выглядит следующим образом:

class Foo  
    def initialize bar, buz, ...
        @bar, @buz, ... = bar, buz, ...
    end
end

Есть ли способ сделать это с помощью простой команды, например:

class Foo
    attr_constructor :bar, :buz, ...
end

где символы представляют собой имя переменных экземпляра (с духом/ароматом attr_accessor, attr_reader, attr_writer)?


Мне было интересно, есть ли встроенный способ или более элегантный способ сделать что-то вроде этого:

class Class
    def attr_constructor *vars
        define_method("initialize") do |*vals|
            vars.zip(vals){|var, val| instance_variable_set("@#{var}", val)}
        end
    end
end

чтобы я мог использовать его следующим образом:

class Foo
    attr_constructor :foo, :bar, :buz
end

p Foo.new('a', 'b', 'c')      # => #<Foo:0x93f3e4c @foo="a", @bar="b", @buz="c">
p Foo.new('a', 'b', 'c', 'd') # => #<Foo:0x93f3e4d @foo="a", @bar="b", @buz="c">
p Foo.new('a', 'b')           # => #<Foo:0x93f3e4e @foo="a", @bar="b", @buz=nil>

Ответ 1

Будет ли это работать для вас?

class Foo  
    def initialize(hash)
        hash.each { |k,v| instance_variable_set("@#{k}", v) }
    end
end

Ответ 2

Я бы использовал OpenStruct:

require 'ostruct'

class Foo < OpenStruct
end

f = Foo.new(:bar => "baz")
f.bar
#=> "baz"

Изменить: Ах, да, извините, неправильно поняли вас. Как насчет просто:

class Foo   
  def initialize(*args)
    @baz, @buz = args
  end
end

Ответ 3

Интересный вопрос. Небольшое метапрограммирование должно позаботиться об этом.

module Attrs
  def self.included(base)
    base.extend ClassMethods
    base.class_eval do
      class << self
        attr_accessor :attrs
      end
    end
  end

  module ClassMethods
    # Define the attributes that each instance of the class should have
    def has_attrs(*attrs)
      self.attrs = attrs
      attr_accessor *attrs
    end
  end

  def initialize(*args)
    raise ArgumentError, "You passed too many arguments!" if args.size > self.class.attrs.size
    # Loop through each arg, assigning it to the appropriate attribute (based on the order)
    args.each_with_index do |val, i|
      attr = self.class.attrs[i]
      instance_variable_set "@#{attr}", val
    end
  end
end

class Foo
  include Attrs
  has_attrs :bar, :buz
end

f = Foo.new('One', 'Two')
puts f.bar
puts f.buz

Конечно, недостатком этого является негибкость - вам нужно передать аргументы конструктора в определенном порядке. Конечно, как большинство языков программирования. Rails люди могут утверждать, что вы должны делать

f = Foo.new(:bar => 'One', :baz => 'Two')

который позволит вам проходить в attrs в любом порядке, а также удалять большую часть метапрограмм. Но это намного больше, чтобы напечатать.