使用哈希参数进行Ruby初始化

Kyl*_*ell 53 ruby idioms initialization dry

我发现自己对构造函数使用了哈希参数,特别是在编写用于配置的DSL或最终用户将要暴露的其他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
Run Code Online (Sandbox Code Playgroud)

有没有更惯用的方法来实现这一目标?抛弃常数和符号到字符串转换似乎特别令人震惊.

Mla*_*vić 81

你不需要常量,但我认为你不能消除符号到字符串:

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
Run Code Online (Sandbox Code Playgroud)

顺便说一句,你可能会看一下(如果你还没有)在Struct类生成器类中,它有点类似于你正在做的事情,但是没有哈希类型的初始化(但我想要制作足够的发生器并不困难)类).

HasProperties

试图实现hurikhan的想法,这就是我的意思:

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
Run Code Online (Sandbox Code Playgroud)

因为我不熟悉元编程,所以我做了答案社区维基,所以任何人都可以自由地改变实现.

Struct.hash_initialized

扩展Marc-Andre的答案,这是一个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>
Run Code Online (Sandbox Code Playgroud)


Mar*_*une 32

该StructCLAS能帮助你建立这样一个类.初始化器逐个接受参数而不是哈希,但是转换它很容易:

class Example < Struct.new(:name, :age)
    def initialize(h)
        super(*h.values_at(:name, :age))
    end
end
Run Code Online (Sandbox Code Playgroud)

如果你想保持更通用,你可以打电话values_at(*self.class.members).


Gra*_*ton 10

Ruby中有一些有用的东西可以用来做这种事情.OpenStruct类将传递给它的initialize方法的值作为类的属性.

require 'ostruct'

class InheritanceExample < OpenStruct
end

example1 = InheritanceExample.new(:some => 'thing', :foo => 'bar')

puts example1.some  # => thing
puts example1.foo   # => bar
Run Code Online (Sandbox Code Playgroud)

文档在这里:http: //www.ruby-doc.org/stdlib-1.9.3/libdoc/ostruct/rdoc/OpenStruct.html

如果您不想从OpenStruct继承(或者不能,因为您已经继承了其他内容),该怎么办?您可以使用Forwardable将所有方法调用委托给OpenStruct实例.

require 'forwardable'
require 'ostruct'

class DelegationExample
  extend Forwardable

  def initialize(options = {})
    @options = OpenStruct.new(options)
    self.class.instance_eval do
      def_delegators :@options, *options.keys
    end
  end
end

example2 = DelegationExample.new(:some => 'thing', :foo => 'bar')

puts example2.some  # => thing
puts example2.foo   # => bar
Run Code Online (Sandbox Code Playgroud)

可转发的文档在这里:http://www.ruby-doc.org/stdlib-1.9.3/libdoc/forwardable/rdoc/Forwardable.html