【问题标题】:Validating the shape of Ruby hashes?验证 Ruby 哈希的形状?
【发布时间】:2021-11-14 15:09:29
【问题描述】:

该问题的另一种说法是:“有哪些 Ruby 库或方法可以测试任意数据结构(散列、数组、整数、浮点数、字符串等)的形状?”

首先,让我举一个简单的例子:

hash_1 = {
  k1: 1.0,
  k2: 42,
  k3: {
    k4: "100.00",
    k5: "dollars"
  }
}

接下来,我想验证它——我的意思是与形状/架构/模板进行比较,例如:

shape_a = {
  k1: Float,
  k2: Integer,
  k3: Hash
}

或者,也许是更具体的形状:

shape_b = {
  k1: Float,
  k2: Integer,
  k3: {
    k4: String,
    k5: String
  }
}

一种可能的 API 可能如下所示:

require '_____________'

hash_1.schema = shape_a
hash_1.valid? # => true

hash_1.schema = shape_b
hash_1.valid? # => true

这些只是示例,我对其他方法持开放态度。

大约 3 年前,我写了schema_hash 来挠痒痒。我计划更新它,但首先我想了解替代方案和更好的方法。

这个问题的动机来自一个 Mongo 用例,但这个问题不一定是 Mongo 特有的。

就像我在顶部提到的那样,我希望看到或构建验证任意数据结构的能力:散列、数组、原语等,在任何嵌套组合中。

“你不需要 Mongo 的模式,那么你为什么要关心?”

  • 就像我上面提到的,我并不是专门考虑 Mongo 用例
  • 但即使在 Mongo 的上下文中,即使您不想要求数据结构采用某种形状,根据形状测试数据结构或模式并采取相应的行动。

“为什么不写自定义验证?”

当我从事以前的项目时,这正是我开始的地方。为嵌套哈希重复编写验证是很痛苦的。我开始思考怎样才能让它变得更容易,我想出了一个类似于我上面分享的语法。

外面有什么?我应该尝试什么?

说了这么多,我很好奇其他人在做什么。有没有“黄金之路”?我正在尝试不同的方法,包括嵌入文档和 validates_associated 与 Mongoid 例如...但是当哈希嵌套超过一个级别或这么深时,这些方法似乎有点过头了。

我四处寻找Validation on Ruby Toolbox 进行验证(双关语),但没有找到我要找的东西。当我在那里时,我建议了一个名为“验证”的新类别。

很可能我所问的内容不太适合“验证”主题领域,而更适合其他领域,例如“数据结构”和“遍历”。如果是这样,请指出正确的方向。

【问题讨论】:

  • 我不想围绕我放在一起的特定 API 和代码进行过多的对话——我对替代品更感兴趣——但如果比较有用,请点击此处是schema_hash gem 的两个核心部分:schemable.rbtraversable.rb
  • 关于黄金之路,五年过去了:现在有许多“嵌套模型”方法值得一试,其中包括 Disposable、ArDocStore、dry-types 和 Rails 5 属性 API。这个想法是您将嵌入文档视为比散列更高级别且更受约束的东西。验证是其中的一部分,但不是唯一的部分。人们还可能受益于将哈希表示为具有访问器方法的对象树、类型强制以及能够重用文档形状等。

标签: ruby mongodb


【解决方案1】:

编辑:重读您的问题,我的回答似乎有点过于简单。我会留给你是否删除它,请通过评论告诉我。

一个非常简单的方法是这样的:

class Hash
  def has_shape?(shape)
    all? { |k, v| shape[k] === v }
  end
end

这样使用:

hash_1.has_shape?(shape_a) #=> true
shape_b = { k1: Float, k2: Integer, k3: Integer }
hash_1.has_shape?(shape_b) #=> false

这似乎已经很好地处理了您描述的第一个版本。将其分解到库中并不难,因此它不会对Hash 进行猴子补丁。向has_shape? 方法添加递归将处理您更复杂的情况。

更新:这是一个带有递归的版本:

class Hash
  def has_shape?(shape)
    all? do |k, v|
      Hash === v ? v.has_shape?(shape[k]) : shape[k] === v
    end
  end
end

【讨论】:

  • 这不是一个糟糕的开始,但是(正如您所提到的)它不处理嵌套形状。另外,我不是要求人们为我编写新代码 --- 除非你真的想 :) -- 我真的只是希望找到已经存在的东西。
  • 这是一个处理嵌套形状的版本,我继续将它添加到我的 Ruby 随机添加库中:github.com/citizen428/shenanigans/blob/master/lib/shenanigans/… 如果您愿意,整个库都在 Rubygems.org 上。
  • 我没有针对各种测试用例运行它,但它看起来很干净并且涵盖了我给出的示例。谢谢。
  • 另外,我想补充一点:验证哈希,取决于你想要什么,可能意味着:(1)所有必需的键都存在并且它们是否具有有效值? (2) 没有额外的钥匙吗?在我编写的代码中,我想检查两者是否都满足,这增加了一些复杂性。
  • 很高兴这至少有点用处。据我所知,我的解决方案涵盖 (1) 和 (2),因为额外的密钥将为 shape[k] 返回 nil,因此不会满足 all?
【解决方案2】:

这可能是您正在寻找的:

https://github.com/JamesBrooks/hash_validator

【讨论】:

    【解决方案3】:

    如果您使用 Rails 并且只想验证密钥,可以使用assert_valid_keys

    { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age"
    { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'"
    { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age)   # => passes, raises nothing
    

    https://apidock.com/rails/Hash/assert_valid_keys

    【讨论】:

      【解决方案4】:

      我写了一个 gem 来验证像数据http://rubygems.org/gems/validates_simple 这样的散列,并且很容易添加验证来检查值是否是类的成员。至于验证嵌套值,如果需要,我将不得不更新 gem 以支持这一点。添加规则很简单

        module Validation
          module Rules
            def validates_is_a_member_of(field, class, message='')
               callback = lambda do |data|
                 data[field].is_a? Class
               end
               validates(field, callback, message)
            end
          end
        end
      

      之后,您可以执行 validator.validates_is_a_member_of('age', Integer, 'age must be an int') 并执行 validator.validate({'age': 232423})

      【讨论】:

        【解决方案5】:

        这是对 Michael Kohl 的递归 has_shape 的轻微调整?适用于 shape_a 和 shape_b 的方法(添加了一个测试来检查形状是否有要递归的子哈希):

          def has_shape?(shape)
            all? do |k, v|
              (Hash === v && Hash === shape[k]) ? v.has_shape?(shape[k]) : shape[k] === v
            end
          end
        

        另外,FWIW 我找到了 is_a?比 === 更具可读性。例如:

          def has_shape?(shape)
            all? do |k, v|
              (v.is_a?(Hash) && shape[k].is_a?(Hash)) ? v.has_shape?(shape[k]) : v.is_a?(shape[k])
            end
          end
        

        但是,如果您的形状与 JSON 兼容,那么您可能需要考虑使用 Ruby JSON Schema Validator。它更重量级、更冗长,但您可以使用它来验证哈希以及 JSON,它为您提供更多控制权(例如,必需键、可选键等)。

        【讨论】:

          【解决方案6】:

          似乎Schemacop 非常适合这种场合。

          【讨论】:

            【解决方案7】:

            这是一个使用表达式列表的版本:

            class Hash
              def has_shape?(shape)
                all? do |key, value|
                  if value.is_a? Hash
                    value.has_shape?(shape[key])
                  else
                    shape[key].all? { |expression| expression.call(value) }
                  end
                end
              end
            end
            

            用法:

            shape = {
              type: [
                -> (type) { type.is_a? String },
                -> (type) { !type.blank?  },
                -> (type) { %(text number).include? type }
              ]
            }
            
            { type: "text" }.has_shape? shape # => true
            

            有了这个,您应该能够非常动态地确保哈希的形状。

            更新:

            在这个版本中,您无法检查是否缺少键/值对,因为我们正在询问所有内容?从我们正在调用该方法的哈希中。这结果呢?仅迭代该哈希上存在的键/值。

            我已经更新了实现以尊重“目标”哈希进行比较。现在has_shape?方法成功地确定了缺失的键/值对。

            class Hash
              def has_shape?(shape)
                shape.all? do |key, _|
                  if self[key.to_s].is_a? Hash
                    self[key.to_s].has_shape?(shape[key])
                  else
                    shape[key].all? { |expression| expression.call(self[key.to_s]) }
                  end
                end
              end
            end
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2012-08-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-12-30
              • 2012-11-04
              • 1970-01-01
              相关资源
              最近更新 更多