plutonium 0.33.1 → 0.34.0
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/# Plutonium: The pre-alpha demo.md +4 -2
- data/.claude/skills/assets/SKILL.md +416 -0
- data/.claude/skills/connect-resource/SKILL.md +112 -0
- data/.claude/skills/controller/SKILL.md +302 -0
- data/.claude/skills/create-resource/SKILL.md +240 -0
- data/.claude/skills/definition/SKILL.md +218 -0
- data/.claude/skills/definition-actions/SKILL.md +386 -0
- data/.claude/skills/definition-fields/SKILL.md +474 -0
- data/.claude/skills/definition-query/SKILL.md +334 -0
- data/.claude/skills/forms/SKILL.md +439 -0
- data/.claude/skills/installation/SKILL.md +300 -0
- data/.claude/skills/interaction/SKILL.md +382 -0
- data/.claude/skills/model/SKILL.md +267 -0
- data/.claude/skills/model-features/SKILL.md +286 -0
- data/.claude/skills/nested-resources/SKILL.md +274 -0
- data/.claude/skills/package/SKILL.md +191 -0
- data/.claude/skills/policy/SKILL.md +352 -0
- data/.claude/skills/portal/SKILL.md +400 -0
- data/.claude/skills/resource/SKILL.md +281 -0
- data/.claude/skills/rodauth/SKILL.md +452 -0
- data/.claude/skills/views/SKILL.md +563 -0
- data/Appraisals +46 -4
- data/CHANGELOG.md +32 -1
- data/app/assets/plutonium.css +2 -2
- data/config/brakeman.ignore +239 -0
- data/config/initializers/action_policy.rb +1 -1
- data/docs/.vitepress/config.ts +132 -47
- data/docs/concepts/architecture.md +226 -0
- data/docs/concepts/auto-detection.md +254 -0
- data/docs/concepts/index.md +61 -0
- data/docs/concepts/packages-portals.md +304 -0
- data/docs/concepts/resources.md +224 -0
- data/docs/cookbook/blog.md +412 -0
- data/docs/cookbook/index.md +289 -0
- data/docs/cookbook/saas.md +481 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +146 -0
- data/docs/getting-started/tutorial/01-setup.md +118 -0
- data/docs/getting-started/tutorial/02-first-resource.md +180 -0
- data/docs/getting-started/tutorial/03-authentication.md +246 -0
- data/docs/getting-started/tutorial/04-authorization.md +170 -0
- data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
- data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
- data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
- data/docs/getting-started/tutorial/index.md +64 -0
- data/docs/guides/adding-resources.md +420 -0
- data/docs/guides/authentication.md +551 -0
- data/docs/guides/authorization.md +468 -0
- data/docs/guides/creating-packages.md +380 -0
- data/docs/guides/custom-actions.md +523 -0
- data/docs/guides/index.md +45 -0
- data/docs/guides/multi-tenancy.md +302 -0
- data/docs/guides/nested-resources.md +411 -0
- data/docs/guides/search-filtering.md +266 -0
- data/docs/guides/theming.md +321 -0
- data/docs/index.md +67 -26
- data/docs/public/CLAUDE.md +64 -21
- data/docs/reference/assets/index.md +496 -0
- data/docs/reference/controller/index.md +363 -0
- data/docs/reference/definition/actions.md +400 -0
- data/docs/reference/definition/fields.md +350 -0
- data/docs/reference/definition/index.md +252 -0
- data/docs/reference/definition/query.md +342 -0
- data/docs/reference/generators/index.md +469 -0
- data/docs/reference/index.md +49 -0
- data/docs/reference/interaction/index.md +445 -0
- data/docs/reference/model/features.md +248 -0
- data/docs/reference/model/index.md +219 -0
- data/docs/reference/policy/index.md +385 -0
- data/docs/reference/portal/index.md +382 -0
- data/docs/reference/views/forms.md +396 -0
- data/docs/reference/views/index.md +479 -0
- data/gemfiles/rails_7.gemfile +9 -2
- data/gemfiles/rails_7.gemfile.lock +146 -111
- data/gemfiles/rails_8.0.gemfile +20 -0
- data/gemfiles/rails_8.0.gemfile.lock +417 -0
- data/gemfiles/rails_8.1.gemfile +20 -0
- data/gemfiles/rails_8.1.gemfile.lock +419 -0
- data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
- data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
- data/lib/generators/pu/pkg/portal/USAGE +65 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
- data/lib/generators/pu/res/conn/USAGE +71 -0
- data/lib/generators/pu/res/model/USAGE +106 -110
- data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
- data/lib/generators/pu/res/scaffold/USAGE +85 -0
- data/lib/generators/pu/rodauth/install_generator.rb +2 -6
- data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
- data/lib/generators/pu/skills/sync/USAGE +14 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
- data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
- data/lib/plutonium/core/controller.rb +2 -2
- data/lib/plutonium/interaction/base.rb +1 -0
- data/lib/plutonium/package/engine.rb +2 -2
- data/lib/plutonium/query/adhoc_block.rb +6 -2
- data/lib/plutonium/query/model_scope.rb +1 -1
- data/lib/plutonium/railtie.rb +4 -0
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
- data/lib/plutonium/resource/query_object.rb +38 -8
- data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +19 -4
- data/package.json +1 -1
- metadata +76 -39
- data/brakeman.ignore +0 -28
- data/docs/api-examples.md +0 -49
- data/docs/guide/claude-code-guide.md +0 -74
- data/docs/guide/deep-dive/authorization.md +0 -189
- data/docs/guide/deep-dive/multitenancy.md +0 -256
- data/docs/guide/deep-dive/resources.md +0 -390
- data/docs/guide/getting-started/01-installation.md +0 -165
- data/docs/guide/index.md +0 -28
- data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
- data/docs/guide/introduction/02-core-concepts.md +0 -440
- data/docs/guide/tutorial/01-project-setup.md +0 -75
- data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
- data/docs/guide/tutorial/03-defining-resources.md +0 -90
- data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
- data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
- data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
- data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
- data/docs/markdown-examples.md +0 -85
- data/docs/modules/action.md +0 -244
- data/docs/modules/authentication.md +0 -236
- data/docs/modules/configuration.md +0 -599
- data/docs/modules/controller.md +0 -443
- data/docs/modules/core.md +0 -316
- data/docs/modules/definition.md +0 -1308
- data/docs/modules/display.md +0 -759
- data/docs/modules/form.md +0 -495
- data/docs/modules/generator.md +0 -400
- data/docs/modules/index.md +0 -167
- data/docs/modules/interaction.md +0 -642
- data/docs/modules/package.md +0 -151
- data/docs/modules/policy.md +0 -176
- data/docs/modules/portal.md +0 -710
- data/docs/modules/query.md +0 -297
- data/docs/modules/resource_record.md +0 -618
- data/docs/modules/routing.md +0 -690
- data/docs/modules/table.md +0 -301
- data/docs/modules/ui.md +0 -631
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: definition-query
|
|
3
|
+
description: Configure search, filters, scopes, and sorting for Plutonium resources
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Definition Query
|
|
7
|
+
|
|
8
|
+
Configure how users can search, filter, and sort resource collections.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
class PostDefinition < ResourceDefinition
|
|
14
|
+
# Search - global text search
|
|
15
|
+
search do |scope, query|
|
|
16
|
+
scope.where("title ILIKE ?", "%#{query}%")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Filters - sidebar filter inputs
|
|
20
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
21
|
+
|
|
22
|
+
# Scopes - quick filter buttons
|
|
23
|
+
scope :published
|
|
24
|
+
scope :draft
|
|
25
|
+
|
|
26
|
+
# Sorting - sortable columns
|
|
27
|
+
sort :title
|
|
28
|
+
sort :created_at
|
|
29
|
+
|
|
30
|
+
# Default sort
|
|
31
|
+
default_sort :created_at, :desc
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Search
|
|
36
|
+
|
|
37
|
+
Define global search across fields:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
# Single field
|
|
41
|
+
search do |scope, query|
|
|
42
|
+
scope.where("title ILIKE ?", "%#{query}%")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Multiple fields
|
|
46
|
+
search do |scope, query|
|
|
47
|
+
scope.where(
|
|
48
|
+
"title ILIKE :q OR content ILIKE :q OR author_name ILIKE :q",
|
|
49
|
+
q: "%#{query}%"
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# With associations
|
|
54
|
+
search do |scope, query|
|
|
55
|
+
scope.joins(:author).where(
|
|
56
|
+
"posts.title ILIKE :q OR users.name ILIKE :q",
|
|
57
|
+
q: "%#{query}%"
|
|
58
|
+
).distinct
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Split search terms
|
|
62
|
+
search do |scope, query|
|
|
63
|
+
terms = query.split(/\s+/)
|
|
64
|
+
terms.reduce(scope) do |current_scope, term|
|
|
65
|
+
current_scope.where("title ILIKE ?", "%#{term}%")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Filters
|
|
71
|
+
|
|
72
|
+
Currently Plutonium provides the **Text filter** with various predicates.
|
|
73
|
+
|
|
74
|
+
### Text Filter Predicates
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
# Exact match
|
|
78
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
79
|
+
|
|
80
|
+
# Not equal
|
|
81
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :not_eq
|
|
82
|
+
|
|
83
|
+
# Contains (LIKE %value%)
|
|
84
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :contains
|
|
85
|
+
|
|
86
|
+
# Not contains
|
|
87
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :not_contains
|
|
88
|
+
|
|
89
|
+
# Starts with (LIKE value%)
|
|
90
|
+
filter :slug, with: Plutonium::Query::Filters::Text, predicate: :starts_with
|
|
91
|
+
|
|
92
|
+
# Ends with (LIKE %value)
|
|
93
|
+
filter :email, with: Plutonium::Query::Filters::Text, predicate: :ends_with
|
|
94
|
+
|
|
95
|
+
# Pattern match (* becomes %)
|
|
96
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :matches
|
|
97
|
+
|
|
98
|
+
# Not matching pattern
|
|
99
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :not_matches
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Custom Filter with Lambda
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
filter :published, with: ->(scope, value) {
|
|
106
|
+
value == "true" ? scope.where.not(published_at: nil) : scope.where(published_at: nil)
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Custom Filter Class
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
# Define custom filter
|
|
114
|
+
class DateRangeFilter < Plutonium::Query::Filter
|
|
115
|
+
def apply(scope, start_date: nil, end_date: nil)
|
|
116
|
+
scope = scope.where("#{key} >= ?", start_date.beginning_of_day) if start_date.present?
|
|
117
|
+
scope = scope.where("#{key} <= ?", end_date.end_of_day) if end_date.present?
|
|
118
|
+
scope
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def customize_inputs
|
|
122
|
+
input :start_date, as: :date
|
|
123
|
+
input :end_date, as: :date
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Use in definition
|
|
128
|
+
filter :created_at, with: DateRangeFilter
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Scopes
|
|
132
|
+
|
|
133
|
+
Scopes appear as quick filter buttons. They reference model scopes.
|
|
134
|
+
|
|
135
|
+
### Basic Usage
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
class PostDefinition < ResourceDefinition
|
|
139
|
+
scope :published # Uses Post.published
|
|
140
|
+
scope :draft # Uses Post.draft
|
|
141
|
+
scope :featured # Uses Post.featured
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Inline Scope
|
|
146
|
+
|
|
147
|
+
Use block syntax with the scope passed as an argument:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
scope(:recent) { |scope| scope.where('created_at > ?', 1.week.ago) }
|
|
151
|
+
scope(:this_month) { |scope| scope.where(created_at: Time.current.all_month) }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### With Controller Context
|
|
155
|
+
|
|
156
|
+
Inline scopes have access to controller context like `current_user`:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
scope(:mine) { |scope| scope.where(author: current_user) }
|
|
160
|
+
scope(:my_team) { |scope| scope.where(team: current_user.team) }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Default Scope
|
|
164
|
+
|
|
165
|
+
Set a scope as default to apply it when no scope is explicitly selected:
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
class PostDefinition < ResourceDefinition
|
|
169
|
+
scope :published, default: true # Applied by default
|
|
170
|
+
scope :draft
|
|
171
|
+
scope :archived
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
When a default scope is set:
|
|
176
|
+
- The default scope is applied on initial page load
|
|
177
|
+
- The default scope button is highlighted (not "All")
|
|
178
|
+
- Clicking "All" shows all records without any scope filter
|
|
179
|
+
- URL without scope param uses the default; URL with `?q[scope]=` uses "All"
|
|
180
|
+
|
|
181
|
+
## Sorting
|
|
182
|
+
|
|
183
|
+
### Basic Sorting
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
sort :title
|
|
187
|
+
sort :created_at
|
|
188
|
+
sort :view_count
|
|
189
|
+
|
|
190
|
+
# Multiple at once
|
|
191
|
+
sorts :title, :created_at, :view_count
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Default Sort
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
# Field and direction
|
|
198
|
+
default_sort :created_at, :desc
|
|
199
|
+
default_sort :title, :asc
|
|
200
|
+
|
|
201
|
+
# Complex sorting with block
|
|
202
|
+
default_sort { |scope| scope.order(featured: :desc, created_at: :desc) }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Note:** Default sort only applies when no sort params are provided.
|
|
206
|
+
|
|
207
|
+
## URL Parameters
|
|
208
|
+
|
|
209
|
+
Query parameters are structured under `q`:
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
/posts?q[search]=rails
|
|
213
|
+
/posts?q[status][query]=published
|
|
214
|
+
/posts?q[scope]=recent
|
|
215
|
+
/posts?q[sort_fields][]=created_at&q[sort_directions][created_at]=desc
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Combined:
|
|
219
|
+
```
|
|
220
|
+
/posts?q[search]=rails&q[scope]=published&q[sort_fields][]=created_at&q[sort_directions][created_at]=desc
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Common Patterns
|
|
224
|
+
|
|
225
|
+
### Status Filter
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
class PostDefinition < ResourceDefinition
|
|
229
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
230
|
+
|
|
231
|
+
scope :draft
|
|
232
|
+
scope :published
|
|
233
|
+
scope :archived
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Date-Based Scopes
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
class PostDefinition < ResourceDefinition
|
|
241
|
+
scope(:today) { |scope| scope.where(created_at: Time.current.all_day) }
|
|
242
|
+
scope(:this_week) { |scope| scope.where(created_at: Time.current.all_week) }
|
|
243
|
+
scope(:this_month) { |scope| scope.where(created_at: Time.current.all_month) }
|
|
244
|
+
end
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Archive State Scopes
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
class PostDefinition < ResourceDefinition
|
|
251
|
+
scope :active
|
|
252
|
+
scope :archived
|
|
253
|
+
|
|
254
|
+
# Default to showing only active
|
|
255
|
+
default_sort { |scope| scope.active.order(created_at: :desc) }
|
|
256
|
+
end
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Full-Text Search with pg_search
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
# Model
|
|
263
|
+
class Post < ApplicationRecord
|
|
264
|
+
include PgSearch::Model
|
|
265
|
+
pg_search_scope :search_content, against: [:title, :content]
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Definition
|
|
269
|
+
class PostDefinition < ResourceDefinition
|
|
270
|
+
search do |scope, query|
|
|
271
|
+
scope.search_content(query)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Association Filtering
|
|
277
|
+
|
|
278
|
+
```ruby
|
|
279
|
+
class PostDefinition < ResourceDefinition
|
|
280
|
+
filter :author_name, with: Plutonium::Query::Filters::Text, predicate: :contains
|
|
281
|
+
|
|
282
|
+
search do |scope, query|
|
|
283
|
+
scope.joins(:author).where(
|
|
284
|
+
"posts.title ILIKE :q OR users.name ILIKE :q",
|
|
285
|
+
q: "%#{query}%"
|
|
286
|
+
).distinct
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Complete Example
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
class PostDefinition < ResourceDefinition
|
|
295
|
+
# Full-text search
|
|
296
|
+
search do |scope, query|
|
|
297
|
+
scope.where(
|
|
298
|
+
"title ILIKE :q OR content ILIKE :q",
|
|
299
|
+
q: "%#{query}%"
|
|
300
|
+
)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Filters
|
|
304
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
305
|
+
filter :category, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
306
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :contains
|
|
307
|
+
|
|
308
|
+
# Quick scopes (reference model scopes)
|
|
309
|
+
scope :published
|
|
310
|
+
scope :draft
|
|
311
|
+
scope :featured
|
|
312
|
+
scope(:recent) { |scope| scope.where('created_at > ?', 1.week.ago) }
|
|
313
|
+
|
|
314
|
+
# Sortable columns
|
|
315
|
+
sorts :title, :created_at, :view_count, :published_at
|
|
316
|
+
|
|
317
|
+
# Default: newest first
|
|
318
|
+
default_sort :created_at, :desc
|
|
319
|
+
end
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Performance Tips
|
|
323
|
+
|
|
324
|
+
1. **Add indexes** for filtered/sorted columns
|
|
325
|
+
2. **Use `.distinct`** when joining associations in search
|
|
326
|
+
3. **Consider `pg_search`** for complex full-text search
|
|
327
|
+
4. **Limit search fields** to indexed columns
|
|
328
|
+
5. **Use scopes** instead of filters for common queries
|
|
329
|
+
|
|
330
|
+
## Related Skills
|
|
331
|
+
|
|
332
|
+
- `definition` - Overview and structure
|
|
333
|
+
- `definition-fields` - Fields, inputs, displays
|
|
334
|
+
- `definition-actions` - Actions and interactions
|