plutonium 0.64.0 → 0.66.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 +44 -41
- data/.claude/skills/plutonium-app/SKILL.md +115 -59
- data/.claude/skills/plutonium-async-interactions/SKILL.md +19 -19
- data/.claude/skills/plutonium-auth/SKILL.md +119 -53
- data/.claude/skills/plutonium-behavior/SKILL.md +110 -78
- data/.claude/skills/plutonium-dashboard/SKILL.md +168 -0
- data/.claude/skills/plutonium-kanban/SKILL.md +81 -49
- data/.claude/skills/plutonium-resource/SKILL.md +236 -145
- data/.claude/skills/plutonium-tenancy/SKILL.md +104 -55
- data/.claude/skills/plutonium-testing/SKILL.md +130 -33
- data/.claude/skills/plutonium-ui/SKILL.md +171 -95
- data/.claude/skills/plutonium-wizard/SKILL.md +93 -82
- data/CHANGELOG.md +80 -0
- data/CLAUDE.md +282 -210
- data/README.md +9 -9
- data/SECURITY.md +1 -1
- data/app/assets/plutonium-charts.js +20604 -0
- data/app/assets/plutonium-charts.js.map +7 -0
- data/app/assets/plutonium-charts.min.js +41 -0
- data/app/assets/plutonium-charts.min.js.map +7 -0
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +794 -629
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +48 -48
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/plutonium/_flash_alerts.html.erb +2 -2
- data/app/views/plutonium/_resource_header.html.erb +2 -2
- data/app/views/plutonium/_resource_sidebar.html.erb +11 -2
- data/app/views/plutonium/_toast.html.erb +2 -2
- data/app/views/rodauth/_login_form.html.erb +1 -1
- data/app/views/rodauth/_password_visibility.html.erb +1 -1
- data/app/views/rodauth/add_recovery_codes.html.erb +2 -2
- data/app/views/rodauth/change_login.html.erb +2 -2
- data/app/views/rodauth/create_account.html.erb +4 -4
- data/app/views/rodauth/reset_password_request.html.erb +1 -1
- data/app/views/rodauth/verify_account_resend.html.erb +1 -1
- data/app/views/rodauth/webauthn_remove.html.erb +1 -1
- data/config/brakeman.ignore +4 -4
- data/config/initializers/rabl.rb +0 -40
- data/config/locales/en/api_client.yml +22 -0
- data/config/locales/en/async.yml +34 -0
- data/config/locales/en/dashboard.yml +25 -0
- data/config/locales/en/invites.yml +37 -0
- data/config/locales/en/js.yml +40 -0
- data/config/locales/en/kanban.yml +28 -0
- data/config/locales/en/plutonium.yml +26 -0
- data/config/locales/en/positioning.yml +14 -0
- data/config/locales/en/profile.yml +29 -0
- data/config/locales/en/resource.yml +60 -0
- data/config/locales/en/ui.yml +93 -0
- data/config/locales/en/wizard.yml +46 -0
- data/docs/.vitepress/config.ts +34 -1
- data/docs/.vitepress/sync-skills.mjs +6 -3
- data/docs/.vitepress/theme/components/HomeHero.vue +1 -1
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +14 -0
- data/docs/.vitepress/theme/custom.css +34 -377
- data/docs/blog/introducing-plutonium-dashboards.md +174 -0
- data/docs/blog/introducing-plutonium-i18n.md +90 -0
- data/docs/blog/introducing-plutonium.md +4 -5
- data/docs/blog/whats-new-async-kanban-wizards.md +1 -1
- data/docs/getting-started/installation.md +5 -5
- data/docs/getting-started/tutorial/02-first-resource.md +3 -3
- data/docs/getting-started/tutorial/03-authentication.md +7 -7
- data/docs/getting-started/tutorial/04-authorization.md +21 -4
- data/docs/getting-started/tutorial/05-custom-actions.md +2 -2
- data/docs/getting-started/tutorial/06-nested-resources.md +5 -2
- data/docs/getting-started/tutorial/07-author-portal.md +2 -2
- data/docs/getting-started/tutorial/08-customizing-ui.md +45 -30
- data/docs/getting-started/tutorial/index.md +1 -1
- data/docs/guides/adding-resources.md +10 -7
- data/docs/guides/authentication.md +25 -25
- data/docs/guides/authorization.md +24 -24
- data/docs/guides/creating-packages.md +17 -17
- data/docs/guides/custom-actions.md +32 -32
- data/docs/guides/customizing-ui.md +29 -26
- data/docs/guides/dashboards.md +300 -0
- data/docs/guides/index.md +4 -3
- data/docs/guides/kanban.md +56 -56
- data/docs/guides/multi-tenancy.md +35 -22
- data/docs/guides/nested-resources.md +21 -21
- data/docs/guides/performance.md +3 -3
- data/docs/guides/search-filtering.md +13 -13
- data/docs/guides/testing.md +16 -12
- data/docs/guides/theming.md +32 -17
- data/docs/guides/troubleshooting.md +2 -2
- data/docs/guides/user-invites.md +17 -17
- data/docs/guides/user-profile.md +51 -24
- data/docs/guides/wizards.md +55 -55
- data/docs/public/images/blog/dashboards-card-error.png +0 -0
- data/docs/public/images/blog/dashboards-loading.png +0 -0
- data/docs/public/images/blog/dashboards-overview.png +0 -0
- data/docs/public/images/blog/dashboards-sidebar.png +0 -0
- data/docs/public/images/blog/i18n-es-admin.png +0 -0
- data/docs/public/images/guides/dashboard-card-error.png +0 -0
- data/docs/public/images/guides/dashboard-loading.png +0 -0
- data/docs/public/images/guides/dashboard-overview.png +0 -0
- data/docs/public/images/guides/dashboard-sidebar.png +0 -0
- data/docs/reference/app/generators.md +24 -24
- data/docs/reference/app/index.md +15 -18
- data/docs/reference/app/packages.md +8 -8
- data/docs/reference/app/portals.md +77 -29
- data/docs/reference/auth/accounts.md +15 -15
- data/docs/reference/auth/index.md +12 -12
- data/docs/reference/auth/profile.md +67 -29
- data/docs/reference/behavior/async-interactions.md +24 -24
- data/docs/reference/behavior/controllers.md +28 -28
- data/docs/reference/behavior/index.md +5 -5
- data/docs/reference/behavior/interactions.md +44 -44
- data/docs/reference/behavior/policies.md +48 -28
- data/docs/reference/configuration.md +88 -31
- data/docs/reference/dashboard/dsl.md +123 -0
- data/docs/reference/dashboard/index.md +35 -0
- data/docs/reference/dashboard/registration.md +76 -0
- data/docs/reference/generators/lite.md +7 -7
- data/docs/reference/i18n.md +262 -0
- data/docs/reference/index.md +11 -2
- data/docs/reference/kanban/authorization.md +9 -9
- data/docs/reference/kanban/dsl.md +33 -33
- data/docs/reference/kanban/index.md +1 -1
- data/docs/reference/kanban/positioning.md +18 -16
- data/docs/reference/resource/actions.md +51 -51
- data/docs/reference/resource/definition.md +166 -88
- data/docs/reference/resource/export.md +11 -7
- data/docs/reference/resource/index.md +16 -14
- data/docs/reference/resource/model.md +24 -24
- data/docs/reference/{positioning.md → resource/positioning.md} +78 -75
- data/docs/reference/resource/query.md +14 -12
- data/docs/reference/tenancy/entity-scoping.md +65 -35
- data/docs/reference/tenancy/index.md +11 -11
- data/docs/reference/tenancy/invites.md +20 -20
- data/docs/reference/tenancy/nested-resources.md +13 -13
- data/docs/reference/testing/index.md +116 -22
- data/docs/reference/ui/assets.md +65 -25
- data/docs/reference/ui/components.md +20 -20
- data/docs/reference/ui/displays.md +19 -22
- data/docs/reference/ui/forms.md +35 -35
- data/docs/reference/ui/index.md +17 -15
- data/docs/reference/ui/layouts.md +21 -21
- data/docs/reference/ui/pages.md +22 -22
- data/docs/reference/ui/tables.md +7 -7
- data/docs/reference/wizard/anchoring-resume.md +33 -32
- data/docs/reference/wizard/dsl.md +45 -45
- data/docs/reference/wizard/index.md +6 -6
- data/docs/reference/wizard/one-time.md +18 -18
- data/docs/reference/wizard/registration-launch.md +32 -32
- data/docs/reference/wizard/storage-config.md +23 -23
- data/esbuild.config.js +5 -2
- data/gemfiles/rails_7.gemfile.lock +2 -2
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/active_model/validations/array_validator.rb +1 -1
- data/lib/active_model/validations/attached_validator.rb +1 -1
- data/lib/active_model/validations/url_validator.rb +2 -2
- data/lib/generators/pu/core/assets/assets_generator.rb +36 -21
- data/lib/generators/pu/core/assets/templates/postcss.config.js +1 -1
- data/lib/generators/pu/core/update/update_generator.rb +3 -2
- data/lib/generators/pu/dashboard/dashboard_generator.rb +96 -0
- data/lib/generators/pu/dashboard/templates/dashboard.rb.tt +25 -0
- data/lib/generators/pu/docker/install/install_generator.rb +58 -0
- data/lib/generators/pu/docker/install/templates/Dockerfile.dev.tt +18 -3
- data/lib/generators/pu/docker/install/templates/Dockerfile.tt +20 -5
- data/lib/generators/pu/docker/install/templates/{docker-compose.yml → docker-compose.yml.tt} +1 -1
- data/lib/generators/pu/gem/dotenv/dotenv_generator.rb +1 -1
- data/lib/generators/pu/invites/invitable_generator.rb +18 -1
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +0 -95
- data/lib/generators/pu/lib/plutonium_generators/concerns/js_package_manager.rb +146 -0
- data/lib/generators/pu/lib/plutonium_generators/generator.rb +1 -17
- data/lib/generators/pu/pkg/package/package_generator.rb +1 -0
- data/lib/generators/pu/pkg/package/templates/config/locales/en.yml.tt +28 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +1 -0
- data/lib/generators/pu/pkg/portal/templates/config/locales/en.yml.tt +30 -0
- data/lib/generators/pu/profile/conn_generator.rb +6 -0
- data/lib/generators/pu/res/scaffold/scaffold_generator.rb +0 -8
- data/lib/generators/pu/rodauth/concerns/feature_selector.rb +0 -18
- data/lib/plutonium/action/base.rb +27 -3
- data/lib/plutonium/action/interactive.rb +17 -4
- data/lib/plutonium/api_client/concerns/create_api_client.rb +12 -11
- data/lib/plutonium/api_client/concerns/disable_api_client.rb +3 -3
- data/lib/plutonium/auth/sequel_adapter.rb +1 -1
- data/lib/plutonium/configuration.rb +5 -0
- data/lib/plutonium/core/controller.rb +25 -1
- data/lib/plutonium/core/controllers/authorizable.rb +1 -1
- data/lib/plutonium/dashboard/base.rb +114 -0
- data/lib/plutonium/dashboard/card.rb +200 -0
- data/lib/plutonium/dashboard/controller.rb +101 -0
- data/lib/plutonium/dashboard/dsl.rb +104 -0
- data/lib/plutonium/dashboard/register.rb +40 -0
- data/lib/plutonium/dashboard/route_resolution.rb +30 -0
- data/lib/plutonium/dashboard.rb +42 -0
- data/lib/plutonium/definition/actions.rb +6 -1
- data/lib/plutonium/definition/base.rb +41 -3
- data/lib/plutonium/definition/index_views.rb +1 -1
- data/lib/plutonium/definition/positioning.rb +1 -1
- data/lib/plutonium/definition/presentable.rb +2 -2
- data/lib/plutonium/helpers/assets_helper.rb +15 -2
- data/lib/plutonium/helpers/display_helper.rb +10 -2
- data/lib/plutonium/interaction/async/context.rb +6 -8
- data/lib/plutonium/interaction/async/executor.rb +6 -7
- data/lib/plutonium/interaction/async/run.rb +2 -2
- data/lib/plutonium/interaction/async/run_definition.rb +1 -1
- data/lib/plutonium/interaction/base.rb +2 -0
- data/lib/plutonium/interaction/concerns/dispatchable.rb +15 -3
- data/lib/plutonium/invites/concerns/cancel_invite.rb +3 -3
- data/lib/plutonium/invites/concerns/invite_token.rb +25 -7
- data/lib/plutonium/invites/concerns/invite_user.rb +10 -5
- data/lib/plutonium/invites/concerns/resend_invite.rb +4 -4
- data/lib/plutonium/invites/controller.rb +11 -11
- data/lib/plutonium/kanban/column.rb +13 -3
- data/lib/plutonium/kanban/dsl.rb +7 -4
- data/lib/plutonium/models/has_cents.rb +1 -1
- data/lib/plutonium/profile/security_section.rb +10 -18
- data/lib/plutonium/query/filter.rb +10 -1
- data/lib/plutonium/query/filters/association.rb +27 -2
- data/lib/plutonium/query/filters/boolean.rb +4 -4
- data/lib/plutonium/query/filters/date.rb +4 -17
- data/lib/plutonium/query/filters/date_range.rb +2 -2
- data/lib/plutonium/query/filters/select.rb +10 -1
- data/lib/plutonium/query/filters/text.rb +9 -12
- data/lib/plutonium/railtie.rb +17 -0
- data/lib/plutonium/resource/controller.rb +10 -5
- data/lib/plutonium/resource/controllers/crud_actions.rb +17 -20
- data/lib/plutonium/resource/controllers/export_csv.rb +19 -9
- data/lib/plutonium/resource/controllers/kanban_actions.rb +16 -16
- data/lib/plutonium/resource/controllers/position_actions.rb +5 -5
- data/lib/plutonium/resource/controllers/typeahead.rb +24 -1
- data/lib/plutonium/resource/query_object.rb +30 -3
- data/lib/plutonium/resource/record/associated_with.rb +23 -2
- data/lib/plutonium/routing/dashboard_registration.rb +137 -0
- data/lib/plutonium/routing/resource_registration.rb +5 -0
- data/lib/plutonium/routing/route_set_extensions.rb +2 -1
- data/lib/plutonium/translation.rb +227 -0
- data/lib/plutonium/ui/actions_dropdown.rb +2 -2
- data/lib/plutonium/ui/breadcrumbs.rb +3 -3
- data/lib/plutonium/ui/color_mode_selector.rb +1 -1
- data/lib/plutonium/ui/component/behaviour.rb +46 -0
- data/lib/plutonium/ui/component/methods.rb +4 -0
- data/lib/plutonium/ui/dashboard/board.rb +80 -0
- data/lib/plutonium/ui/dashboard/card.rb +127 -0
- data/lib/plutonium/ui/dashboard/chart.rb +43 -0
- data/lib/plutonium/ui/dashboard/custom.rb +32 -0
- data/lib/plutonium/ui/dashboard/frame.rb +25 -0
- data/lib/plutonium/ui/dashboard/metric.rb +137 -0
- data/lib/plutonium/ui/dashboard/skeleton.rb +40 -0
- data/lib/plutonium/ui/display/components/attachment.rb +1 -1
- data/lib/plutonium/ui/display/components/badge.rb +7 -1
- data/lib/plutonium/ui/display/components/boolean.rb +2 -2
- data/lib/plutonium/ui/display/resource.rb +23 -16
- data/lib/plutonium/ui/export_button.rb +3 -3
- data/lib/plutonium/ui/form/components/key_value_store.rb +5 -5
- data/lib/plutonium/ui/form/components/password.rb +2 -2
- data/lib/plutonium/ui/form/components/resource_select.rb +27 -0
- data/lib/plutonium/ui/form/components/secure_association.rb +2 -0
- data/lib/plutonium/ui/form/components/uppy.rb +11 -3
- data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +1 -1
- data/lib/plutonium/ui/form/concerns/renders_repeater_row_controls.rb +5 -4
- data/lib/plutonium/ui/form/concerns/renders_structured_inputs.rb +1 -1
- data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +7 -1
- data/lib/plutonium/ui/form/query.rb +8 -8
- data/lib/plutonium/ui/form/resource.rb +54 -26
- data/lib/plutonium/ui/frame_navigator_panel.rb +4 -4
- data/lib/plutonium/ui/grid/card.rb +3 -3
- data/lib/plutonium/ui/grid/resource.rb +3 -3
- data/lib/plutonium/ui/interaction/async/run_progress.rb +6 -6
- data/lib/plutonium/ui/interaction/async/running_banner.rb +2 -2
- data/lib/plutonium/ui/kanban/column.rb +6 -6
- data/lib/plutonium/ui/layout/base.rb +19 -1
- data/lib/plutonium/ui/layout/header.rb +1 -1
- data/lib/plutonium/ui/layout/icon_rail.rb +4 -4
- data/lib/plutonium/ui/layout/rodauth_layout.rb +1 -1
- data/lib/plutonium/ui/layout/sidebar.rb +1 -1
- data/lib/plutonium/ui/layout/topbar.rb +2 -2
- data/lib/plutonium/ui/modal/base.rb +4 -4
- data/lib/plutonium/ui/nav_grid_menu.rb +1 -1
- data/lib/plutonium/ui/nav_user.rb +2 -2
- data/lib/plutonium/ui/page/base.rb +10 -2
- data/lib/plutonium/ui/page/dashboard.rb +54 -0
- data/lib/plutonium/ui/page/edit.rb +3 -3
- data/lib/plutonium/ui/page/interactive_action.rb +2 -2
- data/lib/plutonium/ui/page/new.rb +3 -3
- data/lib/plutonium/ui/page/show.rb +2 -2
- data/lib/plutonium/ui/page/wizard.rb +22 -16
- data/lib/plutonium/ui/page/wizard_chooser.rb +10 -10
- data/lib/plutonium/ui/page/wizard_completed.rb +2 -2
- data/lib/plutonium/ui/skeleton_table.rb +1 -1
- data/lib/plutonium/ui/table/base.rb +2 -2
- data/lib/plutonium/ui/table/components/attachment.rb +1 -1
- data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +19 -3
- data/lib/plutonium/ui/table/components/drag_handle.rb +3 -3
- data/lib/plutonium/ui/table/components/filter_form.rb +11 -10
- data/lib/plutonium/ui/table/components/filter_pills.rb +4 -4
- data/lib/plutonium/ui/table/components/pagy_info.rb +61 -17
- data/lib/plutonium/ui/table/components/pagy_pagination.rb +9 -5
- data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +1 -1
- data/lib/plutonium/ui/table/components/scopes_bar.rb +6 -2
- data/lib/plutonium/ui/table/components/scopes_pills.rb +3 -3
- data/lib/plutonium/ui/table/components/selection_column.rb +1 -1
- data/lib/plutonium/ui/table/components/toolbar.rb +2 -2
- data/lib/plutonium/ui/table/components/view_switcher.rb +12 -8
- data/lib/plutonium/ui/table/resource.rb +53 -17
- data/lib/plutonium/ui/wizard/review.rb +7 -7
- data/lib/plutonium/ui/wizard/stepper.rb +1 -1
- data/lib/plutonium/ui/wizard/summary_display.rb +6 -0
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium/wizard/base.rb +2 -0
- data/lib/plutonium/wizard/driving.rb +8 -2
- data/lib/plutonium/wizard/dsl.rb +4 -2
- data/lib/plutonium/wizard/field_importer.rb +1 -8
- data/lib/plutonium/wizard/review_step.rb +8 -1
- data/lib/plutonium/wizard/step.rb +14 -3
- data/package.json +4 -2
- data/plutonium.gemspec +4 -1
- data/postcss-gem-import.cjs +34 -0
- data/postcss-gem-import.js +3 -28
- data/src/css/components.css +101 -10
- data/src/css/slim_select.css +0 -38
- data/src/css/tokens.css +24 -0
- data/src/js/controllers/attachment_input_controller.js +15 -6
- data/src/js/controllers/chart_controller.js +162 -0
- data/src/js/controllers/clipboard_controller.js +3 -2
- data/src/js/controllers/dirty_form_guard_controller.js +4 -3
- data/src/js/controllers/easymde_controller.js +3 -0
- data/src/js/controllers/filter_panel_controller.js +15 -6
- data/src/js/controllers/flatpickr_controller.js +8 -0
- data/src/js/controllers/frame_refresh_controller.js +59 -0
- data/src/js/controllers/intl_tel_input_controller.js +5 -0
- data/src/js/controllers/register_controllers.js +4 -0
- data/src/js/controllers/slim_select_controller.js +79 -176
- data/src/js/core.js +9 -1
- data/src/js/i18n.js +69 -0
- data/src/js/plutonium-charts.js +15 -0
- data/src/js/turbo/turbo_confirm.js +5 -3
- metadata +64 -16
- data/.yarnrc.yml +0 -8
- data/config/initializers/hotwire_turbo_monkey_patches.rb +0 -12
- data/lib/generators/pu/field/input/input_generator.rb +0 -32
- data/lib/generators/pu/field/input/templates/input.rb.tt +0 -15
- data/lib/generators/pu/lib/plutonium_generators/concerns/config.rb +0 -39
- data/lib/generators/pu/lib/plutonium_generators/concerns/serializer.rb +0 -39
- data/lib/generators/pu/lib/plutonium_generators/installer.rb +0 -205
- data/lib/generators/pu/res/scaffold/templates/presenter.rb.tt +0 -13
- data/lib/generators/pu/res/scaffold/templates/query_object.rb.tt +0 -17
- data/public/plutonium-assets/application.js +0 -31419
- data/public/plutonium-assets/plutonium.ico +0 -0
- data/yarn.lock +0 -6858
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# CSV Export
|
|
2
2
|
|
|
3
3
|
Every resource ships with a streamed CSV export, **disabled by default**. It is *not* an
|
|
4
|
-
[action](./actions.md)
|
|
4
|
+
[action](./actions.md) (it streams a file and opens in a new tab), so it is enabled
|
|
5
5
|
through the policy rather than declared with `action :export_csv`. The route
|
|
6
6
|
(`GET /<resources>/export_csv`) is auto-mounted on every resource; a split "Export"
|
|
7
7
|
button appears on the index page once the policy permits it.
|
|
@@ -25,8 +25,8 @@ The control is a split button with two behaviours:
|
|
|
25
25
|
|
|
26
26
|
| | Source | Filename |
|
|
27
27
|
|---|---|---|
|
|
28
|
-
| **Export** (primary) | The current view
|
|
29
|
-
| **Export all** (dropdown) | The entire authorized scope
|
|
28
|
+
| **Export** (primary) | The current view: selected scope + filters + search (the index's `?q`), **all** matching rows (not just the visible page) | `posts_<date>.csv` |
|
|
29
|
+
| **Export all** (dropdown) | The entire authorized scope: ignores scope, filters, search, and default scope | `posts_all_<date>.csv` |
|
|
30
30
|
|
|
31
31
|
"Export all" always exports everything the user is authorized to read, regardless of the
|
|
32
32
|
current scope/filters.
|
|
@@ -73,12 +73,12 @@ For a column **without** an `export` block, the value is read straight off the r
|
|
|
73
73
|
(`record.public_send(name)`):
|
|
74
74
|
|
|
75
75
|
- **Scalars** (strings, numbers, booleans, dates) are written as-is.
|
|
76
|
-
- **Associations** render as their display label
|
|
77
|
-
uses (e.g. `User #5`, or the record's `to_label`/`name`/`title` if defined)
|
|
76
|
+
- **Associations** render as their display label: the same `display_name_of` the index
|
|
77
|
+
uses (e.g. `User #5`, or the record's `to_label`/`name`/`title` if defined), never
|
|
78
78
|
`#<User:0x…>`. Add an `export` block to export a specific field instead (e.g. the email).
|
|
79
79
|
- A name that is **neither** an `export` block **nor** a real method on the record renders
|
|
80
80
|
the placeholder `<<invalid column>>` rather than aborting the (already-streaming) download.
|
|
81
|
-
To export a computed or virtual column, give it an `export` block
|
|
81
|
+
To export a computed or virtual column, give it an `export` block: a `label:`-only
|
|
82
82
|
`export` does **not** supply a value, so it too renders the placeholder.
|
|
83
83
|
|
|
84
84
|
## Notes & limits
|
|
@@ -90,5 +90,9 @@ For a column **without** an `export` block, the value is read straight off the r
|
|
|
90
90
|
intercepting the streamed download.
|
|
91
91
|
- **CSV/formula injection** is neutralized: any cell whose value begins with `=`, `+`,
|
|
92
92
|
`-`, `@`, or a leading tab/CR is prefixed with a single quote so spreadsheet apps import
|
|
93
|
-
it as literal text instead of executing it as a formula.
|
|
93
|
+
it as literal text instead of executing it as a formula. The check is run against the
|
|
94
|
+
NFKC-normalized value, so full-width (double-byte) variants (`=`, `+`, `-`, `@`) that
|
|
95
|
+
East Asian locales treat as formula initiators are neutralized too; the original
|
|
96
|
+
(un-normalized) value is what gets the quote prefix, so legitimate full-width content
|
|
97
|
+
is preserved verbatim.
|
|
94
98
|
- `csv` is a runtime dependency of Plutonium (it is no longer a Ruby default gem on 3.4+).
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Resource Reference
|
|
2
2
|
|
|
3
|
-
A **resource** is the unit Plutonium gives you full CRUD for
|
|
3
|
+
A **resource** is the unit Plutonium gives you full CRUD for (list, show, create, edit, delete) automatically. It's four cooperating layers, plus an optional fifth for business logic.
|
|
4
4
|
|
|
5
5
|
| Layer | File | What it controls |
|
|
6
6
|
|---|---|---|
|
|
7
7
|
| [Model](./model) | `app/models/post.rb` | Data, validations, associations |
|
|
8
|
-
| [Definition](./definition) | `app/definitions/post_definition.rb` | UI
|
|
9
|
-
| Policy | `app/policies/post_policy.rb` | Authorization
|
|
10
|
-
| Controller | `app/controllers/posts_controller.rb` | Request handling
|
|
11
|
-
| Interaction *(optional)* | `app/interactions/publish_post_interaction.rb` | Business logic for custom actions
|
|
8
|
+
| [Definition](./definition) | `app/definitions/post_definition.rb` | UI: which fields, how they render, what actions exist |
|
|
9
|
+
| Policy | `app/policies/post_policy.rb` | Authorization: see [Behavior › Policy](/reference/behavior/policies) |
|
|
10
|
+
| Controller | `app/controllers/posts_controller.rb` | Request handling: see [Behavior › Controller](/reference/behavior/controllers) |
|
|
11
|
+
| Interaction *(optional)* | `app/interactions/publish_post_interaction.rb` | Business logic for custom actions, see [Behavior › Interaction](/reference/behavior/interactions) |
|
|
12
12
|
|
|
13
13
|
## How a resource is born
|
|
14
14
|
|
|
@@ -22,7 +22,7 @@ That single scaffold gives you a working model + migration + controller + policy
|
|
|
22
22
|
|
|
23
23
|
## Auto-detection is the default
|
|
24
24
|
|
|
25
|
-
Plutonium reads your model and renders every attribute automatically
|
|
25
|
+
Plutonium reads your model and renders every attribute automatically: type, label, form widget, display formatter, table column. You only declare overrides:
|
|
26
26
|
|
|
27
27
|
```ruby
|
|
28
28
|
class PostDefinition < Plutonium::Resource::Definition
|
|
@@ -33,18 +33,20 @@ end
|
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
::: warning Don't declare for completeness
|
|
36
|
-
A `field :title` with no options that matches what Plutonium would auto-detect is **dead code
|
|
36
|
+
A `field :title` with no options that matches what Plutonium would auto-detect is **dead code**; it does nothing and clutters the file. Declare ONLY when you need a different type, an option, a `condition:`, a block, or a custom component.
|
|
37
37
|
:::
|
|
38
38
|
|
|
39
39
|
## Sub-pages
|
|
40
40
|
|
|
41
|
-
- [Model](./model)
|
|
42
|
-
- [Definition](./definition)
|
|
43
|
-
- [Query](./query)
|
|
44
|
-
- [Actions](./actions)
|
|
41
|
+
- [Model](./model): `Plutonium::Resource::Record`, `has_cents`, SGID, custom routing, labeling
|
|
42
|
+
- [Definition](./definition): fields, inputs, displays, columns, page chrome, metadata panel, index views
|
|
43
|
+
- [Query](./query): search, filters, scopes, sorting
|
|
44
|
+
- [Actions](./actions): custom actions, bulk actions, interaction integration
|
|
45
|
+
- [Positioning & drag-to-reorder](./positioning): `positioned_on`, `position_on`, the drag grip, `reposition?`
|
|
46
|
+
- [CSV Export](./export): streamed export, opt-in through the policy
|
|
45
47
|
|
|
46
48
|
## Related
|
|
47
49
|
|
|
48
|
-
- [Guides › Adding Resources](/guides/adding-resources)
|
|
49
|
-
- [App › Generators](/reference/app/generators)
|
|
50
|
-
- [Tenancy](/reference/tenancy/)
|
|
50
|
+
- [Guides › Adding Resources](/guides/adding-resources): task recipe
|
|
51
|
+
- [App › Generators](/reference/app/generators): `pu:res:scaffold` / `pu:res:conn` reference
|
|
52
|
+
- [Tenancy](/reference/tenancy/): multi-tenant scoping
|
|
@@ -11,25 +11,25 @@ All resource models inherit from `ResourceRecord` (created by `pu:core:install`)
|
|
|
11
11
|
class Post < ResourceRecord
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
# Inside a feature package
|
|
14
|
+
# Inside a feature package: uses the package's ResourceRecord
|
|
15
15
|
module Blogging
|
|
16
16
|
class Post < Blogging::ResourceRecord
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
`ResourceRecord` is abstract and inherits from `ApplicationRecord`. Standard ActiveRecord features (associations, validations, scopes, callbacks, attribute macros) all work
|
|
21
|
+
`ResourceRecord` is abstract and inherits from `ApplicationRecord`. Standard ActiveRecord features (associations, validations, scopes, callbacks, attribute macros) all work; Plutonium adds capabilities on top.
|
|
22
22
|
|
|
23
23
|
## What `Plutonium::Resource::Record` adds
|
|
24
24
|
|
|
25
25
|
| Module | Purpose | Section |
|
|
26
26
|
|---|---|---|
|
|
27
|
-
| `HasCents` | Money handling
|
|
27
|
+
| `HasCents` | Money handling: cents column ↔ decimal accessor | [has_cents](#has-cents) |
|
|
28
28
|
| `Routes` | URL parameter customization (slugs, dynamic params) | [URL routing](#url-routing) |
|
|
29
29
|
| `Labeling` | `to_label` for human-readable record names | [Labeling](#labeling) |
|
|
30
30
|
| `FieldNames` | Field introspection by category | [Field introspection](#field-introspection) |
|
|
31
31
|
| `Associations` | Auto-generated SGID accessors on every association | [SGID accessors](#sgid-accessors) |
|
|
32
|
-
| `AssociatedWith` | Multi-tenant scoping
|
|
32
|
+
| `AssociatedWith` | Multi-tenant scoping: `Model.associated_with(entity)` | [Tenancy](/reference/tenancy/entity-scoping) |
|
|
33
33
|
|
|
34
34
|
## Section layout
|
|
35
35
|
|
|
@@ -89,7 +89,7 @@ end
|
|
|
89
89
|
|
|
90
90
|
## `has_cents`
|
|
91
91
|
|
|
92
|
-
Stores monetary values as integer cents and exposes a decimal virtual accessor. Use this for money
|
|
92
|
+
Stores monetary values as integer cents and exposes a decimal virtual accessor. Use this for money; never store decimals directly.
|
|
93
93
|
|
|
94
94
|
```ruby
|
|
95
95
|
class Product < ResourceRecord
|
|
@@ -112,16 +112,16 @@ product.price = 10.999
|
|
|
112
112
|
product.price_cents # => 1099
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
**Currency symbol (`unit:`)
|
|
115
|
+
**Currency symbol (`unit:`).** A `String` is used verbatim (`unit: "£"`); a `Symbol`
|
|
116
116
|
names a method read off the record for per-row currencies (`unit: :currency_symbol`
|
|
117
117
|
→ `record.currency_symbol`); `false` explicitly renders no symbol. This unit is picked
|
|
118
|
-
up automatically anywhere the value renders as currency
|
|
119
|
-
grid/kanban cards
|
|
118
|
+
up automatically anywhere the value renders as currency (show pages, tables, and
|
|
119
|
+
grid/kanban cards), so you configure it once on the model. A per-display
|
|
120
120
|
`display :price, as: :currency, unit: …` overrides it for that display.
|
|
121
121
|
|
|
122
122
|
A `has_cents` field also **infers the currency input** on forms: a bare `input :price`
|
|
123
123
|
renders the [currency input](../ui/forms#currency-fields) (number field + unit prefix),
|
|
124
|
-
no `as: :currency` needed
|
|
124
|
+
no `as: :currency` needed, matching the display side.
|
|
125
125
|
|
|
126
126
|
Resolution is `display unit → has_cents unit → config default`, where `nil` means
|
|
127
127
|
"not set, keep looking" and `false` means "stop, no symbol". When nothing is set, it
|
|
@@ -148,7 +148,7 @@ end
|
|
|
148
148
|
field :price, as: :decimal # ✅ virtual name
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
-
Generators sometimes emit the `_cents` name in the policy
|
|
151
|
+
Generators sometimes emit the `_cents` name in the policy; fix by hand (and verify `has_cents` is declared on the model).
|
|
152
152
|
:::
|
|
153
153
|
|
|
154
154
|
### Options
|
|
@@ -176,7 +176,7 @@ product.errors[:price_cents] # => ["must be greater than 0"]
|
|
|
176
176
|
product.errors[:price] # => ["is invalid"]
|
|
177
177
|
```
|
|
178
178
|
|
|
179
|
-
The framework adds an `after_validation` hook that copies `:invalid` from `price_cents` → `price` automatically
|
|
179
|
+
The framework adds an `after_validation` hook that copies `:invalid` from `price_cents` → `price` automatically, no manual wiring needed.
|
|
180
180
|
|
|
181
181
|
### Introspection
|
|
182
182
|
|
|
@@ -196,7 +196,7 @@ post.to_param # => "1" (numeric id)
|
|
|
196
196
|
# URL: /posts/1
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
-
### `path_parameter
|
|
199
|
+
### `path_parameter`: use a stable column
|
|
200
200
|
|
|
201
201
|
Use a column that's unique and human-readable instead of the numeric id:
|
|
202
202
|
|
|
@@ -212,9 +212,9 @@ user.to_param # => "john_doe"
|
|
|
212
212
|
User.from_path_param("john_doe") # finds by username
|
|
213
213
|
```
|
|
214
214
|
|
|
215
|
-
`path_parameter` is a class-level macro (private class method). The column you pass MUST be unique
|
|
215
|
+
`path_parameter` is a class-level macro (private class method). The column you pass MUST be unique; Plutonium uses it for lookup.
|
|
216
216
|
|
|
217
|
-
### `dynamic_path_parameter
|
|
217
|
+
### `dynamic_path_parameter`: SEO-friendly id + slug
|
|
218
218
|
|
|
219
219
|
Combines the id (for stable lookup) with a slug from another column (for SEO):
|
|
220
220
|
|
|
@@ -230,7 +230,7 @@ article.to_param # => "42-hello-world"
|
|
|
230
230
|
Article.from_path_param("42-hello-world") # extracts "42", finds by id
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
-
The slug is informational
|
|
233
|
+
The slug is informational: only the id portion is used for lookup, so changing the title doesn't break old URLs.
|
|
234
234
|
|
|
235
235
|
## Labeling
|
|
236
236
|
|
|
@@ -262,7 +262,7 @@ end
|
|
|
262
262
|
|
|
263
263
|
## SGID accessors
|
|
264
264
|
|
|
265
|
-
Every association on a resource model gets Signed Global ID accessors automatically
|
|
265
|
+
Every association on a resource model gets Signed Global ID accessors automatically, for secure form submission, API payloads, and hidden fields without exposing database ids.
|
|
266
266
|
|
|
267
267
|
### Singular associations (`belongs_to`, `has_one`)
|
|
268
268
|
|
|
@@ -292,7 +292,7 @@ user.add_post_sgid(sgid) # append
|
|
|
292
292
|
user.remove_post_sgid(sgid) # remove
|
|
293
293
|
```
|
|
294
294
|
|
|
295
|
-
These are what `secure_association_tag` uses in forms
|
|
295
|
+
These are what `secure_association_tag` uses in forms; see [UI › Forms](/reference/ui/forms).
|
|
296
296
|
|
|
297
297
|
## Field introspection
|
|
298
298
|
|
|
@@ -335,11 +335,11 @@ Resolution order, association path requirements, three model shapes, and custom
|
|
|
335
335
|
|
|
336
336
|
## Standard ActiveRecord features
|
|
337
337
|
|
|
338
|
-
Everything you'd expect works
|
|
338
|
+
Everything you'd expect works: associations, validations, scopes, callbacks, delegations, `has_rich_text`, `has_secure_token`, `has_one_attached`, etc. Where Plutonium adds twists:
|
|
339
339
|
|
|
340
|
-
- **Section ordering** is by convention, not enforcement
|
|
341
|
-
- **Compound uniqueness for tenant-scoped resources:** `validates :code, uniqueness: {scope: :organization_id}
|
|
342
|
-
- **Keep models thin
|
|
340
|
+
- **Section ordering** is by convention, not enforcement: pick the right slot in the [layout above](#section-layout) so the file stays scannable.
|
|
341
|
+
- **Compound uniqueness for tenant-scoped resources:** `validates :code, uniqueness: {scope: :organization_id}`, without the scope, uniqueness leaks across tenants.
|
|
342
|
+
- **Keep models thin**: business logic that touches multiple records or has multi-step state changes belongs in [interactions](/reference/behavior/interactions), not model methods.
|
|
343
343
|
|
|
344
344
|
## Nested resources
|
|
345
345
|
|
|
@@ -369,6 +369,6 @@ Override with `self.table_name = "posts"` if you need a shared table.
|
|
|
369
369
|
|
|
370
370
|
## Related
|
|
371
371
|
|
|
372
|
-
- [Definition](./definition)
|
|
373
|
-
- [Tenancy › Entity scoping](/reference/tenancy/entity-scoping)
|
|
374
|
-
- [App › Generators](/reference/app/generators)
|
|
372
|
+
- [Definition](./definition): controls how the model's fields render
|
|
373
|
+
- [Tenancy › Entity scoping](/reference/tenancy/entity-scoping): `associated_with`, three model shapes
|
|
374
|
+
- [App › Generators](/reference/app/generators): `pu:res:scaffold` field syntax
|