ActiveRecord makes it easy to store JSON data inside a record (using a jsonb
column in PostgreSQL for example):
create_table "users" do |t|
t.string "name"
t.json "profile"
end
class User < ActiveRecord::Base
# Validations
validates :name, presence: true
validates :profile, presence: true
end
In the above model, we make sure name
and profile
are provided. But what if we want to validate the data inside the profile
object.
We could use a custom validation block:
validates :profile, presence: true,
validates_each :profile, do |record, attr, value|
record.errors.add attr, 'must contain a non-empty list of interests' if !value["interests"] || value["interests"].empty?
end
But there’s got to be a better way 🤓 This is why, in 2013, we built ActiveRecord::JSONValidator
and probably why it’s one of our most popular gem with over 2,000,000 downloads!
Instead of using a custom validation block, we built a custom validator that validates the data against a JSON schema (using the json_schemer
gem).
So let’s start with our schema (that we can persist as a .json_schema
file):
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"interests": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
}
},
"required": ["interests"]
}
We can now get back to our initial model:
class User < ActiveRecord::Base
# Constants
PROFILE_JSON_SCHEMA = Rails.root.join('config', 'schemas', 'user_profile.json_schema')
# Validations
validates :name, presence: true
validates :profile, presence: true, json: { schema: PROFILE_JSON_SCHEMA }
end
user = User.new(name: "Rémi Prévost", profile: { interests: ["foo", "bar"] })
user.valid? #=> true
user = User.new(name: "Rémi Prévost", profile: {})
user.valid? #=> false
user.errors.full_messages # => ["Data root is missing required keys: interests"]
user = User.new(name: "Rémi Prévost", profile: { interests: [] })
user.valid? #=> false
user.errors.full_messages # => ["Data property '/interests' is invalid: error_type=minItems"]
We now have a full validation system using JSON schemas and ActiveRecord::JSONValidator
! 🎉