activeadmin-graphql 0.3.3 → 0.4.1
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/CHANGELOG.md +31 -3
- data/README.md +2 -2
- data/docs/changelogs/20260818111600_rfc-0002-problems.md +1 -1
- data/docs/changelogs/20260818114000_rfc-as-built-coverage.md +1 -1
- data/docs/changelogs/20260818114700_rfc-design-not-version.md +1 -1
- data/docs/changelogs/20260907141000_advisory-lockfile-refresh.md +1 -1
- data/docs/changelogs/20260921103000_mutation-attribute-allowlists.md +1 -1
- data/docs/changelogs/20260921111000_release-0-3-0.md +1 -1
- data/docs/changelogs/20260921112800_drop-standard-rubocop-direct.md +1 -1
- data/docs/changelogs/20260921120000_graphql-floor-and-activeadmin-4.md +1 -1
- data/docs/changelogs/20261010091415_graphql-resource-enabled-default.md +1 -1
- data/docs/changelogs/20261010103800_release-0-3-1.md +1 -1
- data/docs/changelogs/20261010110600_graphql-identity-dsl.md +4 -0
- data/docs/changelogs/20261010111800_release-0-3-2.md +1 -1
- data/docs/changelogs/20261012050051_graphql-surface-toggles.md +1 -1
- data/docs/changelogs/20261012064500_graphql-capabilities.md +1 -1
- data/docs/changelogs/20261012113500_release-0-3-3.md +1 -1
- data/docs/changelogs/20261012200000_host-customization-hooks.md +31 -0
- data/docs/changelogs/20261013080000_release-0-4-0.md +48 -0
- data/docs/changelogs/20261103145000_release-0-4-1.md +26 -0
- data/docs/graphql-api.md +154 -102
- data/docs/issues/20260907141000_engineering-and-dependency-audit.md +1 -1
- data/docs/issues/20260917123253_ci-drop-automatic-dependency-audit.md +1 -1
- data/docs/issues/20260921103000_mutation-attribute-allowlists.md +1 -1
- data/docs/issues/20260921104400_convention-alignment-audit.md +7 -7
- data/docs/issues/20260921114500_dependency-audit.md +13 -13
- data/docs/issues/20260921115500_file-length-metrics.md +1 -1
- data/docs/issues/20261010111159_rails-composite-primary-keys-alignment.md +13 -13
- data/docs/issues/20261010111300_partitions-and-shards-support.md +3 -3
- data/docs/issues/20261010111500_graphql-federation-support.md +3 -3
- data/docs/issues/20261012200000_host-customization-gaps.md +30 -0
- data/docs/issues/20261103145000_engineering-prose-release-audit.md +39 -0
- data/docs/plan/20260817201500_rfc-starter.md +1 -1
- data/docs/reviews/20261028_engineering-audit-release-preparation.md +24 -0
- data/docs/reviews/20261103145000_engineering-prose-release-audit.md +38 -0
- data/lib/active_admin/graphql/capability_definition_dsl.rb +9 -4
- data/lib/active_admin/graphql/collection_query_policy.rb +103 -0
- data/lib/active_admin/graphql/integration.rb +24 -0
- data/lib/active_admin/graphql/member_action_capability.rb +14 -0
- data/lib/active_admin/graphql/member_action_dry_run.rb +121 -0
- data/lib/active_admin/graphql/resource_capability_dsl.rb +4 -3
- data/lib/active_admin/graphql/resource_capability_maps.rb +20 -2
- data/lib/active_admin/graphql/resource_definition_dsl.rb +17 -6
- data/lib/active_admin/graphql/run_action_capability.rb +62 -0
- data/lib/active_admin/graphql/run_action_control_extension.rb +72 -0
- data/lib/active_admin/graphql/run_action_field.rb +103 -0
- data/lib/active_admin/graphql/run_action_mutation_config.rb +8 -0
- data/lib/active_admin/graphql/run_action_mutation_dsl.rb +18 -0
- data/lib/active_admin/graphql/run_action_preview.rb +20 -0
- data/lib/active_admin/graphql/schema_builder/mutation_batch.rb +2 -2
- data/lib/active_admin/graphql/schema_builder/mutation_collection.rb +4 -3
- data/lib/active_admin/graphql/schema_builder/mutation_create.rb +20 -20
- data/lib/active_admin/graphql/schema_builder/mutation_member.rb +8 -3
- data/lib/active_admin/graphql/schema_builder/mutation_update_destroy.rb +2 -4
- data/lib/active_admin/graphql/schema_builder/query_type_member.rb +1 -4
- data/lib/active_admin/graphql/schema_builder/query_type_registered.rb +1 -4
- data/lib/active_admin/graphql/schema_builder/resolvers.rb +46 -7
- data/lib/active_admin/graphql/schema_builder/types_object.rb +2 -1
- data/lib/active_admin/graphql/schema_builder/wire.rb +2 -1
- data/lib/active_admin/graphql/schema_field.rb +85 -10
- data/lib/active_admin/graphql/version.rb +1 -1
- data/lib/active_admin/graphql.rb +7 -0
- metadata +15 -1
data/docs/graphql-api.md
CHANGED
|
@@ -31,11 +31,11 @@ want:
|
|
|
31
31
|
ActiveAdmin.setup do |config|
|
|
32
32
|
config.namespace :admin do |admin|
|
|
33
33
|
admin.graphql = true
|
|
34
|
-
# Optional
|
|
34
|
+
# Optional - default is "graphql", i.e. POST /admin/graphql
|
|
35
35
|
# admin.graphql_path = "graphql"
|
|
36
|
-
# Optional
|
|
36
|
+
# Optional - set false so only resources with a graphql block enter the schema
|
|
37
37
|
# admin.graphql_resource_enabled_default = false
|
|
38
|
-
# Optional
|
|
38
|
+
# Optional - trim always-on surfaces for a narrow API
|
|
39
39
|
# admin.graphql_policies_enabled = false
|
|
40
40
|
# admin.graphql_registered_resource_enabled = false
|
|
41
41
|
# admin.graphql_belongs_to_associations_enabled = false
|
|
@@ -50,71 +50,87 @@ Run `bin/rails active_admin:install` or reload routes if needed so
|
|
|
50
50
|
|
|
51
51
|
Each namespace inherits these options (defaults in parentheses):
|
|
52
52
|
|
|
53
|
-
* `graphql`
|
|
54
|
-
* `graphql_resource_enabled_default`
|
|
53
|
+
* `graphql` - set to `true` to mount the endpoint (`false` by default).
|
|
54
|
+
* `graphql_resource_enabled_default` - whether each registered ActiveRecord
|
|
55
55
|
resource is included in the schema without a `graphql` block (`true` by
|
|
56
56
|
default, matching 0.3.x). Set to `false` for opt-in: only resources that call
|
|
57
57
|
`graphql { }` (or `graphql { enable! }`) appear in the schema. `disable!`
|
|
58
58
|
still omits a resource.
|
|
59
|
-
* `graphql_queries_enabled_default`
|
|
59
|
+
* `graphql_queries_enabled_default` - seed for per-resource read fields
|
|
60
60
|
(`true` by default): named list/show fields and membership in
|
|
61
61
|
`registered_resource` / `ActiveAdminRegisteredResource`. Set `false` and use
|
|
62
62
|
`enable_queries!` for opt-in reads. Object types may still exist for mutations.
|
|
63
|
-
* `graphql_mutations_enabled_default`
|
|
63
|
+
* `graphql_mutations_enabled_default` - seed for per-resource CRUD mutations
|
|
64
64
|
(`true` by default). Set `false` and use `enable_mutations!` for opt-in writes.
|
|
65
|
-
* `graphql_run_actions_enabled_default`
|
|
65
|
+
* `graphql_run_actions_enabled_default` - seed for batch/member/collection
|
|
66
66
|
action mutations (`true` by default).
|
|
67
|
-
* `graphql_policies_enabled`
|
|
67
|
+
* `graphql_policies_enabled` - publish `activeadmin_policies` query fields and
|
|
68
68
|
allow per-object policy fields (`true` by default).
|
|
69
|
-
* `graphql_policies_enabled_default`
|
|
69
|
+
* `graphql_policies_enabled_default` - seed for per-resource `policies_enabled`
|
|
70
70
|
(`true` by default). Use `enable_policies!` / `disable_policies!` per resource.
|
|
71
|
-
* `graphql_policy_adapter`
|
|
71
|
+
* `graphql_policy_adapter` - class that builds `activeadmin_policies` payloads
|
|
72
72
|
(`ActiveAdmin::GraphQL::PolicyAdapter` by default). Set a subclass (or constant
|
|
73
73
|
`String`) to replace payload logic; the default adapter still honors
|
|
74
74
|
`graphql_policy_actions` / `graphql_policy_action_mapper` /
|
|
75
75
|
`graphql_policy_extra` / `graphql_policy_transform`.
|
|
76
|
-
* `graphql_registered_resource_enabled`
|
|
76
|
+
* `graphql_registered_resource_enabled` - include `registered_resource` and the
|
|
77
77
|
`ActiveAdminRegisteredResource` union when at least one queryable resource
|
|
78
78
|
exists (`true` by default).
|
|
79
|
-
* `graphql_belongs_to_associations_enabled`
|
|
79
|
+
* `graphql_belongs_to_associations_enabled` - wire non-polymorphic `belongs_to`
|
|
80
80
|
fields between registered resource types (`true` by default).
|
|
81
|
-
* `graphql_path`
|
|
82
|
-
* `graphql_multiplex_max`
|
|
81
|
+
* `graphql_path` - path segment under the namespace mount (`"graphql"`).
|
|
82
|
+
* `graphql_multiplex_max` - maximum operations per multiplexed JSON array
|
|
83
83
|
request (`20`).
|
|
84
|
-
* `graphql_max_complexity`
|
|
84
|
+
* `graphql_max_complexity` - passed to graphql-ruby `max_complexity` (`300` by
|
|
85
85
|
default; set a higher value if legitimate queries are rejected). Set to
|
|
86
86
|
`nil` to disable the limit.
|
|
87
|
-
* `graphql_max_depth`
|
|
87
|
+
* `graphql_max_depth` - passed to `max_depth` (`18` by default). Set to `nil`
|
|
88
88
|
to disable.
|
|
89
|
-
* `graphql_batch_action_max_ids`
|
|
89
|
+
* `graphql_batch_action_max_ids` - maximum length of the `ids` argument on
|
|
90
90
|
`*_batch_action` mutations (`5000` by default; set `0` to disable the cap).
|
|
91
|
-
* `graphql_default_page_size` / `graphql_default_max_page_size`
|
|
91
|
+
* `graphql_default_page_size` / `graphql_default_max_page_size` - schema
|
|
92
92
|
connection defaults (`25` / `100`); needed for graphql-ruby complexity scoring
|
|
93
93
|
on connection fields when `max_complexity` is enabled.
|
|
94
|
-
* `graphql_dataloader`
|
|
94
|
+
* `graphql_dataloader` - graphql-ruby dataloader plugin (`nil` = use
|
|
95
95
|
`GraphQL::Dataloader`). Set to `GraphQL::Dataloader::AsyncDataloader` when
|
|
96
96
|
the [`async` gem](https://github.com/socketry/async) is loaded if you want
|
|
97
97
|
parallel I/O from custom `GraphQL::Dataloader::Source` `#fetch` methods.
|
|
98
|
-
* `graphql_visibility`
|
|
98
|
+
* `graphql_visibility` - `nil` by default (no graphql-ruby visibility plugin).
|
|
99
99
|
Set to `true` to use [`GraphQL::Schema::Visibility`](https://graphql-ruby.org/schema/visibility.html)
|
|
100
100
|
with default options, or pass a `Hash` of visibility options (`profiles:`,
|
|
101
|
-
`dynamic:`, `preload:`, `migration_errors:`,
|
|
102
|
-
* `graphql_visibility_profile`
|
|
101
|
+
`dynamic:`, `preload:`, `migration_errors:`, ...).
|
|
102
|
+
* `graphql_visibility_profile` - when using named visibility profiles, optional
|
|
103
103
|
default `context[:visibility_profile]` for each HTTP request (symbols accepted).
|
|
104
|
-
* `graphql_schema_visible`
|
|
104
|
+
* `graphql_schema_visible` - optional `Proc` `(context, meta) -> Boolean` when
|
|
105
105
|
`graphql_visibility` is enabled; see Schema visibility below.
|
|
106
|
-
* `graphql_capabilities`
|
|
106
|
+
* `graphql_capabilities` - optional `Proc` `(context) -> Enumerable` of capability
|
|
107
107
|
names for the request (RFC 0008). Normalized into `context[:capabilities]`.
|
|
108
108
|
Cannot be combined with named `graphql_visibility` `profiles:`; schema build
|
|
109
109
|
forces `dynamic: true` and omits `visibility_profile` from the request context
|
|
110
110
|
so introspection uses the live capability set.
|
|
111
|
-
* `
|
|
111
|
+
* `graphql_field_capability_mode_default` - `:omit` or `:null` when a field has a
|
|
112
|
+
capability but no per-field mode (RFC 0009). Default `:omit`.
|
|
113
|
+
* `graphql_field_visible` - optional `Proc` `(context, meta) -> Boolean` to omit
|
|
114
|
+
fields after capability checks (RFC 0009). Meta includes `graphql_type_name`,
|
|
115
|
+
`field_name`, and `resource` when known.
|
|
116
|
+
* `graphql_run_action_audit` - optional `Proc` `(context, meta) -> void` after
|
|
117
|
+
member-action dry-run, refused preview, or completed run (RFC 0010).
|
|
118
|
+
* `graphql_collection_query_policy` - optional `Proc` `(context, meta) -> void`
|
|
119
|
+
at the start of collection resolve (RFC 0011). Meta includes `aa_resource`,
|
|
120
|
+
`q`, `scope`, `order`, and `filter`.
|
|
121
|
+
* `graphql_member_action_capability_default` - optional capability symbol applied
|
|
122
|
+
to member-action mutation fields when the resource map has no entry (RFC 0012).
|
|
123
|
+
* `graphql_batch_action_capability_default` - same for the aggregate batch-action
|
|
124
|
+
field and per-action authorize checks (RFC 0012).
|
|
125
|
+
* `graphql_collection_action_capability_default` - same for collection-action
|
|
126
|
+
mutation fields (RFC 0012).
|
|
127
|
+
* `graphql_configure_schema` - `Proc` receiving the schema class after build
|
|
112
128
|
(extensions, extra types, and so on).
|
|
113
129
|
|
|
114
130
|
### GraphQL::Dataloader
|
|
115
131
|
|
|
116
132
|
The namespace schema calls `schema.use` with the configured dataloader (see
|
|
117
|
-
`graphql_dataloader` above). [graphql-ruby
|
|
133
|
+
`graphql_dataloader` above). [graphql-ruby's
|
|
118
134
|
dataloader](https://graphql-ruby.org/dataloader/overview.html) batches external
|
|
119
135
|
work across sibling fields using Fibers; each HTTP request gets its own
|
|
120
136
|
dataloader instance (available as `context.dataloader` inside resolvers).
|
|
@@ -136,12 +152,12 @@ Global / Relay-style IDs are not generated by Active Admin itself.
|
|
|
136
152
|
### Schema visibility (`GraphQL::Schema::Visibility`)
|
|
137
153
|
|
|
138
154
|
When `graphql_visibility` is set, the namespace schema calls
|
|
139
|
-
`schema.use(GraphQL::Schema::Visibility,
|
|
155
|
+
`schema.use(GraphQL::Schema::Visibility, ...)` so introspection and validation
|
|
140
156
|
respect [visibility](https://graphql-ruby.org/schema/visibility.html).
|
|
141
157
|
|
|
142
158
|
Set `graphql_schema_visible` to a proc that receives the GraphQL `context`
|
|
143
159
|
and a `meta` hash and returns truthy to keep the member visible. The hash
|
|
144
|
-
always includes `:kind`, and often `:graphql_type_name` (the resource
|
|
160
|
+
always includes `:kind`, and often `:graphql_type_name` (the resource's GraphQL
|
|
145
161
|
basename), `:resource` (`ActiveAdmin::Resource`), and other keys:
|
|
146
162
|
|
|
147
163
|
| `kind` | Meaning |
|
|
@@ -149,29 +165,29 @@ basename), `:resource` (`ActiveAdmin::Resource`), and other keys:
|
|
|
149
165
|
| `:resource_interface` | `ActiveAdminResource` interface |
|
|
150
166
|
| `:resource_object` | Resource object type |
|
|
151
167
|
| `:resource_enum` | Rails enum type; `meta` includes `:column` |
|
|
152
|
-
| `:input_object` | Create/update/where/list-filter input; `meta` includes `:role` (`:create_input`,
|
|
168
|
+
| `:input_object` | Create/update/where/list-filter input; `meta` includes `:role` (`:create_input`, ...) |
|
|
153
169
|
| `:registered_resource_union` | `ActiveAdminRegisteredResource` union |
|
|
154
170
|
| `:run_action_payload` | Batch/member/collection action payload type |
|
|
155
171
|
| `:query_registered_resource` | `registered_resource` query field |
|
|
156
172
|
| `:query_collection_field` / `:query_member_field` | Resource list/detail fields; `meta` includes `:field_name` |
|
|
157
173
|
| `:query_page_field` | Custom `register_page` field; `meta` includes `:page` |
|
|
158
|
-
| `:mutation_create`, `:mutation_update`, `:mutation_delete`,
|
|
174
|
+
| `:mutation_create`, `:mutation_update`, `:mutation_delete`, ... | CRUD and action mutations |
|
|
159
175
|
|
|
160
176
|
Root `Query` / `Mutation` fields are defined with
|
|
161
177
|
`ActiveAdmin::GraphQL::SchemaField`, which supports a `visibility:` keyword:
|
|
162
178
|
a metadata `Hash` (same shape as the `meta` argument above) for
|
|
163
179
|
`graphql_schema_visible`, not a graphql-ruby core option. Resource `graphql`
|
|
164
180
|
`configure` blocks use that field class too, so custom fields may pass
|
|
165
|
-
`visibility: {
|
|
181
|
+
`visibility: { ... }` when you want the hook to run.
|
|
166
182
|
|
|
167
183
|
You can also subclass your own `GraphQL::Schema::Object` / `Field` types in
|
|
168
|
-
`graphql_configure_schema` and implement `visible?` there; Active Admin
|
|
184
|
+
`graphql_configure_schema` and implement `visible?` there; Active Admin's
|
|
169
185
|
helpers call `super` first so those implementations compose.
|
|
170
186
|
|
|
171
187
|
Interfaces and unions: graphql-ruby may keep an interface or union visible
|
|
172
188
|
when all members are hidden unless you return `false` from
|
|
173
189
|
`graphql_schema_visible` for `:resource_interface` or `:registered_resource_union`
|
|
174
|
-
(or implement `.visible?` on custom types). See graphql-ruby
|
|
190
|
+
(or implement `.visible?` on custom types). See graphql-ruby's visibility migration
|
|
175
191
|
notes for edge cases.
|
|
176
192
|
|
|
177
193
|
## Making requests
|
|
@@ -184,11 +200,11 @@ Send JSON with `query` and optional `variables`. For
|
|
|
184
200
|
be a JSON array of such objects; the size is limited by `graphql_multiplex_max`.
|
|
185
201
|
|
|
186
202
|
Requests are handled by `ActiveAdmin::GraphqlController` (an engine controller,
|
|
187
|
-
not a generated `Admin
|
|
203
|
+
not a generated `Admin::...` subclass).
|
|
188
204
|
|
|
189
205
|
### CSRF and cookie sessions
|
|
190
206
|
|
|
191
|
-
`GraphqlController` uses Rails
|
|
207
|
+
`GraphqlController` uses Rails' default CSRF protection
|
|
192
208
|
(`protect_from_forgery with: :exception`). Clients that rely on session cookies
|
|
193
209
|
(for example a typical Devise session) must submit a valid CSRF token with each
|
|
194
210
|
`POST`, such as the `authenticity_token` used by the HTML admin. API-style
|
|
@@ -235,6 +251,11 @@ resource model (`authorized?` with the model class, in the same way as
|
|
|
235
251
|
collection queries). They then run the same controller actions and blocks as the
|
|
236
252
|
UI, including any `authorize!` or `authorize_resource!` calls.
|
|
237
253
|
|
|
254
|
+
When a record exists but instance-level read is denied, singular member and
|
|
255
|
+
`registered_resource` queries return `null` (same shape as a missing id). Update
|
|
256
|
+
and destroy mutations return the GraphQL error `not found` for the same case.
|
|
257
|
+
Class-level denials still raise `not authorized ...`.
|
|
258
|
+
|
|
238
259
|
See the ActiveAdmin guide [Authorization adapter](https://activeadmin.info/13-authorization-adapter) for adapter setup; the same rules apply to GraphQL.
|
|
239
260
|
|
|
240
261
|
Policy preflight fields (`activeadmin_policies`, `activeadmin_policies_for`, and
|
|
@@ -270,7 +291,7 @@ end
|
|
|
270
291
|
|
|
271
292
|
Kwargs (`fields:`, `queries:`, `mutations:`, `member_actions:`,
|
|
272
293
|
`batch_actions:`, `collection_actions:`) work without a block. Query aliases:
|
|
273
|
-
`index` / `list`
|
|
294
|
+
`index` / `list` -> collection, `show` -> member. Mutation alias: `delete` ->
|
|
274
295
|
`destroy`. Run-action sugar builds keys like `member_publish`.
|
|
275
296
|
|
|
276
297
|
Grant capabilities per request with `graphql_capabilities`. Map OAuth scopes or
|
|
@@ -284,14 +305,27 @@ admin.graphql_capabilities = lambda { |ctx|
|
|
|
284
305
|
|
|
285
306
|
A gated member fails closed when the capability is missing from that set:
|
|
286
307
|
resolve returns null for the field (graphql-ruby unauthorized field). Enable
|
|
287
|
-
`graphql_visibility` so introspection and validation also omit ungated members
|
|
308
|
+
`graphql_visibility` so introspection and validation also omit ungated members
|
|
309
|
+
when the field mode is `:omit` (default). Use `capability_mode: :null` (or
|
|
310
|
+
`capability :pii, fields: %i[body], mode: :null`) so the field stays in the
|
|
311
|
+
schema and resolves to `null` without the grant (RFC 0009).
|
|
312
|
+
|
|
288
313
|
`fields:` applies to generated attributes and wired `belongs_to` associations.
|
|
289
314
|
Unknown field names raise at schema build. Capabilities do not replace the
|
|
290
315
|
authorization adapter.
|
|
291
316
|
|
|
292
|
-
Custom `SchemaField` definitions may pass `capability: :name`
|
|
293
|
-
|
|
294
|
-
(
|
|
317
|
+
Custom `SchemaField` definitions may pass `capability: :name` and
|
|
318
|
+
`capability_mode: :null` the same way. Set `graphql_field_visible` for a second
|
|
319
|
+
omit gate (for example a request allowlist). Do not use named visibility
|
|
320
|
+
`profiles:` together with `graphql_capabilities` (cached profile context ignores
|
|
321
|
+
per-request grants).
|
|
322
|
+
|
|
323
|
+
Collection list fields may call a host `graphql_collection_query_policy`. The
|
|
324
|
+
helper `ActiveAdmin::GraphQL::CollectionQueryPolicy.require_visible_fields!`
|
|
325
|
+
rejects Ransack `q` keys for hidden fields and requires capabilities from the
|
|
326
|
+
resource field map by default (needed for `capability_mode: :null` columns).
|
|
327
|
+
Optional `field_grants` overrides inference; `infer_field_grants: false` turns
|
|
328
|
+
it off (RFC 0011).
|
|
295
329
|
|
|
296
330
|
## Schema overview
|
|
297
331
|
|
|
@@ -335,7 +369,7 @@ omit `created_at` and `updated_at`.
|
|
|
335
369
|
GraphQL `id` always comes from `ActiveAdminResource` (`ID!`). An ActiveRecord
|
|
336
370
|
`id` column is never added as a second field. When `self.primary_key` is blank
|
|
337
371
|
but the model still has an `id` column (common for database views that project
|
|
338
|
-
|
|
372
|
+
`... AS id`), that column supplies the GraphQL `id` value. When the model has
|
|
339
373
|
neither a primary key nor an `id` column, declare GraphQL identity on the
|
|
340
374
|
resource (RFC 0007), set `self.primary_key` on the model, or omit the resource
|
|
341
375
|
with `graphql { disable! }`. A single-column primary key named something other
|
|
@@ -355,7 +389,7 @@ end
|
|
|
355
389
|
```
|
|
356
390
|
|
|
357
391
|
With `separator: ":"`, GraphQL `id` is `entry_type:entry_id:event_type`. Keep
|
|
358
|
-
the model
|
|
392
|
+
the model's `def id` (if any) in the same shape so HTML and GraphQL share
|
|
359
393
|
member ids. Without `separator`, multi-column identity uses the same JSON
|
|
360
394
|
object string as composite ActiveRecord primary keys.
|
|
361
395
|
|
|
@@ -364,19 +398,19 @@ object string as composite ActiveRecord primary keys.
|
|
|
364
398
|
For models with `self.primary_key = [:book_code, :seq]` (and no separate `id`
|
|
365
399
|
column), Active Admin treats GraphQL as follows:
|
|
366
400
|
|
|
367
|
-
* `id` on the object type
|
|
401
|
+
* `id` on the object type - JSON object string with all key columns in
|
|
368
402
|
model order, for example `{"book_code":"CPK","seq":7}`. This is the GraphQL
|
|
369
403
|
wire format. It is not the Rails form/URL underscore packing described in
|
|
370
404
|
the Composite Primary Keys guide (`/books/2_25` with
|
|
371
405
|
`params.extract_value(:id)`).
|
|
372
|
-
* Readable fields
|
|
406
|
+
* Readable fields - each primary-key column is also exposed as its own field
|
|
373
407
|
(in addition to `id`) so clients can load scalars without parsing JSON.
|
|
374
|
-
* `LibraryEditionWhereInput` (and similar)
|
|
408
|
+
* `LibraryEditionWhereInput` (and similar) - either optional `id` (that JSON
|
|
375
409
|
string) or every primary-key column; `id` remains required when there is
|
|
376
410
|
only one key column.
|
|
377
|
-
* Singular query field
|
|
411
|
+
* Singular query field - pass the JSON `id`, pass `where`, or pass one
|
|
378
412
|
GraphQL argument per primary-key column.
|
|
379
|
-
* Create mutations
|
|
413
|
+
* Create mutations - composite key attributes stay included in create
|
|
380
414
|
inputs (unlike single `id` tables, where the key is omitted from assignable
|
|
381
415
|
attributes).
|
|
382
416
|
|
|
@@ -385,8 +419,8 @@ composite ids from `params[:id]` or from individual primary-key params. Stock
|
|
|
385
419
|
ActiveAdmin 3.x only passes a single `params[:id]` into `find`; an ActiveAdmin
|
|
386
420
|
fork (or patch) that implements composite-aware `find_resource` keeps the
|
|
387
421
|
admin UI aligned with GraphQL. After decoding the GraphQL JSON `id`, the gem
|
|
388
|
-
uses Rails
|
|
389
|
-
`where(primary_key => [tuple,
|
|
422
|
+
uses Rails' array/tuple conventions for `Relation#find` and
|
|
423
|
+
`where(primary_key => [tuple, ...])` as in the guide.
|
|
390
424
|
|
|
391
425
|
The gem ships `ActiveAdmin::PrimaryKey` when the core constant is missing
|
|
392
426
|
(upstream). A fork may define `PrimaryKey` and extended `find_resource` in
|
|
@@ -399,18 +433,18 @@ Alignment notes versus the Rails guide:
|
|
|
399
433
|
|
|
400
434
|
These align with REST index parameters where possible:
|
|
401
435
|
|
|
402
|
-
* `filter`
|
|
403
|
-
predicates as JSON
|
|
404
|
-
* `scope`
|
|
436
|
+
* `filter` - optional `PostListFilterInput` with `scope`, `order`, `q` (Ransack
|
|
437
|
+
predicates as JSON - kept as JSON because predicate keys are open-ended), and the nested parent id when `belongs_to` applies.
|
|
438
|
+
* `scope` - menu scope id (string); still accepted on the field for backward
|
|
405
439
|
compatibility.
|
|
406
|
-
* `q`
|
|
407
|
-
* `order`
|
|
408
|
-
* Parent foreign key
|
|
440
|
+
* `q` - Ransack predicates (JSON object).
|
|
441
|
+
* `order` - index sort string (for example `id_desc`).
|
|
442
|
+
* Parent foreign key - when the resource uses `belongs_to`, the parent param
|
|
409
443
|
(for example `user_id`) stays available on the connection field as today.
|
|
410
444
|
|
|
411
445
|
### Singular query arguments
|
|
412
446
|
|
|
413
|
-
* `where`
|
|
447
|
+
* `where` - `PostWhereInput` with required `id` (single-key tables) or,
|
|
414
448
|
for composite keys, `id` (JSON) and/or each key column; optional parent ids for
|
|
415
449
|
nested resources.
|
|
416
450
|
* Legacy `id` / parent arguments behave as before (composite models also accept
|
|
@@ -420,12 +454,12 @@ These align with REST index parameters where possible:
|
|
|
420
454
|
|
|
421
455
|
Typical names for a `Post` resource:
|
|
422
456
|
|
|
423
|
-
* `create_post(input: PostCreateInput!)`
|
|
457
|
+
* `create_post(input: PostCreateInput!)` - attribute fields match assignable
|
|
424
458
|
columns minus `created_at` / `updated_at` unless listed in `permit_params`
|
|
425
459
|
(and the nested parent param when `belongs_to` is configured).
|
|
426
460
|
* `update_post(where: PostWhereInput!, input: PostUpdateInput!)`
|
|
427
|
-
* `delete_post(where: PostWhereInput!)`
|
|
428
|
-
* `destroy_post(where: PostWhereInput!)`
|
|
461
|
+
* `delete_post(where: PostWhereInput!)` - kept for existing clients.
|
|
462
|
+
* `destroy_post(where: PostWhereInput!)` - Rails / ActiveAdmin action name; same resolver as `delete_post`.
|
|
429
463
|
|
|
430
464
|
Nested resources require the parent id on `where`, `input`, or list `filter`
|
|
431
465
|
when the association is required.
|
|
@@ -436,39 +470,39 @@ If you are updating clients that targeted an older embedded GraphQL integration
|
|
|
436
470
|
|
|
437
471
|
* Rails enum GraphQL names use an `Enum` infix (for example `PostEnumStatus`, not `PostStatus`). Update persisted operations, codegen, and fragments to match; see Rails enum columns below.
|
|
438
472
|
|
|
439
|
-
* CRUD mutations use typed `input` and `where` arguments (`PostCreateInput`, `PostWhereInput`,
|
|
473
|
+
* CRUD mutations use typed `input` and `where` arguments (`PostCreateInput`, `PostWhereInput`, ...), not a single loose `attributes` JSON object and bare `id` on update/delete. List/detail fields may use `where` / `filter` input objects; legacy scalar arguments on fields are unchanged where still exposed.
|
|
440
474
|
|
|
441
|
-
* `registered_resource(path:
|
|
475
|
+
* `registered_resource(path: ...)`, batch `inputs`, and member/collection `params` use `[ActiveAdminKeyValuePair!]` (a list of `{ key, value }` strings), not arbitrary JSON objects. Express maps as list items; duplicate keys keep the last value. Ransack `q` (on list `filter` / field args) stays a JSON object.
|
|
442
476
|
|
|
443
|
-
These rules match the current schema described in this guide. The core `activeadmin` gem
|
|
477
|
+
These rules match the current schema described in this guide. The core `activeadmin` gem's `UPGRADING.md` may only mention them if you use a fork that references this extension.
|
|
444
478
|
|
|
445
479
|
### `registered_resource` query
|
|
446
480
|
|
|
447
481
|
`registered_resource(type_name: String!, id: ID!, path: [ActiveAdminKeyValuePair!])`
|
|
448
482
|
returns `ActiveAdminRegisteredResource`, the union of all resource object types in
|
|
449
|
-
the namespace. Pass nested parent route params as ordered `path: [{ key: "user_id", value: "1" },
|
|
483
|
+
the namespace. Pass nested parent route params as ordered `path: [{ key: "user_id", value: "1" }, ...]`
|
|
450
484
|
(same keys as REST path segments). Resolve with inline fragments on concrete types.
|
|
451
485
|
|
|
452
|
-
`ActiveAdminKeyValuePair` is `{ key: String!, value: String! }`
|
|
486
|
+
`ActiveAdminKeyValuePair` is `{ key: String!, value: String! }` - flat strings only, like query/form fields.
|
|
453
487
|
|
|
454
488
|
### Batch, member, and collection actions
|
|
455
489
|
|
|
456
490
|
For a route key of `posts`, fields look like:
|
|
457
491
|
|
|
458
|
-
* `posts_batch_action(batch_action: String!, ids: [ID!]!, inputs: [ActiveAdminKeyValuePair!],
|
|
492
|
+
* `posts_batch_action(batch_action: String!, ids: [ID!]!, inputs: [ActiveAdminKeyValuePair!], ...)` -
|
|
459
493
|
runs the registered batch action; `inputs` is converted to `batch_action_inputs` (flat key/value map)
|
|
460
494
|
like the HTML form. Omitted when batch actions are disabled or none are registered.
|
|
461
|
-
* `posts_member_action(action: String!, id: ID!, params: [ActiveAdminKeyValuePair!],
|
|
495
|
+
* `posts_member_action(action: String!, id: ID!, params: [ActiveAdminKeyValuePair!], ...)` -
|
|
462
496
|
aggregate field: pick the `member_action` by `action` string (same as today).
|
|
463
|
-
* One GraphQL field per registered action
|
|
497
|
+
* One GraphQL field per registered action - `{plural}_member_{action_name}` with
|
|
464
498
|
underscores (e.g. `posts_member_publish`, `posts_member_append_title_bang`): same
|
|
465
499
|
`id`, `params`, and parent ids as the aggregate field, without an `action:` argument.
|
|
466
|
-
Use `graphql { member_action_mutation(:publish) {
|
|
467
|
-
`resolve`, and extra `arguments {
|
|
468
|
-
* `posts_collection_action(action: String!, params: [ActiveAdminKeyValuePair!],
|
|
500
|
+
Use `graphql { member_action_mutation(:publish) { ... } }` to set return type,
|
|
501
|
+
`resolve`, and extra `arguments { ... }` for that action only.
|
|
502
|
+
* `posts_collection_action(action: String!, params: [ActiveAdminKeyValuePair!], ...)` -
|
|
469
503
|
aggregate collection actions by name.
|
|
470
504
|
* Per collection action: `{plural}_collection_{action_name}` (e.g.
|
|
471
|
-
`posts_collection_posts_count`). Configure with `collection_action_mutation(:posts_count) {
|
|
505
|
+
`posts_collection_posts_count`). Configure with `collection_action_mutation(:posts_count) { ... }`.
|
|
472
506
|
|
|
473
507
|
Action names are validated against registered definitions; arbitrary controller
|
|
474
508
|
methods are not exposed.
|
|
@@ -512,27 +546,27 @@ ActiveAdmin.register Post do
|
|
|
512
546
|
end
|
|
513
547
|
```
|
|
514
548
|
|
|
515
|
-
* `enable!`
|
|
549
|
+
* `enable!` - include this resource when `graphql_resource_enabled_default` is
|
|
516
550
|
`false`. An empty `graphql` block has the same effect.
|
|
517
|
-
* `disable!`
|
|
518
|
-
* `enable_queries!` / `disable_queries!`
|
|
551
|
+
* `disable!` - resource is not included in Query or Mutation.
|
|
552
|
+
* `enable_queries!` / `disable_queries!` - list/show fields and
|
|
519
553
|
`registered_resource` membership for this resource.
|
|
520
|
-
* `enable_mutations!` / `disable_mutations!`
|
|
521
|
-
* `enable_run_actions!` / `disable_run_actions!`
|
|
554
|
+
* `enable_mutations!` / `disable_mutations!` - create/update/delete mutations.
|
|
555
|
+
* `enable_run_actions!` / `disable_run_actions!` - batch/member/collection
|
|
522
556
|
action mutations.
|
|
523
|
-
* `enable_policies!` / `disable_policies!`
|
|
557
|
+
* `enable_policies!` / `disable_policies!` - object `activeadmin_policies` and
|
|
524
558
|
inclusion in the `activeadmin_policies` resources matrix.
|
|
525
|
-
* `identity`
|
|
559
|
+
* `identity` - declare GraphQL id columns (and optional `separator`, `encode`,
|
|
526
560
|
`decode`) when the model has no ActiveRecord primary key and no `id` column
|
|
527
561
|
(RFC 0007).
|
|
528
|
-
* `type_name` (or `graphql_name`)
|
|
562
|
+
* `type_name` (or `graphql_name`) - overrides the default GraphQL type name
|
|
529
563
|
derived from the model (object types, mutations, and Rails enum GraphQL
|
|
530
564
|
types use this basename).
|
|
531
|
-
* `only` / `except` (or `exclude`)
|
|
565
|
+
* `only` / `except` (or `exclude`) - restrict columns on object types. These
|
|
532
566
|
names also cap mutation inputs. This is not ActiveAdmin `only:` / `except:`
|
|
533
567
|
for controller actions (`action_item only: :show`). The primary key remains
|
|
534
568
|
readable as `id`.
|
|
535
|
-
* `permit_params` (or `permit`)
|
|
569
|
+
* `permit_params` (or `permit`) - restrict create and update input keys without
|
|
536
570
|
hiding query fields, same name as ActiveAdmin HTML `permit_params`. Nested
|
|
537
571
|
`create` / `update` blocks replace that shared list for one operation and
|
|
538
572
|
may set `resolve`. Names outside `only` / `except` are dropped. A static HTML
|
|
@@ -540,7 +574,7 @@ end
|
|
|
540
574
|
unset. A request-time HTML `permit_params` block is not copied. With no list
|
|
541
575
|
set, mutation inputs omit `created_at` and `updated_at`. Empty `permit_params`
|
|
542
576
|
yields an input with no column arguments.
|
|
543
|
-
* `configure`
|
|
577
|
+
* `configure` - `field` and resolver methods are evaluated on the generated
|
|
544
578
|
object type class.
|
|
545
579
|
|
|
546
580
|
### Run action return types (batch / member / collection mutations)
|
|
@@ -549,8 +583,8 @@ Return type and optional resolver for each kind live together on
|
|
|
549
583
|
`ActiveAdmin::GraphQL::RunActionMutationConfig` (`payload_type`, `resolve_proc`),
|
|
550
584
|
exposed through the DSL in a graphql-ruby-like way.
|
|
551
585
|
|
|
552
|
-
Use a `GraphQL::Schema::Object` subclass
|
|
553
|
-
`ActiveAdmin::GraphQL::RunActionPayload`
|
|
586
|
+
Use a `GraphQL::Schema::Object` subclass - typically subclass
|
|
587
|
+
`ActiveAdmin::GraphQL::RunActionPayload` - then add fields and resolver methods.
|
|
554
588
|
The mutation object passed to field methods is always
|
|
555
589
|
`RunActionPayload::Result` (`ok`, `status`, `location`, `body`).
|
|
556
590
|
|
|
@@ -572,8 +606,12 @@ graphql do
|
|
|
572
606
|
type(Members::MemberRunPayload)
|
|
573
607
|
end
|
|
574
608
|
|
|
575
|
-
member_action_mutation(:send_invoice) do
|
|
609
|
+
member_action_mutation(:send_invoice, dry_run: true, capability: :support_act) do
|
|
576
610
|
type(Members::SendInvoicePayload)
|
|
611
|
+
preview do |record, **|
|
|
612
|
+
record.may_send_invoice? ? ok("would_run:send_invoice:#{record.id}") : refuse("not_ready")
|
|
613
|
+
end
|
|
614
|
+
extra_params :note
|
|
577
615
|
arguments do
|
|
578
616
|
argument :note, GraphQL::Types::String, required: false, camelize: false
|
|
579
617
|
end
|
|
@@ -581,11 +619,25 @@ graphql do
|
|
|
581
619
|
end
|
|
582
620
|
```
|
|
583
621
|
|
|
584
|
-
Call the named field as `posts_member_send_invoice(id: "...", note: "
|
|
622
|
+
Call the named field as `posts_member_send_invoice(id: "...", note: "...")`. Extra
|
|
585
623
|
arguments are merged with `params` for the controller and passed as keyword args
|
|
586
|
-
to a custom `resolve` block.
|
|
587
|
-
|
|
588
|
-
|
|
624
|
+
to a custom `resolve` block. With `dry_run: true`, the field gains a Boolean
|
|
625
|
+
`dry_run` argument: preview runs first, dry-run returns without
|
|
626
|
+
`run_member_action`, and a refused preview never runs the action (RFC 0010).
|
|
627
|
+
`extra_params` allowlists which GraphQL kwargs forward into the AA request
|
|
628
|
+
(`id` cannot be rebound). `capability:` on batch / member / collection mutation
|
|
629
|
+
DSL overrides the resource map; otherwise the matching
|
|
630
|
+
`graphql_*_action_capability_default` applies (RFC 0012).
|
|
631
|
+
|
|
632
|
+
Custom mutations in `graphql_configure_schema` may use
|
|
633
|
+
`ActiveAdmin::GraphQL::RunActionField.define` (or `SchemaField` keywords
|
|
634
|
+
`dry_run:`, `confirm:`, `preview:`) for confirm/dry-run wiring (RFC 0013). When
|
|
635
|
+
`confirm` is required and both flags are false, the field returns
|
|
636
|
+
`body: "confirm_required"`. When `preview` is set, dry-run and refused preview
|
|
637
|
+
return without calling `resolve`; without `preview`, dry-run still calls
|
|
638
|
+
`resolve` with `dry_run: true`.
|
|
639
|
+
|
|
640
|
+
With no symbol argument, `member_action_mutation { ... }` configures only the
|
|
589
641
|
aggregate `posts_member_action` field. With a symbol, it configures the
|
|
590
642
|
per-action field `posts_member_<name>`.
|
|
591
643
|
|
|
@@ -601,7 +653,7 @@ One-shot aliases (same underlying config):
|
|
|
601
653
|
| `batch_action_run_action_payload_type(MyType)` | Same as `batch_action_mutation { type(MyType) }`. |
|
|
602
654
|
| `member_action_run_action_payload_type(MyType)` | Same as `member_action_mutation { type(MyType) }`. |
|
|
603
655
|
| `collection_action_run_action_payload_type(MyType)` | Same as `collection_action_mutation { type(MyType) }`. |
|
|
604
|
-
| `resolve_batch_action {
|
|
656
|
+
| `resolve_batch_action { ... }` | Same as `batch_action_mutation { resolve { ... } }` (and likewise for member/collection). |
|
|
605
657
|
|
|
606
658
|
Per-kind `type` wins over `run_action_payload_type`; any unset kind falls back
|
|
607
659
|
to `run_action_payload_type`, then `ActiveAdminRunActionPayload`. Set
|
|
@@ -620,16 +672,16 @@ replace the Ruby body that loads data or runs a mutation (similar in spirit to
|
|
|
620
672
|
overriding `find_resource` / `find_collection` rather than inventing new field
|
|
621
673
|
names):
|
|
622
674
|
|
|
623
|
-
| DSL method | Replaces default
|
|
675
|
+
| DSL method | Replaces default ... |
|
|
624
676
|
|------------|-------------------|
|
|
625
|
-
| `resolve_index` (alias `resolve_collection`) | List connection resolver; must return an `ActiveRecord::Relation` (or compatible). Receives `proxy`, `context`, `auth`, `aa_resource`, `graph_params`, and the list field arguments (`filter`, `scope`, `q`, `order`, parent ids,
|
|
677
|
+
| `resolve_index` (alias `resolve_collection`) | List connection resolver; must return an `ActiveRecord::Relation` (or compatible). Receives `proxy`, `context`, `auth`, `aa_resource`, `graph_params`, and the list field arguments (`filter`, `scope`, `q`, `order`, parent ids, ...). |
|
|
626
678
|
| `resolve_show` (alias `resolve_member`) | Singular field and `registered_resource` for this resource; must return a record or `nil`. Receives `proxy`, `id`, `graph_params`, and for the generated `post` field also `where` / `id_argument` when present. |
|
|
627
|
-
| `resolve_create` | After class-level create authorization: build and persist. Receives `proxy`, `input`, `attributes` (assignable slice), `context`, `auth`, `aa_resource`. Must return the created record; instance-level `authorized?` runs
|
|
679
|
+
| `resolve_create` | After class-level create authorization: build and persist inside a nested transaction. Receives `proxy`, `input`, `attributes` (assignable slice), `context`, `auth`, `aa_resource`. Must return the created record; instance-level `authorized?` runs before the transaction commits, so a denied hook persist rolls back when the hook shared that transaction. |
|
|
628
680
|
| `resolve_update` | After load and update authorization: apply changes. Receives `record`, `input`, `attributes`, plus `proxy`, `context`, `auth`, `aa_resource`. Must return the updated record. |
|
|
629
681
|
| `resolve_destroy` | After destroy authorization: delete or archive. Receives `record`, `proxy`, `context`, `auth`, `aa_resource`. Default is `record.destroy!`. |
|
|
630
|
-
| `resolve_batch_action` | Batch mutation; receives `batch_action`, `ids`, `inputs` (string-key `Hash` after converting GraphQL key/value list), `proxy`,
|
|
631
|
-
| `resolve_member_action` | Member-action mutation; receives `action`, `id`, `params` (string-key `Hash`), `proxy`,
|
|
632
|
-
| `resolve_collection_action` | Collection-action mutation; receives `action`, `params` (string-key `Hash`), `proxy`,
|
|
682
|
+
| `resolve_batch_action` | Batch mutation; receives `batch_action`, `ids`, `inputs` (string-key `Hash` after converting GraphQL key/value list), `proxy`, ... Default calls `ResourceQueryProxy#run_batch_action`. |
|
|
683
|
+
| `resolve_member_action` | Member-action mutation; receives `action`, `id`, `params` (string-key `Hash`), `proxy`, ... |
|
|
684
|
+
| `resolve_collection_action` | Collection-action mutation; receives `action`, `params` (string-key `Hash`), `proxy`, ... |
|
|
633
685
|
|
|
634
686
|
Standard authorization checks around each field or mutation still run; hooks
|
|
635
687
|
are for customizing how data is fetched or saved, not for skipping policy.
|
|
@@ -641,16 +693,16 @@ exposed as GraphQL enums. Each enum type is named
|
|
|
641
693
|
|
|
642
694
|
`{GraphQLTypeBasename}Enum{ColumnNameInPascalCase}`
|
|
643
695
|
|
|
644
|
-
where `GraphQLTypeBasename` is the resource
|
|
696
|
+
where `GraphQLTypeBasename` is the resource's GraphQL type name (from `type_name`
|
|
645
697
|
or the model name), and `ColumnNameInPascalCase` is the database attribute in
|
|
646
|
-
PascalCase (for example `notification_channel`
|
|
698
|
+
PascalCase (for example `notification_channel` -> `NotificationChannel`).
|
|
647
699
|
|
|
648
700
|
Examples:
|
|
649
701
|
|
|
650
|
-
* `User` + column `notification_channel`
|
|
651
|
-
* `UserNotification` + column `channel`
|
|
702
|
+
* `User` + column `notification_channel` -> `UserEnumNotificationChannel`
|
|
703
|
+
* `UserNotification` + column `channel` -> `UserNotificationEnumChannel`
|
|
652
704
|
|
|
653
|
-
Think of `PostEnumStatus` as the enum for `Post
|
|
705
|
+
Think of `PostEnumStatus` as the enum for `Post`'s `status` attribute, not as a
|
|
654
706
|
single merged word like `PostStatus`. Putting `Enum` immediately after the
|
|
655
707
|
resource basename (instead of a suffix such as `PostStatusEnum`) keeps the
|
|
656
708
|
basename and column distinct when both are camelized. That avoids the duplicates
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Participants
|
|
4
4
|
|
|
5
|
-
Andrei Makarov
|
|
5
|
+
Andrei Makarov (@amkisko)
|
|
6
6
|
|
|
7
7
|
## Decisions
|
|
8
8
|
|
|
@@ -22,12 +22,6 @@ GraphqlController logs namespace, operation name, and error_count without the qu
|
|
|
22
22
|
|
|
23
23
|
ResourceMethods lives in lib/active_admin/graphql/resource_methods.rb.
|
|
24
24
|
|
|
25
|
-
## Effects
|
|
26
|
-
|
|
27
|
-
First pass used create_only. Second pass replaced it after a convention check against graphql-ruby 2.6.7, Rails 8.1 nested_attributes update_only, upsert_all update_only, and ActiveAdmin 3.5.2 permit_params.
|
|
28
|
-
|
|
29
|
-
bundle exec rspec spec/requests/graphql_mutation_permit_params_spec.rb: 8 examples, 0 failures after the rename.
|
|
30
|
-
|
|
31
25
|
## Findings
|
|
32
26
|
|
|
33
27
|
Severity high. Confidence high. Location SchemaBuilder TypesInputs default create and update inputs. Why it matters: extra-field write of created_at on create, observed in spec/requests/graphql_mutation_permit_params_spec.rb. Evidence kind observed. Security disposition confirmed. Smallest credible fix: graphql { permit_params :title, :body } in the host app. Deeper fix: omit created_at and updated_at from default mutation inputs in a later RFC, matching ActiveAdmin generators/boilerplate.rb assignable_attributes.
|
|
@@ -46,6 +40,12 @@ Severity low. Confidence high. Location GraphqlController, no request correlatio
|
|
|
46
40
|
|
|
47
41
|
Severity low. Confidence high. Location lib/active_admin/graphql/integration.rb line count 176. Why it matters: file is above the 150 line preference. Evidence kind observed. Smallest credible fix: extract ResourceMethods. Not done this pass.
|
|
48
42
|
|
|
43
|
+
## Effects
|
|
44
|
+
|
|
45
|
+
First pass used create_only. Second pass replaced it after a convention check against graphql-ruby 2.6.7, Rails 8.1 nested_attributes update_only, upsert_all update_only, and ActiveAdmin 3.5.2 permit_params.
|
|
46
|
+
|
|
47
|
+
bundle exec rspec spec/requests/graphql_mutation_permit_params_spec.rb: 8 examples, 0 failures after the rename.
|
|
48
|
+
|
|
49
49
|
## Next
|
|
50
50
|
|
|
51
51
|
Findings from the 2026-09-21 audit pass are addressed except replacing CRUD return types with graphql-ruby payload objects, which would break RFC 0004 field types. Keep RFC 0006 Proposed. 0.3.0 metadata includes the graphql 2.6.9 floor and ActiveAdmin 4 Rails 8 matrix; tag and gem push wait on make release.
|