ActiveFields: Search

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,…


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


Print Share Comment Cite Upload Translate Updates
APA

Kirill Usanov | Sciencx (2025-11-27T17:05:43+00:00) ActiveFields: Search. Retrieved from https://www.scien.cx/2025/11/27/activefields-search/

MLA
" » ActiveFields: Search." Kirill Usanov | Sciencx - Thursday November 27, 2025, https://www.scien.cx/2025/11/27/activefields-search/
HARVARD
Kirill Usanov | Sciencx Thursday November 27, 2025 » ActiveFields: Search., viewed ,<https://www.scien.cx/2025/11/27/activefields-search/>
VANCOUVER
Kirill Usanov | Sciencx - » ActiveFields: Search. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/11/27/activefields-search/
CHICAGO
" » ActiveFields: Search." Kirill Usanov | Sciencx - Accessed . https://www.scien.cx/2025/11/27/activefields-search/
IEEE
" » ActiveFields: Search." Kirill Usanov | Sciencx [Online]. Available: https://www.scien.cx/2025/11/27/activefields-search/. [Accessed: ]
rf:citation
» ActiveFields: Search | Kirill Usanov | Sciencx | https://www.scien.cx/2025/11/27/activefields-search/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.