[英]How to define a Ruby Struct which accepts its initialization arguments as a hash?
我有一种情况,我想创建一个接受许多参数的类,并且在尽可能少的代码行中具有setter和getter(为了可维护性)。 我认为使用Struct会是一个好主意:
Customer = Struct.new(:id, :username, :first_name, :last_name, :address1, ...etc...)
Customer.new(123, 'joe', 'Joe', ...etc...)
但是,我不想知道属性的确切顺序。 我更喜欢Ruby 2的关键字参数功能:
class Customer
attr_accessor :id, :username, :first_name, ...etc...
def initialize(id:, username:, first_name:, last_name:, address1:, ...etc...)
@id = id
@username = username
@first_name = first_name
...etc...
end
end
Customer.new(id: 123, username: 'joe', first_name: 'Joe', ...etc...)
然而,写出这一切需要更多的代码,而且更加乏味。 有没有办法在像Struct这样的简短手中实现同样的目标?
在ruby 2.5中,您可以执行以下操作:
Customer = Struct.new(
:id,
:username,
:first_name,
keyword_init: true
)
Customer.new(username: "al1ce", first_name: "alice", id: 123)
=> #<struct Customer id=123, username="al1ce", first_name="alice">
引用:
你不能这样做:
def initialize(hash)
hash.each do |key, value|
send("#{key}=", value)
end
end
更新:
要指定默认值,您可以:
def initialize(hash)
default_values = {
first_name: ''
}
default_values.merge(hash).each do |key, value|
send("#{key}=", value)
end
end
如果要指定给定属性是必需的,但没有默认值,则可以执行以下操作:
def initialize(hash)
requried_keys = [:id, :username]
default_values = {
first_name: ''
}
raise 'Required param missing' unless (required_keys - hash.keys).empty?
default_values.merge(hash).each do |key, value|
send("#{key}=", value)
end
end
如果您不关心性能,可以使用OpenStruct
。
require 'ostruct'
user = OpenStruct.new(id: 1, username: 'joe', first_name: 'Joe', ...)
user.first_name
=> "Joe"
有关详细信息,请参阅此
完全有可能使它成为一个类并在其上定义方法:
class Customer < Openstruct
def hello
"world"
end
end
joe = Customer.new(id: 1, username: 'joe', first_name: 'Joe', ...)
joe.hello
=> "world"
但同样,因为OpenStructs是使用method_missing
和define_method
,所以它们非常慢。 我会选择BroiSatse的答案。 如果你关心所需的参数,你应该这样做
def initialize(params = {})
if missing_required_param?(params)
raise ArgumentError.new("Missing required parameter")
end
params.each do |k,v|
send("#{k}=", v)
end
end
def missing_required_params?(params)
...
end
这是您可以使用的一种方法。
class A
def initialize(h)
h.each do |k, v|
instance_variable_set("@#{k}", v)
create_method("#{k}=") { |v|instance_variable_set("@#{k}", v ) }
create_method("#{k}") { instance_variable_get("@#{k}") }
end
end
end
def create_method(name, &block)
self.class.send(:define_method, name, &block)
end
a = A.new(apple: 1, orange: 2)
a.apple #=> 1
a.apple = 3
a.apple #=> 3
a.orange #=> 2
create_method
直接来自Module#define_method的文档。
您可以使用Struct并通过添加工厂方法(此处称为build)以及必要时对struct的validate方法将代码量减少到最少
Struct.new("Example",:a,:b) do
def build(a:, b:nil)
s = Struct::Example.new(a,b)
s.validate
return s
end
def validate
unless a == 'stuff' || a == 'nonsense'
raise ValidationError, "broken"
end
end
end
m = Struct.Example.build(a: 'stuff')
其中validate的目的是使得检查字符串具有某些值,而不是仅仅依赖于所需的参数检查。
现在,您只需要记住一次编写构建方法时的顺序
主题的变化,但更精致,适用于任何红宝石
class Struct
module HashConstructable
def from_hash hash
rv = new
hash.each do |k, v|
rv.send("#{k}=", v)
end
rv
end
# alias_method :[], :from_hash
end
end
接着
class MyStruct < Struct.new(:foo, :boo)
extend Struct::HashConstructable
end
并且你以这种方式拥有两全其美 - 没有有趣的名字冲突和副作用,很明显你想做的时候想要做的事情:
MyStruct.from_hash(foo: 'foo')
完全按照你的想法行事。 有了更多可能的副作用但更好的语法你可以添加alias_method :[], :from_hash
部分,这允许你:
MyStruct[foo: 'foo']
这也很好,因为它提醒(我) Hash[]
构造函数,它从非哈希的东西中创建一个哈希。
此外,您可以依赖Struct
本身的构造函数。
def initialize(*args)
super(*args)
# put your magic here!
end
这样可以避免命名参数等的副作用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.