Ruby:如何进行类/结构输入验证

Ruby: How to do class/struct input validation

提问人:PKP 提问时间:1/6/2023 更新时间:1/7/2023 访问量:298

问:

我正在编写一个基于结构的类,但我正在努力寻找一种进行输入验证的好方法。

结构定义了许多成员。有些成员是必需的 (),有些是可选的 ()。创建类的实例时,必须至少提供所有必需的输入,除非您不提供任何输入,在这种情况下,它们将初始化为 nil。如果提供的参数太少或太多,则应引发异常。此外,您应该能够使用哈希来初始化实例,其中所有键都与结构的成员匹配。如果有任何键与成员不匹配,则应引发异常。mo

请考虑以下代码:

MyClass = Struct.new(:m1, :m2, :o1) do
    # Write class content...
end

# Should be allowed and initialize m1, m2 and o1 to nil - works out of the box
instance1 = MyClass.new

# Should be allowed - works out of the box
instance2 = MyClass.new("m1", "m2") # o1 automatically initialized to nil
instance3 = MyClass.new("m1", "m2", "o1")

# Should be allowed and should map hash values and keys to struct members
instance4 = MyClass.new({m1: "m1", m2: "m2"}) # o1 automatically initialized to nil
instance5 = MyClass.new({m1: "m1", m2: "m2", o1: "o1"})

# Should raise exception saying too many arguments
instance6 = MyClass.new({m1: "m1", m2: "m2", o1: "o1", extra: "extra"})
instance7 = MyClass.new("m1", "m2", "o1", "extra")

# Should raise exception saying "m2" is missing
instance8 = MyClass.new({m1: "m1"})
instance9 = MyClass.new("m1")

# Should raise exception saying "other" key isn't allowed
instance10 = MyClass.new({m1: "m1", other: "other"})

我试图在类中定义常量,以说明哪些输入是必需的和可选的,然后循环访问它们。但这似乎是错误和繁琐的。我还认为常量在我的类之外泄漏,因为如果我在另一个类中使用相同的常量名称,我会收到重新定义警告。

MyClass = Struct.new(:m1, :m2, :o1) do
    MANDATORY_INPUT = [:m1, :m2]
    OPTIONAL_INPUT = [:o1]
    def initialize(args = nil)
        if args != nil
            # loop through arrays above and raise exception if necessary
        end
        # if input is a Hash assign members
    end
end

请注意,Ruby 对我来说是很陌生的,所以我的问题可能会有明显的答案/问题。

Ruby 验证 结构

评论

2赞 Konstantin Strukov 1/6/2023
看看/和朋友也许?dry-schemadry-validation
1赞 spickermann 1/6/2023
我想知道 surcease 是什么样子的,需要能够处理 sich 一个奇怪的组合或论点?为什么初始化后需要同时处理单个值和一个哈希值?为什么需要使用 Struct 而不是自定义类?返回不同数据结构的数据从何而来?
0赞 PKP 1/6/2023
好吧,哈希格式的数据是通过专有的 json 协议来的。然后它被解析为 ruby 哈希。然后我想象使用哈希实例化这个类的实例。如果数据未正确发送,我将挽救异常,并通过相同的协议传递合适的错误响应作为回复。添加“正常”输入参数的原因只是为了让应用程序能够以不那么麻烦的方式创建实例。这是继发于上述情况的。
0赞 engineersmnky 1/6/2023
@KonstantinStrukov说的。这些库快速、轻量级且非常便携。

答:

0赞 max 1/7/2023 #1

这里的第一个问题是这不是结构体的正确使用。

当您想要拥有一个简单的对象来封装一些数据但实际上没有太多逻辑时,结构体是合适的。否则,请使用类。

常量“泄漏”到外部作用域的原因是由于常量作用域和模块嵌套在 Ruby 中的工作方式。当前的模块嵌套仅由 and 关键字更改,而不是块:moduleclass

module Foo
  BAR = 1
end

puts BAR # uninitialized constant BAR (NameError)

Baz = Module.new do
  BAR = "ooops"
end

puts BAR # "ooops"

如果您使用它,它会将常量范围限定为 Struct,但这是一件愚蠢的事情。self::MANDATORY_INPUT = [:m1, :m2]

如果你真的需要验证,这也是值得怀疑的 - 你要做的大部分事情似乎都暗示你真正想要的只是关键字参数

class MyClass 
  def initialize(required_arg_1:, required_arg_2:, optional_arg: nil, **kwargs)
    # ...
    @required_1 = required_arg_1
    @required_2 = required_arg_2
    @optional = optional_arg
    @extra_options = kwargs
  end
end

将哈希作为位置参数是一种非常过时的做法——关键字参数是在 Ruby 2.0 中引入的,那是很久以前的事了。

如果您以后确实需要复杂的规则来验证参数的格式、类型等,请稍后添加 - 但首先花一分钟时间思考一下您需要构建什么样的对象以及它们应该具有什么样的签名。它们可能比你想象的要灵活得多。

好吧,哈希格式的数据是通过专有的 json 协议来的。然后它被解析为 ruby 哈希。然后我想象使用哈希实例化这个类的实例。

这不应该在类的初始值设定项中全部处理。

相反,您可以使用工厂方法(返回实例的类方法),甚至可以更好地将输入数据规范化为您自己的表示形式。

class MyClass 
  def self.from_wonky_json(garbage_input)
    # map the input from the API to your own class signature
    new(
      foo: garbage_input.dig("a", "b", "c"),
      bar: garbage_input.dig("c", "d")&.frobnobize
    )    
  end
end

如果遵循单一责任原则,则对象不应同时负责数据并对其进行规范化。

评论

0赞 PKP 1/10/2023
我放弃了结构,转而使用常规类,并包含了您的初始化方法和工厂方法。但是,在没有所有必需符号的情况下调用工厂方法时,我似乎没有得到一个。我需要验证此输入以及使用普通初始化方法构造类的时间。ArgumentError
0赞 PKP 1/10/2023
我想我可以按照你说的去做;使工厂方法返回执行验证的单独类的对象(使用常规关键字参数)。但是,有没有办法返回类本身的对象,但是如果缺少任何必需参数,则引发异常?