plutonium 0.60.5 → 0.61.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 +19 -1
- data/.claude/skills/plutonium-app/SKILL.md +41 -0
- data/.claude/skills/plutonium-auth/SKILL.md +40 -0
- data/.claude/skills/plutonium-behavior/SKILL.md +47 -1
- data/.claude/skills/plutonium-kanban/SKILL.md +313 -0
- data/.claude/skills/plutonium-resource/SKILL.md +40 -0
- data/.claude/skills/plutonium-tenancy/SKILL.md +43 -0
- data/.claude/skills/plutonium-testing/SKILL.md +38 -0
- data/.claude/skills/plutonium-ui/SKILL.md +51 -0
- data/.claude/skills/plutonium-wizard/SKILL.md +469 -0
- data/.cliff.toml +6 -0
- data/Appraisals +3 -0
- data/CHANGELOG.md +549 -439
- data/CLAUDE.md +15 -7
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +895 -193
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +53 -53
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/layouts/basic.html.erb +7 -0
- data/app/views/plutonium/_flash_toasts.html.erb +2 -46
- data/app/views/plutonium/_toast.html.erb +52 -0
- data/app/views/resource/_resource_kanban.html.erb +1 -0
- data/db/migrate/wizard/20260615000001_create_plutonium_wizard_sessions.rb +57 -0
- data/docs/.vitepress/config.ts +24 -0
- data/docs/guides/index.md +2 -0
- data/docs/guides/kanban.md +447 -0
- data/docs/guides/wizards.md +447 -0
- data/docs/public/images/guides/kanban-after-move.png +0 -0
- data/docs/public/images/guides/kanban-board-light.png +0 -0
- data/docs/public/images/guides/kanban-board.png +0 -0
- data/docs/public/images/guides/kanban-show-centered-modal.png +0 -0
- data/docs/public/images/guides/kanban-wip-toast.png +0 -0
- data/docs/public/images/guides/wizards-chooser.png +0 -0
- data/docs/public/images/guides/wizards-completed.png +0 -0
- data/docs/public/images/guides/wizards-index-action.png +0 -0
- data/docs/public/images/guides/wizards-repeater.png +0 -0
- data/docs/public/images/guides/wizards-review.png +0 -0
- data/docs/public/images/guides/wizards-step.png +0 -0
- data/docs/reference/behavior/policies.md +1 -1
- data/docs/reference/index.md +14 -0
- data/docs/reference/kanban/authorization.md +62 -0
- data/docs/reference/kanban/dsl.md +293 -0
- data/docs/reference/kanban/index.md +40 -0
- data/docs/reference/kanban/positioning.md +162 -0
- data/docs/reference/resource/definition.md +16 -0
- data/docs/reference/ui/forms.md +36 -0
- data/docs/reference/ui/pages.md +2 -0
- data/docs/reference/wizard/anchoring-resume.md +194 -0
- data/docs/reference/wizard/dsl.md +332 -0
- data/docs/reference/wizard/index.md +33 -0
- data/docs/reference/wizard/one-time.md +129 -0
- data/docs/reference/wizard/registration-launch.md +177 -0
- data/docs/reference/wizard/storage-config.md +151 -0
- data/docs/superpowers/plans/2026-06-14-form-sectioning.md +2 -2
- data/docs/superpowers/plans/2026-06-15-wizard-dsl.md +1619 -0
- data/docs/superpowers/plans/2026-06-15-wizard-dsl.md.tasks.json +68 -0
- data/docs/superpowers/plans/2026-06-26-kanban-dsl.md +1128 -0
- data/docs/superpowers/plans/2026-06-26-kanban-dsl.md.tasks.json +24 -0
- data/docs/superpowers/specs/2026-06-15-wizard-dsl-design.md +836 -0
- data/docs/superpowers/specs/2026-06-15-wizard-dsl-examples.rb +245 -0
- data/docs/superpowers/specs/2026-06-17-wizard-relaunch-prompt-design.md +86 -0
- data/docs/superpowers/specs/2026-06-18-wizard-attachments-design.md +101 -0
- data/docs/superpowers/specs/2026-06-18-wizard-hosting-design.md +220 -0
- data/docs/superpowers/specs/2026-06-26-kanban-dsl-design.md +388 -0
- data/gemfiles/postgres.gemfile +8 -0
- data/gemfiles/postgres.gemfile.lock +321 -0
- data/gemfiles/rails_7.gemfile +1 -0
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile +1 -0
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile +1 -0
- data/gemfiles/rails_8.1.gemfile.lock +14 -1
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +6 -1
- data/lib/plutonium/action/base.rb +9 -0
- data/lib/plutonium/auth/rodauth.rb +1 -2
- data/lib/plutonium/configuration.rb +4 -0
- data/lib/plutonium/core/controller.rb +20 -1
- data/lib/plutonium/definition/base.rb +25 -0
- data/lib/plutonium/definition/form_layout.rb +54 -35
- data/lib/plutonium/definition/index_views.rb +54 -1
- data/lib/plutonium/definition/wizards.rb +209 -0
- data/lib/plutonium/invites/concerns/invite_token.rb +9 -0
- data/lib/plutonium/invites/concerns/invite_user.rb +9 -0
- data/lib/plutonium/invites/controller.rb +4 -1
- data/lib/plutonium/kanban/action.rb +7 -0
- data/lib/plutonium/kanban/board.rb +40 -0
- data/lib/plutonium/kanban/broadcaster.rb +54 -0
- data/lib/plutonium/kanban/column.rb +69 -0
- data/lib/plutonium/kanban/context.rb +15 -0
- data/lib/plutonium/kanban/dsl.rb +71 -0
- data/lib/plutonium/kanban/grouping.rb +51 -0
- data/lib/plutonium/kanban/positioning.rb +75 -0
- data/lib/plutonium/kanban.rb +11 -0
- data/lib/plutonium/migrations.rb +40 -0
- data/lib/plutonium/positioning.rb +146 -0
- data/lib/plutonium/railtie.rb +33 -0
- data/lib/plutonium/resource/controller.rb +2 -0
- data/lib/plutonium/resource/controllers/crud_actions.rb +1 -1
- data/lib/plutonium/resource/controllers/kanban_actions.rb +455 -0
- data/lib/plutonium/resource/controllers/wizard_actions.rb +165 -0
- data/lib/plutonium/resource/policy.rb +8 -0
- data/lib/plutonium/routing/mapper_extensions.rb +44 -0
- data/lib/plutonium/routing/wizard_registration.rb +289 -0
- data/lib/plutonium/ui/display/resource.rb +17 -12
- data/lib/plutonium/ui/form/base.rb +19 -5
- data/lib/plutonium/ui/form/components/password.rb +126 -0
- data/lib/plutonium/ui/form/components/uppy.rb +6 -3
- data/lib/plutonium/ui/form/options/inferred_types.rb +20 -0
- data/lib/plutonium/ui/form/resource.rb +1 -1
- data/lib/plutonium/ui/form/wizard.rb +63 -0
- data/lib/plutonium/ui/grid/card.rb +16 -5
- data/lib/plutonium/ui/kanban/card.rb +67 -0
- data/lib/plutonium/ui/kanban/color_dot.rb +36 -0
- data/lib/plutonium/ui/kanban/column.rb +324 -0
- data/lib/plutonium/ui/kanban/resource.rb +212 -0
- data/lib/plutonium/ui/layout/resource_layout.rb +7 -1
- data/lib/plutonium/ui/modal/base.rb +30 -3
- data/lib/plutonium/ui/modal/centered.rb +5 -2
- data/lib/plutonium/ui/page/index.rb +1 -0
- data/lib/plutonium/ui/page/show.rb +23 -0
- data/lib/plutonium/ui/page/wizard.rb +371 -0
- data/lib/plutonium/ui/page/wizard_chooser.rb +97 -0
- data/lib/plutonium/ui/page/wizard_completed.rb +86 -0
- data/lib/plutonium/ui/table/base.rb +1 -1
- data/lib/plutonium/ui/table/components/view_switcher.rb +2 -1
- data/lib/plutonium/ui/wizard/review.rb +196 -0
- data/lib/plutonium/ui/wizard/stepper.rb +122 -0
- data/lib/plutonium/ui/wizard/summary_display.rb +59 -0
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium/wizard/attachment_data.rb +42 -0
- data/lib/plutonium/wizard/attachments.rb +226 -0
- data/lib/plutonium/wizard/base.rb +216 -0
- data/lib/plutonium/wizard/base_controller.rb +31 -0
- data/lib/plutonium/wizard/configuration.rb +42 -0
- data/lib/plutonium/wizard/controller.rb +162 -0
- data/lib/plutonium/wizard/data.rb +134 -0
- data/lib/plutonium/wizard/driving.rb +639 -0
- data/lib/plutonium/wizard/dsl.rb +336 -0
- data/lib/plutonium/wizard/errors.rb +27 -0
- data/lib/plutonium/wizard/field_capture.rb +157 -0
- data/lib/plutonium/wizard/field_importer.rb +208 -0
- data/lib/plutonium/wizard/gate.rb +171 -0
- data/lib/plutonium/wizard/instance_key.rb +97 -0
- data/lib/plutonium/wizard/lazy_persisted.rb +77 -0
- data/lib/plutonium/wizard/resume.rb +250 -0
- data/lib/plutonium/wizard/review_step.rb +48 -0
- data/lib/plutonium/wizard/route_resolution.rb +40 -0
- data/lib/plutonium/wizard/runner.rb +684 -0
- data/lib/plutonium/wizard/session.rb +53 -0
- data/lib/plutonium/wizard/state.rb +35 -0
- data/lib/plutonium/wizard/step.rb +61 -0
- data/lib/plutonium/wizard/step_adapter.rb +103 -0
- data/lib/plutonium/wizard/store/active_record.rb +174 -0
- data/lib/plutonium/wizard/store/base.rb +42 -0
- data/lib/plutonium/wizard/store/memory.rb +44 -0
- data/lib/plutonium/wizard/sweep_job.rb +76 -0
- data/lib/plutonium/wizard.rb +86 -0
- data/lib/plutonium.rb +5 -0
- data/lib/rodauth/features/case_insensitive_login.rb +1 -1
- data/lib/tasks/release.rake +144 -191
- data/package.json +3 -3
- data/src/css/components.css +132 -0
- data/src/js/controllers/attachment_input_controller.js +15 -1
- data/src/js/controllers/dirty_form_guard_controller.js +155 -27
- data/src/js/controllers/kanban_controller.js +330 -0
- data/src/js/controllers/password_sentinel_controller.js +39 -0
- data/src/js/controllers/register_controllers.js +6 -0
- data/src/js/controllers/remote_modal_controller.js +10 -0
- data/src/js/controllers/row_click_controller.js +14 -1
- data/src/js/controllers/wizard_controller.js +54 -0
- data/src/js/turbo/turbo_confirm.js +1 -1
- data/yarn.lock +271 -282
- metadata +100 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "plutonium/positioning"
|
|
4
|
+
|
|
5
|
+
module Plutonium
|
|
6
|
+
module Kanban
|
|
7
|
+
module Positioning
|
|
8
|
+
# Value object passed to Mode B blocks, carrying the full drop context.
|
|
9
|
+
Move = Data.define(:record, :column, :prev, :next, :index)
|
|
10
|
+
|
|
11
|
+
# Strategy configuration object created by the `position_on` DSL.
|
|
12
|
+
#
|
|
13
|
+
# Three modes:
|
|
14
|
+
# Mode A (:delegate) — delegate reposition! to Plutonium::Positioning concern
|
|
15
|
+
# Mode B (:block) — call a user-supplied block with a Move
|
|
16
|
+
# Mode C (:disabled) — no ordering; relation returned unchanged
|
|
17
|
+
class Config
|
|
18
|
+
# Mode A, default attribute :position
|
|
19
|
+
def self.default
|
|
20
|
+
new(:delegate, :position, nil)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Mode A, custom attribute
|
|
24
|
+
def self.attribute(attr)
|
|
25
|
+
new(:delegate, attr.to_sym, nil)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Mode B — orders by attr, write delegated to block
|
|
29
|
+
def self.with_block(attr, block)
|
|
30
|
+
new(:block, attr.to_sym, block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Mode C — disabled
|
|
34
|
+
def self.disabled
|
|
35
|
+
new(:disabled, nil, nil)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
attr_reader :attribute
|
|
39
|
+
|
|
40
|
+
def initialize(mode, attribute, block)
|
|
41
|
+
@mode = mode
|
|
42
|
+
@attribute = attribute
|
|
43
|
+
@block = block
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def disabled?
|
|
47
|
+
@mode == :disabled
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Apply positional ordering to a relation.
|
|
51
|
+
# Mode A/B: relation.reorder(attribute)
|
|
52
|
+
# Mode C: return relation unchanged
|
|
53
|
+
def order(relation)
|
|
54
|
+
return relation if disabled?
|
|
55
|
+
relation.reorder(@attribute)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Persist the new position for a dropped record.
|
|
59
|
+
# Mode A: delegate to record.reposition!(prev_record:, next_record:)
|
|
60
|
+
# Mode B: call the user block with a Move
|
|
61
|
+
# Mode C: no-op
|
|
62
|
+
def reposition!(record:, column:, prev_record:, next_record:, index:)
|
|
63
|
+
case @mode
|
|
64
|
+
when :delegate
|
|
65
|
+
record.reposition!(prev_record:, next_record:)
|
|
66
|
+
when :block
|
|
67
|
+
@block.call(Move.new(record:, column:, prev: prev_record, next: next_record, index:))
|
|
68
|
+
when :disabled
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "plutonium/positioning"
|
|
4
|
+
require "plutonium/kanban/positioning"
|
|
5
|
+
require "plutonium/kanban/action"
|
|
6
|
+
require "plutonium/kanban/column"
|
|
7
|
+
require "plutonium/kanban/board"
|
|
8
|
+
require "plutonium/kanban/dsl"
|
|
9
|
+
require "plutonium/kanban/context"
|
|
10
|
+
require "plutonium/kanban/grouping"
|
|
11
|
+
require "plutonium/kanban/broadcaster"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plutonium
|
|
4
|
+
# Registry of per-feature migration paths.
|
|
5
|
+
#
|
|
6
|
+
# Features register their migration directory against a configuration key. A
|
|
7
|
+
# path is only surfaced by {.enabled_paths} when its feature's config
|
|
8
|
+
# (+Plutonium.configuration.<feature>+) responds to +.enabled+ and it is true.
|
|
9
|
+
module Migrations
|
|
10
|
+
@registry = {}
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
# Register a migration path for a feature.
|
|
14
|
+
#
|
|
15
|
+
# @param feature [Symbol, String] the configuration key (e.g. +:wizards+)
|
|
16
|
+
# @param path [String] the absolute migration directory path
|
|
17
|
+
# @return [String] the registered path
|
|
18
|
+
def register(feature, path)
|
|
19
|
+
@registry[feature.to_sym] = path
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Clear the registry. Intended for tests.
|
|
23
|
+
#
|
|
24
|
+
# @return [Hash]
|
|
25
|
+
def reset!
|
|
26
|
+
@registry = {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Migration paths for features that are currently enabled.
|
|
30
|
+
#
|
|
31
|
+
# @return [Array<String>]
|
|
32
|
+
def enabled_paths
|
|
33
|
+
@registry.filter_map do |feature, path|
|
|
34
|
+
cfg = Plutonium.configuration.public_send(feature) if Plutonium.configuration.respond_to?(feature)
|
|
35
|
+
path if cfg&.respond_to?(:enabled) && cfg.enabled
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plutonium
|
|
4
|
+
# Standalone decimal/fractional ordering. Kanban-independent.
|
|
5
|
+
#
|
|
6
|
+
# Including this concern and calling `positioned_on` gives a model:
|
|
7
|
+
# - automatic position assignment on create (appends to the end of its scope group)
|
|
8
|
+
# - `reposition!(prev_record:, next_record:)` for drag-and-drop reordering
|
|
9
|
+
# - `backfill_positions!` class method to number existing rows
|
|
10
|
+
#
|
|
11
|
+
# Pure math helpers are exposed as module-level methods so they can be
|
|
12
|
+
# called without an AR instance:
|
|
13
|
+
# Plutonium::Positioning.position_between(1.0, 3.0) # => 2.0
|
|
14
|
+
# Plutonium::Positioning.gap_exhausted?(1.0, 1.0) # => true
|
|
15
|
+
module Positioning
|
|
16
|
+
extend ActiveSupport::Concern
|
|
17
|
+
|
|
18
|
+
EPSILON = 1e-6
|
|
19
|
+
|
|
20
|
+
# Returns the position that sits between +prev_val+ and +next_val+.
|
|
21
|
+
#
|
|
22
|
+
# Rules:
|
|
23
|
+
# both nil → 0.0 (first item in an empty list)
|
|
24
|
+
# prev nil → next_val - 1 (prepend)
|
|
25
|
+
# next nil → prev_val + 1 (append)
|
|
26
|
+
# else → midpoint
|
|
27
|
+
def self.position_between(prev_val, next_val)
|
|
28
|
+
return 0.0 if prev_val.nil? && next_val.nil?
|
|
29
|
+
return next_val - 1 if prev_val.nil?
|
|
30
|
+
return prev_val + 1 if next_val.nil?
|
|
31
|
+
(prev_val + next_val) / 2.0
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns true when +prev_val+ and +next_val+ are so close together
|
|
35
|
+
# that inserting a new midpoint would produce a duplicate.
|
|
36
|
+
def self.gap_exhausted?(prev_val, next_val)
|
|
37
|
+
return false if prev_val.nil? || next_val.nil?
|
|
38
|
+
(next_val - prev_val).abs < EPSILON
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
included do
|
|
42
|
+
class_attribute :positioning_column, instance_accessor: false, default: :position
|
|
43
|
+
class_attribute :positioning_scope_attr, instance_accessor: false, default: nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class_methods do
|
|
47
|
+
# Opt in to positional ordering.
|
|
48
|
+
#
|
|
49
|
+
# positioned_on :position, scope: :status
|
|
50
|
+
#
|
|
51
|
+
# @param column [Symbol] the decimal column that stores positions
|
|
52
|
+
# @param scope [Symbol, nil] group rows by this column; nil = single global group
|
|
53
|
+
def positioned_on(column = :position, scope: nil)
|
|
54
|
+
self.positioning_column = column
|
|
55
|
+
self.positioning_scope_attr = scope
|
|
56
|
+
before_create :assign_initial_position
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Number every row in the table per scope group as 1.0, 2.0, … in
|
|
60
|
+
# +order+ order. Safe to call on an empty table.
|
|
61
|
+
#
|
|
62
|
+
# @param order [Symbol] column to sort by when assigning positions
|
|
63
|
+
def backfill_positions!(order: :created_at)
|
|
64
|
+
groups = positioning_scope_attr ? all.group_by(&positioning_scope_attr) : {nil => all.to_a}
|
|
65
|
+
groups.each_value do |rows|
|
|
66
|
+
ActiveRecord::Base.transaction do
|
|
67
|
+
rows.sort_by { |r| r.public_send(order) }.each_with_index do |row, i|
|
|
68
|
+
row.update_column(positioning_column, (i + 1).to_f)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Move this record so it sits between +prev_record+ and +next_record+
|
|
76
|
+
# within its scope group. Pass nil for either neighbor to move to an end.
|
|
77
|
+
#
|
|
78
|
+
# If the gap between the two neighbors is exhausted (too small to split)
|
|
79
|
+
# the scope group is rebalanced first so that fresh integer positions are
|
|
80
|
+
# available, then the record is positioned between the reloaded neighbors.
|
|
81
|
+
#
|
|
82
|
+
# @param prev_record [ActiveRecord::Base, nil]
|
|
83
|
+
# @param next_record [ActiveRecord::Base, nil]
|
|
84
|
+
def reposition!(prev_record:, next_record:)
|
|
85
|
+
col = self.class.positioning_column
|
|
86
|
+
prev_val = prev_record&.public_send(col)
|
|
87
|
+
next_val = next_record&.public_send(col)
|
|
88
|
+
if Plutonium::Positioning.gap_exhausted?(prev_val, next_val)
|
|
89
|
+
rebalance_scope_group!
|
|
90
|
+
prev_val = prev_record&.reload&.public_send(col)
|
|
91
|
+
next_val = next_record&.reload&.public_send(col)
|
|
92
|
+
end
|
|
93
|
+
update!(col => Plutonium::Positioning.position_between(prev_val, next_val))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def assign_initial_position
|
|
99
|
+
col = self.class.positioning_column
|
|
100
|
+
return if public_send(col).present?
|
|
101
|
+
max = positioning_group_relation.maximum(col) || 0.0
|
|
102
|
+
public_send(:"#{col}=", max + 1)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def positioning_group_relation
|
|
106
|
+
rel = self.class.all
|
|
107
|
+
attr = self.class.positioning_scope_attr
|
|
108
|
+
attr ? rel.where(attr => public_send(attr)) : rel
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def rebalance_scope_group!
|
|
112
|
+
col = self.class.positioning_column
|
|
113
|
+
ActiveRecord::Base.transaction do
|
|
114
|
+
positioning_group_relation.order(col).each_with_index do |row, i|
|
|
115
|
+
row.update_column(col, (i + 1).to_f)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Migration helper that adds a position column pre-tuned for fractional
|
|
121
|
+
# ordering. Mixed into ActiveRecord's table-definition classes by the
|
|
122
|
+
# railtie, so it is available in both create_table and change_table:
|
|
123
|
+
#
|
|
124
|
+
# create_table :tasks do |t|
|
|
125
|
+
# t.position # decimal :position, precision: 16, scale: 8
|
|
126
|
+
# t.position :sort_order # custom column name
|
|
127
|
+
# t.position index: true # also add a single-column index
|
|
128
|
+
# end
|
|
129
|
+
#
|
|
130
|
+
# change_table :tasks do |t|
|
|
131
|
+
# t.position
|
|
132
|
+
# end
|
|
133
|
+
#
|
|
134
|
+
# The precision/scale give midpoints ample headroom over the rebalance
|
|
135
|
+
# threshold (EPSILON = 1e-6) — a too-small scale lets the last subdivision
|
|
136
|
+
# round to a neighbor. Pass precision:/scale: to override.
|
|
137
|
+
module MigrationHelpers
|
|
138
|
+
DEFAULT_PRECISION = 16
|
|
139
|
+
DEFAULT_SCALE = 8
|
|
140
|
+
|
|
141
|
+
def position(name = :position, **options)
|
|
142
|
+
column name, :decimal, precision: DEFAULT_PRECISION, scale: DEFAULT_SCALE, **options
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
data/lib/plutonium/railtie.rb
CHANGED
|
@@ -39,6 +39,30 @@ module Plutonium
|
|
|
39
39
|
load_plutonium_initializers
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
initializer "plutonium.register_migrations" do
|
|
43
|
+
Plutonium::Migrations.register(:wizards, Plutonium.root.join("db/migrate/wizard").to_s)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Runs after the host's config/initializers/plutonium.rb (load_config_initializers)
|
|
47
|
+
# so feature flags like config.wizards.enabled are honoured. Railtie
|
|
48
|
+
# initializers otherwise run before host initializers, leaving the flag false.
|
|
49
|
+
initializer "plutonium.migrations", after: :load_config_initializers do |app|
|
|
50
|
+
# v1 supports `config.wizards.database == :primary` only; the option is
|
|
51
|
+
# reserved for future multi-db routing of wizard sessions. Fail loudly on an
|
|
52
|
+
# unsupported value rather than silently registering on the primary database.
|
|
53
|
+
if Plutonium.configuration.wizards.enabled &&
|
|
54
|
+
Plutonium.configuration.wizards.database != :primary
|
|
55
|
+
raise ArgumentError,
|
|
56
|
+
"config.wizards.database = #{Plutonium.configuration.wizards.database.inspect} is not " \
|
|
57
|
+
"supported (only :primary). Multi-database routing for wizard sessions is a roadmap follow-up."
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
Plutonium::Migrations.enabled_paths.each do |path|
|
|
61
|
+
app.config.paths["db/migrate"] << path
|
|
62
|
+
ActiveRecord::Migrator.migrations_paths << path unless ActiveRecord::Migrator.migrations_paths.include?(path)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
42
66
|
initializer "plutonium.asset_server" do
|
|
43
67
|
setup_development_asset_server if Plutonium.configuration.development?
|
|
44
68
|
end
|
|
@@ -47,6 +71,14 @@ module Plutonium
|
|
|
47
71
|
extend_action_dispatch
|
|
48
72
|
end
|
|
49
73
|
|
|
74
|
+
# Make `t.position` available in create_table / change_table blocks.
|
|
75
|
+
initializer "plutonium.positioning" do
|
|
76
|
+
ActiveSupport.on_load(:active_record) do
|
|
77
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.include(Plutonium::Positioning::MigrationHelpers)
|
|
78
|
+
ActiveRecord::ConnectionAdapters::Table.include(Plutonium::Positioning::MigrationHelpers)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
50
82
|
initializer "plutonium.phlexi_themes" do
|
|
51
83
|
setup_phlexi_themes
|
|
52
84
|
end
|
|
@@ -97,6 +129,7 @@ module Plutonium
|
|
|
97
129
|
|
|
98
130
|
def extend_action_dispatch
|
|
99
131
|
ActionDispatch::Routing::Mapper.prepend Plutonium::Routing::MapperExtensions
|
|
132
|
+
ActionDispatch::Routing::Mapper.prepend Plutonium::Routing::WizardRegistration
|
|
100
133
|
ActionDispatch::Routing::RouteSet.prepend Plutonium::Routing::RouteSetExtensions
|
|
101
134
|
Rails::Engine.include Plutonium::Routing::ResourceRegistration
|
|
102
135
|
end
|
|
@@ -14,7 +14,9 @@ module Plutonium
|
|
|
14
14
|
include Plutonium::Resource::Controllers::Presentable
|
|
15
15
|
include Plutonium::Resource::Controllers::Queryable
|
|
16
16
|
include Plutonium::Resource::Controllers::CrudActions
|
|
17
|
+
include Plutonium::Resource::Controllers::KanbanActions
|
|
17
18
|
include Plutonium::Resource::Controllers::InteractiveActions
|
|
19
|
+
include Plutonium::Resource::Controllers::WizardActions
|
|
18
20
|
include Plutonium::Resource::Controllers::Typeahead
|
|
19
21
|
include Plutonium::Resource::Controllers::ExportCsv
|
|
20
22
|
include Plutonium::StructuredInputs::ParamsConcern
|
|
@@ -142,7 +142,7 @@ module Plutonium
|
|
|
142
142
|
notice: "#{resource_class.model_name.human} was successfully deleted."
|
|
143
143
|
end
|
|
144
144
|
format.json { head :no_content }
|
|
145
|
-
rescue ActiveRecord::InvalidForeignKey
|
|
145
|
+
rescue ActiveRecord::InvalidForeignKey, ActiveRecord::DeleteRestrictionError
|
|
146
146
|
format.turbo_stream do
|
|
147
147
|
flash.alert = "#{resource_class.model_name.human} is referenced by other records."
|
|
148
148
|
render turbo_stream: helpers.turbo_stream_redirect(resource_url_for(resource_record!))
|