plutonium 0.34.1 → 0.35.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/.claude/skills/plutonium/skill.md +53 -0
- data/.claude/skills/{assets → plutonium-assets}/SKILL.md +13 -8
- data/.claude/skills/{connect-resource → plutonium-connect-resource}/SKILL.md +1 -1
- data/.claude/skills/{controller → plutonium-controller}/SKILL.md +27 -13
- data/.claude/skills/{create-resource → plutonium-create-resource}/SKILL.md +1 -1
- data/.claude/skills/{definition → plutonium-definition}/SKILL.md +10 -10
- data/.claude/skills/{definition-actions → plutonium-definition-actions}/SKILL.md +34 -9
- data/.claude/skills/{definition-fields → plutonium-definition-fields}/SKILL.md +38 -10
- data/.claude/skills/plutonium-definition-query/SKILL.md +356 -0
- data/.claude/skills/{forms → plutonium-forms}/SKILL.md +6 -6
- data/.claude/skills/{installation → plutonium-installation}/SKILL.md +9 -9
- data/.claude/skills/{interaction → plutonium-interaction}/SKILL.md +20 -19
- data/.claude/skills/{model → plutonium-model}/SKILL.md +3 -3
- data/.claude/skills/{model-features → plutonium-model-features}/SKILL.md +3 -3
- data/.claude/skills/{nested-resources → plutonium-nested-resources}/SKILL.md +5 -5
- data/.claude/skills/{package → plutonium-package}/SKILL.md +7 -8
- data/.claude/skills/{policy → plutonium-policy}/SKILL.md +26 -4
- data/.claude/skills/{portal → plutonium-portal}/SKILL.md +33 -31
- data/.claude/skills/{resource → plutonium-resource}/SKILL.md +27 -27
- data/.claude/skills/{rodauth → plutonium-rodauth}/SKILL.md +5 -5
- data/.claude/skills/plutonium-theming/SKILL.md +424 -0
- data/.claude/skills/{views → plutonium-views}/SKILL.md +7 -7
- data/CHANGELOG.md +52 -0
- data/CLAUDE.md +215 -0
- data/CONTRIBUTING.md +72 -18
- data/README.md +100 -19
- data/app/assets/plutonium.css +1 -11
- data/app/assets/plutonium.js +1685 -1146
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +70 -70
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/resource/interactive_bulk_action.html.erb +1 -5
- data/app/views/rodauth/_email_auth_request_form.html.erb +1 -1
- data/app/views/rodauth/_login_form.html.erb +15 -55
- data/app/views/rodauth/_login_form_footer.html.erb +2 -2
- data/app/views/rodauth/_password_visibility.html.erb +2 -8
- data/app/views/rodauth/add_recovery_codes.html.erb +2 -2
- data/app/views/rodauth/change_login.html.erb +36 -19
- data/app/views/rodauth/change_password.html.erb +34 -10
- data/app/views/rodauth/close_account.html.erb +12 -4
- data/app/views/rodauth/confirm_password.html.erb +19 -17
- data/app/views/rodauth/create_account.html.erb +30 -109
- data/app/views/rodauth/email_auth.html.erb +1 -1
- data/app/views/rodauth/logout.html.erb +4 -4
- data/app/views/rodauth/otp_auth.html.erb +13 -4
- data/app/views/rodauth/otp_disable.html.erb +12 -4
- data/app/views/rodauth/otp_setup.html.erb +29 -12
- data/app/views/rodauth/otp_unlock.html.erb +19 -10
- data/app/views/rodauth/otp_unlock_not_available.html.erb +7 -7
- data/app/views/rodauth/recovery_auth.html.erb +12 -4
- data/app/views/rodauth/recovery_codes.html.erb +12 -4
- data/app/views/rodauth/remember.html.erb +7 -7
- data/app/views/rodauth/reset_password.html.erb +23 -7
- data/app/views/rodauth/reset_password_request.html.erb +14 -10
- data/app/views/rodauth/sms_auth.html.erb +13 -4
- data/app/views/rodauth/sms_confirm.html.erb +13 -4
- data/app/views/rodauth/sms_disable.html.erb +12 -4
- data/app/views/rodauth/sms_request.html.erb +1 -1
- data/app/views/rodauth/sms_setup.html.erb +23 -7
- data/app/views/rodauth/two_factor_auth.html.erb +2 -2
- data/app/views/rodauth/two_factor_disable.html.erb +12 -4
- data/app/views/rodauth/two_factor_manage.html.erb +7 -7
- data/app/views/rodauth/unlock_account.html.erb +13 -5
- data/app/views/rodauth/unlock_account_request.html.erb +2 -2
- data/app/views/rodauth/verify_account.html.erb +25 -7
- data/app/views/rodauth/verify_account_resend.html.erb +14 -10
- data/app/views/rodauth/verify_login_change.html.erb +1 -1
- data/app/views/rodauth/webauthn_auth.html.erb +1 -1
- data/app/views/rodauth/webauthn_remove.html.erb +18 -8
- data/app/views/rodauth/webauthn_setup.html.erb +12 -4
- data/docs/.vitepress/config.ts +15 -26
- data/docs/.vitepress/theme/custom.css +388 -29
- data/docs/getting-started/index.md +1 -1
- data/docs/getting-started/tutorial/02-first-resource.md +9 -0
- data/docs/getting-started/tutorial/06-nested-resources.md +2 -2
- data/docs/getting-started/tutorial/07-author-portal.md +191 -0
- data/docs/getting-started/tutorial/{07-customizing-ui.md → 08-customizing-ui.md} +7 -7
- data/docs/getting-started/tutorial/index.md +5 -2
- data/docs/guides/authorization.md +33 -0
- data/docs/guides/creating-packages.md +12 -16
- data/docs/guides/custom-actions.md +36 -0
- data/docs/guides/search-filtering.md +121 -42
- data/docs/guides/theming.md +232 -36
- data/docs/index.md +203 -57
- data/docs/public/og-image.png +0 -0
- data/docs/reference/controller/index.md +14 -16
- data/docs/reference/definition/actions.md +38 -3
- data/docs/reference/definition/fields.md +3 -3
- data/docs/reference/definition/index.md +2 -2
- data/docs/reference/generators/index.md +0 -1
- data/docs/reference/interaction/index.md +14 -10
- data/docs/reference/model/index.md +0 -1
- data/docs/reference/portal/index.md +13 -27
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/pkg/portal/portal_generator.rb +0 -2
- data/lib/generators/pu/pkg/portal/templates/app/views/package/dashboard/index.html.erb +28 -72
- data/lib/plutonium/action/interactive.rb +2 -2
- data/lib/plutonium/core/controller.rb +2 -1
- data/lib/plutonium/definition/actions.rb +2 -2
- data/lib/plutonium/lib/deep_freezer.rb +3 -7
- data/lib/plutonium/query/filter.rb +14 -0
- data/lib/plutonium/query/filters/association.rb +49 -0
- data/lib/plutonium/query/filters/boolean.rb +35 -0
- data/lib/plutonium/query/filters/date.rb +97 -0
- data/lib/plutonium/query/filters/date_range.rb +58 -0
- data/lib/plutonium/query/filters/select.rb +55 -0
- data/lib/plutonium/resource/controllers/crud_actions.rb +24 -6
- data/lib/plutonium/resource/controllers/interactive_actions.rb +76 -58
- data/lib/plutonium/resource/controllers/queryable.rb +4 -2
- data/lib/plutonium/resource/query_object.rb +1 -1
- data/lib/plutonium/ui/action_button.rb +23 -65
- data/lib/plutonium/ui/actions_dropdown.rb +103 -0
- data/lib/plutonium/ui/block.rb +1 -1
- data/lib/plutonium/ui/breadcrumbs.rb +12 -19
- data/lib/plutonium/ui/color_mode_selector.rb +1 -1
- data/lib/plutonium/ui/component/kit.rb +6 -0
- data/lib/plutonium/ui/component_classes.rb +102 -0
- data/lib/plutonium/ui/display/base.rb +15 -0
- data/lib/plutonium/ui/display/components/attachment.rb +6 -5
- data/lib/plutonium/ui/display/components/boolean.rb +23 -0
- data/lib/plutonium/ui/display/components/color.rb +23 -0
- data/lib/plutonium/ui/display/resource.rb +1 -1
- data/lib/plutonium/ui/display/theme.rb +29 -15
- data/lib/plutonium/ui/empty_card.rb +3 -3
- data/lib/plutonium/ui/form/base.rb +20 -0
- data/lib/plutonium/ui/form/components/key_value_store.rb +11 -11
- data/lib/plutonium/ui/form/components/resource_select.rb +31 -0
- data/lib/plutonium/ui/form/components/secure_association.rb +1 -2
- data/lib/plutonium/ui/form/components/uppy.rb +5 -4
- data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +4 -4
- data/lib/plutonium/ui/form/interaction.rb +17 -1
- data/lib/plutonium/ui/form/query.rb +133 -80
- data/lib/plutonium/ui/form/theme.rb +50 -35
- data/lib/plutonium/ui/frame_navigator_panel.rb +2 -2
- data/lib/plutonium/ui/layout/base.rb +1 -1
- data/lib/plutonium/ui/layout/header.rb +4 -7
- data/lib/plutonium/ui/layout/rodauth_layout.rb +7 -7
- data/lib/plutonium/ui/layout/sidebar.rb +1 -1
- data/lib/plutonium/ui/nav_grid_menu.rb +7 -6
- data/lib/plutonium/ui/nav_user.rb +9 -8
- data/lib/plutonium/ui/page/interactive_action.rb +5 -5
- data/lib/plutonium/ui/page_header.rb +29 -10
- data/lib/plutonium/ui/panel.rb +4 -4
- data/lib/plutonium/ui/sidebar_menu.rb +8 -8
- data/lib/plutonium/ui/skeleton_table.rb +7 -8
- data/lib/plutonium/ui/tab_list.rb +5 -5
- data/lib/plutonium/ui/table/base.rb +3 -0
- data/lib/plutonium/ui/table/components/attachment.rb +4 -3
- data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +82 -0
- data/lib/plutonium/ui/table/components/pagy_info.rb +2 -2
- data/lib/plutonium/ui/table/components/pagy_pagination.rb +13 -8
- data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +101 -0
- data/lib/plutonium/ui/table/components/scopes_bar.rb +2 -2
- data/lib/plutonium/ui/table/components/selection_column.rb +100 -0
- data/lib/plutonium/ui/table/display_theme.rb +6 -6
- data/lib/plutonium/ui/table/resource.rb +93 -52
- data/lib/plutonium/ui/table/theme.rb +28 -15
- data/lib/plutonium/version.rb +1 -1
- data/package.json +2 -2
- data/plutonium.gemspec +5 -4
- data/src/css/components.css +471 -0
- data/src/css/intl_tel_input.css +2 -2
- data/src/css/plutonium.css +2 -0
- data/src/css/tokens.css +149 -0
- data/src/js/controllers/bulk_actions_controller.js +109 -0
- data/src/js/controllers/filter_panel_controller.js +35 -0
- data/src/js/controllers/register_controllers.js +5 -1
- data/src/js/controllers/resource_drop_down_controller.js +25 -1
- data/src/js/controllers/slim_select_controller.js +6 -2
- data/src/js/turbo/turbo_actions.js +1 -1
- metadata +52 -39
- data/.claude/skills/definition-query/SKILL.md +0 -334
- data/docs/concepts/architecture.md +0 -226
- data/docs/concepts/auto-detection.md +0 -254
- data/docs/concepts/index.md +0 -61
- data/docs/concepts/packages-portals.md +0 -304
- data/docs/concepts/resources.md +0 -224
- data/docs/cookbook/blog.md +0 -411
- data/docs/cookbook/index.md +0 -289
- data/docs/cookbook/saas.md +0 -481
- data/docs/public/CLAUDE.md +0 -578
- data/lib/generators/pu/pkg/portal/templates/app/controllers/resource_controller.rb.tt +0 -5
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plutonium-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 - dropdown filter panel
|
|
20
|
+
filter :title, with: :text, predicate: :contains
|
|
21
|
+
filter :status, with: :select, choices: %w[draft published archived]
|
|
22
|
+
filter :published, with: :boolean
|
|
23
|
+
filter :created_at, with: :date_range
|
|
24
|
+
filter :category, with: :association
|
|
25
|
+
|
|
26
|
+
# Scopes - quick filter buttons
|
|
27
|
+
scope :published
|
|
28
|
+
scope :draft
|
|
29
|
+
|
|
30
|
+
# Sorting - sortable columns
|
|
31
|
+
sort :title
|
|
32
|
+
sort :created_at
|
|
33
|
+
|
|
34
|
+
# Default sort
|
|
35
|
+
default_sort :created_at, :desc
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Search
|
|
40
|
+
|
|
41
|
+
Define global search across fields:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
# Single field
|
|
45
|
+
search do |scope, query|
|
|
46
|
+
scope.where("title ILIKE ?", "%#{query}%")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Multiple fields
|
|
50
|
+
search do |scope, query|
|
|
51
|
+
scope.where(
|
|
52
|
+
"title ILIKE :q OR content ILIKE :q OR author_name ILIKE :q",
|
|
53
|
+
q: "%#{query}%"
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# With associations
|
|
58
|
+
search do |scope, query|
|
|
59
|
+
scope.joins(:author).where(
|
|
60
|
+
"posts.title ILIKE :q OR users.name ILIKE :q",
|
|
61
|
+
q: "%#{query}%"
|
|
62
|
+
).distinct
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Split search terms
|
|
66
|
+
search do |scope, query|
|
|
67
|
+
terms = query.split(/\s+/)
|
|
68
|
+
terms.reduce(scope) do |current_scope, term|
|
|
69
|
+
current_scope.where("title ILIKE ?", "%#{term}%")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Filters
|
|
75
|
+
|
|
76
|
+
Plutonium provides **6 built-in filter types**. Use shorthand symbols or full class names.
|
|
77
|
+
|
|
78
|
+
### Text Filter
|
|
79
|
+
|
|
80
|
+
String/text filtering with pattern matching.
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
# Shorthand (recommended)
|
|
84
|
+
filter :title, with: :text, predicate: :contains
|
|
85
|
+
filter :status, with: :text, predicate: :eq
|
|
86
|
+
|
|
87
|
+
# Full class name
|
|
88
|
+
filter :title, with: Plutonium::Query::Filters::Text, predicate: :contains
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Predicates:**
|
|
92
|
+
|
|
93
|
+
| Predicate | SQL | Description |
|
|
94
|
+
|-----------|-----|-------------|
|
|
95
|
+
| `:eq` | `= value` | Exact match (default) |
|
|
96
|
+
| `:not_eq` | `!= value` | Not equal |
|
|
97
|
+
| `:contains` | `LIKE %value%` | Contains substring |
|
|
98
|
+
| `:not_contains` | `NOT LIKE %value%` | Does not contain |
|
|
99
|
+
| `:starts_with` | `LIKE value%` | Starts with |
|
|
100
|
+
| `:ends_with` | `LIKE %value` | Ends with |
|
|
101
|
+
| `:matches` | `LIKE value` | Pattern match (`*` becomes `%`) |
|
|
102
|
+
| `:not_matches` | `NOT LIKE value` | Does not match pattern |
|
|
103
|
+
|
|
104
|
+
### Boolean Filter
|
|
105
|
+
|
|
106
|
+
True/false filtering for boolean columns.
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
# Basic
|
|
110
|
+
filter :active, with: :boolean
|
|
111
|
+
|
|
112
|
+
# Custom labels
|
|
113
|
+
filter :published, with: :boolean, true_label: "Published", false_label: "Draft"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Renders a select dropdown with "All", true label ("Yes"), and false label ("No").
|
|
117
|
+
|
|
118
|
+
### Date Filter
|
|
119
|
+
|
|
120
|
+
Single date filtering with comparison predicates.
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
filter :created_at, with: :date, predicate: :gteq # On or after
|
|
124
|
+
filter :due_date, with: :date, predicate: :lt # Before
|
|
125
|
+
filter :published_at, with: :date, predicate: :eq # On exact date
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Predicates:**
|
|
129
|
+
|
|
130
|
+
| Predicate | Description |
|
|
131
|
+
|-----------|-------------|
|
|
132
|
+
| `:eq` | On this date (default) |
|
|
133
|
+
| `:not_eq` | Not on this date |
|
|
134
|
+
| `:lt` | Before date |
|
|
135
|
+
| `:lteq` | On or before date |
|
|
136
|
+
| `:gt` | After date |
|
|
137
|
+
| `:gteq` | On or after date |
|
|
138
|
+
|
|
139
|
+
### Date Range Filter
|
|
140
|
+
|
|
141
|
+
Filter between two dates (from/to).
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# Basic
|
|
145
|
+
filter :created_at, with: :date_range
|
|
146
|
+
|
|
147
|
+
# Custom labels
|
|
148
|
+
filter :published_at, with: :date_range,
|
|
149
|
+
from_label: "Published from",
|
|
150
|
+
to_label: "Published to"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Renders two date pickers. Both are optional - users can filter with just "from" or just "to".
|
|
154
|
+
|
|
155
|
+
### Select Filter
|
|
156
|
+
|
|
157
|
+
Filter from predefined choices.
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
# Static choices (array)
|
|
161
|
+
filter :status, with: :select, choices: %w[draft published archived]
|
|
162
|
+
|
|
163
|
+
# Dynamic choices (proc)
|
|
164
|
+
filter :category, with: :select, choices: -> { Category.pluck(:name) }
|
|
165
|
+
|
|
166
|
+
# Multiple selection
|
|
167
|
+
filter :tags, with: :select, choices: %w[ruby rails js], multiple: true
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Association Filter
|
|
171
|
+
|
|
172
|
+
Filter by associated record.
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
# Basic - infers Category class from :category key
|
|
176
|
+
filter :category, with: :association
|
|
177
|
+
|
|
178
|
+
# Explicit class
|
|
179
|
+
filter :author, with: :association, class_name: User
|
|
180
|
+
|
|
181
|
+
# Multiple selection
|
|
182
|
+
filter :tags, with: :association, class_name: Tag, multiple: true
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Renders a resource select dropdown. Converts filter key to foreign key (`:category` -> `:category_id`).
|
|
186
|
+
|
|
187
|
+
## Custom Filters
|
|
188
|
+
|
|
189
|
+
### Custom Filter Class
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
class PriceRangeFilter < Plutonium::Query::Filter
|
|
193
|
+
def apply(scope, min: nil, max: nil)
|
|
194
|
+
scope = scope.where("price >= ?", min) if min.present?
|
|
195
|
+
scope = scope.where("price <= ?", max) if max.present?
|
|
196
|
+
scope
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def customize_inputs
|
|
200
|
+
input :min, as: :number
|
|
201
|
+
input :max, as: :number
|
|
202
|
+
field :min, placeholder: "Min price..."
|
|
203
|
+
field :max, placeholder: "Max price..."
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Use in definition
|
|
208
|
+
filter :price, with: PriceRangeFilter
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Scopes
|
|
212
|
+
|
|
213
|
+
Scopes appear as quick filter buttons. They reference model scopes.
|
|
214
|
+
|
|
215
|
+
### Basic Usage
|
|
216
|
+
|
|
217
|
+
```ruby
|
|
218
|
+
class PostDefinition < ResourceDefinition
|
|
219
|
+
scope :published # Uses Post.published
|
|
220
|
+
scope :draft # Uses Post.draft
|
|
221
|
+
scope :featured # Uses Post.featured
|
|
222
|
+
end
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Inline Scope
|
|
226
|
+
|
|
227
|
+
Use block syntax with the scope passed as an argument:
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
scope(:recent) { |scope| scope.where('created_at > ?', 1.week.ago) }
|
|
231
|
+
scope(:this_month) { |scope| scope.where(created_at: Time.current.all_month) }
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### With Controller Context
|
|
235
|
+
|
|
236
|
+
Inline scopes have access to controller context like `current_user`:
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
scope(:mine) { |scope| scope.where(author: current_user) }
|
|
240
|
+
scope(:my_team) { |scope| scope.where(team: current_user.team) }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Default Scope
|
|
244
|
+
|
|
245
|
+
Set a scope as default to apply it when no scope is explicitly selected:
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
class PostDefinition < ResourceDefinition
|
|
249
|
+
scope :published, default: true # Applied by default
|
|
250
|
+
scope :draft
|
|
251
|
+
scope :archived
|
|
252
|
+
end
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
When a default scope is set:
|
|
256
|
+
- The default scope is applied on initial page load
|
|
257
|
+
- The default scope button is highlighted (not "All")
|
|
258
|
+
- Clicking "All" shows all records without any scope filter
|
|
259
|
+
- URL without scope param uses the default; URL with `?q[scope]=` uses "All"
|
|
260
|
+
|
|
261
|
+
## Sorting
|
|
262
|
+
|
|
263
|
+
### Basic Sorting
|
|
264
|
+
|
|
265
|
+
```ruby
|
|
266
|
+
sort :title
|
|
267
|
+
sort :created_at
|
|
268
|
+
sort :view_count
|
|
269
|
+
|
|
270
|
+
# Multiple at once
|
|
271
|
+
sorts :title, :created_at, :view_count
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Default Sort
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
# Field and direction
|
|
278
|
+
default_sort :created_at, :desc
|
|
279
|
+
default_sort :title, :asc
|
|
280
|
+
|
|
281
|
+
# Complex sorting with block
|
|
282
|
+
default_sort { |scope| scope.order(featured: :desc, created_at: :desc) }
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Note:** Default sort only applies when no sort params are provided.
|
|
286
|
+
|
|
287
|
+
## URL Parameters
|
|
288
|
+
|
|
289
|
+
Query parameters are structured under `q`:
|
|
290
|
+
|
|
291
|
+
```
|
|
292
|
+
/posts?q[search]=rails
|
|
293
|
+
/posts?q[title][query]=widget
|
|
294
|
+
/posts?q[status][value]=published
|
|
295
|
+
/posts?q[created_at][from]=2024-01-01&q[created_at][to]=2024-12-31
|
|
296
|
+
/posts?q[scope]=recent
|
|
297
|
+
/posts?q[sort_fields][]=created_at&q[sort_directions][created_at]=desc
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Filter Summary Table
|
|
301
|
+
|
|
302
|
+
| Type | Symbol | Input Params | Options |
|
|
303
|
+
|------|--------|--------------|---------|
|
|
304
|
+
| Text | `:text` | `query` | `predicate:` |
|
|
305
|
+
| Boolean | `:boolean` | `value` | `true_label:`, `false_label:` |
|
|
306
|
+
| Date | `:date` | `value` | `predicate:` |
|
|
307
|
+
| Date Range | `:date_range` | `from`, `to` | `from_label:`, `to_label:` |
|
|
308
|
+
| Select | `:select` | `value` | `choices:`, `multiple:` |
|
|
309
|
+
| Association | `:association` | `value` | `class_name:`, `multiple:` |
|
|
310
|
+
|
|
311
|
+
## Complete Example
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
class ProductDefinition < ResourceDefinition
|
|
315
|
+
# Full-text search
|
|
316
|
+
search do |scope, query|
|
|
317
|
+
scope.where(
|
|
318
|
+
"name ILIKE :q OR description ILIKE :q",
|
|
319
|
+
q: "%#{query}%"
|
|
320
|
+
)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Filters
|
|
324
|
+
filter :name, with: :text, predicate: :contains
|
|
325
|
+
filter :status, with: :select, choices: %w[draft active discontinued]
|
|
326
|
+
filter :featured, with: :boolean
|
|
327
|
+
filter :created_at, with: :date_range
|
|
328
|
+
filter :price, with: :date, predicate: :gteq
|
|
329
|
+
filter :category, with: :association
|
|
330
|
+
|
|
331
|
+
# Quick scopes
|
|
332
|
+
scope :active, default: true
|
|
333
|
+
scope :featured
|
|
334
|
+
scope(:recent) { |scope| scope.where('created_at > ?', 1.week.ago) }
|
|
335
|
+
|
|
336
|
+
# Sortable columns
|
|
337
|
+
sorts :name, :price, :created_at
|
|
338
|
+
|
|
339
|
+
# Default sort
|
|
340
|
+
default_sort :created_at, :desc
|
|
341
|
+
end
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Performance Tips
|
|
345
|
+
|
|
346
|
+
1. **Add indexes** for filtered/sorted columns
|
|
347
|
+
2. **Use `.distinct`** when joining associations in search
|
|
348
|
+
3. **Consider `pg_search`** for complex full-text search
|
|
349
|
+
4. **Limit search fields** to indexed columns
|
|
350
|
+
5. **Use scopes** instead of filters for common queries
|
|
351
|
+
|
|
352
|
+
## Related Skills
|
|
353
|
+
|
|
354
|
+
- `plutonium-definition` - Overview and structure
|
|
355
|
+
- `plutonium-definition-fields` - Fields, inputs, displays
|
|
356
|
+
- `plutonium-definition-actions` - Actions and interactions
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: forms
|
|
2
|
+
name: plutonium-forms
|
|
3
3
|
description: Plutonium forms - custom templates, Phlex form components, field builders, and theming
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -432,8 +432,8 @@ end
|
|
|
432
432
|
|
|
433
433
|
## Related Skills
|
|
434
434
|
|
|
435
|
-
- `definition-fields` - Input configuration (as:, hint:, condition:)
|
|
436
|
-
- `views` - Custom page classes
|
|
437
|
-
- `assets` - TailwindCSS and component theming
|
|
438
|
-
- `interaction` - Interactive action forms
|
|
439
|
-
- `nested-resources` - Parent/child forms
|
|
435
|
+
- `plutonium-definition-fields` - Input configuration (as:, hint:, condition:)
|
|
436
|
+
- `plutonium-views` - Custom page classes
|
|
437
|
+
- `plutonium-assets` - TailwindCSS and component theming
|
|
438
|
+
- `plutonium-interaction` - Interactive action forms
|
|
439
|
+
- `plutonium-nested-resources` - Parent/child forms
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: installation
|
|
2
|
+
name: plutonium-installation
|
|
3
3
|
description: Installing Plutonium in a Rails application - setup, generators, and configuration
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -290,11 +290,11 @@ For models that already exist in your app:
|
|
|
290
290
|
|
|
291
291
|
## Related Skills
|
|
292
292
|
|
|
293
|
-
- `resource` - Resource architecture overview
|
|
294
|
-
- `rodauth` - Authentication setup and configuration
|
|
295
|
-
- `package` - Feature and portal packages
|
|
296
|
-
- `portal` - Portal configuration
|
|
297
|
-
- `views` - Custom pages, layouts, and Phlex components
|
|
298
|
-
- `assets` - TailwindCSS and custom styling
|
|
299
|
-
- `create-resource` - Resource scaffold options
|
|
300
|
-
- `connect-resource` - Portal connection
|
|
293
|
+
- `plutonium-resource` - Resource architecture overview
|
|
294
|
+
- `plutonium-rodauth` - Authentication setup and configuration
|
|
295
|
+
- `plutonium-package` - Feature and portal packages
|
|
296
|
+
- `plutonium-portal` - Portal configuration
|
|
297
|
+
- `plutonium-views` - Custom pages, layouts, and Phlex components
|
|
298
|
+
- `plutonium-assets` - TailwindCSS and custom styling
|
|
299
|
+
- `plutonium-create-resource` - Resource scaffold options
|
|
300
|
+
- `plutonium-connect-resource` - Portal connection
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: interaction
|
|
2
|
+
name: plutonium-interaction
|
|
3
3
|
description: Plutonium interactions - encapsulated business logic for custom actions
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -68,7 +68,7 @@ input :content, as: :text
|
|
|
68
68
|
input :date, as: :date
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
See `definition-fields` skill for all input types and options.
|
|
71
|
+
See `plutonium-definition-fields` skill for all input types and options.
|
|
72
72
|
|
|
73
73
|
## Presentation
|
|
74
74
|
|
|
@@ -103,25 +103,22 @@ end
|
|
|
103
103
|
### Success Outcomes
|
|
104
104
|
|
|
105
105
|
```ruby
|
|
106
|
-
# Basic success
|
|
106
|
+
# Basic success (redirects automatically to resource)
|
|
107
107
|
succeed(resource)
|
|
108
108
|
|
|
109
109
|
# With message
|
|
110
110
|
succeed(resource).with_message("Done!")
|
|
111
111
|
succeed(resource).with_message("Warning!", :alert)
|
|
112
112
|
|
|
113
|
-
# With redirect
|
|
114
|
-
succeed(resource).with_redirect_response(
|
|
113
|
+
# With custom redirect (only if different from default)
|
|
114
|
+
succeed(resource).with_redirect_response(custom_path)
|
|
115
115
|
|
|
116
116
|
# With file download
|
|
117
117
|
succeed(resource).with_file_response(file_path, filename: "report.pdf")
|
|
118
|
-
|
|
119
|
-
# Chaining
|
|
120
|
-
succeed(resource)
|
|
121
|
-
.with_message("Created!")
|
|
122
|
-
.with_redirect_response(resource_path(resource))
|
|
123
118
|
```
|
|
124
119
|
|
|
120
|
+
**Note:** Redirect is automatic on success - the controller redirects to the resource by default. Only use `with_redirect_response` if you need a different destination.
|
|
121
|
+
|
|
125
122
|
### Failure Outcomes
|
|
126
123
|
|
|
127
124
|
```ruby
|
|
@@ -183,10 +180,14 @@ class ArchiveInteraction < Plutonium::Resource::Interaction
|
|
|
183
180
|
def execute
|
|
184
181
|
resource.update!(archived: true)
|
|
185
182
|
succeed(resource)
|
|
183
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
184
|
+
failed(e.record.errors)
|
|
186
185
|
end
|
|
187
186
|
end
|
|
188
187
|
```
|
|
189
188
|
|
|
189
|
+
**Note:** `ActiveRecord::RecordInvalid` is NOT rescued automatically. Always rescue it when using bang methods (`create!`, `update!`, `save!`).
|
|
190
|
+
|
|
190
191
|
### Resource Actions
|
|
191
192
|
|
|
192
193
|
Act at the collection/class level (no specific record):
|
|
@@ -208,11 +209,11 @@ end
|
|
|
208
209
|
|
|
209
210
|
### Bulk Actions (Multiple Records)
|
|
210
211
|
|
|
211
|
-
Act on multiple selected records
|
|
212
|
+
Act on multiple selected records. When registered, the table shows checkboxes and a toolbar appears when records are selected.
|
|
212
213
|
|
|
213
214
|
```ruby
|
|
214
215
|
class BulkArchiveInteraction < Plutonium::Resource::Interaction
|
|
215
|
-
attribute :resources # Collection of records
|
|
216
|
+
attribute :resources # Collection of records (note: plural)
|
|
216
217
|
|
|
217
218
|
def execute
|
|
218
219
|
resources.update_all(archived: true)
|
|
@@ -221,6 +222,8 @@ class BulkArchiveInteraction < Plutonium::Resource::Interaction
|
|
|
221
222
|
end
|
|
222
223
|
```
|
|
223
224
|
|
|
225
|
+
**Authorization:** Bulk actions use per-record authorization. The policy method is checked for each selected record - if any fails, the entire request is rejected. The UI only shows actions that all selected records support.
|
|
226
|
+
|
|
224
227
|
## Connecting to Definitions
|
|
225
228
|
|
|
226
229
|
Register interactions as actions:
|
|
@@ -344,9 +347,7 @@ class Company::InviteUserInteraction < Plutonium::Resource::Interaction
|
|
|
344
347
|
)
|
|
345
348
|
UserInviteMailer.invitation(invite).deliver_later
|
|
346
349
|
|
|
347
|
-
succeed(resource)
|
|
348
|
-
.with_message("Invitation sent to #{email}")
|
|
349
|
-
.with_redirect_response(resource)
|
|
350
|
+
succeed(resource).with_message("Invitation sent to #{email}")
|
|
350
351
|
rescue ActiveRecord::RecordInvalid => e
|
|
351
352
|
failed(e.record.errors)
|
|
352
353
|
end
|
|
@@ -376,7 +377,7 @@ end
|
|
|
376
377
|
|
|
377
378
|
## Related Skills
|
|
378
379
|
|
|
379
|
-
- `definition-actions` - Declaring actions in definitions
|
|
380
|
-
- `forms` - Custom interaction form templates
|
|
381
|
-
- `policy` - Controlling access to actions
|
|
382
|
-
- `resource` - How interactions fit in the architecture
|
|
380
|
+
- `plutonium-definition-actions` - Declaring actions in definitions
|
|
381
|
+
- `plutonium-forms` - Custom interaction form templates
|
|
382
|
+
- `plutonium-policy` - Controlling access to actions
|
|
383
|
+
- `plutonium-resource` - How interactions fit in the architecture
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: model
|
|
2
|
+
name: plutonium-model
|
|
3
3
|
description: Overview of Plutonium resource models - structure, setup, and best practices
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -263,5 +263,5 @@ Models integrate with:
|
|
|
263
263
|
|
|
264
264
|
## Related Skills
|
|
265
265
|
|
|
266
|
-
- `model-features` - has_cents, associations, scopes, routes
|
|
267
|
-
- `create-resource` - Scaffold generator for new resources
|
|
266
|
+
- `plutonium-model-features` - has_cents, associations, scopes, routes
|
|
267
|
+
- `plutonium-create-resource` - Scaffold generator for new resources
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: model-features
|
|
2
|
+
name: plutonium-model-features
|
|
3
3
|
description: Plutonium model features - has_cents, associations, scopes, and routing
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -282,5 +282,5 @@ sgid_array.each { |sgid| user.add_post_sgid(sgid) }
|
|
|
282
282
|
|
|
283
283
|
## Related Skills
|
|
284
284
|
|
|
285
|
-
- `model` - Model overview and structure
|
|
286
|
-
- `create-resource` - Scaffold generator
|
|
285
|
+
- `plutonium-model` - Model overview and structure
|
|
286
|
+
- `plutonium-create-resource` - Scaffold generator
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: nested-resources
|
|
2
|
+
name: plutonium-nested-resources
|
|
3
3
|
description: Plutonium nested resources - parent/child routes, scoping, and URL generation
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -268,7 +268,7 @@ Generates nested routes:
|
|
|
268
268
|
|
|
269
269
|
## Related Skills
|
|
270
270
|
|
|
271
|
-
- `portal` - Route registration
|
|
272
|
-
- `policy` - Authorization and scoping
|
|
273
|
-
- `controller` - Presentation hooks
|
|
274
|
-
- `model-features` - associated_with scope
|
|
271
|
+
- `plutonium-portal` - Route registration
|
|
272
|
+
- `plutonium-policy` - Authorization and scoping
|
|
273
|
+
- `plutonium-controller` - Presentation hooks
|
|
274
|
+
- `plutonium-model-features` - associated_with scope
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: package
|
|
2
|
+
name: plutonium-package
|
|
3
3
|
description: Plutonium packages - modular Rails engines for organizing features and portals
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -74,8 +74,7 @@ packages/admin_portal/
|
|
|
74
74
|
│ ├── controllers/admin_portal/
|
|
75
75
|
│ │ ├── concerns/controller.rb
|
|
76
76
|
│ │ ├── dashboard_controller.rb
|
|
77
|
-
│ │
|
|
78
|
-
│ │ └── resource_controller.rb
|
|
77
|
+
│ │ └── plutonium_controller.rb
|
|
79
78
|
│ ├── definitions/admin_portal/ # Portal-specific overrides
|
|
80
79
|
│ ├── policies/admin_portal/ # Portal-specific overrides
|
|
81
80
|
│ └── views/
|
|
@@ -101,7 +100,7 @@ module AdminPortal
|
|
|
101
100
|
end
|
|
102
101
|
```
|
|
103
102
|
|
|
104
|
-
See `portal` skill for portal-specific features.
|
|
103
|
+
See `plutonium-portal` skill for portal-specific features.
|
|
105
104
|
|
|
106
105
|
## Package Loading
|
|
107
106
|
|
|
@@ -185,7 +184,7 @@ rails db:migrate # Runs migrations from all packages
|
|
|
185
184
|
|
|
186
185
|
## Related Skills
|
|
187
186
|
|
|
188
|
-
- `portal` - Portal-specific features (auth, entity scoping, routes)
|
|
189
|
-
- `resource` - Resource architecture overview
|
|
190
|
-
- `connect-resource` - Connecting resources to portals
|
|
191
|
-
- `create-resource` - Creating resources
|
|
187
|
+
- `plutonium-portal` - Portal-specific features (auth, entity scoping, routes)
|
|
188
|
+
- `plutonium-resource` - Resource architecture overview
|
|
189
|
+
- `plutonium-connect-resource` - Connecting resources to portals
|
|
190
|
+
- `plutonium-create-resource` - Creating resources
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: policy
|
|
2
|
+
name: plutonium-policy
|
|
3
3
|
description: Plutonium resource policies - authorization, attribute permissions, and scoping
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -87,6 +87,28 @@ end
|
|
|
87
87
|
|
|
88
88
|
Actions are secure by default - undefined methods return `false`.
|
|
89
89
|
|
|
90
|
+
### Bulk Action Authorization
|
|
91
|
+
|
|
92
|
+
Bulk actions (operating on multiple selected records) support **per-record authorization**:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
def bulk_archive?
|
|
96
|
+
create? && !record.locked? # Per-record check
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def bulk_publish?
|
|
100
|
+
user.admin? || record.author == user
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**How bulk authorization works:**
|
|
105
|
+
1. Policy method (e.g., `bulk_archive?`) is checked **per record** in the selection
|
|
106
|
+
2. **Backend:** If any selected record fails authorization, the entire request is rejected
|
|
107
|
+
3. **UI:** Only actions that **all** selected records support are shown (intersection)
|
|
108
|
+
4. Records are fetched via `current_authorized_scope` - only accessible records can be selected
|
|
109
|
+
|
|
110
|
+
This provides full per-record authorization while keeping the UI clean - users only see actions they can actually perform on their entire selection.
|
|
111
|
+
|
|
90
112
|
## Attribute Permissions
|
|
91
113
|
|
|
92
114
|
### Core Methods (Must Override for Production)
|
|
@@ -347,6 +369,6 @@ end
|
|
|
347
369
|
|
|
348
370
|
## Related Skills
|
|
349
371
|
|
|
350
|
-
- `resource` - How policies fit in the resource architecture
|
|
351
|
-
- `definition-actions` - Actions that need policy methods
|
|
352
|
-
- `controller` - How controllers use policies
|
|
372
|
+
- `plutonium-resource` - How policies fit in the resource architecture
|
|
373
|
+
- `plutonium-definition-actions` - Actions that need policy methods
|
|
374
|
+
- `plutonium-controller` - How controllers use policies
|