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,342 @@
|
|
|
1
|
+
# Definition Query
|
|
2
|
+
|
|
3
|
+
Complete reference for search, filters, scopes, and sorting.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
class PostDefinition < Plutonium::Resource::Definition
|
|
9
|
+
# Search - global text search
|
|
10
|
+
search do |scope, query|
|
|
11
|
+
scope.where("title ILIKE ?", "%#{query}%")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Filters - sidebar filter inputs
|
|
15
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
16
|
+
|
|
17
|
+
# Scopes - quick filter buttons
|
|
18
|
+
scope :published
|
|
19
|
+
scope :draft
|
|
20
|
+
|
|
21
|
+
# Sorting - sortable columns
|
|
22
|
+
sort :title
|
|
23
|
+
sort :created_at
|
|
24
|
+
|
|
25
|
+
# Default sort
|
|
26
|
+
default_sort :created_at, :desc
|
|
27
|
+
end
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Search
|
|
31
|
+
|
|
32
|
+
Define global search across fields:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
# Single field
|
|
36
|
+
search do |scope, query|
|
|
37
|
+
scope.where("title ILIKE ?", "%#{query}%")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Multiple fields
|
|
41
|
+
search do |scope, query|
|
|
42
|
+
scope.where(
|
|
43
|
+
"title ILIKE :q OR content ILIKE :q OR author_name ILIKE :q",
|
|
44
|
+
q: "%#{query}%"
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# With associations
|
|
49
|
+
search do |scope, query|
|
|
50
|
+
scope.joins(:author).where(
|
|
51
|
+
"posts.title ILIKE :q OR users.name ILIKE :q",
|
|
52
|
+
q: "%#{query}%"
|
|
53
|
+
).distinct
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Split search terms
|
|
57
|
+
search do |scope, query|
|
|
58
|
+
terms = query.split(/\s+/)
|
|
59
|
+
terms.reduce(scope) do |current_scope, term|
|
|
60
|
+
current_scope.where("title ILIKE ?", "%#{term}%")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Filters
|
|
66
|
+
|
|
67
|
+
Currently Plutonium provides the **Text filter** with various predicates.
|
|
68
|
+
|
|
69
|
+
### Text Filter Predicates
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# Exact match
|
|
73
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
74
|
+
|
|
75
|
+
# Not equal
|
|
76
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :not_eq
|
|
77
|
+
|
|
78
|
+
# Contains (LIKE %value%)
|
|
79
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :contains
|
|
80
|
+
|
|
81
|
+
# Not contains
|
|
82
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :not_contains
|
|
83
|
+
|
|
84
|
+
# Starts with (LIKE value%)
|
|
85
|
+
filter :slug, with: Plutonium::Query::Filters::Text, predicate: :starts_with
|
|
86
|
+
|
|
87
|
+
# Ends with (LIKE %value)
|
|
88
|
+
filter :email, with: Plutonium::Query::Filters::Text, predicate: :ends_with
|
|
89
|
+
|
|
90
|
+
# Pattern match (* becomes %)
|
|
91
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :matches
|
|
92
|
+
|
|
93
|
+
# Not matching pattern
|
|
94
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :not_matches
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Custom Filter with Lambda
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
filter :published, with: ->(scope, value) {
|
|
101
|
+
value == "true" ? scope.where.not(published_at: nil) : scope.where(published_at: nil)
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Custom Filter Class
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
# Define custom filter
|
|
109
|
+
class DateRangeFilter < Plutonium::Query::Filter
|
|
110
|
+
def apply(scope, start_date: nil, end_date: nil)
|
|
111
|
+
scope = scope.where("#{key} >= ?", start_date.beginning_of_day) if start_date.present?
|
|
112
|
+
scope = scope.where("#{key} <= ?", end_date.end_of_day) if end_date.present?
|
|
113
|
+
scope
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def customize_inputs
|
|
117
|
+
input :start_date, as: :date
|
|
118
|
+
input :end_date, as: :date
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Use in definition
|
|
123
|
+
filter :created_at, with: DateRangeFilter
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Scopes
|
|
127
|
+
|
|
128
|
+
Scopes appear as quick filter buttons. They reference model scopes by name.
|
|
129
|
+
|
|
130
|
+
### Basic Usage
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
class PostDefinition < Plutonium::Resource::Definition
|
|
134
|
+
scope :published # Calls Post.published
|
|
135
|
+
scope :draft # Calls Post.draft
|
|
136
|
+
scope :featured # Calls Post.featured
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The model must define these scopes:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
class Post < ResourceRecord
|
|
144
|
+
scope :published, -> { where.not(published_at: nil) }
|
|
145
|
+
scope :draft, -> { where(published_at: nil) }
|
|
146
|
+
scope :featured, -> { where(featured: true) }
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Default Scope
|
|
151
|
+
|
|
152
|
+
Mark a scope as the default selection:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
scope :active, default: true
|
|
156
|
+
scope :archived
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Inline Scope (Block Syntax)
|
|
160
|
+
|
|
161
|
+
For scopes that don't exist on the model, use block syntax with the scope as an argument:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
scope(:recent) { |scope| scope.where('created_at > ?', 1.week.ago) }
|
|
165
|
+
scope(:this_month) { |scope| scope.where(created_at: Time.current.all_month) }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### With Controller Context
|
|
169
|
+
|
|
170
|
+
Inline scopes have access to controller context like `current_user`:
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
scope(:mine) { |scope| scope.where(author: current_user) }
|
|
174
|
+
scope(:my_team) { |scope| scope.where(team: current_user.team) }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Sorting
|
|
178
|
+
|
|
179
|
+
### Basic Sorting
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
sort :title
|
|
183
|
+
sort :created_at
|
|
184
|
+
sort :view_count
|
|
185
|
+
|
|
186
|
+
# Multiple at once
|
|
187
|
+
sorts :title, :created_at, :view_count
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Default Sort
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
# Field and direction
|
|
194
|
+
default_sort :created_at, :desc
|
|
195
|
+
default_sort :title, :asc
|
|
196
|
+
|
|
197
|
+
# Complex sorting with block
|
|
198
|
+
default_sort { |scope| scope.order(featured: :desc, created_at: :desc) }
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Note:** Default sort only applies when no sort params are provided. The framework default is `id DESC`.
|
|
202
|
+
|
|
203
|
+
## URL Parameters
|
|
204
|
+
|
|
205
|
+
Query parameters are structured under `q`:
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
/posts?q[search]=rails
|
|
209
|
+
/posts?q[status][query]=published
|
|
210
|
+
/posts?q[scope]=recent
|
|
211
|
+
/posts?q[sort_fields][]=created_at&q[sort_directions][created_at]=desc
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Combined:
|
|
215
|
+
```
|
|
216
|
+
/posts?q[search]=rails&q[scope]=published&q[sort_fields][]=created_at&q[sort_directions][created_at]=desc
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Common Patterns
|
|
220
|
+
|
|
221
|
+
### Status Filter
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
class PostDefinition < Plutonium::Resource::Definition
|
|
225
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
226
|
+
|
|
227
|
+
scope :draft
|
|
228
|
+
scope :published
|
|
229
|
+
scope :archived
|
|
230
|
+
end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Date-Based Scopes
|
|
234
|
+
|
|
235
|
+
Define scopes on the model:
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
class Post < ResourceRecord
|
|
239
|
+
scope :today, -> { where(created_at: Time.current.all_day) }
|
|
240
|
+
scope :this_week, -> { where(created_at: Time.current.all_week) }
|
|
241
|
+
scope :this_month, -> { where(created_at: Time.current.all_month) }
|
|
242
|
+
end
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Then reference them in the definition:
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
class PostDefinition < Plutonium::Resource::Definition
|
|
249
|
+
scope :today
|
|
250
|
+
scope :this_week
|
|
251
|
+
scope :this_month
|
|
252
|
+
end
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Archive State Scopes
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
class PostDefinition < Plutonium::Resource::Definition
|
|
259
|
+
scope :active
|
|
260
|
+
scope :archived
|
|
261
|
+
|
|
262
|
+
# Default to showing only active
|
|
263
|
+
default_sort { |scope| scope.active.order(created_at: :desc) }
|
|
264
|
+
end
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Full-Text Search with pg_search
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
# Model
|
|
271
|
+
class Post < ApplicationRecord
|
|
272
|
+
include PgSearch::Model
|
|
273
|
+
pg_search_scope :search_content, against: [:title, :content]
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Definition
|
|
277
|
+
class PostDefinition < Plutonium::Resource::Definition
|
|
278
|
+
search do |scope, query|
|
|
279
|
+
scope.search_content(query)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Association Filtering
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
class PostDefinition < Plutonium::Resource::Definition
|
|
288
|
+
filter :author_name, with: Plutonium::Query::Filters::Text, predicate: :contains
|
|
289
|
+
|
|
290
|
+
search do |scope, query|
|
|
291
|
+
scope.joins(:author).where(
|
|
292
|
+
"posts.title ILIKE :q OR users.name ILIKE :q",
|
|
293
|
+
q: "%#{query}%"
|
|
294
|
+
).distinct
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Complete Example
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
class PostDefinition < Plutonium::Resource::Definition
|
|
303
|
+
# Full-text search
|
|
304
|
+
search do |scope, query|
|
|
305
|
+
scope.where(
|
|
306
|
+
"title ILIKE :q OR content ILIKE :q",
|
|
307
|
+
q: "%#{query}%"
|
|
308
|
+
)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Filters
|
|
312
|
+
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
313
|
+
filter :category, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
314
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :contains
|
|
315
|
+
|
|
316
|
+
# Quick scopes (reference model scopes)
|
|
317
|
+
scope :published
|
|
318
|
+
scope :draft
|
|
319
|
+
scope :featured
|
|
320
|
+
scope(:recent) { |scope| scope.where('created_at > ?', 1.week.ago) }
|
|
321
|
+
|
|
322
|
+
# Sortable columns
|
|
323
|
+
sorts :title, :created_at, :view_count, :published_at
|
|
324
|
+
|
|
325
|
+
# Default: newest first
|
|
326
|
+
default_sort :created_at, :desc
|
|
327
|
+
end
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Performance Tips
|
|
331
|
+
|
|
332
|
+
1. **Add indexes** for filtered/sorted columns
|
|
333
|
+
2. **Use `.distinct`** when joining associations in search
|
|
334
|
+
3. **Consider `pg_search`** for complex full-text search
|
|
335
|
+
4. **Limit search fields** to indexed columns
|
|
336
|
+
5. **Use scopes** instead of filters for common queries
|
|
337
|
+
|
|
338
|
+
## Related
|
|
339
|
+
|
|
340
|
+
- [Definition Reference](./index)
|
|
341
|
+
- [Fields Reference](./fields)
|
|
342
|
+
- [Actions Reference](./actions)
|