plutonium-definition-query
Configure search, filters, scopes, and sorting for Plutonium resources
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
Definition Query
Configure how users can search, filter, and sort resource collections.
Overview
class PostDefinition < ResourceDefinition
# Search - global text search
search do |scope, query|
scope.where("title ILIKE ?", "%#{query}%")
end
# Filters - dropdown filter panel
filter :title, with: :text, predicate: :contains
filter :status, with: :select, choices: %w[draft published archived]
filter :published, with: :boolean
filter :created_at, with: :date_range
filter :category, with: :association
# Scopes - quick filter buttons
scope :published
scope :draft
# Sorting - sortable columns
sort :title
sort :created_at
# Default sort
default_sort :created_at, :desc
end
Search
Define global search across fields:
# Single field
search do |scope, query|
scope.where("title ILIKE ?", "%#{query}%")
end
# Multiple fields
search do |scope, query|
scope.where(
"title ILIKE :q OR content ILIKE :q OR author_name ILIKE :q",
q: "%#{query}%"
)
end
# With associations
search do |scope, query|
scope.joins(:author).where(
"posts.title ILIKE :q OR users.name ILIKE :q",
q: "%#{query}%"
).distinct
end
# Split search terms
search do |scope, query|
terms = query.split(/\s+/)
terms.reduce(scope) do |current_scope, term|
current_scope.where("title ILIKE ?", "%#{term}%")
end
end
Filters
Plutonium provides 6 built-in filter types. Use shorthand symbols or full class names.
Text Filter
String/text filtering with pattern matching.
# Shorthand (recommended)
filter :title, with: :text, predicate: :contains
filter :status, with: :text, predicate: :eq
# Full class name
filter :title, with: Plutonium::Query::Filters::Text, predicate: :contains
Predicates:
| Predicate | SQL | Description |
|---|---|---|
:eq |
= value |
Exact match (default) |
:not_eq |
!= value |
Not equal |
:contains |
LIKE %value% |
Contains substring |
: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 date
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: true
Association 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: true
Renders a resource select dropdown. Converts filter key to foreign key (:category -> :category_id).
Custom Filters
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: PriceRangeFilter
Scopes
Scopes appear as quick filter buttons. They reference model scopes.
Basic Usage
class PostDefinition < ResourceDefinition
scope :published # Uses Post.published
scope :draft # Uses Post.draft
scope :featured # Uses Post.featured
end
Inline Scope
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 to apply it when no scope is explicitly selected:
class PostDefinition < ResourceDefinition
scope :published, default: true # Applied by default
scope :draft
scope :archived
end
When 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
- URL without scope param uses the default; URL with
?q[scope]=uses "All"
Sorting
Basic Sorting
sort :title
sort :created_at
sort :view_count
# Multiple at once
sorts :title, :created_at, :view_count
Default Sort
# 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.
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]=desc
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: |
Complete 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
scope :active, default: true
scope :featured
scope(:recent) { |scope| scope.where('created_at > ?', 1.week.ago) }
# Sortable columns
sorts :name, :price, :created_at
# Default sort
default_sort :created_at, :desc
end
Performance Tips
- Add indexes for filtered/sorted columns
-
Use
.distinctwhen joining associations in search -
Consider
pg_searchfor complex full-text search - Limit search fields to indexed columns
- Use scopes instead of filters for common queries
Related Skills
-
plutonium-definition- Overview and structure -
plutonium-definition-fields- Fields, inputs, displays -
plutonium-definition-actions- Actions and interactions
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-01-26
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!