Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Validating the shape of Ruby hashes?

Tags:

ruby

mongodb

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.

"You don't need a schema for Mongo, so why do you care?"

  • Like I mention above, I am not exclusively thinking about Mongo use cases
  • But even in the context of Mongo, even if you don't want to require a data structure to take a certain shape, it still can be useful to test a data structure against a shape or pattern and act accordingly.

"Why not just write custom validations?"

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.

What is out there? What should I try?

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.

like image 704
David J. Avatar asked Jun 19 '12 18:06

David J.


2 Answers

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
like image 123
Michael Kohl Avatar answered Sep 28 '22 03:09

Michael Kohl


This might be what you're looking for:

https://github.com/JamesBrooks/hash_validator

like image 45
James Brooks Avatar answered Sep 28 '22 01:09

James Brooks