DRYruby初始化与散列参数
我发现自己对构造函数使用散列参数相当多,特别是在编写用于configuration的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
有没有更多的地道方式来实现这一目标? 抛出常量和符号到string的转换看起来特别exception。
你不需要常量,但我不认为你可以消除符号到string:
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
顺便说一句,你可能会看看(如果你还没有)在类的生成器类,它有点类似于你在做什么,但没有哈希types的初始化(但我想这将不难发电机类)。
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
由于我不熟悉元编程,所以我创build了答案社区wiki,以便任何人都可以自由地更改实现。
Struct.hash_initialized
扩展Marc-Andre的答案,这是一个通用的基于Struct
的方法来创build哈希初始化的类:
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 pm #=>#<struct MyClass name="asd", age=32>
Struct
类可以帮助你build立这样一个类。 初始化程序逐个使用参数而不是散列,但是很容易将其转换为:
class Example < Struct.new(:name, :age) def initialize(h) super(*h.values_at(:name, :age)) end end
如果你想保持更通用的,你可以调用values_at(*self.class.members)
。
Ruby中有一些有用的事情可以做这种事情。 OpenStruct类将使a的值作为类的属性传递给它的initialize方法。
require 'ostruct' class InheritanceExample < OpenStruct end example1 = InheritanceExample.new(:some => 'thing', :foo => 'bar') puts example1.some # => thing puts example1.foo # => bar
文档在这里: http : //www.ruby-doc.org/stdlib-1.9.3/libdoc/ostruct/rdoc/OpenStruct.html
如果你不想从OpenStructinheritance(或者不能,因为你已经从别的东西inheritance了)? 您可以使用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
可转换的文档在这里: http ://www.ruby-doc.org/stdlib-1.9.3/libdoc/forwardable/rdoc/Forwardable.html
鉴于你的哈希将包括ActiveSupport::CoreExtensions::Hash::Slice
,有一个非常好的解决scheme:
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”方法来设置属性,并做适当的初始化(这是未经testing的,把它作为伪代码):
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
我的解决scheme与Marc-AndréLafortune类似。 不同之处在于每个值都从input哈希中删除,因为它用于分配成员variables。 然后,Struct-derived类可以对可能留在哈希中的任何东西进行进一步的处理。 例如,下面的JobRequest在选项字段中保留了来自哈希的任何“额外”参数。
module Message def init_from_params(params) members.each {|m| self[m] ||= params.delete(m)} end end class JobRequest < Struct.new(:url, :file, :id, :command, :created_at, :options) include Message # Initialize from a Hash of symbols to values. def initialize(params) init_from_params(params) self.created_at ||= Time.now self.options = params end end
请看看我的宝贝,有价值的:
class PhoneNumber < Valuable has_value :description has_value :number end class Person < Valuable has_value :name has_value :favorite_color, :default => 'red' has_value :age, :klass => :integer has_collection :phone_numbers, :klass => PhoneNumber end jackson = Person.new(name: 'Michael Jackson', age: '50', phone_numbers: [{description: 'home', number: '800-867-5309'}, {description: 'cell', number: '123-456-7890'}) > jackson.name => "Michael Jackson" > jackson.age => 50 > jackson.favorite_color => "red" >> jackson.phone_numbers.first => #<PhoneNumber:0x1d5a0 @attributes={:description=>"home", :number=>"800-867-5309"}>
我使用它从search类(EmployeeSearch,TimeEntrySearch)到报告(EmployeesWhoDidNotClockOutReport,ExecutiveSummaryReport)到API端点的演示者。 如果您添加一些ActiveModel位,您可以轻松地将这些类挂钩到收集条件的表单。 希望对你有帮助。