plutonium 0.23.3 → 0.23.5
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/app/assets/plutonium.css +2 -2
- data/config/initializers/sqlite_json_alias.rb +1 -1
- data/docs/.vitepress/config.ts +60 -19
- data/docs/guide/cursor-rules.md +75 -0
- data/docs/guide/deep-dive/authorization.md +189 -0
- data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
- data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
- data/docs/guide/index.md +28 -0
- data/docs/guide/introduction/02-core-concepts.md +440 -0
- data/docs/guide/tutorial/01-project-setup.md +75 -0
- data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
- data/docs/guide/tutorial/03-defining-resources.md +90 -0
- data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
- data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
- data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
- data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
- data/docs/index.md +24 -31
- data/docs/modules/action.md +190 -0
- data/docs/modules/authentication.md +236 -0
- data/docs/modules/configuration.md +599 -0
- data/docs/modules/controller.md +398 -0
- data/docs/modules/core.md +316 -0
- data/docs/modules/definition.md +876 -0
- data/docs/modules/display.md +759 -0
- data/docs/modules/form.md +605 -0
- data/docs/modules/generator.md +288 -0
- data/docs/modules/index.md +167 -0
- data/docs/modules/interaction.md +470 -0
- data/docs/modules/package.md +151 -0
- data/docs/modules/policy.md +176 -0
- data/docs/modules/portal.md +710 -0
- data/docs/modules/query.md +287 -0
- data/docs/modules/resource_record.md +618 -0
- data/docs/modules/routing.md +641 -0
- data/docs/modules/table.md +293 -0
- data/docs/modules/ui.md +631 -0
- data/docs/public/plutonium.mdc +667 -0
- data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
- data/lib/plutonium/ui/display/resource.rb +7 -2
- data/lib/plutonium/ui/table/resource.rb +8 -3
- data/lib/plutonium/version.rb +1 -1
- data/src/css/intl_tel_input.css +259 -0
- data/src/css/plutonium.css +1 -0
- data/src/css/slim_select.css +3 -2
- metadata +37 -9
- data/docs/guide/getting-started/authorization.md +0 -296
- data/docs/guide/getting-started/core-concepts.md +0 -432
- data/docs/guide/getting-started/index.md +0 -21
- data/docs/guide/tutorial.md +0 -401
- /data/docs/guide/{what-is-plutonium.md → introduction/01-what-is-plutonium.md} +0 -0
@@ -59,9 +59,14 @@ module Plutonium
|
|
59
59
|
column_definition = resource_definition.defined_columns[name] || {}
|
60
60
|
column_options = column_definition[:options] || {}
|
61
61
|
|
62
|
+
# Check for conditional rendering
|
63
|
+
condition = column_options[:condition] || display_options[:condition] || field_options[:condition]
|
64
|
+
conditionally_hidden = condition && !instance_exec(&condition)
|
65
|
+
next if conditionally_hidden
|
66
|
+
|
62
67
|
tag = column_options[:as] || display_definition[:as] || field_options[:as]
|
63
|
-
display_tag_attributes = display_options.except(:wrapper, :as)
|
64
|
-
column_tag_attributes = column_options.except(:wrapper, :as, :align)
|
68
|
+
display_tag_attributes = display_options.except(:wrapper, :as, :condition)
|
69
|
+
column_tag_attributes = column_options.except(:wrapper, :as, :align, :condition)
|
65
70
|
tag_attributes = display_tag_attributes.merge(column_tag_attributes)
|
66
71
|
tag_block = column_definition[:block] || ->(wrapped_object, key) {
|
67
72
|
f = wrapped_object.field(key)
|
@@ -69,7 +74,7 @@ module Plutonium
|
|
69
74
|
f.send(:"#{tag}_tag", **tag_attributes)
|
70
75
|
}
|
71
76
|
|
72
|
-
field_options = field_options.merge(**column_options.slice(:align))
|
77
|
+
field_options = field_options.except(:condition).merge(**column_options.slice(:align))
|
73
78
|
table.column name,
|
74
79
|
**field_options,
|
75
80
|
sort_params: current_query_object.sort_params_for(name),
|
data/lib/plutonium/version.rb
CHANGED
@@ -0,0 +1,259 @@
|
|
1
|
+
/**
|
2
|
+
* International Telephone Input - Complete Tailwind v4 Overrides
|
3
|
+
* Overrides to match Plutonium form theme using Tailwind colors
|
4
|
+
* Import this AFTER the original intl_tel_input.css
|
5
|
+
*/
|
6
|
+
|
7
|
+
/* Override CSS variables with Tailwind colors */
|
8
|
+
:root {
|
9
|
+
--iti-hover-color: theme(colors.primary.50);
|
10
|
+
--iti-border-color: theme(colors.gray.300);
|
11
|
+
--iti-dialcode-color: theme(colors.gray.500);
|
12
|
+
--iti-dropdown-bg: theme(colors.white);
|
13
|
+
--iti-arrow-color: theme(colors.gray.700);
|
14
|
+
}
|
15
|
+
|
16
|
+
/* Dark mode overrides */
|
17
|
+
@media (prefers-color-scheme: dark) {
|
18
|
+
:root {
|
19
|
+
--iti-hover-color: theme(colors.primary.900 / 0.3);
|
20
|
+
--iti-border-color: theme(colors.gray.600);
|
21
|
+
--iti-dialcode-color: theme(colors.gray.400);
|
22
|
+
--iti-dropdown-bg: theme(colors.gray.700);
|
23
|
+
--iti-arrow-color: theme(colors.white);
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
/* Manual dark mode class support */
|
28
|
+
.dark {
|
29
|
+
--iti-hover-color: theme(colors.primary.900 / 0.3);
|
30
|
+
--iti-border-color: theme(colors.gray.600);
|
31
|
+
--iti-dialcode-color: theme(colors.gray.400);
|
32
|
+
--iti-dropdown-bg: theme(colors.gray.700);
|
33
|
+
--iti-arrow-color: theme(colors.white);
|
34
|
+
}
|
35
|
+
|
36
|
+
/* ITI wrapper to ensure full width */
|
37
|
+
.iti {
|
38
|
+
@apply w-full;
|
39
|
+
}
|
40
|
+
|
41
|
+
/* Main container - match form input styling */
|
42
|
+
.iti input.iti__tel-input,
|
43
|
+
.iti input.iti__tel-input[type=tel],
|
44
|
+
.iti input.iti__tel-input[type=text] {
|
45
|
+
@apply w-full border border-gray-300 rounded-md shadow-sm font-medium text-sm bg-white text-gray-700 outline-none transition-colors duration-200 focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:placeholder-gray-400;
|
46
|
+
padding: theme(spacing.2);
|
47
|
+
padding-left: 52px;
|
48
|
+
/* Space for country selector */
|
49
|
+
}
|
50
|
+
|
51
|
+
/* Country container positioning */
|
52
|
+
.iti .iti__country-container {
|
53
|
+
@apply absolute top-0 bottom-0 left-0 z-10;
|
54
|
+
padding: theme(spacing.2);
|
55
|
+
width: 52px;
|
56
|
+
/* Fixed width to match input padding */
|
57
|
+
}
|
58
|
+
|
59
|
+
/* Selected country button styling */
|
60
|
+
.iti .iti__selected-country {
|
61
|
+
@apply bg-transparent border-0 text-gray-700 dark:text-white;
|
62
|
+
}
|
63
|
+
|
64
|
+
/* Selected country primary container */
|
65
|
+
.iti .iti__selected-country-primary {
|
66
|
+
@apply px-2 text-gray-700 dark:text-white;
|
67
|
+
}
|
68
|
+
|
69
|
+
/* Arrow styling with Tailwind colors */
|
70
|
+
.iti .iti__arrow {
|
71
|
+
@apply ml-1;
|
72
|
+
border-top-color: theme(colors.gray.700);
|
73
|
+
}
|
74
|
+
|
75
|
+
.dark .iti .iti__arrow {
|
76
|
+
border-top-color: theme(colors.white);
|
77
|
+
}
|
78
|
+
|
79
|
+
.iti .iti__arrow--up {
|
80
|
+
border-bottom-color: theme(colors.gray.700);
|
81
|
+
border-top-color: transparent;
|
82
|
+
}
|
83
|
+
|
84
|
+
.dark .iti .iti__arrow--up {
|
85
|
+
border-bottom-color: theme(colors.white);
|
86
|
+
}
|
87
|
+
|
88
|
+
/* Dropdown content styling to match form theme */
|
89
|
+
.iti .iti__dropdown-content {
|
90
|
+
@apply border border-gray-300 bg-white shadow-sm rounded-md dark:bg-gray-700 dark:border-gray-600;
|
91
|
+
}
|
92
|
+
|
93
|
+
/* Search input to match form theme */
|
94
|
+
.iti .iti__search-input {
|
95
|
+
@apply w-full p-2 border border-gray-300 rounded-md shadow-sm font-medium text-sm bg-white text-gray-700 outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:placeholder-gray-400;
|
96
|
+
margin: theme(spacing.2);
|
97
|
+
width: calc(100% - theme(spacing.4));
|
98
|
+
}
|
99
|
+
|
100
|
+
/* Search input border top */
|
101
|
+
.iti .iti__search-input+.iti__country-list {
|
102
|
+
@apply border-t border-gray-300 dark:border-gray-600;
|
103
|
+
}
|
104
|
+
|
105
|
+
/* Country list styling */
|
106
|
+
.iti .iti__country-list {
|
107
|
+
@apply max-h-48 overflow-y-auto;
|
108
|
+
}
|
109
|
+
|
110
|
+
/* Individual country styling */
|
111
|
+
.iti .iti__country {
|
112
|
+
@apply px-2 py-2 text-gray-700 hover:bg-primary-500 hover:text-white dark:text-white dark:hover:bg-primary-500;
|
113
|
+
}
|
114
|
+
|
115
|
+
/* Country name and flag spacing */
|
116
|
+
.iti .iti__country-name,
|
117
|
+
.iti .iti__country-list .iti__flag {
|
118
|
+
@apply mr-2 dark:mr-2;
|
119
|
+
}
|
120
|
+
|
121
|
+
/* RTL support */
|
122
|
+
[dir="rtl"] .iti .iti__country-name,
|
123
|
+
[dir="rtl"] .iti .iti__country-list .iti__flag {
|
124
|
+
@apply mr-0 ml-2;
|
125
|
+
}
|
126
|
+
|
127
|
+
/* Dial code styling */
|
128
|
+
.iti .iti__dial-code {
|
129
|
+
@apply text-gray-500 dark:text-gray-400;
|
130
|
+
}
|
131
|
+
|
132
|
+
/* Selected dial code */
|
133
|
+
.iti .iti__selected-dial-code {
|
134
|
+
@apply ml-1 text-gray-500 dark:text-gray-400;
|
135
|
+
}
|
136
|
+
|
137
|
+
[dir="rtl"] .iti .iti__selected-dial-code {
|
138
|
+
@apply ml-0 mr-1;
|
139
|
+
}
|
140
|
+
|
141
|
+
/* Prevent any styling when aria-activedescendant is set (dropdown navigation) */
|
142
|
+
.iti .iti__selected-country[aria-activedescendant],
|
143
|
+
.iti .iti__selected-country-primary[aria-activedescendant] {
|
144
|
+
background-color: transparent !important;
|
145
|
+
}
|
146
|
+
|
147
|
+
/* Prevent hover styling when dropdown content is being hovered */
|
148
|
+
.iti .iti__selected-country:has(+ .iti__dropdown-content:hover),
|
149
|
+
.iti .iti__selected-country-primary:has(~ .iti__dropdown-content:hover) {
|
150
|
+
background-color: transparent !important;
|
151
|
+
}
|
152
|
+
|
153
|
+
/* Remove all hover states from country container completely */
|
154
|
+
.iti--allow-dropdown .iti__country-container:hover,
|
155
|
+
.iti--allow-dropdown .iti__country-container:hover .iti__selected-country,
|
156
|
+
.iti--allow-dropdown .iti__country-container:hover .iti__selected-country-primary {
|
157
|
+
background-color: transparent !important;
|
158
|
+
}
|
159
|
+
|
160
|
+
/* Ensure flags never get background colors on hover or when aria-activedescendant is set */
|
161
|
+
.iti .iti__flag,
|
162
|
+
.iti .iti__selected-country[aria-activedescendant] .iti__flag,
|
163
|
+
.iti .iti__selected-country-primary[aria-activedescendant] .iti__flag,
|
164
|
+
.iti .iti__country-container:hover .iti__flag {
|
165
|
+
background-color: transparent !important;
|
166
|
+
}
|
167
|
+
|
168
|
+
/* Globe icon maintains its specific background */
|
169
|
+
.iti .iti__flag.iti__globe {
|
170
|
+
@apply bg-gray-100 dark:bg-gray-600;
|
171
|
+
background-color: theme(colors.gray.100) !important;
|
172
|
+
}
|
173
|
+
|
174
|
+
.dark .iti .iti__flag.iti__globe {
|
175
|
+
background-color: theme(colors.gray.600) !important;
|
176
|
+
}
|
177
|
+
|
178
|
+
/* Country highlight state */
|
179
|
+
.iti .iti__country.iti__highlight {
|
180
|
+
@apply bg-primary-500 text-white;
|
181
|
+
}
|
182
|
+
|
183
|
+
/* Fullscreen popup styling for mobile */
|
184
|
+
.iti--fullscreen-popup.iti--container {
|
185
|
+
@apply bg-black/50;
|
186
|
+
}
|
187
|
+
|
188
|
+
.iti--fullscreen-popup .iti__dropdown-content {
|
189
|
+
@apply bg-white dark:bg-gray-700;
|
190
|
+
}
|
191
|
+
|
192
|
+
.iti--fullscreen-popup .iti__country {
|
193
|
+
@apply py-3 text-base;
|
194
|
+
}
|
195
|
+
|
196
|
+
/* Globe icon visibility fix for dark mode */
|
197
|
+
.iti .iti__flag.iti__globe {
|
198
|
+
@apply bg-gray-100 dark:bg-gray-600;
|
199
|
+
background-blend-mode: multiply;
|
200
|
+
}
|
201
|
+
|
202
|
+
.dark .iti .iti__flag.iti__globe {
|
203
|
+
filter: invert(1) brightness(0.8);
|
204
|
+
}
|
205
|
+
|
206
|
+
/* Validation state overrides */
|
207
|
+
.iti.iti-invalid input.iti__tel-input,
|
208
|
+
.iti.iti-invalid input.iti__tel-input[type=tel],
|
209
|
+
.iti.iti-invalid input.iti__tel-input[type=text] {
|
210
|
+
@apply bg-red-50 border-red-500 text-red-900 focus:ring-red-500 focus:border-red-500 dark:border-red-500 dark:text-red-500;
|
211
|
+
}
|
212
|
+
|
213
|
+
.iti.iti-valid input.iti__tel-input,
|
214
|
+
.iti.iti-valid input.iti__tel-input[type=tel],
|
215
|
+
.iti.iti-valid input.iti__tel-input[type=text] {
|
216
|
+
@apply bg-green-50 border-green-500 text-green-900 focus:ring-green-500 focus:border-green-500 dark:border-green-500 dark:text-green-400;
|
217
|
+
}
|
218
|
+
|
219
|
+
/* Validation state arrow colors */
|
220
|
+
.iti.iti-invalid .iti__arrow {
|
221
|
+
border-top-color: theme(colors.red.500);
|
222
|
+
}
|
223
|
+
|
224
|
+
.iti.iti-invalid .iti__arrow--up {
|
225
|
+
border-bottom-color: theme(colors.red.500);
|
226
|
+
border-top-color: transparent;
|
227
|
+
}
|
228
|
+
|
229
|
+
.iti.iti-valid .iti__arrow {
|
230
|
+
border-top-color: theme(colors.green.500);
|
231
|
+
}
|
232
|
+
|
233
|
+
.iti.iti-valid .iti__arrow--up {
|
234
|
+
border-bottom-color: theme(colors.green.500);
|
235
|
+
border-top-color: transparent;
|
236
|
+
}
|
237
|
+
|
238
|
+
/* Disabled state */
|
239
|
+
.iti input.iti__tel-input:disabled,
|
240
|
+
.iti input.iti__tel-input[type=tel]:disabled,
|
241
|
+
.iti input.iti__tel-input[type=text]:disabled {
|
242
|
+
@apply cursor-not-allowed bg-gray-100 text-gray-500 dark:bg-gray-900 dark:text-gray-400;
|
243
|
+
}
|
244
|
+
|
245
|
+
.iti input.iti__tel-input:disabled+.iti__country-container {
|
246
|
+
@apply cursor-not-allowed;
|
247
|
+
}
|
248
|
+
|
249
|
+
.iti input.iti__tel-input:disabled+.iti__country-container .iti__selected-country {
|
250
|
+
@apply text-gray-500 dark:text-gray-400;
|
251
|
+
}
|
252
|
+
|
253
|
+
.iti input.iti__tel-input:disabled+.iti__country-container .iti__arrow {
|
254
|
+
border-top-color: theme(colors.gray.400);
|
255
|
+
}
|
256
|
+
|
257
|
+
.dark .iti input.iti__tel-input:disabled+.iti__country-container .iti__arrow {
|
258
|
+
border-top-color: theme(colors.gray.600);
|
259
|
+
}
|
data/src/css/plutonium.css
CHANGED
data/src/css/slim_select.css
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
/**
|
2
|
-
* Slim Select with Tailwind CSS
|
3
|
-
*
|
2
|
+
* Slim Select with Tailwind CSS
|
3
|
+
* Overrides for theming with Tailwind colors and dark mode support
|
4
|
+
* Import this AFTER the original slim_select.css
|
4
5
|
*/
|
5
6
|
|
6
7
|
/* Animation keyframes */
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: plutonium
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.23.
|
4
|
+
version: 0.23.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Froelich
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zeitwerk
|
@@ -493,21 +493,48 @@ files:
|
|
493
493
|
- docs/.vitepress/theme/custom.css
|
494
494
|
- docs/.vitepress/theme/index.ts
|
495
495
|
- docs/api-examples.md
|
496
|
-
- docs/guide/
|
497
|
-
- docs/guide/
|
498
|
-
- docs/guide/
|
499
|
-
- docs/guide/getting-started/installation.md
|
500
|
-
- docs/guide/
|
501
|
-
- docs/guide/
|
502
|
-
- docs/guide/
|
496
|
+
- docs/guide/cursor-rules.md
|
497
|
+
- docs/guide/deep-dive/authorization.md
|
498
|
+
- docs/guide/deep-dive/resources.md
|
499
|
+
- docs/guide/getting-started/01-installation.md
|
500
|
+
- docs/guide/index.md
|
501
|
+
- docs/guide/introduction/01-what-is-plutonium.md
|
502
|
+
- docs/guide/introduction/02-core-concepts.md
|
503
|
+
- docs/guide/tutorial/01-project-setup.md
|
504
|
+
- docs/guide/tutorial/02-creating-a-feature-package.md
|
505
|
+
- docs/guide/tutorial/03-defining-resources.md
|
506
|
+
- docs/guide/tutorial/04-creating-a-portal.md
|
507
|
+
- docs/guide/tutorial/05-customizing-the-ui.md
|
508
|
+
- docs/guide/tutorial/06-adding-custom-actions.md
|
509
|
+
- docs/guide/tutorial/07-implementing-authorization.md
|
503
510
|
- docs/index.md
|
504
511
|
- docs/markdown-examples.md
|
512
|
+
- docs/modules/action.md
|
513
|
+
- docs/modules/authentication.md
|
514
|
+
- docs/modules/configuration.md
|
515
|
+
- docs/modules/controller.md
|
516
|
+
- docs/modules/core.md
|
517
|
+
- docs/modules/definition.md
|
518
|
+
- docs/modules/display.md
|
519
|
+
- docs/modules/form.md
|
520
|
+
- docs/modules/generator.md
|
521
|
+
- docs/modules/index.md
|
522
|
+
- docs/modules/interaction.md
|
523
|
+
- docs/modules/package.md
|
524
|
+
- docs/modules/policy.md
|
525
|
+
- docs/modules/portal.md
|
526
|
+
- docs/modules/query.md
|
527
|
+
- docs/modules/resource_record.md
|
528
|
+
- docs/modules/routing.md
|
529
|
+
- docs/modules/table.md
|
530
|
+
- docs/modules/ui.md
|
505
531
|
- docs/public/android-chrome-192x192.png
|
506
532
|
- docs/public/android-chrome-512x512.png
|
507
533
|
- docs/public/apple-touch-icon.png
|
508
534
|
- docs/public/favicon-16x16.png
|
509
535
|
- docs/public/favicon-32x32.png
|
510
536
|
- docs/public/favicon.ico
|
537
|
+
- docs/public/plutonium.mdc
|
511
538
|
- docs/public/plutonium.png
|
512
539
|
- docs/public/site.webmanifest
|
513
540
|
- docs/public/templates/base.rb
|
@@ -866,6 +893,7 @@ files:
|
|
866
893
|
- src/.npmignore
|
867
894
|
- src/css/core.css
|
868
895
|
- src/css/easymde.css
|
896
|
+
- src/css/intl_tel_input.css
|
869
897
|
- src/css/plutonium.css
|
870
898
|
- src/css/plutonium.entry.css
|
871
899
|
- src/css/slim_select.css
|
@@ -1,296 +0,0 @@
|
|
1
|
-
# Authorization and Access Control
|
2
|
-
|
3
|
-
Plutonium provides a robust authorization system built on top of [Action Policy](https://actionpolicy.evilmartians.io/), offering fine-grained access control, resource scoping, and entity-based authorization.
|
4
|
-
|
5
|
-
## Overview
|
6
|
-
|
7
|
-
Authorization in Plutonium operates at multiple levels:
|
8
|
-
- Resource-level policies
|
9
|
-
- Action-based permissions
|
10
|
-
- Entity-based scoping
|
11
|
-
- Attribute-level access control
|
12
|
-
|
13
|
-
## Basic Policy Definition
|
14
|
-
|
15
|
-
Every resource in Plutonium requires a policy. Here's a basic example:
|
16
|
-
|
17
|
-
```ruby
|
18
|
-
class BlogPolicy < ResourcePolicy
|
19
|
-
# Core CRUD Permissions
|
20
|
-
|
21
|
-
def create?
|
22
|
-
# All authenticated users can create blogs
|
23
|
-
true
|
24
|
-
end
|
25
|
-
|
26
|
-
def read?
|
27
|
-
# Allow anyone to read blogs
|
28
|
-
true
|
29
|
-
end
|
30
|
-
|
31
|
-
def update?
|
32
|
-
# Allow only the blog owner to update
|
33
|
-
owner?
|
34
|
-
end
|
35
|
-
|
36
|
-
def destroy?
|
37
|
-
# Allow only the blog owner or admins to destroy
|
38
|
-
owner? || user.admin?
|
39
|
-
end
|
40
|
-
|
41
|
-
# Attribute Control
|
42
|
-
|
43
|
-
def permitted_attributes_for_create
|
44
|
-
[:title, :content, :category]
|
45
|
-
end
|
46
|
-
|
47
|
-
def permitted_attributes_for_read
|
48
|
-
[:title, :content, :category, :created_at, :updated_at, :user_id]
|
49
|
-
end
|
50
|
-
|
51
|
-
def permitted_attributes_for_update
|
52
|
-
[:title, :content, :category]
|
53
|
-
end
|
54
|
-
|
55
|
-
# Association Access
|
56
|
-
|
57
|
-
def permitted_associations
|
58
|
-
[:comments]
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
63
|
-
def owner?
|
64
|
-
record.user_id == user.id
|
65
|
-
end
|
66
|
-
end
|
67
|
-
```
|
68
|
-
|
69
|
-
## Default Behaviors in Plutonium Policies
|
70
|
-
|
71
|
-
::: info Overview
|
72
|
-
Plutonium's Policy system implements a secure-by-default pattern with clear inheritance chains for both permissions and attribute access.
|
73
|
-
:::
|
74
|
-
|
75
|
-
### Permission Defaults
|
76
|
-
|
77
|
-
Permission methods follow two key patterns: core permissions that default to `false`, and derived permissions that inherit from core ones.
|
78
|
-
|
79
|
-
#### Core Permission Chain
|
80
|
-
|
81
|
-
```mermaid
|
82
|
-
graph TD
|
83
|
-
subgraph Core Permissions
|
84
|
-
A[create? ❌] --> B[update? ❌]
|
85
|
-
A --> C[destroy? ❌]
|
86
|
-
D[read? ❌] --> E[index? ❌]
|
87
|
-
D --> F[show? ❌]
|
88
|
-
end
|
89
|
-
```
|
90
|
-
|
91
|
-
::: warning Security First
|
92
|
-
All core permissions (`create?` and `read?`) default to `false`. You must explicitly override these to grant access.
|
93
|
-
```ruby
|
94
|
-
# Default implementations
|
95
|
-
def create?
|
96
|
-
false
|
97
|
-
end
|
98
|
-
|
99
|
-
def read?
|
100
|
-
false
|
101
|
-
end
|
102
|
-
```
|
103
|
-
:::
|
104
|
-
|
105
|
-
#### Permission Inheritance
|
106
|
-
|
107
|
-
::: code-group
|
108
|
-
```ruby [Action Methods]
|
109
|
-
def update?
|
110
|
-
create? # Inherits from create?
|
111
|
-
end
|
112
|
-
|
113
|
-
def destroy?
|
114
|
-
create? # Inherits from create?
|
115
|
-
end
|
116
|
-
|
117
|
-
def index?
|
118
|
-
read? # Inherits from read?
|
119
|
-
end
|
120
|
-
|
121
|
-
def show?
|
122
|
-
read? # Inherits from read?
|
123
|
-
end
|
124
|
-
```
|
125
|
-
|
126
|
-
```ruby [Helper Methods]
|
127
|
-
def new?
|
128
|
-
create? # Matches create?
|
129
|
-
end
|
130
|
-
|
131
|
-
def edit?
|
132
|
-
update? # Matches update?
|
133
|
-
end
|
134
|
-
|
135
|
-
def search?
|
136
|
-
index? # Matches index?
|
137
|
-
end
|
138
|
-
```
|
139
|
-
:::
|
140
|
-
|
141
|
-
### Attribute Permission Defaults
|
142
|
-
|
143
|
-
Attribute permissions also follow an inheritance pattern, but with auto-detection in development:
|
144
|
-
|
145
|
-
::: details Inheritance Chain
|
146
|
-
```mermaid
|
147
|
-
graph TD
|
148
|
-
subgraph Core Attributes
|
149
|
-
A[permitted_attributes_for_create] --> B[permitted_attributes_for_update]
|
150
|
-
C[permitted_attributes_for_read] --> D[permitted_attributes_for_show]
|
151
|
-
C --> E[permitted_attributes_for_index]
|
152
|
-
end
|
153
|
-
```
|
154
|
-
:::
|
155
|
-
|
156
|
-
#### Core Implementation Details
|
157
|
-
|
158
|
-
::: code-group
|
159
|
-
```ruby [Create Attributes]
|
160
|
-
def permitted_attributes_for_create
|
161
|
-
# Auto-detects fields but excludes system columns
|
162
|
-
autodetect_permitted_fields(:permitted_attributes_for_create) - [
|
163
|
-
resource_class.primary_key.to_sym,
|
164
|
-
:created_at,
|
165
|
-
:updated_at
|
166
|
-
]
|
167
|
-
end
|
168
|
-
```
|
169
|
-
|
170
|
-
```ruby [Read Attributes]
|
171
|
-
def permitted_attributes_for_read
|
172
|
-
# Auto-detects all fields
|
173
|
-
autodetect_permitted_fields(:permitted_attributes_for_read)
|
174
|
-
end
|
175
|
-
```
|
176
|
-
|
177
|
-
```ruby [Update Attributes]
|
178
|
-
def permitted_attributes_for_update
|
179
|
-
# Inherits from create
|
180
|
-
permitted_attributes_for_create
|
181
|
-
end
|
182
|
-
```
|
183
|
-
:::
|
184
|
-
|
185
|
-
::: danger Auto-detection Warning
|
186
|
-
The default attribute detection:
|
187
|
-
- Only works in development
|
188
|
-
- Raises errors in other environments
|
189
|
-
- Shows warning messages
|
190
|
-
- Must be overridden in production
|
191
|
-
:::
|
192
|
-
|
193
|
-
### Association Defaults
|
194
|
-
|
195
|
-
By default, no associations are permitted:
|
196
|
-
|
197
|
-
```ruby
|
198
|
-
def permitted_associations
|
199
|
-
[] # Must be explicitly defined
|
200
|
-
end
|
201
|
-
```
|
202
|
-
|
203
|
-
### Context Object Defaults
|
204
|
-
|
205
|
-
Two built-in authorization contexts:
|
206
|
-
|
207
|
-
::: code-group
|
208
|
-
```ruby [Required Context]
|
209
|
-
# User must be present
|
210
|
-
authorize :user, allow_nil: false
|
211
|
-
```
|
212
|
-
|
213
|
-
```ruby [Optional Context]
|
214
|
-
# Entity scope is optional
|
215
|
-
authorize :entity_scope, allow_nil: true
|
216
|
-
```
|
217
|
-
:::
|
218
|
-
|
219
|
-
#### Default Scoping Behavior
|
220
|
-
|
221
|
-
When an entity scope exists, records are automatically filtered:
|
222
|
-
|
223
|
-
```ruby
|
224
|
-
relation_scope do |relation|
|
225
|
-
next relation unless entity_scope
|
226
|
-
relation.associated_with(entity_scope)
|
227
|
-
end
|
228
|
-
```
|
229
|
-
|
230
|
-
#### Adding Custom Contexts
|
231
|
-
|
232
|
-
You can add additional authorization contexts:
|
233
|
-
|
234
|
-
::: code-group
|
235
|
-
```ruby [Policy]
|
236
|
-
class BlogPolicy < ResourcePolicy
|
237
|
-
authorize :ability, allow_nil: true
|
238
|
-
|
239
|
-
def promote?
|
240
|
-
user.admin? && ability&.can?(:promote, record)
|
241
|
-
end
|
242
|
-
end
|
243
|
-
```
|
244
|
-
|
245
|
-
```ruby [Controller]
|
246
|
-
class BlogsController < ResourceController
|
247
|
-
authorize :ability, through: :current_ability
|
248
|
-
|
249
|
-
private
|
250
|
-
|
251
|
-
def current_ability
|
252
|
-
@current_ability ||= Ability.new(current_user)
|
253
|
-
end
|
254
|
-
end
|
255
|
-
```
|
256
|
-
:::
|
257
|
-
|
258
|
-
## Quick Reference
|
259
|
-
|
260
|
-
| Method Type | Default | Inherits From |
|
261
|
-
|------------|---------|---------------|
|
262
|
-
| create? | false | - |
|
263
|
-
| read? | false | - |
|
264
|
-
| update? | false | create? |
|
265
|
-
| destroy? | false | create? |
|
266
|
-
| index? | false | read? |
|
267
|
-
| show? | false | read? |
|
268
|
-
| attributes_for_create | auto* | - |
|
269
|
-
| attributes_for_read | auto* | - |
|
270
|
-
| attributes_for_update | auto* | create |
|
271
|
-
| associations | [] | - |
|
272
|
-
|
273
|
-
::: warning
|
274
|
-
\*Auto-detection only works in development
|
275
|
-
:::
|
276
|
-
|
277
|
-
## Security Best Practices
|
278
|
-
|
279
|
-
### 1. Never Skip Authorization
|
280
|
-
|
281
|
-
Plutonium controllers automatically verify authorization:
|
282
|
-
|
283
|
-
```ruby
|
284
|
-
class BlogsController < ResourceController
|
285
|
-
def show
|
286
|
-
# This will raise an error if you forget to authorize
|
287
|
-
# authorize! resource_record
|
288
|
-
render :show
|
289
|
-
end
|
290
|
-
end
|
291
|
-
```
|
292
|
-
|
293
|
-
## Related Resources
|
294
|
-
|
295
|
-
- [Action Policy Documentation](https://actionpolicy.evilmartians.io/)
|
296
|
-
- [Rails Security Guide](https://guides.rubyonrails.org/security.html)
|