DRY инициализация Ruby с аргументом хеша

Я использую аргументы хеша конструкторам вполне немного, особенно при записи DSLs для конфигурации или других битов API, которому будет выставлен конечный пользователь. То, что я заканчиваю тем, что делал, является чем-то как следующее:

class Example

    PROPERTIES = [:name, :age]

    PROPERTIES.each { |p| attr_reader p }

    def initialize(args)
        PROPERTIES.each do |p|
            self.instance_variable_set "@#{p}", args[p] if not args[p].nil?
        end
    end

end

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

53
задан Kyle E. Mitchell 20 April 2010 в 18:23
поделиться

3 ответа

Вам не нужна константа, но я не думаю, что вы можете исключить преобразование символа в строку:

class Example
  attr_reader :name, :age

  def initialize args
    args.each do |k,v|
      instance_variable_set("@#{k}", v) unless v.nil?
    end
  end
end
#=> nil
e1 = Example.new :name => 'foo', :age => 33
#=> #<Example:0x3f9a1c @name="foo", @age=33>
e2 = Example.new :name => 'bar'
#=> #<Example:0x3eb15c @name="bar">
e1.name
#=> "foo"
e1.age
#=> 33
e2.name
#=> "bar"
e2.age
#=> nil

Кстати, вы можете взять взгляните (если вы еще этого не сделали) на класс генератора классов Struct , он несколько похож на то, что вы делаете, но без инициализации хеш-типа (но я думаю, что это будет несложно сделать адекватный класс генератора).

HasProperties

Пытаясь реализовать идею Гурикхана, я пришел к следующему:

module HasProperties
  attr_accessor :props

  def has_properties *args
    @props = args
    instance_eval { attr_reader *args }
  end

  def self.included base
    base.extend self
  end

  def initialize(args)
    args.each {|k,v|
      instance_variable_set "@#{k}", v if self.class.props.member?(k)
    } if args.is_a? Hash
  end
end

class Example
  include HasProperties

  has_properties :foo, :bar

  # you'll have to call super if you want custom constructor
  def initialize args
    super
    puts 'init example'
  end
end

e = Example.new :foo => 'asd', :bar => 23
p e.foo
#=> "asd"
p e.bar
#=> 23

Поскольку я не настолько разбираюсь в метапрограммировании, я сделал вики-страницу сообщества ответов, чтобы любой мог изменить реализацию.

Struct.hash_initialized

В продолжение ответа Марка-Андре, вот общий метод на основе Struct для создания классов с хеш-инициализацией:

class Struct
  def self.hash_initialized *params
    klass = Class.new(self.new(*params))

    klass.class_eval do
      define_method(:initialize) do |h|
        super(*h.values_at(*params))
      end
    end
    klass
  end
end

# create class and give it a list of properties
MyClass = Struct.hash_initialized :name, :age

# initialize an instance with a hash
m = MyClass.new :name => 'asd', :age => 32
p m
#=>#<struct MyClass name="asd", age=32>
81
ответ дан 7 November 2019 в 08:28
поделиться

Класс Struct может помочь вам создать такой класс. Инициализатор принимает аргументы один за другим, а не как хэш, но это легко преобразовать:

class Example < Struct.new(:name, :age)
    def initialize(h)
        super(*h.values_at(:name, :age))
    end
end

Если вы хотите сохранить более общий характер, вы можете вызвать values_at (* self.class.members) вместо.

32
ответ дан 7 November 2019 в 08:28
поделиться

Учитывая, что ваши хэши будут включать ActiveSupport :: CoreExtensions :: Hash :: Slice , есть очень хорошее решение:

class Example

  PROPERTIES = [:name, :age]

  attr_reader *PROPERTIES  #<-- use the star expansion operator here

  def initialize(args)
    args.slice(PROPERTIES).each {|k,v|  #<-- slice comes from ActiveSupport
      instance_variable_set "@#{k}", v
    } if args.is_a? Hash
  end
end

Я бы абстрагировал это до универсального модуля, который вы могли бы включить и который определяет метод has_properties для установки свойств и правильной инициализации (это не проверено, принимайте это как псевдокод):

module HasProperties
  def self.has_properties *args
    class_eval { attr_reader *args }
  end

  def self.included base
    base.extend InstanceMethods
  end

  module InstanceMethods
    def initialize(args)
      args.slice(PROPERTIES).each {|k,v|
        instance_variable_set "@#{k}", v
      } if args.is_a? Hash
    end
  end
end
3
ответ дан 7 November 2019 в 08:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: