activeadmin-graphql 0.3.1 → 0.3.3

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/app/controllers/active_admin/graphql_controller.rb +19 -4
  4. data/docs/changelogs/20261010091415_graphql-resource-enabled-default.md +34 -0
  5. data/docs/changelogs/20261010101500_nonconventional-id-fields.md +16 -2
  6. data/docs/changelogs/20261010103800_release-0-3-1.md +9 -2
  7. data/docs/changelogs/20261010110600_graphql-identity-dsl.md +34 -0
  8. data/docs/changelogs/20261010111800_release-0-3-2.md +46 -0
  9. data/docs/changelogs/20261012050051_graphql-surface-toggles.md +40 -0
  10. data/docs/changelogs/20261012064500_graphql-capabilities.md +35 -0
  11. data/docs/changelogs/20261012113500_release-0-3-3.md +45 -0
  12. data/docs/graphql-api.md +166 -20
  13. data/docs/issues/20261010111159_rails-composite-primary-keys-alignment.md +81 -0
  14. data/docs/issues/20261010111300_partitions-and-shards-support.md +55 -0
  15. data/docs/issues/20261010111500_graphql-federation-support.md +46 -0
  16. data/lib/active_admin/graphql/capability.rb +35 -0
  17. data/lib/active_admin/graphql/capability_definition_dsl.rb +64 -0
  18. data/lib/active_admin/graphql/integration.rb +19 -1
  19. data/lib/active_admin/graphql/policy_adapter.rb +94 -0
  20. data/lib/active_admin/graphql/policy_set_cache.rb +18 -9
  21. data/lib/active_admin/graphql/resource_capability_dsl.rb +31 -0
  22. data/lib/active_admin/graphql/resource_capability_maps.rb +59 -0
  23. data/lib/active_admin/graphql/resource_config.rb +23 -2
  24. data/lib/active_admin/graphql/resource_definition_dsl.rb +12 -2
  25. data/lib/active_admin/graphql/resource_identity.rb +98 -0
  26. data/lib/active_admin/graphql/resource_methods.rb +8 -2
  27. data/lib/active_admin/graphql/resource_query_proxy/identity.rb +4 -4
  28. data/lib/active_admin/graphql/resource_query_proxy.rb +5 -4
  29. data/lib/active_admin/graphql/resource_surface_dsl.rb +48 -0
  30. data/lib/active_admin/graphql/schema_builder/build.rb +52 -3
  31. data/lib/active_admin/graphql/schema_builder/mutation_batch.rb +7 -0
  32. data/lib/active_admin/graphql/schema_builder/mutation_collection.rb +8 -0
  33. data/lib/active_admin/graphql/schema_builder/mutation_create.rb +1 -0
  34. data/lib/active_admin/graphql/schema_builder/mutation_member.rb +8 -0
  35. data/lib/active_admin/graphql/schema_builder/mutation_type_builder.rb +15 -10
  36. data/lib/active_admin/graphql/schema_builder/mutation_update_destroy.rb +4 -2
  37. data/lib/active_admin/graphql/schema_builder/query_type.rb +2 -1
  38. data/lib/active_admin/graphql/schema_builder/query_type_collection.rb +3 -0
  39. data/lib/active_admin/graphql/schema_builder/query_type_member.rb +5 -2
  40. data/lib/active_admin/graphql/schema_builder/query_type_policies.rb +9 -63
  41. data/lib/active_admin/graphql/schema_builder/resources.rb +34 -0
  42. data/lib/active_admin/graphql/schema_builder/types_object.rb +27 -11
  43. data/lib/active_admin/graphql/schema_builder/visibility.rb +22 -7
  44. data/lib/active_admin/graphql/schema_builder/wire.rb +3 -1
  45. data/lib/active_admin/graphql/schema_field.rb +18 -2
  46. data/lib/active_admin/graphql/version.rb +1 -1
  47. data/lib/active_admin/graphql.rb +3 -0
  48. metadata +17 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5fc45489403fdf5c4d16bd66966cc4c7c5641440663d3f5b229b0d627a0d0978
4
- data.tar.gz: '04786131baffbec749af13c347660836c1ca31efa074c73eddfc49c5fbfb588e'
3
+ metadata.gz: 36bec145bd90193ae32e34aa54b7a6ed01dff13a8b104269b771f797262b2480
4
+ data.tar.gz: 82724e08af33a67a1a9146813965e5d836006620ff503099842addcdeaab16cb
5
5
  SHA512:
6
- metadata.gz: 1c93c24f91a208c8eb00076d1fd3d8b398c99d155a8ce92f4aaa075b6615ea321a3303b2186e85fa32178a4e8ad158fb4ab1f081752993c6a44e5466fad1ac83
7
- data.tar.gz: f5c1acc62e5f1aa0cd925234c4df76d9870dd592cb97aeae7927df578340d70936e02bcf727635a8f07d7caacfb84316e037383c7666b35cb472d11b8776240e
6
+ metadata.gz: 0b9ddc6e9d85447807e5cbbf9d1e5c9b5c87b756eaa8325cb57fe8ce291e221b40e1afd200ddb683a73cba036b83b6eac6f8f5ab3e0ff9788c1864e7f550cc16
7
+ data.tar.gz: 806443d03640f31e48d893998cd9f693bafd5efce162798ff406ed2eee2ad43d7c0c588467b8fbd37fdab4a3a6049a9efcbadffbe337469cac7bc5ff958bf36f
data/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.3 (2026-10-12)
6
+
7
+ - Add `graphql { capability ... }` and namespace `graphql_capabilities` so one named grant can gate many fields, queries, and mutations (RFC 0008).
8
+ - Apply `fields:` capabilities to wired `belongs_to` associations; reject unknown capability field names and named visibility `profiles:` combined with `graphql_capabilities`.
9
+ - Add namespace setting `graphql_resource_enabled_default` (`true` by default) and `graphql { enable! }` so apps can opt resources into the schema instead of opting out with `disable!`.
10
+ - Add namespace toggles for `registered_resource` and `belongs_to` wiring (`graphql_registered_resource_enabled`, `graphql_belongs_to_associations_enabled`; both `true` by default).
11
+ - Add `graphql_policies_enabled` / `graphql_policies_enabled_default` for policy field publishing and per-resource seeds, plus `graphql_policy_adapter` (default `ActiveAdmin::GraphQL::PolicyAdapter`) for payload construction.
12
+ - Add per-resource surface defaults and DSL (`graphql_queries_enabled_default`, `graphql_mutations_enabled_default`, `graphql_run_actions_enabled_default`, plus `enable_*!` / `disable_*!`) so apps can trim list/show, CRUD, run-action, and policy fields without disabling the whole resource.
13
+ - Treat `queries_enabled` as the read surface: omit `registered_resource` union membership when queries are off; fail schema build when `Query` would have no fields.
14
+
15
+ ## 0.3.2 (2026-10-10)
16
+
17
+ - Add `graphql { identity ... }` so keyless views can declare GraphQL id columns, optional `separator`, and `encode` / `decode` (RFC 0007).
18
+ - Fail schema build when a registered model has neither a primary key nor an `id` column and no `identity` declaration; set `self.primary_key`, use `identity`, or `graphql { disable! }`.
19
+
5
20
  ## 0.3.1 (2026-10-10)
6
21
 
7
22
  - Fix GraphQL identity handling for database views and nonconventional primary keys: keep one `id` field, support inferred `id` lookups, reject ambiguous identifiers, and honor attribute filters.
@@ -51,16 +51,31 @@ module ActiveAdmin
51
51
 
52
52
  def graphql_context
53
53
  ns = active_admin_namespace
54
- {
54
+ context = {
55
55
  auth: ActiveAdmin::GraphQL::AuthContext.new(
56
56
  user: current_active_admin_user,
57
57
  namespace: ns
58
58
  ),
59
59
  namespace: ns,
60
60
  request: request,
61
- current_user: current_active_admin_user,
62
- visibility_profile: graphql_visibility_profile_for(ns)
63
- }.compact
61
+ current_user: current_active_admin_user
62
+ }
63
+ # Named visibility profiles cache a fixed context; per-request capabilities
64
+ # need the live request context (dynamic visibility). Skip the profile key.
65
+ unless ns.graphql_capabilities.respond_to?(:call)
66
+ profile = graphql_visibility_profile_for(ns)
67
+ context[:visibility_profile] = profile if profile
68
+ end
69
+ capabilities = graphql_capabilities_for(ns, context)
70
+ context[:capabilities] = capabilities unless capabilities.nil?
71
+ context
72
+ end
73
+
74
+ def graphql_capabilities_for(ns, context)
75
+ hook = ns.graphql_capabilities
76
+ return nil unless hook.respond_to?(:call)
77
+
78
+ ActiveAdmin::GraphQL::Capability.normalize_set(hook.call(context))
64
79
  end
65
80
 
66
81
  def graphql_visibility_profile_for(ns)
@@ -0,0 +1,34 @@
1
+ # GraphQL resource enabled default
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov
6
+
7
+ ## Decisions
8
+
9
+ Add namespace setting `graphql_resource_enabled_default`, default `true`, so
10
+ 0.3.x behavior stays: turning on the namespace endpoint still includes every
11
+ ActiveRecord resource unless `graphql { disable! }` is set.
12
+
13
+ Apps that want support-style opt-in set the setting to `false`. Then a resource
14
+ enters the schema only when registration runs `graphql { }` or
15
+ `graphql { enable! }`. `disable!` still omits.
16
+
17
+ `ResourceConfig` takes the initial `enabled` flag from the namespace setting.
18
+ Entering a `graphql` block sets `enabled = true` before the block body runs so
19
+ an empty block is a conscious opt-in.
20
+
21
+ ## Effects
22
+
23
+ New request specs cover default true, default false without a block, empty
24
+ block, `enable!`, and `disable!`.
25
+
26
+ ## Next
27
+
28
+ Folded into 0.3.3 release metadata.
29
+
30
+ ## Source
31
+
32
+ CHANGELOG 0.3.3. docs/graphql-api.md namespace settings and per-resource
33
+ DSL. spec/requests/graphql_resource_enabled_default_spec.rb.
34
+ docs/changelogs/20261012113500_release-0-3-3.md.
@@ -11,7 +11,10 @@ primary key is blank. Object types skip emitting a column field named `id`
11
11
  (interface owns GraphQL `id`) and expose a single non-`id` primary-key column
12
12
  as a readable scalar unless the resource's attribute configuration omits it.
13
13
  Models with neither a primary key nor an `id` column fail schema build with an
14
- `ArgumentError` that asks for `self.primary_key`. Inferred `id` values must be
14
+ `ArgumentError` that asks for `self.primary_key`. On 0.3.0 those models could
15
+ still enter the schema with a broken GraphQL `id`. Apps that keep such a
16
+ resource registered must set `self.primary_key` or use `graphql { disable! }`
17
+ so the rest of the namespace schema still builds. Inferred `id` values must be
15
18
  unique and non-null; ambiguous member and batched lookups fail closed.
16
19
 
17
20
  Member lookup uses the inferred column while preserving an explicitly
@@ -21,9 +24,19 @@ entries for distinct records when ActiveRecord has no declared primary key.
21
24
  Dummy coverage includes SQLite views that project `code AS id` and
22
25
  `id AS row_key`, plus a table with an `id` column and no primary key.
23
26
 
27
+ ## Later pass 2026-10-10
28
+
29
+ Downstream check: a database view with neither primary key nor `id` column
30
+ could stay GraphQL-disabled via `graphql { disable! }`. Spec covers that
31
+ escape hatch next to the ArgumentError path.
32
+
33
+ Later the same day: RFC 0007 adds `graphql { identity ... }` so those views
34
+ can stay enabled when columns (and optional separator) are declared. See
35
+ docs/changelogs/20261010110600_graphql-identity-dsl.md.
36
+
24
37
  ## Next
25
38
 
26
- Folded into 0.3.1 release metadata. Tag and gem push wait on make release.
39
+ Core fix shipped in 0.3.1. Identity DSL follow-on is 0.3.2.
27
40
 
28
41
  ## Source
29
42
 
@@ -32,3 +45,4 @@ Folded into 0.3.1 release metadata. Tag and gem push wait on make release.
32
45
  `spec/unit/resource_query_proxy_identity_spec.rb`,
33
46
  `spec/unit/policy_set_cache_spec.rb`
34
47
  - Views: `spec/dummy/db/schema.rb` (`string_id_records`, `alternate_key_records`)
48
+ - Follow-on: docs/changelogs/20261010110600_graphql-identity-dsl.md
@@ -6,7 +6,11 @@ Andrei Makarov
6
6
 
7
7
  ## Decisions
8
8
 
9
- Cut 0.3.1 after published 0.3.0. One user-visible fix: GraphQL identity for database views and nonconventional primary keys.
9
+ Cut 0.3.1 after published 0.3.0. User-visible work: GraphQL identity for
10
+ database views and nonconventional primary keys; fail-closed when a model has
11
+ neither a primary key nor an `id` column without configuration; and
12
+ `graphql { identity ... }` (RFC 0007) for keyless views with a virtual
13
+ composite id.
10
14
 
11
15
  CHANGELOG 0.3.1 dated 2026-10-10. Version constant is 0.3.1. Path gem pins in Gemfile.lock and appraisal lockfiles match.
12
16
 
@@ -24,9 +28,12 @@ PATH specs for activeadmin-graphql in Gemfile.lock, gemfiles/rails72.gemfile.loc
24
28
 
25
29
  ## Next
26
30
 
27
- Run make release from a clean tree when ready to push the gem and tag 0.3.1.
31
+ 0.3.1 tagged and published on RubyGems. Post-tag identity DSL work ships in
32
+ 0.3.2; see docs/changelogs/20261010111800_release-0-3-2.md.
28
33
 
29
34
  ## Source
30
35
 
31
36
  docs/changelogs/20261010101500_nonconventional-id-fields.md
37
+ docs/changelogs/20261010110600_graphql-identity-dsl.md
38
+ rfcs/0007-graphql-identity.md
32
39
  usr/bin/release.rb
@@ -0,0 +1,34 @@
1
+ # GraphQL identity DSL (RFC 0007)
2
+
3
+ ## Decisions
4
+
5
+ Ship `graphql { identity *columns, separator:, encode:, decode: }` so resources
6
+ without an ActiveRecord primary key and without an `id` column can stay in the
7
+ schema. Prefer resource configuration over forcing `self.primary_key`.
8
+
9
+ Separator form matches HTML admins that use a virtual `id` method such as
10
+ `[entry_type, entry_id, event_type].join(":")`. Without separator, multi-column
11
+ identity keeps the existing composite JSON string.
12
+
13
+ ## Effects
14
+
15
+ Schema build, member lookup, mutations that resolve by where/id, and policy
16
+ cache keys go through `ActiveAdmin::GraphQL::ResourceIdentity` when identity is
17
+ configured. Ambiguous matches still fail closed.
18
+
19
+ Dummy coverage: view `alert_events` / model `AlertEvent` with virtual composite
20
+ `id`.
21
+
22
+ ## Next
23
+
24
+ Folded into 0.3.2 release metadata (0.3.1 already published without this DSL).
25
+ Tag and gem push wait on make release.
26
+
27
+ ## Source
28
+
29
+ rfcs/0007-graphql-identity.md
30
+ docs/changelogs/20261010101500_nonconventional-id-fields.md
31
+ docs/issues/20261010111159_rails-composite-primary-keys-alignment.md
32
+ https://guides.rubyonrails.org/active_record_composite_primary_keys.html
33
+ spec/unit/resource_identity_spec.rb
34
+ spec/requests/graphql_identity_dsl_spec.rb
@@ -0,0 +1,46 @@
1
+ # Release 0.3.2 metadata
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov
6
+
7
+ ## Decisions
8
+
9
+ Cut 0.3.2 after published 0.3.1. User-visible work since the 0.3.1 tag:
10
+ `graphql { identity ... }` (RFC 0007) for keyless views, and schema-build
11
+ error text that names `identity` alongside `self.primary_key` and `disable!`.
12
+
13
+ CHANGELOG 0.3.1 restored to the published one-bullet text. Identity DSL bullets
14
+ live under 0.3.2 dated 2026-10-10.
15
+
16
+ Version constant is 0.3.2. Path gem pins in Gemfile.lock and appraisal
17
+ lockfiles match.
18
+
19
+ Docs-only notes (Rails composite PK alignment, partitions/shards unsupported,
20
+ federation unsupported) stay out of CHANGELOG.md.
21
+
22
+ make release (usr/bin/release.rb) still owns gem build, gem push, git tag, and
23
+ GitHub release. This pass only prepares metadata.
24
+
25
+ ## Effects
26
+
27
+ lib/active_admin/graphql/version.rb VERSION is 0.3.2.
28
+
29
+ CHANGELOG identity DSL bullets moved from the post-tag 0.3.1 draft under 0.3.2.
30
+
31
+ PATH specs for activeadmin-graphql in Gemfile.lock,
32
+ gemfiles/rails72.gemfile.lock, gemfiles/rails8ruby4.gemfile.lock, and
33
+ gemfiles/rails8ruby34.gemfile.lock are 0.3.2.
34
+
35
+ ## Next
36
+
37
+ Run make release from a clean tree when ready to push the gem and tag 0.3.2.
38
+
39
+ ## Source
40
+
41
+ docs/changelogs/20261010110600_graphql-identity-dsl.md
42
+ rfcs/0007-graphql-identity.md
43
+ docs/issues/20261010111159_rails-composite-primary-keys-alignment.md
44
+ docs/issues/20261010111300_partitions-and-shards-support.md
45
+ docs/issues/20261010111500_graphql-federation-support.md
46
+ usr/bin/release.rb
@@ -0,0 +1,40 @@
1
+ # GraphQL surface toggles
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov
6
+
7
+ ## Decisions
8
+
9
+ Add namespace and per-resource controls for schema surfaces that were always on
10
+ once a resource was enabled. Defaults stay `true` so 0.3.x behavior is
11
+ unchanged.
12
+
13
+ Namespace-only switches: `graphql_registered_resource_enabled`,
14
+ `graphql_belongs_to_associations_enabled`, `graphql_policies_enabled`.
15
+ Policy payloads use `graphql_policy_adapter` (default `PolicyAdapter`).
16
+ Per-resource policy seed is `graphql_policies_enabled_default`.
17
+
18
+ Per-resource seeds from namespace defaults: queries, mutations (CRUD),
19
+ run-actions, and policies. Resource DSL exposes matching `enable_*!` /
20
+ `disable_*!` methods. `queries_enabled` also gates `registered_resource`
21
+ membership. Schema build raises if `Query` would have no fields.
22
+
23
+ ## Effects
24
+
25
+ Schema builder skips policy query fields, registered_resource union, belongs_to
26
+ wiring, list/show fields, CRUD mutations, and run-action mutations when the
27
+ matching flag is off. Request specs cover the new settings and DSL, including
28
+ empty-Query rejection and registered_resource gating by queries.
29
+
30
+ ## Next
31
+
32
+ Folded into 0.3.3 release metadata.
33
+
34
+ ## Source
35
+
36
+ CHANGELOG 0.3.3. docs/graphql-api.md.
37
+ spec/requests/graphql_surface_toggles_spec.rb.
38
+ spec/requests/graphql_policy_surface_spec.rb.
39
+ spec/requests/graphql_query_surface_contract_spec.rb.
40
+ docs/changelogs/20261012113500_release-0-3-3.md.
@@ -0,0 +1,35 @@
1
+ # GraphQL capabilities
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov
6
+
7
+ ## Decisions
8
+
9
+ Add RFC 0008 capability grants. Resource DSL `capability` associates many
10
+ object fields, queries, and mutations with one name. Namespace
11
+ `graphql_capabilities` fills `context[:capabilities]` per request. SchemaField
12
+ checks capability in `visible?` and `authorized?`. Capabilities do not replace
13
+ ActiveAdmin authorization. Subscriptions stay out of scope.
14
+
15
+ ## Effects
16
+
17
+ Gated members fail closed without the grant. With `graphql_visibility`,
18
+ introspection omits them. Aggregate run-action resolvers also check
19
+ per-action capability keys (`member_*`, `batch_*`, `collection_*`).
20
+ `belongs_to` association fields honor `fields:` capabilities. Schema build
21
+ rejects unknown capability field names and rejects named visibility profiles
22
+ combined with `graphql_capabilities` (dynamic visibility required).
23
+
24
+ ## Next
25
+
26
+ Folded into 0.3.3 release metadata. Follow-ons remain: policy payloads listing
27
+ field capabilities, mutation input attribute maps, and transport gates if
28
+ subscriptions ship later.
29
+
30
+ ## Source
31
+
32
+ RFC 0008. CHANGELOG 0.3.3. docs/graphql-api.md. spec/unit/capability_spec.rb,
33
+ spec/unit/capability_dsl_spec.rb, spec/requests/graphql_capabilities_spec.rb,
34
+ spec/requests/graphql_capabilities_edge_spec.rb.
35
+ docs/changelogs/20261012113500_release-0-3-3.md.
@@ -0,0 +1,45 @@
1
+ # Release 0.3.3 metadata
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov
6
+
7
+ ## Decisions
8
+
9
+ Cut 0.3.3 after published 0.3.2. User-visible work since the 0.3.2 tag:
10
+ `graphql_resource_enabled_default` and `enable!` opt-in, surface toggles for
11
+ queries, mutations, run-actions, policies, `registered_resource`, and
12
+ `belongs_to`, plus GraphQL capabilities (RFC 0008). Defaults stay `true` so
13
+ 0.3.x schema shape is unchanged unless an app opts in to narrower surfaces.
14
+
15
+ CHANGELOG Unreleased bullets moved under 0.3.3 dated 2026-10-12.
16
+
17
+ Version constant is 0.3.3. Path gem pins in Gemfile.lock and appraisal
18
+ lockfiles match.
19
+
20
+ Internal `stringify_attrs` safe-navigation cleanup stays out of CHANGELOG.md.
21
+
22
+ make release (usr/bin/release.rb) still owns gem build, gem push, git tag, and
23
+ GitHub release. This pass only prepares metadata.
24
+
25
+ ## Effects
26
+
27
+ lib/active_admin/graphql/version.rb VERSION is 0.3.3.
28
+
29
+ CHANGELOG surface-toggle and capability bullets live under 0.3.3.
30
+
31
+ PATH specs for activeadmin-graphql in Gemfile.lock,
32
+ gemfiles/rails72.gemfile.lock, gemfiles/rails8ruby4.gemfile.lock, and
33
+ gemfiles/rails8ruby34.gemfile.lock are 0.3.3.
34
+
35
+ ## Next
36
+
37
+ Run make release from a clean tree when ready to push the gem and tag 0.3.3.
38
+
39
+ ## Source
40
+
41
+ docs/changelogs/20261010091415_graphql-resource-enabled-default.md
42
+ docs/changelogs/20261012050051_graphql-surface-toggles.md
43
+ docs/changelogs/20261012064500_graphql-capabilities.md
44
+ rfcs/0008-graphql-capabilities.md
45
+ usr/bin/release.rb
data/docs/graphql-api.md CHANGED
@@ -33,6 +33,12 @@ ActiveAdmin.setup do |config|
33
33
  admin.graphql = true
34
34
  # Optional — default is "graphql", i.e. POST /admin/graphql
35
35
  # admin.graphql_path = "graphql"
36
+ # Optional — set false so only resources with a graphql block enter the schema
37
+ # admin.graphql_resource_enabled_default = false
38
+ # Optional — trim always-on surfaces for a narrow API
39
+ # admin.graphql_policies_enabled = false
40
+ # admin.graphql_registered_resource_enabled = false
41
+ # admin.graphql_belongs_to_associations_enabled = false
36
42
  end
37
43
  end
38
44
  ```
@@ -45,6 +51,33 @@ Run `bin/rails active_admin:install` or reload routes if needed so
45
51
  Each namespace inherits these options (defaults in parentheses):
46
52
 
47
53
  * `graphql` — set to `true` to mount the endpoint (`false` by default).
54
+ * `graphql_resource_enabled_default` — whether each registered ActiveRecord
55
+ resource is included in the schema without a `graphql` block (`true` by
56
+ default, matching 0.3.x). Set to `false` for opt-in: only resources that call
57
+ `graphql { }` (or `graphql { enable! }`) appear in the schema. `disable!`
58
+ still omits a resource.
59
+ * `graphql_queries_enabled_default` — seed for per-resource read fields
60
+ (`true` by default): named list/show fields and membership in
61
+ `registered_resource` / `ActiveAdminRegisteredResource`. Set `false` and use
62
+ `enable_queries!` for opt-in reads. Object types may still exist for mutations.
63
+ * `graphql_mutations_enabled_default` — seed for per-resource CRUD mutations
64
+ (`true` by default). Set `false` and use `enable_mutations!` for opt-in writes.
65
+ * `graphql_run_actions_enabled_default` — seed for batch/member/collection
66
+ action mutations (`true` by default).
67
+ * `graphql_policies_enabled` — publish `activeadmin_policies` query fields and
68
+ allow per-object policy fields (`true` by default).
69
+ * `graphql_policies_enabled_default` — seed for per-resource `policies_enabled`
70
+ (`true` by default). Use `enable_policies!` / `disable_policies!` per resource.
71
+ * `graphql_policy_adapter` — class that builds `activeadmin_policies` payloads
72
+ (`ActiveAdmin::GraphQL::PolicyAdapter` by default). Set a subclass (or constant
73
+ `String`) to replace payload logic; the default adapter still honors
74
+ `graphql_policy_actions` / `graphql_policy_action_mapper` /
75
+ `graphql_policy_extra` / `graphql_policy_transform`.
76
+ * `graphql_registered_resource_enabled` — include `registered_resource` and the
77
+ `ActiveAdminRegisteredResource` union when at least one queryable resource
78
+ exists (`true` by default).
79
+ * `graphql_belongs_to_associations_enabled` — wire non-polymorphic `belongs_to`
80
+ fields between registered resource types (`true` by default).
48
81
  * `graphql_path` — path segment under the namespace mount (`"graphql"`).
49
82
  * `graphql_multiplex_max` — maximum operations per multiplexed JSON array
50
83
  request (`20`).
@@ -70,6 +103,11 @@ Each namespace inherits these options (defaults in parentheses):
70
103
  default `context[:visibility_profile]` for each HTTP request (symbols accepted).
71
104
  * `graphql_schema_visible` — optional `Proc` `(context, meta) -> Boolean` when
72
105
  `graphql_visibility` is enabled; see Schema visibility below.
106
+ * `graphql_capabilities` — optional `Proc` `(context) -> Enumerable` of capability
107
+ names for the request (RFC 0008). Normalized into `context[:capabilities]`.
108
+ Cannot be combined with named `graphql_visibility` `profiles:`; schema build
109
+ forces `dynamic: true` and omits `visibility_profile` from the request context
110
+ so introspection uses the live capability set.
73
111
  * `graphql_configure_schema` — `Proc` receiving the schema class after build
74
112
  (extensions, extra types, and so on).
75
113
 
@@ -199,21 +237,90 @@ UI, including any `authorize!` or `authorize_resource!` calls.
199
237
 
200
238
  See the ActiveAdmin guide [Authorization adapter](https://activeadmin.info/13-authorization-adapter) for adapter setup; the same rules apply to GraphQL.
201
239
 
240
+ Policy preflight fields (`activeadmin_policies`, `activeadmin_policies_for`, and
241
+ per-object `activeadmin_policies`) are published when `graphql_policies_enabled`
242
+ is true. Payloads come from `graphql_policy_adapter`. The default
243
+ `ActiveAdmin::GraphQL::PolicyAdapter` asks the authorization adapter which
244
+ actions are allowed and applies the optional `graphql_policy_*` hooks. Assign a
245
+ subclass that implements `policy_set` (and optionally `include_resource?`) to
246
+ replace the payload builder.
247
+
248
+ ### Capabilities (RFC 0008)
249
+
250
+ Associate many object fields, queries, and mutations with one named capability
251
+ inside `graphql do`. The same capability name can cover a mixed set of members:
252
+
253
+ ```ruby
254
+ ActiveAdmin.register Post do
255
+ graphql do
256
+ capability :pii do
257
+ fields :body
258
+ end
259
+
260
+ capability :editor do
261
+ fields :title
262
+ queries :index, :show
263
+ mutations :create, :update, :destroy
264
+ end
265
+
266
+ capability :publish, member_actions: %i[publish]
267
+ end
268
+ end
269
+ ```
270
+
271
+ Kwargs (`fields:`, `queries:`, `mutations:`, `member_actions:`,
272
+ `batch_actions:`, `collection_actions:`) work without a block. Query aliases:
273
+ `index` / `list` → collection, `show` → member. Mutation alias: `delete` →
274
+ `destroy`. Run-action sugar builds keys like `member_publish`.
275
+
276
+ Grant capabilities per request with `graphql_capabilities`. Map OAuth scopes or
277
+ roles there; the gem stores a symbol set on `context[:capabilities]`:
278
+
279
+ ```ruby
280
+ admin.graphql_capabilities = lambda { |ctx|
281
+ Array(ctx[:current_user]&.oauth_scopes)
282
+ }
283
+ ```
284
+
285
+ A gated member fails closed when the capability is missing from that set:
286
+ resolve returns null for the field (graphql-ruby unauthorized field). Enable
287
+ `graphql_visibility` so introspection and validation also omit ungated members.
288
+ `fields:` applies to generated attributes and wired `belongs_to` associations.
289
+ Unknown field names raise at schema build. Capabilities do not replace the
290
+ authorization adapter.
291
+
292
+ Custom `SchemaField` definitions may pass `capability: :name` the same way.
293
+ Do not use named visibility `profiles:` together with `graphql_capabilities`
294
+ (cached profile context ignores per-request grants).
295
+
202
296
  ## Schema overview
203
297
 
204
- The `Query` type includes one Relay-style connection field per resource route
205
- (for example `posts`), plus a singular field for a single record (for example
206
- `post`). Optional `graphql_field` entries on `register_page` appear here too,
207
- plus `registered_resource` (returning the `ActiveAdminRegisteredResource` union)
208
- when at least one resource is exposed.
298
+ With the default surface flags (`true`), the `Query` type includes one
299
+ Relay-style connection field per resource route (for example `posts`), plus a
300
+ singular field for a single record (for example `post`). Optional
301
+ `graphql_field` entries on `register_page` appear here too, plus
302
+ `registered_resource` (returning the `ActiveAdminRegisteredResource` union)
303
+ when at least one queryable resource exists.
304
+
305
+ List and show fields require per-resource `queries_enabled` (seeded from
306
+ `graphql_queries_enabled_default`). Turning queries off still leaves the object
307
+ type available for mutations. `registered_resource` and the union also require
308
+ `graphql_registered_resource_enabled`. Policy preflight fields require
309
+ `graphql_policies_enabled`. Schema build fails when `Query` would have no
310
+ fields: no list/show, no policies, no `registered_resource`, and no page
311
+ `graphql_field`.
209
312
 
210
313
  Every resource object type implements the `ActiveAdminResource` interface
211
314
  (`id: ID!`) so clients can share fragments or rely on a common shape.
315
+ Non-polymorphic `belongs_to` fields between registered types are wired when
316
+ `graphql_belongs_to_associations_enabled` is true.
212
317
 
213
318
  The `Mutation` type is present when at least one operation exists: per-resource
214
- `create_*`, `update_*`, and `delete_*` when those controller actions exist, plus
215
- `*_batch_action`, `*_member_action`, and `*_collection_action` fields when the
216
- resource defines the corresponding batch, member, or collection actions.
319
+ `create_*`, `update_*`, and `delete_*` when those controller actions exist and
320
+ `mutations_enabled` is true, plus `*_batch_action`, `*_member_action`, and
321
+ `*_collection_action` fields when the resource defines the corresponding batch,
322
+ member, or collection actions and `run_actions_enabled` is true. Both flags are
323
+ seeded from the matching namespace defaults.
217
324
 
218
325
  Object type names default to the model name (for example `Post`). Typed input
219
326
  objects use assignable columns (see the per-resource section below):
@@ -229,12 +336,28 @@ GraphQL `id` always comes from `ActiveAdminResource` (`ID!`). An ActiveRecord
229
336
  `id` column is never added as a second field. When `self.primary_key` is blank
230
337
  but the model still has an `id` column (common for database views that project
231
338
  `… AS id`), that column supplies the GraphQL `id` value. When the model has
232
- neither a primary key nor an `id` column, schema build raises until you set
233
- `self.primary_key` on the model (for example a view keyed by `row_key`). A
234
- single-column primary key named something other than `id` is also exposed as
235
- its own readable field beside `id`, unless `only` or `exclude` omits it. An
236
- inferred `id` column must contain a unique, non-null value for every row;
237
- ambiguous member and batched lookups fail instead of selecting one row.
339
+ neither a primary key nor an `id` column, declare GraphQL identity on the
340
+ resource (RFC 0007), set `self.primary_key` on the model, or omit the resource
341
+ with `graphql { disable! }`. A single-column primary key named something other
342
+ than `id` is also exposed as its own readable field beside `id`, unless `only`
343
+ or `exclude` omits it. An inferred `id` column must contain a unique, non-null
344
+ value for every row; ambiguous member and batched lookups fail instead of
345
+ selecting one row.
346
+
347
+ ```ruby
348
+ # View with no PK and no id column; virtual identity matches HTML admin.
349
+ ActiveAdmin.register AlertEvent do
350
+ graphql do
351
+ identity :entry_type, :entry_id, :event_type, separator: ":"
352
+ # optional: encode: ->(record) { record.id }, decode: ->(id) { ... }
353
+ end
354
+ end
355
+ ```
356
+
357
+ With `separator: ":"`, GraphQL `id` is `entry_type:entry_id:event_type`. Keep
358
+ the model’s `def id` (if any) in the same shape so HTML and GraphQL share
359
+ member ids. Without `separator`, multi-column identity uses the same JSON
360
+ object string as composite ActiveRecord primary keys.
238
361
 
239
362
  ### Composite primary keys (Rails 7.1+)
240
363
 
@@ -242,8 +365,10 @@ For models with `self.primary_key = [:book_code, :seq]` (and no separate `id`
242
365
  column), Active Admin treats GraphQL as follows:
243
366
 
244
367
  * `id` on the object type — JSON object string with all key columns in
245
- model order (same format Rails uses when casting composite ids), for example
246
- `{"book_code":"CPK","seq":7}`.
368
+ model order, for example `{"book_code":"CPK","seq":7}`. This is the GraphQL
369
+ wire format. It is not the Rails form/URL underscore packing described in
370
+ the Composite Primary Keys guide (`/books/2_25` with
371
+ `params.extract_value(:id)`).
247
372
  * Readable fields — each primary-key column is also exposed as its own field
248
373
  (in addition to `id`) so clients can load scalars without parsing JSON.
249
374
  * `LibraryEditionWhereInput` (and similar) — either optional `id` (that JSON
@@ -255,12 +380,20 @@ column), Active Admin treats GraphQL as follows:
255
380
  inputs (unlike single `id` tables, where the key is omitted from assignable
256
381
  attributes).
257
382
 
258
- For REST/HTML member routes, `ResourceController#find_resource` must resolve composite ids from `params[:id]` (JSON string) or from individual primary-key params. Stock ActiveAdmin 3.x only passes a single `params[:id]` into `find`; an ActiveAdmin fork (or patch) that implements composite-aware `find_resource` keeps the admin UI aligned with GraphQL. The GraphQL layer also works with `ActiveAdmin::GraphQL::ResourceQueryProxy` using Rails’ tuple id convention for `Relation#find`.
383
+ For REST/HTML member routes, `ResourceController#find_resource` must resolve
384
+ composite ids from `params[:id]` or from individual primary-key params. Stock
385
+ ActiveAdmin 3.x only passes a single `params[:id]` into `find`; an ActiveAdmin
386
+ fork (or patch) that implements composite-aware `find_resource` keeps the
387
+ admin UI aligned with GraphQL. After decoding the GraphQL JSON `id`, the gem
388
+ uses Rails’ array/tuple conventions for `Relation#find` and
389
+ `where(primary_key => [tuple, …])` as in the guide.
259
390
 
