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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/plutonium.css +2 -2
  3. data/config/initializers/sqlite_json_alias.rb +1 -1
  4. data/docs/.vitepress/config.ts +60 -19
  5. data/docs/guide/cursor-rules.md +75 -0
  6. data/docs/guide/deep-dive/authorization.md +189 -0
  7. data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
  8. data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
  9. data/docs/guide/index.md +28 -0
  10. data/docs/guide/introduction/02-core-concepts.md +440 -0
  11. data/docs/guide/tutorial/01-project-setup.md +75 -0
  12. data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
  13. data/docs/guide/tutorial/03-defining-resources.md +90 -0
  14. data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
  15. data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
  16. data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
  17. data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
  18. data/docs/index.md +24 -31
  19. data/docs/modules/action.md +190 -0
  20. data/docs/modules/authentication.md +236 -0
  21. data/docs/modules/configuration.md +599 -0
  22. data/docs/modules/controller.md +398 -0
  23. data/docs/modules/core.md +316 -0
  24. data/docs/modules/definition.md +876 -0
  25. data/docs/modules/display.md +759 -0
  26. data/docs/modules/form.md +605 -0
  27. data/docs/modules/generator.md +288 -0
  28. data/docs/modules/index.md +167 -0
  29. data/docs/modules/interaction.md +470 -0
  30. data/docs/modules/package.md +151 -0
  31. data/docs/modules/policy.md +176 -0
  32. data/docs/modules/portal.md +710 -0
  33. data/docs/modules/query.md +287 -0
  34. data/docs/modules/resource_record.md +618 -0
  35. data/docs/modules/routing.md +641 -0
  36. data/docs/modules/table.md +293 -0
  37. data/docs/modules/ui.md +631 -0
  38. data/docs/public/plutonium.mdc +667 -0
  39. data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
  40. data/lib/plutonium/ui/display/resource.rb +7 -2
  41. data/lib/plutonium/ui/table/resource.rb +8 -3
  42. data/lib/plutonium/version.rb +1 -1
  43. metadata +36 -9
  44. data/docs/guide/getting-started/authorization.md +0 -296
  45. data/docs/guide/getting-started/core-concepts.md +0 -432
  46. data/docs/guide/getting-started/index.md +0 -21
  47. data/docs/guide/tutorial.md +0 -401
  48. /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