Search and Filtering
This guide covers implementing search, filters, scopes, and sorting.
Overview
Plutonium provides built-in support for:
- Search - Full-text search across fields
- Filters - Input filters for specific fields (dropdown panel)
- Scopes - Predefined query shortcuts (quick filter buttons)
- Sorting - Column-based ordering
Search
Define global search in the definition:
class PostDefinition < ResourceDefinition
search do |scope, query|
scope.where("title ILIKE ?", "%#{query}%")
end
endMulti-Field Search
search do |scope, query|
scope.where(
"title ILIKE :q OR content ILIKE :q OR author_name ILIKE :q",
q: "%#{query}%"
)
endSearch with Associations
search do |scope, query|
scope.joins(:author).where(
"posts.title ILIKE :q OR users.name ILIKE :q",
q: "%#{query}%"
).distinct
endSplit Search Terms
search do |scope, query|
terms = query.split(/\s+/)
terms.reduce(scope) do |current_scope, term|
current_scope.where("title ILIKE ?", "%#{term}%")
end
endFull-Text Search (PostgreSQL)
search do |scope, query|
scope.where(
"to_tsvector('english', title || ' ' || body) @@ plainto_tsquery('english', ?)",
query
)
endFilters
Plutonium provides 6 built-in filter types. Use shorthand symbols or full class names.
Text Filter
String/text filtering with pattern matching:
class PostDefinition < ResourceDefinition
# Shorthand (recommended)
filter :title, with: :text, predicate: :contains
filter :status, with: :text, predicate: :eq
# Full class name also works
filter :slug, with: Plutonium::Query::Filters::Text, predicate: :starts_with
endAvailable Predicates
| Predicate | SQL | Description |
|---|---|---|
:eq | = value | Exact match (default) |
:not_eq | != value | Not equal |
:contains | LIKE %value% | Contains text |
:not_contains | NOT LIKE %value% | Does not contain |
:starts_with | LIKE value% | Starts with |
:ends_with | LIKE %value | Ends with |
:matches | LIKE value | Pattern match (* becomes %) |
:not_matches | NOT LIKE value | Does not match pattern |
Boolean Filter
True/false filtering for boolean columns:
# Basic
filter :active, with: :boolean
# Custom labels
filter :published, with: :boolean, true_label: "Published", false_label: "Draft"Renders a select dropdown with "All", true label ("Yes"), and false label ("No").
Date Filter
Single date filtering with comparison predicates:
filter :created_at, with: :date, predicate: :gteq # On or after
filter :due_date, with: :date, predicate: :lt # Before
filter :published_at, with: :date, predicate: :eq # On exact dateAvailable Predicates
| Predicate | Description |
|---|---|
:eq | On this date (default) |
:not_eq | Not on this date |
:lt | Before date |
:lteq | On or before date |
:gt | After date |
:gteq | On or after date |
Date Range Filter
Filter between two dates (from/to):
# Basic
filter :created_at, with: :date_range
# Custom labels
filter :published_at, with: :date_range,
from_label: "Published from",
to_label: "Published to"Renders two date pickers. Both are optional - users can filter with just "from" or just "to".
Select Filter
Filter from predefined choices:
# Static choices (array)
filter :status, with: :select, choices: %w[draft published archived]
# Dynamic choices (proc)
filter :category, with: :select, choices: -> { Category.pluck(:name) }
# Multiple selection
filter :tags, with: :select, choices: %w[ruby rails js], multiple: trueAssociation Filter
Filter by associated record:
# Basic - infers Category class from :category key
filter :category, with: :association
# Explicit class
filter :author, with: :association, class_name: User
# Multiple selection
filter :tags, with: :association, class_name: Tag, multiple: trueRenders a resource select dropdown. Converts filter key to foreign key (:category -> :category_id).
Filter Summary Table
| Type | Symbol | Input Params | Options |
|---|---|---|---|
| Text | :text | query | predicate: |
| Boolean | :boolean | value | true_label:, false_label: |
| Date | :date | value | predicate: |
| Date Range | :date_range | from, to | from_label:, to_label: |
| Select | :select | value | choices:, multiple: |
| Association | :association | value | class_name:, multiple: |
Custom Filter Class
class PriceRangeFilter < Plutonium::Query::Filter
def apply(scope, min: nil, max: nil)
scope = scope.where("price >= ?", min) if min.present?
scope = scope.where("price <= ?", max) if max.present?
scope
end
def customize_inputs
input :min, as: :number
input :max, as: :number
field :min, placeholder: "Min price..."
field :max, placeholder: "Max price..."
end
end
# Use in definition
filter :price, with: PriceRangeFilterScopes
Scopes appear as quick filter buttons. They reference model scopes or use inline blocks.
Basic Scopes
Reference existing model scopes:
class PostDefinition < ResourceDefinition
scope :published # Uses Post.published
scope :draft # Uses Post.draft
scope :featured # Uses Post.featured
endInline Scopes
Use block syntax with the scope passed as an argument:
scope(:recent) { |scope| scope.where("created_at > ?", 1.week.ago) }
scope(:this_month) { |scope| scope.where(created_at: Time.current.all_month) }With Controller Context
Inline scopes have access to controller context like current_user:
scope(:mine) { |scope| scope.where(author: current_user) }
scope(:my_team) { |scope| scope.where(team: current_user.team) }Default Scope
Set a scope as default:
class PostDefinition < ResourceDefinition
scope :published, default: true # Applied by default
scope :draft
scope :archived
endWhen a default scope is set:
- The default scope is applied on initial page load
- The default scope button is highlighted (not "All")
- Clicking "All" shows all records without any scope filter
Sorting
Define Sortable Fields
class PostDefinition < ResourceDefinition
sort :title
sort :created_at
sort :view_count
# Multiple at once
sorts :title, :created_at, :view_count
endDefault Sort Order
# Field and direction
default_sort :created_at, :desc
default_sort :title, :asc
# Complex sorting with block
default_sort { |scope| scope.order(featured: :desc, created_at: :desc) }Note: Default sort only applies when no sort params are provided. The system default is :id, :desc.
URL Parameters
Query parameters are structured under q:
/posts?q[search]=rails
/posts?q[title][query]=widget
/posts?q[status][value]=published
/posts?q[created_at][from]=2024-01-01&q[created_at][to]=2024-12-31
/posts?q[scope]=recent
/posts?q[sort_fields][]=created_at&q[sort_directions][created_at]=descComplete Example
class ProductDefinition < ResourceDefinition
# Full-text search
search do |scope, query|
scope.where(
"name ILIKE :q OR description ILIKE :q",
q: "%#{query}%"
)
end
# Filters
filter :name, with: :text, predicate: :contains
filter :status, with: :select, choices: %w[draft active discontinued]
filter :featured, with: :boolean
filter :created_at, with: :date_range
filter :price, with: :date, predicate: :gteq
filter :category, with: :association
# Quick scopes (reference model scopes)
scope :active, default: true
scope :featured
scope(:recent) { |scope| scope.where("created_at > ?", 1.week.ago) }
# Sortable columns
sorts :name, :price, :created_at
# Default: newest first
default_sort :created_at, :desc
end