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.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/# Plutonium: The pre-alpha demo.md +4 -2
  3. data/.claude/skills/assets/SKILL.md +416 -0
  4. data/.claude/skills/connect-resource/SKILL.md +112 -0
  5. data/.claude/skills/controller/SKILL.md +302 -0
  6. data/.claude/skills/create-resource/SKILL.md +240 -0
  7. data/.claude/skills/definition/SKILL.md +218 -0
  8. data/.claude/skills/definition-actions/SKILL.md +386 -0
  9. data/.claude/skills/definition-fields/SKILL.md +474 -0
  10. data/.claude/skills/definition-query/SKILL.md +334 -0
  11. data/.claude/skills/forms/SKILL.md +439 -0
  12. data/.claude/skills/installation/SKILL.md +300 -0
  13. data/.claude/skills/interaction/SKILL.md +382 -0
  14. data/.claude/skills/model/SKILL.md +267 -0
  15. data/.claude/skills/model-features/SKILL.md +286 -0
  16. data/.claude/skills/nested-resources/SKILL.md +274 -0
  17. data/.claude/skills/package/SKILL.md +191 -0
  18. data/.claude/skills/policy/SKILL.md +352 -0
  19. data/.claude/skills/portal/SKILL.md +400 -0
  20. data/.claude/skills/resource/SKILL.md +281 -0
  21. data/.claude/skills/rodauth/SKILL.md +452 -0
  22. data/.claude/skills/views/SKILL.md +563 -0
  23. data/Appraisals +46 -4
  24. data/CHANGELOG.md +32 -1
  25. data/app/assets/plutonium.css +2 -2
  26. data/config/brakeman.ignore +239 -0
  27. data/config/initializers/action_policy.rb +1 -1
  28. data/docs/.vitepress/config.ts +132 -47
  29. data/docs/concepts/architecture.md +226 -0
  30. data/docs/concepts/auto-detection.md +254 -0
  31. data/docs/concepts/index.md +61 -0
  32. data/docs/concepts/packages-portals.md +304 -0
  33. data/docs/concepts/resources.md +224 -0
  34. data/docs/cookbook/blog.md +412 -0
  35. data/docs/cookbook/index.md +289 -0
  36. data/docs/cookbook/saas.md +481 -0
  37. data/docs/getting-started/index.md +56 -0
  38. data/docs/getting-started/installation.md +146 -0
  39. data/docs/getting-started/tutorial/01-setup.md +118 -0
  40. data/docs/getting-started/tutorial/02-first-resource.md +180 -0
  41. data/docs/getting-started/tutorial/03-authentication.md +246 -0
  42. data/docs/getting-started/tutorial/04-authorization.md +170 -0
  43. data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
  44. data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
  45. data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
  46. data/docs/getting-started/tutorial/index.md +64 -0
  47. data/docs/guides/adding-resources.md +420 -0
  48. data/docs/guides/authentication.md +551 -0
  49. data/docs/guides/authorization.md +468 -0
  50. data/docs/guides/creating-packages.md +380 -0
  51. data/docs/guides/custom-actions.md +523 -0
  52. data/docs/guides/index.md +45 -0
  53. data/docs/guides/multi-tenancy.md +302 -0
  54. data/docs/guides/nested-resources.md +411 -0
  55. data/docs/guides/search-filtering.md +266 -0
  56. data/docs/guides/theming.md +321 -0
  57. data/docs/index.md +67 -26
  58. data/docs/public/CLAUDE.md +64 -21
  59. data/docs/reference/assets/index.md +496 -0
  60. data/docs/reference/controller/index.md +363 -0
  61. data/docs/reference/definition/actions.md +400 -0
  62. data/docs/reference/definition/fields.md +350 -0
  63. data/docs/reference/definition/index.md +252 -0
  64. data/docs/reference/definition/query.md +342 -0
  65. data/docs/reference/generators/index.md +469 -0
  66. data/docs/reference/index.md +49 -0
  67. data/docs/reference/interaction/index.md +445 -0
  68. data/docs/reference/model/features.md +248 -0
  69. data/docs/reference/model/index.md +219 -0
  70. data/docs/reference/policy/index.md +385 -0
  71. data/docs/reference/portal/index.md +382 -0
  72. data/docs/reference/views/forms.md +396 -0
  73. data/docs/reference/views/index.md +479 -0
  74. data/gemfiles/rails_7.gemfile +9 -2
  75. data/gemfiles/rails_7.gemfile.lock +146 -111
  76. data/gemfiles/rails_8.0.gemfile +20 -0
  77. data/gemfiles/rails_8.0.gemfile.lock +417 -0
  78. data/gemfiles/rails_8.1.gemfile +20 -0
  79. data/gemfiles/rails_8.1.gemfile.lock +419 -0
  80. data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
  81. data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
  82. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
  83. data/lib/generators/pu/pkg/portal/USAGE +65 -0
  84. data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
  85. data/lib/generators/pu/res/conn/USAGE +71 -0
  86. data/lib/generators/pu/res/model/USAGE +106 -110
  87. data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
  88. data/lib/generators/pu/res/scaffold/USAGE +85 -0
  89. data/lib/generators/pu/rodauth/install_generator.rb +2 -6
  90. data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
  91. data/lib/generators/pu/skills/sync/USAGE +14 -0
  92. data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
  93. data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
  94. data/lib/plutonium/core/controller.rb +2 -2
  95. data/lib/plutonium/interaction/base.rb +1 -0
  96. data/lib/plutonium/package/engine.rb +2 -2
  97. data/lib/plutonium/query/adhoc_block.rb +6 -2
  98. data/lib/plutonium/query/model_scope.rb +1 -1
  99. data/lib/plutonium/railtie.rb +4 -0
  100. data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
  101. data/lib/plutonium/resource/query_object.rb +38 -8
  102. data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
  103. data/lib/plutonium/version.rb +1 -1
  104. data/lib/tasks/release.rake +19 -4
  105. data/package.json +1 -1
  106. metadata +76 -39
  107. data/brakeman.ignore +0 -28
  108. data/docs/api-examples.md +0 -49
  109. data/docs/guide/claude-code-guide.md +0 -74
  110. data/docs/guide/deep-dive/authorization.md +0 -189
  111. data/docs/guide/deep-dive/multitenancy.md +0 -256
  112. data/docs/guide/deep-dive/resources.md +0 -390
  113. data/docs/guide/getting-started/01-installation.md +0 -165
  114. data/docs/guide/index.md +0 -28
  115. data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
  116. data/docs/guide/introduction/02-core-concepts.md +0 -440
  117. data/docs/guide/tutorial/01-project-setup.md +0 -75
  118. data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
  119. data/docs/guide/tutorial/03-defining-resources.md +0 -90
  120. data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
  121. data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
  122. data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
  123. data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
  124. data/docs/markdown-examples.md +0 -85
  125. data/docs/modules/action.md +0 -244
  126. data/docs/modules/authentication.md +0 -236
  127. data/docs/modules/configuration.md +0 -599
  128. data/docs/modules/controller.md +0 -443
  129. data/docs/modules/core.md +0 -316
  130. data/docs/modules/definition.md +0 -1308
  131. data/docs/modules/display.md +0 -759
  132. data/docs/modules/form.md +0 -495
  133. data/docs/modules/generator.md +0 -400
  134. data/docs/modules/index.md +0 -167
  135. data/docs/modules/interaction.md +0 -642
  136. data/docs/modules/package.md +0 -151
  137. data/docs/modules/policy.md +0 -176
  138. data/docs/modules/portal.md +0 -710
  139. data/docs/modules/query.md +0 -297
  140. data/docs/modules/resource_record.md +0 -618
  141. data/docs/modules/routing.md +0 -690
  142. data/docs/modules/table.md +0 -301
  143. 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