This content originally appeared on DEV Community and was authored by Kirill Usanov
I’ve been working with ActiveFields, a gem for handling dynamic fields using the Entity-Attribute-Value (EAV) pattern. One of the challenges with EAV is querying those custom fields efficiently. ActiveFields includes a search feature that handles this, and I thought I’d share how it works.
The Problem
When you store custom fields in an EAV pattern, searching becomes tricky. You need to handle type casting, field-specific operators, and construct queries efficiently. ActiveFields provides a unified search interface that supports all built-in field types.
The API
The main method is where_active_fields, which supports all 13 built-in field types with type-specific operations. The method accepts three different input formats:
1. Array of Hashes – useful for programmatic queries:
Post.where_active_fields([
{ name: "integer_array", operator: "any_gteq", value: 5 },
{ "name" => "text", operator: "=", "value" => "Lasso" },
{ n: "boolean", op: "!=", v: false }, # Compact form
])
2. Hash of Hashes – works with Rails fields_for:
Post.where_active_fields({
"0" => { name: "integer_array", operator: "any_gteq", value: 5 },
"1" => { "name" => "text", operator: "=", "value" => "Lasso" },
})
3. Permitted Parameters – direct from controllers:
# In your controller
def index
@posts = Post.where_active_fields(active_fields_finders_params)
end
You can use n/name, op/operator, and v/value in compact form, and mix string or symbol keys as needed.
Field Types and Operations
Each field type has operations suited to its data type. Here are some examples:
Text Fields
Text fields support 13 different operations, including exact matches and case-insensitive substring searches:
# Exact match
Post.where_active_fields([{ name: "title", operator: "=", value: "Hello" }])
# Starts with (case-sensitive)
Post.where_active_fields([{ name: "title", operator: "^", value: "Hello" }])
# Contains (case-insensitive)
Post.where_active_fields([{ name: "description", operator: "~*", value: "ruby" }])
# Doesn't end with
Post.where_active_fields([{ name: "title", operator: "!$", value: "World" }])
Numeric Fields
Integer, Decimal, Date, and DateTime fields support standard comparison operations:
# Greater than or equal
Post.where_active_fields([{ name: "age", operator: ">=", value: 18 }])
# Less than
Order.where_active_fields([{ name: "price", operator: "<", value: 100.50 }])
# Not equal
Post.where_active_fields([{ name: "status", operator: "!=", value: "archived" }])
Array Fields
Array fields support additional operations:
- Any element matching a condition
- All elements matching a condition
- Array size constraints
- Inclusion/exclusion of specific values
# Find posts where tags array contains "ruby"
Post.where_active_fields([{ name: "tags", operator: "|=", value: "ruby" }])
# Find posts where any tag starts with "web"
Post.where_active_fields([{ name: "tags", operator: "|^", value: "web" }])
# Find posts with at least 5 tags
Post.where_active_fields([{ name: "tags", operator: "#>=", value: 5 }])
# Find products where all customer ratings are 4 or higher
Product.where_active_fields([{ name: "ratings", operator: "&>=", value: 4 }])
# Find products where any customer gave a 5+ rating
Product.where_active_fields([{ name: "ratings", operator: "|>=", value: 5 }])
The operators follow a pattern: |= for “includes”, |> for “any greater than”, &> for “all greater than”, #= for “size equals”, etc.
Boolean Fields
# Find published posts
Post.where_active_fields([{ name: "published", operator: "=", value: true }])
# Find unpublished posts
Post.where_active_fields([{ name: "published", operator: "!=", value: true }])
Examples
Here are some practical use cases:
E-commerce Product Search
# Find products with:
# - Price between 50 and 200
# - In stock (boolean = true)
# - Tags include "electronics"
# - Rating >= 4.5
Product.where_active_fields([
{ name: "price", operator: ">=", value: 50 },
{ name: "price", operator: "<=", value: 200 },
{ name: "in_stock", operator: "=", value: true },
{ name: "tags", operator: "|=", value: "electronics" },
{ name: "rating", operator: ">=", value: 4.5 },
])
Content Management System
# Find articles:
# - Published after a certain date
# - Title contains "Rails" (case-insensitive)
# - Has at least 3 categories
# - Author name equals "John"
Article.where_active_fields([
{ name: "published_at", operator: ">=", value: Date.current - 30.days },
{ name: "title", operator: "~*", value: "Rails" },
{ name: "categories", operator: "#>=", value: 3 },
{ name: "author_name", operator: "=", value: "John" },
])
Multi-Tenant Applications
ActiveFields supports scoping, which is useful for multi-tenant applications:
# Search within a specific scope (tenant)
User.where_active_fields(
[
{ name: "department", operator: "=", value: "Engineering" },
{ name: "skills", operator: "|=", value: "Ruby" },
],
scope: Current.tenant.id,
)
Implementation Details
The search functionality uses PostgreSQL 17+ JSON capabilities. For singular fields, it uses CAST operations. For array fields, it uses jsonb_path_exists and jsonb_path_query_array functions with JSONPath expressions.
Some benefits:
- Type-safe: Values are automatically cast to the correct type
- Efficient: Uses PostgreSQL native JSON functions
- Flexible: Supports complex queries without writing raw SQL
- Maintainable: All search logic is encapsulated in finder classes
Building Search Forms
The gem integrates with Rails forms. The scaffold generator provides a helper method to render search forms:
<%= render_active_fields_finders_form(
active_fields: Post.active_fields,
url: posts_path
) %>
This generates a form with appropriate inputs for each field type.
Getting Started
The search feature works once you have ActiveFields set up. Call where_active_fields on any model that has has_active_fields:
class Post < ApplicationRecord
has_active_fields
end
# Then you can search:
Post.where_active_fields([{ name: "custom_field", operator: "=", value: "something" }])
Summary
ActiveFields search feature provides a unified API for querying dynamic fields. The where_active_fields method handles type casting, field-specific operators, and query construction, which simplifies working with custom fields.
Key features:
- One method (
where_active_fields) handles all search scenarios - Supports 13 field types with type-specific operations
- Built on PostgreSQL 17+ JSON functions
- Extensible: you can add custom field types with its own search operations
- Type-safe: automatic value casting
If you need custom fields with search capabilities, ActiveFields might be worth checking out.
This content originally appeared on DEV Community and was authored by Kirill Usanov