问题的另一个更长的替代措辞是:“什么是一些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特定的。
就像我在顶部提到的,我希望看到或构建能力验证任意数据结构:哈希,数组,基元等,以任何嵌套组合。
当我处理以前的项目时,这正是我开始的地方。为嵌套哈希重复编写验证会很痛苦。我开始思考什么会让它变得更容易,我想出了类似于我上面分享的语法。
所有这些都说,我很好奇其他人在做什么。有“黄金之路”吗?我正在尝试不同的方法,包括嵌入式文档和与Mongoid相关的validates_ ...但是当哈希嵌套超过一个级别或更深时,这些方法看起来有点过分。
我四处寻找Validation on Ruby Toolbox进行验证(双关语),但没找到我要找的东西。当我在那里时,我提出了一个名为“验证”的新类别。
很可能我所要求的内容在“验证”主题领域中更少,而在其他领域则更多,例如“数据结构”和“遍历”。如果是这样,请指出我正确的方向。
答案 0 :(得分:4)
编辑:重读您的问题,我的回答似乎有点过于简单了。我会把它留给你,我应该删除它,只是通过评论告诉我。
一个非常简单的方法是:
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
这似乎已经很好地照顾了你的第一个描述版本。将它排除在lib之外并不难,因此它不会修复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
答案 1 :(得分:2)
这可能就是你要找的东西:
答案 2 :(得分:1)
我编写了一个用于验证哈希数据http://rubygems.org/gems/validates_simple的gem,并且很容易添加验证,以检查值是否是类的成员。至于验证嵌套值,如果需要,我将不得不更新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',整数,'年龄必须是一个int')并做validator.validate({'age':232423})
答案 3 :(得分:1)
如果您使用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
答案 4 :(得分:1)
这对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,并为您提供更多控制权(例如,必需的键,可选的键等)。
答案 5 :(得分:0)
似乎Schemacop这样的场合非常适合这种情况。
答案 6 :(得分:0)
以下是使用表达式列表的版本:
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