Ruby Structs中的命名参数

如果这是一个显而易见的问题,我对Ruby很新,所以道歉。

我想在实例化Struct时使用命名参数,即能够指定Struct中哪些项获取什么值,并将其余项默认为nil。

例如,我想做:

Movie = Struct.new :title, :length, :rating m = Movie.new :title => 'Some Movie', :rating => 'R' 

这不起作用。

所以我想出了以下内容:

 class MyStruct  'Some Movie', :rating => 'R' 

这似乎工作得很好,但我不确定是否有更好的方法来做到这一点,或者如果我做的事情非常疯狂。 如果有人能够validation/撕开这种方法,我将非常感激。

UPDATE

我最初在1.9.2中运行它并且它工作正常; 但是在其他版本的Ruby中尝试过它(谢谢rvm),它的工作原理不起作用,如下所示:

  • 1.8.7:不工作
  • 1.9.1:工作
  • 1.9.2:工作
  • JRuby(设置为1.9.2运行):不工作

JRuby对我来说是一个问题,因为我想保持它与部署目的兼容。

再来一次更新

在这个不断增加的漫无边际的问题中,我尝试了各种版本的Ruby并发现1.9.x中的Structs将其成员存储为符号,但在1.8.7和JRuby中,它们存储为字符串,因此我将代码更新为如下(接受已经给出的建议):

 class MyStruct  'Some Movie', :rating => 'R' 

现在这似乎适用于我尝试过的所有Ruby风格。

合成现有答案揭示了Ruby 2.0+的一个更简单的选项:

 class KeywordStruct < Struct def initialize(**kwargs) super(*members.map{|k| kwargs[k] }) end end 

用法与现有的Struct相同,其中未给出的任何参数将默认为nil

 Pet = KeywordStruct.new(:animal, :name) Pet.new(animal: "Horse", name: "Bucephalus") # => # Pet.new(name: "Bob") # => # 

如果你想要像Ruby 2.1 +所需的kwargs这样的参数,这是一个非常小的变化:

 class RequiredKeywordStruct < Struct def initialize(**kwargs) super(*members.map{|k| kwargs.fetch(k) }) end end 

此时,覆盖initialize以给出某些kwargs默认值也是可行的:

 Pet = RequiredKeywordStruct.new(:animal, :name) do def initialize(animal: "Cat", **args) super(**args.merge(animal: animal)) end end Pet.new(name: "Bob") # => # 

你知道的越少越好。 无需知道底层数据结构是使用符号还是字符串,甚至是否可以将其作为Hash进行寻址。 只需使用属性设置器:

 class KwStruct < Struct.new(:qwer, :asdf, :zxcv) def initialize *args opts = args.last.is_a?(Hash) ? args.pop : Hash.new super *args opts.each_pair do |k, v| self.send "#{k}=", v end end end 

它需要位置和关键字参数:

 > KwStruct.new "q", :zxcv => "z" => # 

一个允许Ruby关键字参数的解决方案(Ruby> = 2.0)。

 class KeywordStruct < Struct def initialize(**kwargs) super(kwargs.keys) kwargs.each { |k, v| self[k] = v } end end 

用法:

 class Foo < KeywordStruct.new(:bar, :baz, :qux) end foo = Foo.new(bar: 123, baz: true) foo.bar # --> 123 foo.baz # --> true foo.qux # --> nil foo.fake # --> NoMethodError 

这种结构可以作为一个值对象非常有用,特别是如果你喜欢更严格的方法访问器,它实际上会出错而不是返回nil (一个OpenStruct)。

你考虑过OpenStruct吗?

 require 'ostruct' person = OpenStruct.new(:name => "John", :age => 20) p person # # p person.name # "John" p person.adress # nil 

你可以重新排列if s。

 class MyStruct < Struct # Override the initialize to handle hashes of named parameters def initialize *args # I think this is called a guard clause # I suspect the *args is redundant but I'm not certain return super *args unless (args.length == 1 and args.first.instance_of? Hash) args.first.each_pair do |k, v| # I can't remember what having the conditional on the same line is called self[k] = v if members.include? k end end end 

基于@Andrew Grimm的回答,但使用Ruby 2.0的关键字参数:

 class Struct # allow keyword arguments for Structs def initialize(*args, **kwargs) param_hash = kwargs.any? ? kwargs : Hash[ members.zip(args) ] param_hash.each { |k,v| self[k] = v } end end 

请注意,这不允许混合常规和关键字参数 – 您只能使用其中一个。

如果你确实需要混合常规和关键字参数,你总是可以手工构建初始化程序…

 Movie = Struct.new(:title, :length, :rating) do def initialize(title, length: 0, rating: 'PG13') self.title = title self.length = length self.rating = rating end end m = Movie.new('Star Wars', length: 'too long') => # 

这个标题是强制性的第一个参数,仅用于说明。 它还具有以下优点:您可以为每个关键字参数设置默认值(尽管在处理电影时这不太有用!)。

如果您的哈希键是有序的,您可以调用splat运算符来进行救援:

 NavLink = Struct.new(:name, :url, :title) link = { name: 'Stack Overflow', url: 'https://stackoverflow.com', title: 'Sure whatever' } actual_link = NavLink.new(*link.values) # 

对于具有Struct行为的1对1等价物(当没有给出所需的参数时引发)我有时会使用它(Ruby 2+):

 def Struct.keyed(*attribute_names) Struct.new(*attribute_names) do def initialize(**kwargs) attr_values = attribute_names.map{|a| kwargs.fetch(a) } super(*attr_values) end end end 

并从那里

 class SimpleExecutor < Struct.keyed :foo, :bar ... end 

如果你错过了一个参数,这将引发KeyError ,因此对于具有大量参数,数据传输对象等的更严格的构造函数和构造函数来说真的很好。

这并没有完全回答这个问题,但我发现如果你想说出你希望结构化的价值观,那就可以了。 它具有卸载记住属性顺序的需要,同时也不需要subClass Struct。

MyStruct = Struct.new(:height, :width, :length)

hash = {height: 10, width: 111, length: 20}

MyStruct.new(*MyStruct.members.map {|key| hash[key] })

仅限Ruby 2.x(如果需要必需的关键字args,则为2.1)。 仅在MRI中测试过。

 def Struct.new_with_kwargs(lamb) members = lamb.parameters.map(&:last) Struct.new(*members) do define_method(:initialize) do |*args| super(* lamb.(*args)) end end end Foo = Struct.new_with_kwargs( ->(a, b=1, *splat, c:, d: 2, **kwargs) do # must return an array with values in the same order as lambda args [a, b, splat, c, d, kwargs] end ) 

用法:

 > Foo.new(-1, 3, 4, c: 5, other: 'foo') => #"foo"}> 

一个小缺点是你必须确保lambda以正确的顺序返回值; 最大的好处是你拥有ruby 2的关键词args的全部function。