繁体   English   中英

如何定义一个将其初始化参数作为哈希接受的Ruby Struct?

[英]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_missingdefine_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM