我是Ruby的新手,如果这是一个显而易见的问题,请道歉。
我想在实例化Struct时使用命名参数,即能够指定Struct中哪些项获取什么值,并将其余项默认为nil。
例如,我想这样做:
Movie = Struct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'
这不起作用。
所以我想出了以下内容:
class MyStruct < Struct
# Override the initialize to handle hashes of named parameters
def initialize *args
if (args.length == 1 and args.first.instance_of? Hash) then
args.first.each_pair do |k, v|
if members.include? k then
self[k] = v
end
end
else
super *args
end
end
end
Movie = MyStruct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'
这似乎工作得很好,但我不确定是否有更好的方法来做到这一点,或者如果我做的事情非常疯狂。如果有人能够验证/破解这种方法,我将非常感激。
更新
我最初在1.9.2中运行它并且工作正常;但是在其他版本的Ruby中尝试过它(谢谢rvm),它的工作原理不起作用,如下所示:
JRuby对我来说是一个问题,因为我希望它与部署目的保持兼容。
再次更新
在这个不断增加的漫无边际的问题中,我尝试了各种版本的Ruby并发现1.9.x中的Structs将其成员存储为符号,但在1.8.7和JRuby中,它们存储为字符串,因此我更新了代码如下(接受已经给出的建议):
class MyStruct < Struct
# Override the initialize to handle hashes of named parameters
def initialize *args
return super unless (args.length == 1 and args.first.instance_of? Hash)
args.first.each_pair do |k, v|
self[k] = v if members.map {|x| x.intern}.include? k
end
end
end
Movie = MyStruct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'
现在这似乎适用于我尝试过的所有Ruby风格。
答案 0 :(得分:19)
综合现有答案揭示了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") # => #<struct Pet animal="Horse", name="Bucephalus">
Pet.new(name: "Bob") # => #<struct Pet animal=nil, 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") # => #<struct Pet animal="Cat", name="Bob">
答案 1 :(得分:12)
你知道的越多越好。无需知道底层数据结构是使用符号还是字符串,甚至是否可以将其作为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"
=> #<struct KwStruct qwer="q", asdf=nil, zxcv="z">
答案 2 :(得分:9)
仅允许Ruby关键字参数(Ruby&gt; = 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)。
答案 3 :(得分:5)
你考虑过OpenStruct吗?
require 'ostruct'
person = OpenStruct.new(:name => "John", :age => 20)
p person # #<OpenStruct name="John", age=20>
p person.name # "John"
p person.adress # nil
答案 4 :(得分:4)
您可以重新排列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
答案 5 :(得分:2)
基于@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
请注意,这不允许混合常规和关键字参数 - 您只能使用其中一个。
答案 6 :(得分:1)
如果你确实需要混合常规和关键字参数,你总是可以手工构建初始化程序......
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')
=> #<struct Movie title="Star Wars", length="too long", rating="PG13">
这个标题是强制性的第一个参数,仅用于说明。它还具有以下优点:您可以为每个关键字参数设置默认值(尽管在处理电影时这不太有用!)。
答案 7 :(得分:1)
对于具有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
,对于更严格的构造函数和带有大量参数,数据传输对象等的构造函数来说真的很好。
答案 8 :(得分:1)
如果您的哈希键按顺序排列,您可以调用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 NavLink name="Stack Overflow", url="https://stackoverflow.com", title="Sure whatever">
答案 9 :(得分:0)
这并没有完全回答这个问题,但我发现如果你想说出你希望结构化的价值观,那就可以了。它的好处是可以减少记住属性顺序的需要,同时也不需要subClass Struct。
MyStruct = Struct.new(:height, :width, :length)
hash = {height: 10, width: 111, length: 20}
MyStruct.new(*MyStruct.members.map {|key| hash[key] })
答案 10 :(得分:0)
仅限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')
=> #<struct Foo a=-1, b=3, splat=[4], c=5, d=2, kwargs={:other=>"foo"}>
一个小缺点是你必须确保lambda以正确的顺序返回值;最重要的是你拥有ruby 2的关键词args的全部功能。
答案 11 :(得分:0)
使用较新版本的Ruby,您可以使用keyword_init: true
:
Movie = Struct.new(:title, :length, :rating, keyword_init: true)
Movie.new(title: 'Title', length: '120m', rating: 'R')
# => #<struct Movie title="Title", length="120m", rating="R">