Friendly JSON column queries for ActiveRecord. Use where with JSON attributes just like regular columns.
Supports PostgreSQL, MySQL, and SQLite.
Add to your Gemfile:
gem "jsonq"Add jsonq_queryable to your model:
class Plan < ApplicationRecord
jsonq_queryable
store_accessor :metadata, :private_title, :category, :author
endOrder doesn't matter — jsonq_queryable can go before or after store_accessor.
Now query JSON attributes with where:
Plan.where(private_title: "Draft")
Plan.where(category: "work", status: "active")
Plan.where(private_title: ["Draft", "Review"])
Plan.where(private_title: nil)
Plan.where.not(category: "archived")jsonq intercepts ActiveRecord's PredicateBuilder to translate JSON attribute names into database-specific path expressions:
Plan.where(private_title: "Draft")
Generates:
-- PostgreSQL
WHERE "plans"."metadata"->>'private_title' = 'Draft'
-- MySQL
WHERE JSON_UNQUOTE(JSON_EXTRACT("plans"."metadata", '$.private_title')) = 'Draft'
-- SQLite
WHERE "plans"."metadata"->>'$.private_title' = 'Draft'Any store_accessor attributes on a native JSON/JSONB column are automatically registered. Declarations before or after jsonq_queryable both work.
class Plan < ApplicationRecord
jsonq_queryable
store_accessor :metadata, :private_title, :category
end
Plan.where(private_title: "Draft")When a store_accessor attribute name collides with a real database column, the real column takes precedence.
Attributes declared with prefix: or suffix: are registered by their original key name (the JSON key), not the prefixed accessor name. Use json_attribute to query by a custom name.
For nested JSON paths or attributes without store_accessor, use json_attribute:
class Event < ApplicationRecord
jsonq_queryable
json_attribute :metadata, "address.billing.city", as: :billing_city
json_attribute :metadata, "organizer.name", as: :organizer_name
end
Event.where(billing_city: "Paris")
Event.where(organizer_name: "Alice")The as: option is required.
| Operation | Example |
|---|---|
| Equality | where(key: "value") |
| Nil/NULL | where(key: nil) |
| IN (array) | where(key: ["a", "b"]) |
| Negation | where.not(key: "value") |
| Composition | where(json_key: "x", real_column: "y") |
| Chaining | where(key: "x").where(other_key: "y") |
- PostgreSQL — JSONB and JSON columns
- MySQL 5.7+ — JSON columns
- SQLite 3.38+ — JSON1 extension
The adapter is detected automatically from the ActiveRecord connection.
If you use ActiveRecord without Rails, call Jsonq.setup! after establishing a connection:
require "jsonq"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
Jsonq.setup!- Ruby >= 3.0
- ActiveRecord >= 7.0
MIT
Bug reports and pull requests are welcome on GitHub.