260
- The gem ships `ActiveAdmin::PrimaryKey` when the core constant is missing (upstream). A fork may define `PrimaryKey` and extended `find_resource` in `ResourceController` instead.
391
+ The gem ships `ActiveAdmin::PrimaryKey` when the core constant is missing
392
+ (upstream). A fork may define `PrimaryKey` and extended `find_resource` in
393
+ `ResourceController` instead.
261
394
 
262
- `ActiveAdmin::GraphQL::RecordSource` batch-loads composite associations using
263
- Rails `where(primary_key => [tuple, …])`.
395
+ Alignment notes versus the Rails guide:
396
+ `docs/issues/20261010111159_rails-composite-primary-keys-alignment.md`.
264
397
 
265
398
  ### Collection query arguments
266
399
 
@@ -359,6 +492,7 @@ Inside `ActiveAdmin.register` you can narrow the GraphQL surface:
359
492
  ActiveAdmin.register Post do
360
493
  graphql do
361
494
  disable! # omit this resource from the schema
495
+ # identity :entry_type, :entry_id, :event_type, separator: ":" # RFC 0007
362
496
  type_name "BlogPost" # GraphQL object / mutation type basename (`graphql_name` alias)
363
497
  only :title, :body, :published_at # expose only these attributes on queries
364
498
  exclude :internal_score # or `except` / `exclude`
@@ -378,7 +512,19 @@ ActiveAdmin.register Post do
378
512
  end
379
513
  ```
380
514
 
515
+ * `enable!` — include this resource when `graphql_resource_enabled_default` is
516
+ `false`. An empty `graphql` block has the same effect.
381
517
  * `disable!` — resource is not included in Query or Mutation.
518
+ * `enable_queries!` / `disable_queries!` — list/show fields and
519
+ `registered_resource` membership for this resource.
520
+ * `enable_mutations!` / `disable_mutations!` — create/update/delete mutations.
521
+ * `enable_run_actions!` / `disable_run_actions!` — batch/member/collection
522
+ action mutations.
523
+ * `enable_policies!` / `disable_policies!` — object `activeadmin_policies` and
524
+ inclusion in the `activeadmin_policies` resources matrix.
525
+ * `identity` — declare GraphQL id columns (and optional `separator`, `encode`,
526
+ `decode`) when the model has no ActiveRecord primary key and no `id` column
527
+ (RFC 0007).
382
528
  * `type_name` (or `graphql_name`) — overrides the default GraphQL type name
383
529
  derived from the model (object types, mutations, and Rails enum GraphQL
384
530
  types use this basename).