plutonium 0.23.4 → 0.23.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/plutonium.css +2 -2
- data/config/initializers/sqlite_json_alias.rb +1 -1
- data/docs/.vitepress/config.ts +60 -19
- data/docs/guide/cursor-rules.md +75 -0
- data/docs/guide/deep-dive/authorization.md +189 -0
- data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
- data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
- data/docs/guide/index.md +28 -0
- data/docs/guide/introduction/02-core-concepts.md +440 -0
- data/docs/guide/tutorial/01-project-setup.md +75 -0
- data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
- data/docs/guide/tutorial/03-defining-resources.md +90 -0
- data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
- data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
- data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
- data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
- data/docs/index.md +24 -31
- data/docs/modules/action.md +190 -0
- data/docs/modules/authentication.md +236 -0
- data/docs/modules/configuration.md +599 -0
- data/docs/modules/controller.md +398 -0
- data/docs/modules/core.md +316 -0
- data/docs/modules/definition.md +876 -0
- data/docs/modules/display.md +759 -0
- data/docs/modules/form.md +605 -0
- data/docs/modules/generator.md +288 -0
- data/docs/modules/index.md +167 -0
- data/docs/modules/interaction.md +470 -0
- data/docs/modules/package.md +151 -0
- data/docs/modules/policy.md +176 -0
- data/docs/modules/portal.md +710 -0
- data/docs/modules/query.md +287 -0
- data/docs/modules/resource_record.md +618 -0
- data/docs/modules/routing.md +641 -0
- data/docs/modules/table.md +293 -0
- data/docs/modules/ui.md +631 -0
- data/docs/public/plutonium.mdc +667 -0
- data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
- data/lib/plutonium/ui/display/resource.rb +7 -2
- data/lib/plutonium/ui/table/resource.rb +8 -3
- data/lib/plutonium/version.rb +1 -1
- metadata +36 -9
- data/docs/guide/getting-started/authorization.md +0 -296
- data/docs/guide/getting-started/core-concepts.md +0 -432
- data/docs/guide/getting-started/index.md +0 -21
- data/docs/guide/tutorial.md +0 -401
- /data/docs/guide/{what-is-plutonium.md → introduction/01-what-is-plutonium.md} +0 -0
@@ -0,0 +1,287 @@
|
|
1
|
+
---
|
2
|
+
title: Query Module
|
3
|
+
---
|
4
|
+
|
5
|
+
# Query Module
|
6
|
+
|
7
|
+
The Query module provides a powerful and flexible system for filtering, searching, and sorting resources in Plutonium applications. It enables declarative query configuration and automatic UI generation for data exploration.
|
8
|
+
|
9
|
+
::: tip
|
10
|
+
The Query module is located in `lib/plutonium/query/`. Query logic is typically defined inside a resource's Definition file.
|
11
|
+
:::
|
12
|
+
|
13
|
+
## Overview
|
14
|
+
|
15
|
+
- **Declarative Configuration**: Define filters, scopes, and sorters declaratively in your resource definition.
|
16
|
+
- **Type-Safe Filtering**: Built-in filters for different data types (currently Text filter with various predicates).
|
17
|
+
- **Search Integration**: Full-text search across multiple fields.
|
18
|
+
- **Sorting Support**: Multi-field sorting with configurable directions.
|
19
|
+
- **UI Integration**: Seamlessly powers search bars, filter forms, and sortable table headers.
|
20
|
+
|
21
|
+
## Defining a Query
|
22
|
+
|
23
|
+
All query logic—search, filters, scopes, and sorting—is defined inside a resource definition file. This configuration is then used to build a `QueryObject` in the controller.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
# app/definitions/post_definition.rb
|
27
|
+
class PostDefinition < Plutonium::Resource::Definition
|
28
|
+
# Define the global search logic.
|
29
|
+
search do |scope, search|
|
30
|
+
scope.where("title ILIKE ? OR content ILIKE ?", "%#{search}%", "%#{search}%")
|
31
|
+
end
|
32
|
+
|
33
|
+
# Define available filters (currently only Text filter is implemented).
|
34
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :contains
|
35
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
36
|
+
filter :category, with: Plutonium::Query::Filters::Text, predicate: :eq
|
37
|
+
|
38
|
+
# Define named scopes that appear as buttons.
|
39
|
+
scope :published
|
40
|
+
scope :recent, -> { where('created_at > ?', 1.week.ago) }
|
41
|
+
|
42
|
+
# Define which columns are sortable.
|
43
|
+
sort :title
|
44
|
+
sort :created_at
|
45
|
+
sort :view_count
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
## Built-in Filters
|
50
|
+
|
51
|
+
Currently, Plutonium provides the **Text filter** with various predicates for different matching behaviors.
|
52
|
+
|
53
|
+
::: code-group
|
54
|
+
```ruby [Text Filter with Predicates]
|
55
|
+
# Exact match (default)
|
56
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
57
|
+
|
58
|
+
# Contains (LIKE with wildcards)
|
59
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :contains
|
60
|
+
|
61
|
+
# Starts with
|
62
|
+
filter :slug, with: Plutonium::Query::Filters::Text, predicate: :starts_with
|
63
|
+
|
64
|
+
# Ends with
|
65
|
+
filter :email, with: Plutonium::Query::Filters::Text, predicate: :ends_with
|
66
|
+
|
67
|
+
# Not equal
|
68
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :not_eq
|
69
|
+
|
70
|
+
# Not contains
|
71
|
+
filter :content, with: Plutonium::Query::Filters::Text, predicate: :not_contains
|
72
|
+
|
73
|
+
# Pattern matching with wildcards (* becomes %)
|
74
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :matches
|
75
|
+
|
76
|
+
# Not matching pattern
|
77
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :not_matches
|
78
|
+
```
|
79
|
+
:::
|
80
|
+
|
81
|
+
### Available Text Filter Predicates
|
82
|
+
- `:eq` - Equal (exact match)
|
83
|
+
- `:not_eq` - Not equal
|
84
|
+
- `:contains` - LIKE with wildcards on both sides
|
85
|
+
- `:not_contains` - NOT LIKE with wildcards on both sides
|
86
|
+
- `:starts_with` - LIKE with suffix wildcard
|
87
|
+
- `:ends_with` - LIKE with prefix wildcard
|
88
|
+
- `:matches` - LIKE with user-provided wildcards (* becomes %)
|
89
|
+
- `:not_matches` - NOT LIKE with user-provided wildcards
|
90
|
+
|
91
|
+
## Controller Integration
|
92
|
+
|
93
|
+
The `QueryObject` is automatically created in your resource controllers and applied to the base collection.
|
94
|
+
|
95
|
+
::: code-group
|
96
|
+
```ruby [Controller]
|
97
|
+
class PostsController < ApplicationController
|
98
|
+
include Plutonium::Resource::Controller
|
99
|
+
|
100
|
+
def index
|
101
|
+
# `filtered_resource_collection` applies the query object.
|
102
|
+
# The results are then paginated.
|
103
|
+
@pagy, @resource_records = pagy(filtered_resource_collection)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# This helper method shows how the query is applied.
|
109
|
+
def filtered_resource_collection
|
110
|
+
# 1. Start with the authorized base scope (from the policy).
|
111
|
+
base_query = current_authorized_scope
|
112
|
+
# 2. Apply the filters, search, sort, and scope from the query object.
|
113
|
+
current_query_object.apply(base_query, raw_resource_query_params)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
```
|
117
|
+
```ruby [QueryObject]
|
118
|
+
# The `current_query_object` is built automatically.
|
119
|
+
# This is a simplified view of what happens behind the scenes.
|
120
|
+
query_object = Plutonium::Resource::QueryObject.new(Post, params[:q] || {}) do |query|
|
121
|
+
# Definitions from PostDefinition are added here.
|
122
|
+
query.define_search(...)
|
123
|
+
query.define_filter(:title, ...)
|
124
|
+
query.define_scope(:recent, ...)
|
125
|
+
query.define_sorter(:title, ...)
|
126
|
+
end
|
127
|
+
|
128
|
+
# The `apply` method executes the query.
|
129
|
+
filtered_scope = query_object.apply(Post.all, params[:q])
|
130
|
+
```
|
131
|
+
:::
|
132
|
+
|
133
|
+
## URL Parameters
|
134
|
+
|
135
|
+
Query parameters are structured under the `q` key in the URL.
|
136
|
+
|
137
|
+
::: details URL Parameter Format
|
138
|
+
- **Search**: `?q[search]=rails`
|
139
|
+
- **Filters**: `?q[title][query]=rails&q[status][query]=published`
|
140
|
+
- **Scope**: `?q[scope]=recent`
|
141
|
+
- **Sorting**: `?q[sort_fields][]=created_at&q[sort_directions][created_at]=desc`
|
142
|
+
|
143
|
+
A combined example:
|
144
|
+
`/posts?q[search]=rails&q[title][query]=tutorial&q[scope]=recent&q[sort_fields][]=created_at&q[sort_directions][created_at]=desc`
|
145
|
+
:::
|
146
|
+
|
147
|
+
## Advanced Usage
|
148
|
+
|
149
|
+
::: details Custom Filter Classes
|
150
|
+
You can create custom filter classes for complex logic.
|
151
|
+
```ruby
|
152
|
+
# 1. Define your custom filter class.
|
153
|
+
class CustomRangeFilter < Plutonium::Query::Filter
|
154
|
+
def initialize(key:, **options)
|
155
|
+
super
|
156
|
+
@key = key
|
157
|
+
end
|
158
|
+
|
159
|
+
# This method applies the filter to the scope.
|
160
|
+
def apply(scope, min: nil, max: nil)
|
161
|
+
scope = scope.where("#{@key} >= ?", min) if min.present?
|
162
|
+
scope = scope.where("#{@key} <= ?", max) if max.present?
|
163
|
+
scope
|
164
|
+
end
|
165
|
+
|
166
|
+
# This defines the inputs for the filter form.
|
167
|
+
def customize_inputs
|
168
|
+
input :min, as: :number
|
169
|
+
input :max, as: :number
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# 2. Use it in your resource definition.
|
174
|
+
class PostDefinition < Plutonium::Resource::Definition
|
175
|
+
filter :view_count, with: CustomRangeFilter
|
176
|
+
end
|
177
|
+
```
|
178
|
+
:::
|
179
|
+
|
180
|
+
::: details Complex Search Examples
|
181
|
+
```ruby
|
182
|
+
# Multi-field search with associations
|
183
|
+
search do |scope, search|
|
184
|
+
scope.joins(:author, :tags).where(
|
185
|
+
"posts.title ILIKE :search OR posts.content ILIKE :search OR users.name ILIKE :search",
|
186
|
+
search: "%#{search}%"
|
187
|
+
).distinct
|
188
|
+
end
|
189
|
+
|
190
|
+
# Search with term splitting
|
191
|
+
search do |scope, search|
|
192
|
+
terms = search.split(/\s+/)
|
193
|
+
terms.reduce(scope) do |current_scope, term|
|
194
|
+
current_scope.where(
|
195
|
+
"title ILIKE ? OR content ILIKE ?",
|
196
|
+
"%#{term}%", "%#{term}%"
|
197
|
+
)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
```
|
201
|
+
:::
|
202
|
+
|
203
|
+
## Query Object API
|
204
|
+
|
205
|
+
The QueryObject provides methods for building URLs and applying filters programmatically.
|
206
|
+
|
207
|
+
### Key Methods
|
208
|
+
|
209
|
+
- `define_filter(name, body)` - Define a custom filter
|
210
|
+
- `define_scope(name, body)` - Define a scope filter
|
211
|
+
- `define_sorter(name, body)` - Define a custom sorter
|
212
|
+
- `define_search(body)` - Define search functionality
|
213
|
+
- `apply(scope, params)` - Apply filters and sorting to a scope
|
214
|
+
- `build_url(**options)` - Build URLs with query parameters
|
215
|
+
|
216
|
+
### Usage Example
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
# Automatic usage through controllers
|
220
|
+
GET /posts?q[search]=rails&q[title][query]=tutorial&q[sort_fields][]=created_at&q[sort_directions][created_at]=desc
|
221
|
+
|
222
|
+
# Manual usage in controller
|
223
|
+
query_object = current_query_object
|
224
|
+
@resource_records = query_object.apply(current_authorized_scope, params[:q])
|
225
|
+
|
226
|
+
# Building URLs for links
|
227
|
+
next_page_url = query_object.build_url(search: "new search term")
|
228
|
+
sorted_url = query_object.build_url(sort: :title)
|
229
|
+
```
|
230
|
+
|
231
|
+
## Advanced Query Object Examples
|
232
|
+
|
233
|
+
**Custom Date Range Filter**
|
234
|
+
```ruby
|
235
|
+
class DateRangeFilter < Plutonium::Query::Filter
|
236
|
+
def apply(scope, start_date: nil, end_date: nil)
|
237
|
+
scope = scope.where("#{key} >= ?", start_date.beginning_of_day) if start_date.present?
|
238
|
+
scope = scope.where("#{key} <= ?", end_date.end_of_day) if end_date.present?
|
239
|
+
scope
|
240
|
+
end
|
241
|
+
|
242
|
+
def customize_inputs
|
243
|
+
input :start_date, as: :date
|
244
|
+
input :end_date, as: :date
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Usage in definition
|
249
|
+
filter :created_at, with: DateRangeFilter
|
250
|
+
```
|
251
|
+
|
252
|
+
**Conditional Scoping with User Context**
|
253
|
+
```ruby
|
254
|
+
class PostDefinition < Plutonium::Resource::Definition
|
255
|
+
scope :my_posts, -> { where(author: current_user) }
|
256
|
+
scope :drafts, -> { where(published: false) }
|
257
|
+
scope :published_last_week, -> {
|
258
|
+
where(published: true, created_at: 1.week.ago..Time.current)
|
259
|
+
}
|
260
|
+
end
|
261
|
+
```
|
262
|
+
|
263
|
+
## Best Practices
|
264
|
+
|
265
|
+
### Filter Design
|
266
|
+
- Use Text filters with appropriate predicates for most use cases
|
267
|
+
- Create custom filter classes for complex multi-input filters (date ranges, number ranges)
|
268
|
+
- Keep filter logic focused and single-responsibility
|
269
|
+
|
270
|
+
### Search Implementation
|
271
|
+
- Include commonly searched fields in your search scope
|
272
|
+
- Use ILIKE for case-insensitive matching
|
273
|
+
- Consider performance impact of JOINs in search queries
|
274
|
+
- Use `.distinct` when searching across associations
|
275
|
+
|
276
|
+
### URL Structure
|
277
|
+
- The `q` parameter namespace keeps query params organized
|
278
|
+
- All filter inputs are nested under their filter name
|
279
|
+
- Sort parameters are arrays to support multi-column sorting
|
280
|
+
|
281
|
+
The Query module provides a powerful foundation for building searchable, filterable interfaces while maintaining clean separation of concerns and consistent URL patterns.
|
282
|
+
|
283
|
+
## Related Modules
|
284
|
+
|
285
|
+
- **[Definition](./definition.md)** - Resource definitions and DSL
|
286
|
+
- **[Resource Record](./resource_record.md)** - Resource controllers and CRUD operations
|
287
|
+
- **[UI](./ui.md)** - User interface components
|