ActiveFields: Search



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