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,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)