A longer, alternative phrasing of the question is: "What are some Ruby libraries or approaches to test the shape of arbitrary data structures (hashes, arrays, integers, floats, strings, and so on)?"
To start, let me show a simple example:
hash_1 = {
k1: 1.0,
k2: 42,
k3: {
k4: "100.00",
k5: "dollars"
}
}
Next, I want to validate it -- by this I mean compare against a shape / schema / template such as:
shape_a = {
k1: Float,
k2: Integer,
k3: Hash
}
Or, perhaps, a more specific shape:
shape_b = {
k1: Float,
k2: Integer,
k3: {
k4: String,
k5: String
}
}
One possible API might look like:
require '_____________'
hash_1.schema = shape_a
hash_1.valid? # => true
hash_1.schema = shape_b
hash_1.valid? # => true
These are just examples, I'm open to other approaches.
About 3 years ago, I wrote schema_hash to scratch my own itch. I plan to update it, but first I wanted to get a sense for alternatives and better approaches.
The motivation for this question comes from a Mongo use case, but the question is not necessarily Mongo specific.
Like I mentioned at the top, I would like to see or build the ability validate arbitrary data structures: hashes, arrays, primitives, and so on, in any nested combination.
When I worked on a previous project, this is exactly where I started. It got to be painful to write validations repeatedly for nested hashes. I started thinking about what would make it easier, and I came up with a syntax similar to what I shared above.
All of this said, I'm curious about what other people are doing. Is there a "golden path"? I'm trying out different approaches, including embedded documents and validates_associated with Mongoid for example... but those approaches seem like overkill when hashes get nested more than a level or so deep.
I looked around for Validation on Ruby Toolbox for validation (pun intended), but didn't find what I was looking for. While I was there, I suggested a new category called "Validation."
It is quite possible that what I'm asking fits less in the "validation" topic area and more in others, such as "data structures" and "traversals". If so, please point me in the right direction.
Edit: Rereading your question my answer seems a bit too simplistic. I'll leave it up to your wether I should delete it or not, just let me know through a comment.
A very simple approach would be this:
class Hash
def has_shape?(shape)
all? { |k, v| shape[k] === v }
end
end
Use like this:
hash_1.has_shape?(shape_a) #=> true
shape_b = { k1: Float, k2: Integer, k3: Integer }
hash_1.has_shape?(shape_b) #=> false
This already seems to be taking care of your first described version quite well. It wouldn't be hard to factor this out into a lib so it doesn't monkey-patch Hash
. Adding recursion to the has_shape?
method will take care of your more elaborate case.
Update: Here's a version with recursion:
class Hash
def has_shape?(shape)
all? do |k, v|
Hash === v ? v.has_shape?(shape[k]) : shape[k] === v
end
end
end
This might be what you're looking for:
https://github.com/JamesBrooks/hash_validator
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With