plutonium 0.33.1 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/# Plutonium: The pre-alpha demo.md +4 -2
- data/.claude/skills/assets/SKILL.md +416 -0
- data/.claude/skills/connect-resource/SKILL.md +112 -0
- data/.claude/skills/controller/SKILL.md +302 -0
- data/.claude/skills/create-resource/SKILL.md +240 -0
- data/.claude/skills/definition/SKILL.md +218 -0
- data/.claude/skills/definition-actions/SKILL.md +386 -0
- data/.claude/skills/definition-fields/SKILL.md +474 -0
- data/.claude/skills/definition-query/SKILL.md +334 -0
- data/.claude/skills/forms/SKILL.md +439 -0
- data/.claude/skills/installation/SKILL.md +300 -0
- data/.claude/skills/interaction/SKILL.md +382 -0
- data/.claude/skills/model/SKILL.md +267 -0
- data/.claude/skills/model-features/SKILL.md +286 -0
- data/.claude/skills/nested-resources/SKILL.md +274 -0
- data/.claude/skills/package/SKILL.md +191 -0
- data/.claude/skills/policy/SKILL.md +352 -0
- data/.claude/skills/portal/SKILL.md +400 -0
- data/.claude/skills/resource/SKILL.md +281 -0
- data/.claude/skills/rodauth/SKILL.md +452 -0
- data/.claude/skills/views/SKILL.md +563 -0
- data/Appraisals +46 -4
- data/CHANGELOG.md +32 -1
- data/app/assets/plutonium.css +2 -2
- data/config/brakeman.ignore +239 -0
- data/config/initializers/action_policy.rb +1 -1
- data/docs/.vitepress/config.ts +132 -47
- data/docs/concepts/architecture.md +226 -0
- data/docs/concepts/auto-detection.md +254 -0
- data/docs/concepts/index.md +61 -0
- data/docs/concepts/packages-portals.md +304 -0
- data/docs/concepts/resources.md +224 -0
- data/docs/cookbook/blog.md +412 -0
- data/docs/cookbook/index.md +289 -0
- data/docs/cookbook/saas.md +481 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +146 -0
- data/docs/getting-started/tutorial/01-setup.md +118 -0
- data/docs/getting-started/tutorial/02-first-resource.md +180 -0
- data/docs/getting-started/tutorial/03-authentication.md +246 -0
- data/docs/getting-started/tutorial/04-authorization.md +170 -0
- data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
- data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
- data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
- data/docs/getting-started/tutorial/index.md +64 -0
- data/docs/guides/adding-resources.md +420 -0
- data/docs/guides/authentication.md +551 -0
- data/docs/guides/authorization.md +468 -0
- data/docs/guides/creating-packages.md +380 -0
- data/docs/guides/custom-actions.md +523 -0
- data/docs/guides/index.md +45 -0
- data/docs/guides/multi-tenancy.md +302 -0
- data/docs/guides/nested-resources.md +411 -0
- data/docs/guides/search-filtering.md +266 -0
- data/docs/guides/theming.md +321 -0
- data/docs/index.md +67 -26
- data/docs/public/CLAUDE.md +64 -21
- data/docs/reference/assets/index.md +496 -0
- data/docs/reference/controller/index.md +363 -0
- data/docs/reference/definition/actions.md +400 -0
- data/docs/reference/definition/fields.md +350 -0
- data/docs/reference/definition/index.md +252 -0
- data/docs/reference/definition/query.md +342 -0
- data/docs/reference/generators/index.md +469 -0
- data/docs/reference/index.md +49 -0
- data/docs/reference/interaction/index.md +445 -0
- data/docs/reference/model/features.md +248 -0
- data/docs/reference/model/index.md +219 -0
- data/docs/reference/policy/index.md +385 -0
- data/docs/reference/portal/index.md +382 -0
- data/docs/reference/views/forms.md +396 -0
- data/docs/reference/views/index.md +479 -0
- data/gemfiles/rails_7.gemfile +9 -2
- data/gemfiles/rails_7.gemfile.lock +146 -111
- data/gemfiles/rails_8.0.gemfile +20 -0
- data/gemfiles/rails_8.0.gemfile.lock +417 -0
- data/gemfiles/rails_8.1.gemfile +20 -0
- data/gemfiles/rails_8.1.gemfile.lock +419 -0
- data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
- data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
- data/lib/generators/pu/pkg/portal/USAGE +65 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
- data/lib/generators/pu/res/conn/USAGE +71 -0
- data/lib/generators/pu/res/model/USAGE +106 -110
- data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
- data/lib/generators/pu/res/scaffold/USAGE +85 -0
- data/lib/generators/pu/rodauth/install_generator.rb +2 -6
- data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
- data/lib/generators/pu/skills/sync/USAGE +14 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
- data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
- data/lib/plutonium/core/controller.rb +2 -2
- data/lib/plutonium/interaction/base.rb +1 -0
- data/lib/plutonium/package/engine.rb +2 -2
- data/lib/plutonium/query/adhoc_block.rb +6 -2
- data/lib/plutonium/query/model_scope.rb +1 -1
- data/lib/plutonium/railtie.rb +4 -0
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
- data/lib/plutonium/resource/query_object.rb +38 -8
- data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +19 -4
- data/package.json +1 -1
- metadata +76 -39
- data/brakeman.ignore +0 -28
- data/docs/api-examples.md +0 -49
- data/docs/guide/claude-code-guide.md +0 -74
- data/docs/guide/deep-dive/authorization.md +0 -189
- data/docs/guide/deep-dive/multitenancy.md +0 -256
- data/docs/guide/deep-dive/resources.md +0 -390
- data/docs/guide/getting-started/01-installation.md +0 -165
- data/docs/guide/index.md +0 -28
- data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
- data/docs/guide/introduction/02-core-concepts.md +0 -440
- data/docs/guide/tutorial/01-project-setup.md +0 -75
- data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
- data/docs/guide/tutorial/03-defining-resources.md +0 -90
- data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
- data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
- data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
- data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
- data/docs/markdown-examples.md +0 -85
- data/docs/modules/action.md +0 -244
- data/docs/modules/authentication.md +0 -236
- data/docs/modules/configuration.md +0 -599
- data/docs/modules/controller.md +0 -443
- data/docs/modules/core.md +0 -316
- data/docs/modules/definition.md +0 -1308
- data/docs/modules/display.md +0 -759
- data/docs/modules/form.md +0 -495
- data/docs/modules/generator.md +0 -400
- data/docs/modules/index.md +0 -167
- data/docs/modules/interaction.md +0 -642
- data/docs/modules/package.md +0 -151
- data/docs/modules/policy.md +0 -176
- data/docs/modules/portal.md +0 -710
- data/docs/modules/query.md +0 -297
- data/docs/modules/resource_record.md +0 -618
- data/docs/modules/routing.md +0 -690
- data/docs/modules/table.md +0 -301
- data/docs/modules/ui.md +0 -631
|
@@ -1,113 +1,109 @@
|
|
|
1
1
|
Description:
|
|
2
|
-
Generates a
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
`bin/rails generate model product supplier:references{polymorphic}`
|
|
59
|
-
|
|
60
|
-
For integer, string, text and binary fields, an integer in curly braces will
|
|
61
|
-
be set as the limit:
|
|
62
|
-
|
|
63
|
-
`bin/rails generate model user pseudo:string{30}`
|
|
64
|
-
|
|
65
|
-
For decimal, two integers separated by a comma in curly braces will be used
|
|
66
|
-
for precision and scale:
|
|
67
|
-
|
|
68
|
-
`bin/rails generate model product 'price:decimal{10,2}'`
|
|
69
|
-
|
|
70
|
-
You can add a `:uniq` or `:index` suffix for unique or standard indexes
|
|
71
|
-
respectively:
|
|
72
|
-
|
|
73
|
-
`bin/rails generate model user pseudo:string:uniq`
|
|
74
|
-
`bin/rails generate model user pseudo:string:index`
|
|
75
|
-
|
|
76
|
-
You can combine any single curly brace option with the index options:
|
|
77
|
-
|
|
78
|
-
`bin/rails generate model user username:string{30}:uniq`
|
|
79
|
-
`bin/rails generate model product supplier:references{polymorphic}:index`
|
|
80
|
-
|
|
81
|
-
If you require a `password_digest` string column for use with
|
|
82
|
-
has_secure_password, you can specify `password:digest`:
|
|
83
|
-
|
|
84
|
-
`bin/rails generate model user password:digest`
|
|
85
|
-
|
|
86
|
-
If you require a `token` string column for use with
|
|
87
|
-
has_secure_token, you can specify `auth_token:token`:
|
|
88
|
-
|
|
89
|
-
`bin/rails generate model user auth_token:token`
|
|
2
|
+
Generates a Plutonium model with migration.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
rails g pu:res:model NAME [field:type field:type] [options]
|
|
6
|
+
|
|
7
|
+
Options:
|
|
8
|
+
--dest=DESTINATION Target destination (required to avoid interactive prompts)
|
|
9
|
+
Use 'main_app' for main application resources
|
|
10
|
+
Use 'package_name' for feature package resources
|
|
11
|
+
|
|
12
|
+
Field Syntax:
|
|
13
|
+
name:type Required field
|
|
14
|
+
'name:type?' Nullable field (quote to prevent shell expansion)
|
|
15
|
+
name:type:index Field with index
|
|
16
|
+
name:type:uniq Field with unique index
|
|
17
|
+
|
|
18
|
+
Supported Types:
|
|
19
|
+
string VARCHAR
|
|
20
|
+
text TEXT
|
|
21
|
+
integer INTEGER
|
|
22
|
+
float FLOAT
|
|
23
|
+
decimal DECIMAL
|
|
24
|
+
boolean BOOLEAN
|
|
25
|
+
date DATE
|
|
26
|
+
datetime DATETIME
|
|
27
|
+
time TIME
|
|
28
|
+
binary BINARY
|
|
29
|
+
belongs_to Foreign key with association
|
|
30
|
+
references Same as belongs_to
|
|
31
|
+
rich_text ActionText (has_rich_text)
|
|
32
|
+
attachment Single file (has_one_attached)
|
|
33
|
+
attachments Multiple files (has_many_attached)
|
|
34
|
+
token Secure token (has_secure_token, auto unique index)
|
|
35
|
+
password_digest Secure password (has_secure_password)
|
|
36
|
+
|
|
37
|
+
Type Options (in curly braces):
|
|
38
|
+
'field:decimal{10,2}' Decimal precision and scale
|
|
39
|
+
'field:decimal?{10,2}' Nullable with precision
|
|
40
|
+
'field:string{100}' String limit (optional)
|
|
41
|
+
'ref:references{polymorphic}' Polymorphic association (optional)
|
|
42
|
+
|
|
43
|
+
Note: For default values, edit the migration manually after generation.
|
|
44
|
+
|
|
45
|
+
Nullable Fields:
|
|
46
|
+
Append ? to any type to make it nullable. Must quote to prevent shell expansion:
|
|
47
|
+
|
|
48
|
+
'name:string?' null: true
|
|
49
|
+
'score:integer?' null: true
|
|
50
|
+
'parent:belongs_to?' null: true, optional: true in model
|
|
51
|
+
'amount:decimal?{10,2}' null: true with precision
|
|
52
|
+
|
|
53
|
+
Index Types:
|
|
54
|
+
email:string:index Regular index
|
|
55
|
+
email:string:uniq Unique index
|
|
56
|
+
'code:string{50}:uniq' With limit and unique index
|
|
90
57
|
|
|
91
58
|
Examples:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
59
|
+
# Main app model
|
|
60
|
+
rails g pu:res:model post title:string 'body:text?' --dest=main_app
|
|
61
|
+
|
|
62
|
+
# Package model
|
|
63
|
+
rails g pu:res:model post title:string 'body:text?' --dest=blogging
|
|
64
|
+
|
|
65
|
+
# With associations
|
|
66
|
+
rails g pu:res:model article \
|
|
67
|
+
user:belongs_to \
|
|
68
|
+
'category:belongs_to?' \
|
|
69
|
+
title:string \
|
|
70
|
+
slug:string:uniq \
|
|
71
|
+
--dest=blogging
|
|
72
|
+
|
|
73
|
+
# With decimal precision
|
|
74
|
+
rails g pu:res:model product \
|
|
75
|
+
name:string \
|
|
76
|
+
'price:decimal{10,2}' \
|
|
77
|
+
'cost:decimal?{10,2}' \
|
|
78
|
+
--dest=inventory
|
|
79
|
+
|
|
80
|
+
# Cross-package reference
|
|
81
|
+
rails g pu:res:model order_item \
|
|
82
|
+
order:belongs_to \
|
|
83
|
+
inventory/product:belongs_to \
|
|
84
|
+
quantity:integer \
|
|
85
|
+
'unit_price:decimal{10,2}' \
|
|
86
|
+
--dest=sales
|
|
87
|
+
|
|
88
|
+
# With attachments and rich text
|
|
89
|
+
rails g pu:res:model article \
|
|
90
|
+
title:string \
|
|
91
|
+
content:rich_text \
|
|
92
|
+
cover:attachment \
|
|
93
|
+
gallery:attachments \
|
|
94
|
+
--dest=blogging
|
|
95
|
+
|
|
96
|
+
# With token
|
|
97
|
+
rails g pu:res:model api_key \
|
|
98
|
+
user:belongs_to \
|
|
99
|
+
access_token:token \
|
|
100
|
+
'expires_at:datetime?' \
|
|
101
|
+
--dest=auth
|
|
102
|
+
|
|
103
|
+
Generated Files (main_app):
|
|
104
|
+
Model: app/models/[name].rb
|
|
105
|
+
Migration: db/migrate/xxx_create_[names].rb
|
|
106
|
+
|
|
107
|
+
Generated Files (package):
|
|
108
|
+
Model: app/models/[package]/[name].rb
|
|
109
|
+
Migration: db/migrate/xxx_create_[package]_[names].rb
|
|
@@ -6,13 +6,17 @@ require_relative "../<%= class_path.last.underscore %>"
|
|
|
6
6
|
class <%= class_name %> < <%= [feature_package_name, "ResourceRecord"].join "::" %>
|
|
7
7
|
# add concerns above.
|
|
8
8
|
|
|
9
|
+
# add constants above.
|
|
10
|
+
|
|
11
|
+
# add enums above.
|
|
12
|
+
|
|
9
13
|
<% attributes.select(&:cents?).each do |attribute| -%>
|
|
10
14
|
has_cents :<%= attribute.name %>
|
|
11
15
|
<% end -%>
|
|
12
16
|
# add model configurations above.
|
|
13
17
|
|
|
14
18
|
<% attributes.select(&:reference?).each do |attribute| -%>
|
|
15
|
-
belongs_to :<%= attribute.name %><%= ", polymorphic: true" if attribute.polymorphic? %><%= ", class_name: \"#{attribute.attr_options[:class_name]}\"" if attribute.attr_options[:class_name] %>
|
|
19
|
+
belongs_to :<%= attribute.name %><%= ", optional: true" unless attribute.required? %><%= ", polymorphic: true" if attribute.polymorphic? %><%= ", class_name: \"#{attribute.attr_options[:class_name]}\"" if attribute.attr_options[:class_name] %>
|
|
16
20
|
<% end -%>
|
|
17
21
|
# add belongs_to associations above.
|
|
18
22
|
|
|
@@ -51,6 +55,6 @@ class <%= class_name %> < <%= [feature_package_name, "ResourceRecord"].join "::"
|
|
|
51
55
|
<% end -%>
|
|
52
56
|
# add misc attribute macros above.
|
|
53
57
|
|
|
54
|
-
# add methods above.
|
|
58
|
+
# add methods above. add private methods below.
|
|
55
59
|
end
|
|
56
60
|
<% end -%>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Generates a complete Plutonium resource with model, migration, controller,
|
|
3
|
+
policy, and definition.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
rails g pu:res:scaffold NAME [field:type field:type] --dest=DESTINATION
|
|
7
|
+
|
|
8
|
+
Options:
|
|
9
|
+
--dest=DESTINATION Target destination (required to avoid interactive prompts)
|
|
10
|
+
Use 'main_app' for main application resources
|
|
11
|
+
Use 'package_name' for feature package resources
|
|
12
|
+
--no-model Skip model and migration generation
|
|
13
|
+
|
|
14
|
+
Field Syntax:
|
|
15
|
+
name:type Required field
|
|
16
|
+
'name:type?' Nullable field (quote to prevent shell expansion)
|
|
17
|
+
name:type:index Field with index
|
|
18
|
+
name:type:uniq Field with unique index
|
|
19
|
+
|
|
20
|
+
Supported Types:
|
|
21
|
+
string, text, integer, float, decimal, boolean, date, datetime, time,
|
|
22
|
+
binary, belongs_to, references, rich_text, attachment, attachments, token
|
|
23
|
+
|
|
24
|
+
Type Options (decimal only):
|
|
25
|
+
'field:decimal{10,2}' Precision and scale
|
|
26
|
+
'field:decimal?{10,2}' Nullable with precision
|
|
27
|
+
|
|
28
|
+
Note: The {precision,scale} syntax only works for decimal types.
|
|
29
|
+
For default values on other types, edit the migration manually.
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
# Main app resource
|
|
33
|
+
rails g pu:res:scaffold post title:string 'body:text?' --dest=main_app
|
|
34
|
+
|
|
35
|
+
# Package resource with associations and indexes
|
|
36
|
+
rails g pu:res:scaffold article \
|
|
37
|
+
user:belongs_to \
|
|
38
|
+
title:string \
|
|
39
|
+
slug:string:uniq \
|
|
40
|
+
'content:text?' \
|
|
41
|
+
'published_at:datetime?' \
|
|
42
|
+
--dest=blogging
|
|
43
|
+
|
|
44
|
+
# Nullable association
|
|
45
|
+
rails g pu:res:scaffold comment \
|
|
46
|
+
user:belongs_to \
|
|
47
|
+
'parent:belongs_to?' \
|
|
48
|
+
body:text \
|
|
49
|
+
--dest=main_app
|
|
50
|
+
|
|
51
|
+
# With decimal precision
|
|
52
|
+
rails g pu:res:scaffold product \
|
|
53
|
+
name:string \
|
|
54
|
+
'price:decimal{10,2}' \
|
|
55
|
+
'weight:decimal?{8,3}' \
|
|
56
|
+
--dest=inventory
|
|
57
|
+
|
|
58
|
+
# Scaffold existing model (imports columns automatically)
|
|
59
|
+
rails g pu:res:scaffold ExistingModel --dest=main_app
|
|
60
|
+
|
|
61
|
+
# Cross-package reference
|
|
62
|
+
rails g pu:res:scaffold order \
|
|
63
|
+
customer:belongs_to \
|
|
64
|
+
inventory/product:belongs_to \
|
|
65
|
+
quantity:integer \
|
|
66
|
+
--dest=sales
|
|
67
|
+
|
|
68
|
+
Generated Files (main_app):
|
|
69
|
+
Model: app/models/[name].rb
|
|
70
|
+
Migration: db/migrate/xxx_create_[names].rb
|
|
71
|
+
Controller: app/controllers/[names]_controller.rb
|
|
72
|
+
Policy: app/policies/[name]_policy.rb
|
|
73
|
+
Definition: app/definitions/[name]_definition.rb
|
|
74
|
+
|
|
75
|
+
Generated Files (package):
|
|
76
|
+
Model: app/models/[package]/[name].rb
|
|
77
|
+
Migration: db/migrate/xxx_create_[package]_[names].rb
|
|
78
|
+
Controller: packages/[package]/app/controllers/[package]/[names]_controller.rb
|
|
79
|
+
Policy: packages/[package]/app/policies/[package]/[name]_policy.rb
|
|
80
|
+
Definition: packages/[package]/app/definitions/[package]/[name]_definition.rb
|
|
81
|
+
|
|
82
|
+
After Generation:
|
|
83
|
+
1. Review migration (add cascade delete, defaults, composite indexes)
|
|
84
|
+
2. Run: rails db:migrate
|
|
85
|
+
3. Connect to portal: rails g pu:res:conn Post --dest=admin_portal
|
|
@@ -40,12 +40,8 @@ module Pu
|
|
|
40
40
|
template "app/rodauth/rodauth_plugin.rb"
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
insert_into_file "config/environments/development.rb",
|
|
47
|
-
"\n config.action_mailer.default_url_options = { host: '127.0.0.1', port: ENV.fetch('PORT', 3000) }\n",
|
|
48
|
-
before: /^end/
|
|
43
|
+
def create_url_options_initializer
|
|
44
|
+
template "config/initializers/url_options.rb"
|
|
49
45
|
end
|
|
50
46
|
|
|
51
47
|
def create_install_migration
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Rails.application.config.after_initialize do
|
|
4
|
+
default_url = ENV["RAILS_DEFAULT_URL"]
|
|
5
|
+
if default_url && Rails.application.config.action_mailer.default_url_options.blank?
|
|
6
|
+
uri = URI.parse(default_url)
|
|
7
|
+
Rails.application.config.action_mailer.default_url_options = {
|
|
8
|
+
host: uri.host,
|
|
9
|
+
port: uri.port,
|
|
10
|
+
protocol: uri.scheme
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if Rails.application.routes.default_url_options.blank?
|
|
15
|
+
Rails.application.routes.default_url_options = Rails.application.config.action_mailer.default_url_options
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Syncs Claude Code skills from the Plutonium gem into your project's
|
|
3
|
+
.claude/skills/ directory.
|
|
4
|
+
|
|
5
|
+
This gives Claude Code access to Plutonium-specific skills for:
|
|
6
|
+
- Creating resources (scaffold generator)
|
|
7
|
+
- Connecting resources to portals
|
|
8
|
+
- Definition configuration (fields, actions, queries)
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
rails generate pu:skills:sync
|
|
12
|
+
|
|
13
|
+
This will copy all skills from the Plutonium gem to:
|
|
14
|
+
.claude/skills/
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../lib/plutonium_generators"
|
|
4
|
+
|
|
5
|
+
module Pu
|
|
6
|
+
module Skills
|
|
7
|
+
class SyncGenerator < Rails::Generators::Base
|
|
8
|
+
include PlutoniumGenerators::Generator
|
|
9
|
+
|
|
10
|
+
desc "Sync Claude Code skills from Plutonium into your project"
|
|
11
|
+
|
|
12
|
+
def start
|
|
13
|
+
source_dir = Plutonium.root.join(".claude", "skills")
|
|
14
|
+
destination_dir = Rails.root.join(".claude", "skills")
|
|
15
|
+
|
|
16
|
+
unless File.directory?(source_dir)
|
|
17
|
+
say_status("error", "Source skills directory not found: #{source_dir}", :red)
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Create destination directory if it doesn't exist
|
|
22
|
+
FileUtils.mkdir_p(destination_dir)
|
|
23
|
+
|
|
24
|
+
# Get all skill directories
|
|
25
|
+
skill_dirs = Dir.children(source_dir).select { |f| File.directory?(source_dir.join(f)) }
|
|
26
|
+
|
|
27
|
+
if skill_dirs.empty?
|
|
28
|
+
say_status("info", "No skills found to sync", :yellow)
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
say_status("info", "Syncing #{skill_dirs.size} skills from Plutonium...", :blue)
|
|
33
|
+
|
|
34
|
+
skill_dirs.each do |skill_name|
|
|
35
|
+
sync_skill(source_dir, destination_dir, skill_name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
say_status("success", "Skills synced successfully!", :green)
|
|
39
|
+
rescue => e
|
|
40
|
+
exception "#{self.class} failed:", e
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def sync_skill(source_dir, destination_dir, skill_name)
|
|
46
|
+
source_skill_dir = source_dir.join(skill_name)
|
|
47
|
+
dest_skill_dir = destination_dir.join(skill_name)
|
|
48
|
+
|
|
49
|
+
# Create skill directory
|
|
50
|
+
FileUtils.mkdir_p(dest_skill_dir)
|
|
51
|
+
|
|
52
|
+
# Copy all files in the skill directory
|
|
53
|
+
Dir.glob(source_skill_dir.join("*")).each do |source_file|
|
|
54
|
+
next unless File.file?(source_file)
|
|
55
|
+
|
|
56
|
+
filename = File.basename(source_file)
|
|
57
|
+
dest_file = dest_skill_dir.join(filename)
|
|
58
|
+
|
|
59
|
+
FileUtils.cp(source_file, dest_file)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
say_status("synced", skill_name, :green)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -8,8 +8,8 @@ module Plutonium
|
|
|
8
8
|
|
|
9
9
|
included do
|
|
10
10
|
add_flash_types :success, :warning, :error
|
|
11
|
-
|
|
12
|
-
protect_from_forgery with: :null_session, if: -> { request.headers[
|
|
11
|
+
|
|
12
|
+
protect_from_forgery with: :null_session, if: -> { request.headers["Authorization"].present? }
|
|
13
13
|
|
|
14
14
|
before_action do
|
|
15
15
|
next unless defined?(ActiveStorage)
|
|
@@ -27,6 +27,7 @@ module Plutonium
|
|
|
27
27
|
include Plutonium::Definition::Presentable
|
|
28
28
|
include Plutonium::Definition::NestedInputs
|
|
29
29
|
include Plutonium::Interaction::NestedAttributes
|
|
30
|
+
|
|
30
31
|
# include Plutonium::Interaction::Concerns::WorkflowDSL
|
|
31
32
|
|
|
32
33
|
class Form < Plutonium::UI::Form::Interaction; end
|
|
@@ -10,17 +10,17 @@ module Plutonium
|
|
|
10
10
|
config.before_configuration do
|
|
11
11
|
# this touches the internals of rails, but I could not find a good way of doing this
|
|
12
12
|
# we get the initializer instance and set the block property to a noop
|
|
13
|
-
# There is no error handling, to ensure we know when it breaks.
|
|
14
13
|
add_view_paths_initializer = Rails.application.initializers.find do |a|
|
|
15
14
|
a.context_class == self && a.name.to_s == "add_view_paths"
|
|
16
15
|
end
|
|
17
|
-
add_view_paths_initializer
|
|
16
|
+
add_view_paths_initializer&.instance_variable_set(:@block, ->(app) {})
|
|
18
17
|
end
|
|
19
18
|
|
|
20
19
|
initializer :append_migrations do |app|
|
|
21
20
|
unless app.root.to_s.match root.to_s
|
|
22
21
|
config.paths["db/migrate"].expanded.each do |expanded_path|
|
|
23
22
|
app.config.paths["db/migrate"] << expanded_path
|
|
23
|
+
ActiveRecord::Migrator.migrations_paths << expanded_path
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
end
|
data/lib/plutonium/railtie.rb
CHANGED
|
@@ -23,6 +23,10 @@ module Plutonium
|
|
|
23
23
|
Rails.application.class.include Plutonium::Engine
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
initializer "plutonium.rescue_responses" do |app|
|
|
27
|
+
app.config.action_dispatch.rescue_responses["ActionPolicy::Unauthorized"] = :forbidden
|
|
28
|
+
end
|
|
29
|
+
|
|
26
30
|
initializer "plutonium.deprecator" do |app|
|
|
27
31
|
app.deprecators[:plutonium] = Plutonium.deprecator
|
|
28
32
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Plutonium
|
|
2
2
|
module Resource
|
|
3
3
|
class QueryObject
|
|
4
|
-
attr_reader :search_filter, :search_query
|
|
4
|
+
attr_reader :search_filter, :search_query, :default_scope_name
|
|
5
5
|
attr_accessor :default_sort_config
|
|
6
6
|
|
|
7
7
|
# Initializes a QueryObject with the given resource_class and parameters.
|
|
@@ -31,9 +31,11 @@ module Plutonium
|
|
|
31
31
|
#
|
|
32
32
|
# @param name [Symbol] The name of the scope.
|
|
33
33
|
# @param body [Proc, nil] The body of the scope.
|
|
34
|
-
|
|
34
|
+
# @param default [Boolean] Whether this scope is the default.
|
|
35
|
+
def define_scope(name, body = nil, default: false, **)
|
|
35
36
|
body ||= name
|
|
36
37
|
scope_definitions[name] = build_query(body)
|
|
38
|
+
@default_scope_name = name.to_s if default
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
# Defines a sort with the given name and body.
|
|
@@ -68,31 +70,53 @@ module Plutonium
|
|
|
68
70
|
q = {}
|
|
69
71
|
|
|
70
72
|
q[:search] = options.key?(:search) ? options[:search].presence : search_query
|
|
71
|
-
q[:scope] = options.key?(:scope)
|
|
73
|
+
q[:scope] = if options.key?(:scope)
|
|
74
|
+
options[:scope].presence
|
|
75
|
+
else
|
|
76
|
+
selected_scope_filter
|
|
77
|
+
end
|
|
72
78
|
|
|
73
79
|
q[:sort_directions] = selected_sort_directions.dup
|
|
74
80
|
q[:sort_fields] = selected_sort_fields.dup
|
|
75
81
|
handle_sort_options!(q, options)
|
|
76
82
|
|
|
77
83
|
q.merge! params.slice(*filter_definitions.keys)
|
|
78
|
-
|
|
84
|
+
compacted = deep_compact({q: q})
|
|
85
|
+
|
|
86
|
+
# Preserve explicit "All" selection (scope: nil in options means show all)
|
|
87
|
+
if options.key?(:scope) && options[:scope].nil?
|
|
88
|
+
compacted[:q] ||= {}
|
|
89
|
+
compacted[:q][:scope] = ""
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
query_params = compacted.to_param
|
|
79
93
|
"#{@request_path}?#{query_params}"
|
|
80
94
|
end
|
|
81
95
|
|
|
82
96
|
# Applies the defined filters and sorts to the given scope.
|
|
83
97
|
#
|
|
84
98
|
# @param scope [Object] The initial scope to which filters and sorts are applied.
|
|
99
|
+
# @param params [Hash] The query parameters.
|
|
100
|
+
# @param context [Object] Optional context (e.g., controller) for executing scope blocks.
|
|
85
101
|
# @return [Object] The modified scope.
|
|
86
|
-
def apply(scope, params)
|
|
102
|
+
def apply(scope, params, context: nil)
|
|
87
103
|
params = deep_compact(params.with_indifferent_access)
|
|
88
104
|
scope = search_filter.apply(scope, search: params[:search]) if search_filter && params[:search]
|
|
89
|
-
|
|
105
|
+
# Use selected_scope which includes the default when no explicit selection
|
|
106
|
+
effective_scope = @selected_scope_filter
|
|
107
|
+
scope = scope_definitions[effective_scope].apply(scope, context:) if effective_scope && scope_definitions[effective_scope]
|
|
90
108
|
scope = apply_sorts(scope, params)
|
|
91
109
|
apply_filters(scope, params)
|
|
92
110
|
end
|
|
93
111
|
|
|
94
112
|
def scope_definitions = @scope_definitions ||= {}.with_indifferent_access
|
|
95
113
|
|
|
114
|
+
# Returns true if user explicitly selected "All" scope (no filtering)
|
|
115
|
+
def all_scope_selected? = @all_scope_selected
|
|
116
|
+
|
|
117
|
+
# Returns the currently selected scope (may be default if none explicitly selected)
|
|
118
|
+
def selected_scope = @selected_scope_filter
|
|
119
|
+
|
|
96
120
|
def filter_definitions = @filter_definitions ||= {}.with_indifferent_access
|
|
97
121
|
|
|
98
122
|
def sort_definitions = @sort_definitions ||= {}.with_indifferent_access
|
|
@@ -125,8 +149,14 @@ module Plutonium
|
|
|
125
149
|
#
|
|
126
150
|
# @param params [Hash] The parameters to extract.
|
|
127
151
|
def extract_filter_params
|
|
128
|
-
@search_query = params[:search]
|
|
129
|
-
|
|
152
|
+
@search_query = params[:search].presence&.strip
|
|
153
|
+
# Track if user explicitly selected "all" (scope param present but blank)
|
|
154
|
+
@all_scope_selected = params.key?(:scope) && params[:scope].blank?
|
|
155
|
+
@selected_scope_filter = if @all_scope_selected
|
|
156
|
+
nil # User clicked "All"
|
|
157
|
+
else
|
|
158
|
+
params[:scope].presence || default_scope_name
|
|
159
|
+
end
|
|
130
160
|
end
|
|
131
161
|
|
|
132
162
|
# Extracts sort parameters from the given params.
|