activeadmin-graphql 0.3.0 → 0.3.2

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/docs/changelogs/20260818111600_rfc-0002-problems.md +25 -0
  4. data/docs/changelogs/20260818114000_rfc-as-built-coverage.md +27 -0
  5. data/docs/changelogs/20260818114700_rfc-design-not-version.md +27 -0
  6. data/docs/changelogs/20260907141000_advisory-lockfile-refresh.md +21 -0
  7. data/docs/changelogs/20260921103000_mutation-attribute-allowlists.md +24 -0
  8. data/docs/changelogs/20260921111000_release-0-3-0.md +42 -0
  9. data/docs/changelogs/20260921112800_drop-standard-rubocop-direct.md +35 -0
  10. data/docs/changelogs/20260921120000_graphql-floor-and-activeadmin-4.md +41 -0
  11. data/docs/changelogs/20261010101500_nonconventional-id-fields.md +48 -0
  12. data/docs/changelogs/20261010103800_release-0-3-1.md +39 -0
  13. data/docs/changelogs/20261010110600_graphql-identity-dsl.md +34 -0
  14. data/docs/changelogs/20261010111800_release-0-3-2.md +46 -0
  15. data/docs/dependencies/20260907141000_json-cve-2026-71847.md +68 -0
  16. data/docs/dependencies/20260907141000_sqlite3-ghsa-mwm8-39rw-8826.md +62 -0
  17. data/docs/dependencies/20260921114000_rails-json-parse-kwargs.md +61 -0
  18. data/docs/dependencies/20260921114500_graphql-ghsa-rmxg-5p3r-j6hh.md +69 -0
  19. data/docs/dependencies/20261007161308_association-list-combobox.md +23 -0
  20. data/docs/graphql-api.md +49 -6
  21. data/docs/issues/20260907141000_engineering-and-dependency-audit.md +30 -0
  22. data/docs/issues/20260917123253_ci-drop-automatic-dependency-audit.md +29 -0
  23. data/docs/issues/20260921103000_mutation-attribute-allowlists.md +40 -0
  24. data/docs/issues/20260921104400_convention-alignment-audit.md +60 -0
  25. data/docs/issues/20260921114500_dependency-audit.md +90 -0
  26. data/docs/issues/20260921115500_file-length-metrics.md +31 -0
  27. data/docs/issues/20261010111159_rails-composite-primary-keys-alignment.md +81 -0
  28. data/docs/issues/20261010111300_partitions-and-shards-support.md +55 -0
  29. data/docs/issues/20261010111500_graphql-federation-support.md +46 -0
  30. data/docs/plan/20260817201500_rfc-starter.md +31 -0
  31. data/lib/active_admin/graphql/policy_set_cache.rb +10 -3
  32. data/lib/active_admin/graphql/record_source.rb +3 -2
  33. data/lib/active_admin/graphql/resource_config.rb +4 -0
  34. data/lib/active_admin/graphql/resource_definition_dsl.rb +9 -0
  35. data/lib/active_admin/graphql/resource_identity.rb +98 -0
  36. data/lib/active_admin/graphql/resource_methods.rb +1 -1
  37. data/lib/active_admin/graphql/resource_query_proxy/identity.rb +38 -0
  38. data/lib/active_admin/graphql/resource_query_proxy.rb +16 -10
  39. data/lib/active_admin/graphql/schema_builder/mutation_update_destroy.rb +2 -2
  40. data/lib/active_admin/graphql/schema_builder/query_type_member.rb +4 -2
  41. data/lib/active_admin/graphql/schema_builder/types_object.rb +23 -4
  42. data/lib/active_admin/graphql/version.rb +1 -1
  43. data/lib/active_admin/graphql.rb +1 -0
  44. data/lib/active_admin/primary_key.rb +13 -4
  45. metadata +30 -1
@@ -0,0 +1,46 @@
1
+ # GraphQL Federation support
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov
6
+
7
+ ## Decisions
8
+
9
+ activeadmin-graphql does not implement GraphQL Federation (Apollo Federation
10
+ v1/v2, subgraph/supergraph, `@key` entities, `_entities` / `_service`). The
11
+ product is one graphql-ruby schema per ActiveAdmin namespace for that admin
12
+ HTTP endpoint.
13
+
14
+ RFC 0007 `graphql { identity ... }` and `ActiveAdminResource.id` are record
15
+ lookup identity inside that schema. They are not federation entity keys and
16
+ do not emit federation SDL directives.
17
+
18
+ ## Effects
19
+
20
+ Searched lib, docs, rfcs, spec, and the gemspec for federation, subgraph,
21
+ supergraph, Apollo, `@key`, shareable, apollo-federation, `_entities`, and
22
+ `_service`. No product hits.
23
+
24
+ Runtime dependencies are `activeadmin` and `graphql` only. Schema build is
25
+ `Class.new(::GraphQL::Schema)` with query, mutation, dataloader, and optional
26
+ visibility (`lib/active_admin/graphql/schema_builder/build.rb`).
27
+ `ResourceInterface` is a local interface for fragment reuse, not a federation
28
+ entity type.
29
+
30
+ An app could in principle call `graphql_configure_schema` on the built schema
31
+ and layer a federation library itself. That path is neither documented nor
32
+ tested here.
33
+
34
+ ## Next
35
+
36
+ Keep federation out of scope until a host names a concrete subgraph contract
37
+ and an RFC. Prefer documenting “single-schema admin API; federation
38
+ unsupported” if readers keep asking.
39
+
40
+ ## Source
41
+
42
+ activeadmin-graphql.gemspec
43
+ lib/active_admin/graphql/schema_builder/build.rb
44
+ lib/active_admin/graphql/resource_interface.rb
45
+ rfcs/0007-graphql-identity.md
46
+ docs/issues/20261010111300_partitions-and-shards-support.md
@@ -0,0 +1,31 @@
1
+ # RFC starter pack
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov
6
+
7
+ ## Decisions
8
+
9
+ Pilot the portable RFC form: README, template, RFC 0001 Proposed, RFC 0002 Informational positioning, ids/0001 and ids/0002, CONTRIBUTING and GOVERNANCE pointers.
10
+
11
+ Isolation is off. Sequential numbering. An RFC specifies a design. Version numbers belong in changelogs. Shipped design specified after the fact is Stable.
12
+
13
+ ## Effects
14
+
15
+ Starter files written on plan/rfc-process. Process RFC remains Proposed until 2026-08-31.
16
+
17
+ Later pass 2026-08-18: RFC 0002 marked as positioning. Types and statuses cite rfcs/README.md. Relates dropped from RFC 0002. Process text stays aligned with kiskolabs/pray RFC 0001 until that RFC is Stable.
18
+
19
+ Later pass 2026-08-18: RFC 0002 rewritten with concrete problems, shipped solutions, and which surfaces need a later Standards Track RFC. RFC 0001 Motivation names those surfaces.
20
+
21
+ Later pass 2026-08-18: as-built Informational RFCs added for each public contract named in RFC 0002. Describes stays on the current version. Status is Stable. RFC 0002 Relates those documents.
22
+
23
+ Later pass 2026-08-18: product RFCs specify a design. Suggestion, motivation, specification, effects, alternatives, and prior art. Version numbers belong in changelogs. RFC 0002 stays Informational. RFC 0003 and later are Standards Track Stable. Describes dropped.
24
+
25
+ ## Next
26
+
27
+ Discuss RFC 0001. Extract amkisko/rfc-process after kiskolabs/pray RFC 0001 is Stable. Replace this hand-copied pack if the prayer differs.
28
+
29
+ ## Source
30
+
31
+ Upstream: kiskolabs/pray rfcs/0001-rfc-process.md and usr/docs/plan/20260817200400_library-rfc-adoption.md.
@@ -34,7 +34,7 @@ module ActiveAdmin
34
34
 
35
35
  def cache_key(subject_owner, subject)
36
36
  owner_key = subject_owner_key(subject_owner)
37
- "#{owner_key}:#{subject_key(subject)}"
37
+ "#{owner_key}:#{subject_key(subject_owner, subject)}"
38
38
  end
39
39
 
40
40
  private
@@ -47,13 +47,20 @@ module ActiveAdmin
47
47
  end
48
48
  end
49
49
 
50
- def subject_key(subject)
50
+ def subject_key(subject_owner, subject)
51
51
  if subject.is_a?(Class)
52
52
  "class:#{subject.name}"
53
53
  elsif subject.is_a?(ActiveAdmin::Page)
54
54
  "page:#{subject.name}"
55
55
  elsif subject.is_a?(ActiveRecord::Base)
56
- "record:#{subject.class.name}:#{PrimaryKey.graphql_id_value(subject)}"
56
+ identity =
57
+ if subject_owner.respond_to?(:graphql_config)
58
+ ResourceIdentity.graphql_id_value(subject_owner, subject)
59
+ else
60
+ PrimaryKey.graphql_id_value(subject)
61
+ end
62
+ identity = "#{identity}:object:#{subject.object_id}" if subject.class.primary_key.blank?
63
+ "record:#{subject.class.name}:#{identity}"
57
64
  else
58
65
  "object:#{subject.object_id}"
59
66
  end
@@ -6,13 +6,14 @@ module ActiveAdmin
6
6
  class RecordSource < ::GraphQL::Dataloader::Source
7
7
  def initialize(model_class)
8
8
  @model_class = model_class
9
- @pk = model_class.primary_key
9
+ @pk_columns = ActiveAdmin::PrimaryKey.columns(model_class)
10
+ @pk = ActiveAdmin::PrimaryKey.composite?(model_class) ? model_class.primary_key : @pk_columns.first
10
11
  end
11
12
 
12
13
  def fetch(ids)
13
14
  keys = normalized_keys(ids)
14
15
  uniq = keys.compact.uniq
15
- return Array.new(ids.size) { nil } if uniq.empty?
16
+ return Array.new(ids.size) { nil } if uniq.empty? || @pk.nil?
16
17
 
17
18
  indexed = load_records(uniq)
18
19
  keys.map { |key| key.nil? ? nil : indexed[key] }
@@ -14,6 +14,10 @@ module ActiveAdmin
14
14
  attr_accessor :permit_params_attributes
15
15
  attr_accessor :html_permit_params_attributes
16
16
  attr_accessor :extension_block
17
+ attr_accessor :identity_columns
18
+ attr_accessor :identity_separator
19
+ attr_accessor :identity_encode_proc
20
+ attr_accessor :identity_decode_proc
17
21
 
18
22
  # Optional resolver overrides (set from +graphql do+). SchemaBuilder still owns field names,
19
23
  # arguments, and types; procs replace only the Ruby resolution body.
@@ -16,6 +16,15 @@ module ActiveAdmin
16
16
  @config.enabled = false
17
17
  end
18
18
 
19
+ # Declare GraphQL identity columns when ActiveRecord has no primary key / id column.
20
+ # See RFC 0007.
21
+ def identity(*columns, separator: nil, encode: nil, decode: nil)
22
+ @config.identity_columns = columns.flatten.map(&:to_s)
23
+ @config.identity_separator = separator
24
+ @config.identity_encode_proc = encode
25
+ @config.identity_decode_proc = decode
26
+ end
27
+
19
28
  def type_name(name)
20
29
  @config.graphql_type_name = name.to_s
21
30
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module ActiveAdmin
6
+ module GraphQL
7
+ # Resolves GraphQL identity columns and id encode/decode for a registered resource.
8
+ # Prefer `graphql { identity ... }` when ActiveRecord has no primary key / id column.
9
+ module ResourceIdentity
10
+ module_function
11
+
12
+ def configured?(aa_res)
13
+ aa_res.graphql_config.identity_columns.present?
14
+ end
15
+
16
+ def columns(aa_res)
17
+ configured = aa_res.graphql_config.identity_columns
18
+ return configured if configured.present?
19
+
20
+ ActiveAdmin::PrimaryKey.columns(aa_res.resource_class)
21
+ end
22
+
23
+ def graphql_id_value(aa_res, record)
24
+ cfg = aa_res.graphql_config
25
+ if (encode = cfg.identity_encode_proc)
26
+ return encode.call(record).to_s
27
+ end
28
+
29
+ cols = columns(aa_res)
30
+ return ActiveAdmin::PrimaryKey.graphql_id_value(record) unless configured?(aa_res)
31
+
32
+ encode_columns(record, cols, cfg.identity_separator)
33
+ end
34
+
35
+ def attributes_for_id(aa_res, id)
36
+ cfg = aa_res.graphql_config
37
+ if (decode = cfg.identity_decode_proc)
38
+ return stringify_attrs(decode.call(id))
39
+ end
40
+
41
+ cols = columns(aa_res)
42
+ return ActiveAdmin::PrimaryKey.find_attributes(aa_res.resource_class, id) unless configured?(aa_res)
43
+
44
+ decode_columns(cols, id, cfg.identity_separator)
45
+ end
46
+
47
+ def encode_columns(record, cols, separator)
48
+ if cols.size == 1
49
+ record.public_send(cols.first).to_s
50
+ elsif separator
51
+ cols.map { |c| record.public_send(c) }.join(separator)
52
+ else
53
+ payload = cols.each_with_object({}) { |c, memo| memo[c] = record.read_attribute(c) }
54
+ JSON.generate(payload)
55
+ end
56
+ end
57
+
58
+ def decode_columns(cols, id, separator)
59
+ if cols.size == 1
60
+ {cols.first => id.to_s}
61
+ elsif separator
62
+ parts = id.to_s.split(separator, cols.size)
63
+ raise ArgumentError, "identity id segment count mismatch" if parts.size != cols.size
64
+
65
+ cols.zip(parts).to_h
66
+ else
67
+ parsed = JSON.parse(id.to_s)
68
+ raise ArgumentError, "composite id must be a JSON object" unless parsed.is_a?(Hash)
69
+
70
+ parsed.stringify_keys.slice(*cols)
71
+ end
72
+ end
73
+
74
+ def field_kw_to_param_hash(aa_res, id:, **kw)
75
+ return ActiveAdmin::PrimaryKey.field_kw_to_param_hash(aa_res.resource_class, id: id, **kw) unless configured?(aa_res)
76
+
77
+ raise ArgumentError, "id is required" if id.blank?
78
+
79
+ {"id" => id.to_s}
80
+ end
81
+
82
+ def member_param_hash(aa_res, blob)
83
+ return ActiveAdmin::PrimaryKey.member_param_hash(aa_res.resource_class, blob) unless configured?(aa_res)
84
+
85
+ blob = blob.stringify_keys
86
+ raise ArgumentError, "id is required" if blob["id"].blank?
87
+
88
+ {"id" => blob["id"].to_s}
89
+ end
90
+
91
+ def stringify_attrs(hash)
92
+ raise ArgumentError, "identity decode must return a Hash" unless hash.is_a?(Hash)
93
+
94
+ hash.stringify_keys.transform_values { |value| value&.to_s }
95
+ end
96
+ end
97
+ end
98
+ end
@@ -22,7 +22,7 @@ module ActiveAdmin
22
22
 
23
23
  def graphql_assignable_attribute_names
24
24
  names = attributes_for_graphql.map(&:to_s)
25
- pk_cols = ActiveAdmin::PrimaryKey.columns(resource_class)
25
+ pk_cols = ActiveAdmin::GraphQL::ResourceIdentity.columns(self)
26
26
  return names if pk_cols.size > 1
27
27
 
28
28
  names - pk_cols
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ class ResourceQueryProxy
6
+ module Identity
7
+ private
8
+
9
+ def find_identity_member(controller, model, id)
10
+ attrs = ActiveAdmin::GraphQL::ResourceIdentity.attributes_for_id(@aa_resource, id)
11
+ raise ActiveRecord::RecordNotFound if attrs.blank?
12
+
13
+ records = controller.send(:scoped_collection).where(attrs).limit(2).to_a
14
+ raise_ambiguous_id!(model) if records.size > 1
15
+
16
+ records.first || raise(ActiveRecord::RecordNotFound)
17
+ end
18
+
19
+ def index_records_by_id(relation, model, primary_key)
20
+ if model.primary_key.present?
21
+ return relation.index_by { |record| record.public_send(primary_key).to_s }
22
+ end
23
+
24
+ grouped = relation.to_a.group_by { |record| record.public_send(primary_key).to_s }
25
+ if grouped.any? { |_id, matches| matches.size > 1 }
26
+ raise_ambiguous_id!(model)
27
+ end
28
+
29
+ grouped.transform_values(&:first)
30
+ end
31
+
32
+ def raise_ambiguous_id!(model)
33
+ raise ::GraphQL::ExecutionError, "ambiguous GraphQL id for #{model.name}"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -4,14 +4,14 @@ require "rack/mock"
4
4
  require "stringio"
5
5
 
6
6
  require_relative "resource_query_proxy/controller"
7
+ require_relative "resource_query_proxy/identity"
7
8
 
8
9
  module ActiveAdmin
9
10
  module GraphQL
10
- # Reuses {ResourceController} data-access behaviour (+scoped_collection+, +find_collection+,
11
- # +find_resource+, authorization scoping, Ransack, menu scopes, +sorting+, +includes+)
12
- # so GraphQL list/detail mutations align with the HTML/JSON list and member REST endpoints.
11
+ # Reuses ResourceController data access so GraphQL operations align with the REST endpoints.
13
12
  class ResourceQueryProxy
14
13
  include Controller
14
+ include Identity
15
15
 
16
16
  def initialize(aa_resource:, user:, namespace:, graph_params: {})
17
17
  @aa_resource = aa_resource
@@ -26,8 +26,13 @@ module ActiveAdmin
26
26
 
27
27
  def find_member(id)
28
28
  extra = member_route_params_for_find(id)
29
- controller_for("show", extra).send(:find_resource)
30
- rescue ActiveRecord::RecordNotFound
29
+ controller = controller_for("show", extra)
30
+ model = @aa_resource.resource_class
31
+ finder = controller.send(:method_for_find)
32
+ return controller.send(:find_resource) if model.primary_key.present? || finder != :find
33
+
34
+ find_identity_member(controller, model, extra.fetch("id"))
35
+ rescue ActiveRecord::RecordNotFound, ArgumentError
31
36
  nil
32
37
  end
33
38
 
@@ -36,16 +41,17 @@ module ActiveAdmin
36
41
  return {} if string_ids.empty?
37
42
 
38
43
  model = @aa_resource.resource_class
39
- if ActiveAdmin::PrimaryKey.composite?(model)
44
+ identity_cols = ActiveAdmin::GraphQL::ResourceIdentity.columns(@aa_resource)
45
+ if ActiveAdmin::PrimaryKey.composite?(model) || identity_cols.size > 1
40
46
  return string_ids.index_with { |identifier| find_member(identifier) }
41
47
  end
42
48
 
43
49
  controller = controller_for("index")
44
50
  relation = controller.send(:apply_authorization_scope, controller.send(:scoped_collection))
45
- primary_key = model.primary_key.to_sym
46
- indexed = relation.where(primary_key => string_ids).index_by do |record|
47
- record.public_send(primary_key).to_s
48
- end
51
+ primary_key = identity_cols.first&.to_sym
52
+ return string_ids.index_with { nil } if primary_key.nil?
53
+
54
+ indexed = index_records_by_id(relation.where(primary_key => string_ids), model, primary_key)
49
55
  string_ids.index_with { |identifier| indexed[identifier] }
50
56
  end
51
57
 
@@ -24,7 +24,7 @@ module ActiveAdmin
24
24
  blob = where.to_h.stringify_keys
25
25
  graph = sb.graph_params_from_find_blob(aa_res, blob)
26
26
  begin
27
- rid = ActiveAdmin::PrimaryKey.member_param_hash(model, blob)
27
+ rid = ActiveAdmin::GraphQL::ResourceIdentity.member_param_hash(aa_res, blob)
28
28
  rescue ArgumentError => e
29
29
  raise ::GraphQL::ExecutionError, e.message
30
30
  end
@@ -90,7 +90,7 @@ module ActiveAdmin
90
90
  blob = where.to_h.stringify_keys
91
91
  graph = sb.graph_params_from_find_blob(aa_res, blob)
92
92
  begin
93
- rid = ActiveAdmin::PrimaryKey.member_param_hash(model, blob)
93
+ rid = ActiveAdmin::GraphQL::ResourceIdentity.member_param_hash(aa_res, blob)
94
94
  rescue ArgumentError => e
95
95
  raise ::GraphQL::ExecutionError, e.message
96
96
  end
@@ -51,10 +51,12 @@ module ActiveAdmin
51
51
  if where
52
52
  blob = where.to_h.stringify_keys
53
53
  graph = builder.graph_params_from_find_blob(aa_res, blob)
54
- rid = ActiveAdmin::PrimaryKey.member_param_hash(model, blob)
54
+ rid = ActiveAdmin::GraphQL::ResourceIdentity.member_param_hash(aa_res, blob)
55
55
  else
56
56
  graph = builder.graph_params_for_mutation(aa_res, kw)
57
- rid = ActiveAdmin::PrimaryKey.field_kw_to_param_hash(model, id: id, **kw)
57
+ rid = ActiveAdmin::GraphQL::ResourceIdentity.field_kw_to_param_hash(
58
+ aa_res, id: id, **kw
59
+ )
58
60
  end
59
61
  rescue ArgumentError => e
60
62
  raise ::GraphQL::ExecutionError, e.message
@@ -82,13 +82,32 @@ module ActiveAdmin
82
82
  define_singleton_method(:activeadmin_graphql_resource) { aa_res }
83
83
  end
84
84
 
85
- pk_cols = ActiveAdmin::PrimaryKey.columns(model)
85
+ pk_cols = ActiveAdmin::GraphQL::ResourceIdentity.columns(aa_res)
86
+ if pk_cols.empty?
87
+ raise ArgumentError,
88
+ "#{model.name} has no primary key for GraphQL `id`; set self.primary_key on the model " \
89
+ "or graphql { identity ... }"
90
+ end
86
91
 
87
92
  type_class.field :id, ::GraphQL::Types::ID, null: false, authorize: false
88
- type_class.define_method(:id) { ActiveAdmin::PrimaryKey.graphql_id_value(object) }
93
+ type_class.define_method(:id) do
94
+ ActiveAdmin::GraphQL::ResourceIdentity.graphql_id_value(
95
+ self.class.activeadmin_graphql_resource,
96
+ object
97
+ )
98
+ end
89
99
 
90
- attr_names.each do |name|
91
- next if pk_cols.include?(name) && pk_cols.size == 1
100
+ # ActiveAdmin omits a single-column primary key from resource_attributes; still expose
101
+ # non-id key columns as scalars (composite keys already remain in attributes).
102
+ # ActiveAdminResource owns GraphQL `id`; never emit a second field from an `id` column.
103
+ configured_pk_cols = pk_cols
104
+ if (only = aa_res.graphql_config.only_attributes)
105
+ configured_pk_cols &= only.map(&:to_s)
106
+ end
107
+ configured_pk_cols -= aa_res.graphql_config.exclude_attributes.map(&:to_s)
108
+ readable_names = (attr_names + configured_pk_cols).uniq
109
+ readable_names.each do |name|
110
+ next if name == "id"
92
111
 
93
112
  col = cols_by_name[name]
94
113
  next unless col
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveAdmin
4
4
  module GraphQL
5
- VERSION = "0.3.0"
5
+ VERSION = "0.3.2"
6
6
  end
7
7
  end
@@ -71,6 +71,7 @@ module ActiveAdmin
71
71
  graphql/schema_field
72
72
  graphql/auth_context
73
73
  graphql/record_source
74
+ graphql/resource_identity
74
75
  graphql/resource_query_proxy
75
76
  graphql/run_action_payload
76
77
  graphql/run_action_mutation_config
@@ -14,13 +14,22 @@ unless defined?(ActiveAdmin::PrimaryKey)
14
14
  end
15
15
 
16
16
  def ordered_columns(model)
17
- Array(model.primary_key).map(&:to_s)
18
- end
17
+ cols = case (pk = model.primary_key)
18
+ when nil, false then []
19
+ when Array then pk.map(&:to_s).reject(&:empty?)
20
+ else
21
+ (name = pk.to_s).empty? ? [] : [name]
22
+ end
23
+ return cols if cols.any?
24
+ return %w[id] if model.respond_to?(:columns_hash) && model.columns_hash.key?("id")
19
25
 
20
- def columns(model)
21
- ordered_columns(model)
26
+ []
27
+ rescue ActiveRecord::StatementInvalid, ActiveRecord::ConnectionNotEstablished
28
+ []
22
29
  end
23
30
 
31
+ def columns(model) = ordered_columns(model)
32
+
24
33
  def composite_attribute_hash(model, id_param)
25
34
  cols = ordered_columns(model)
26
35
  hash = normalize_composite_input(cols, id_param)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeadmin-graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -366,7 +366,34 @@ files:
366
366
  - README.md
367
367
  - activeadmin-graphql.gemspec
368
368
  - app/controllers/active_admin/graphql_controller.rb
369
+ - docs/changelogs/20260818111600_rfc-0002-problems.md
370
+ - docs/changelogs/20260818114000_rfc-as-built-coverage.md
371
+ - docs/changelogs/20260818114700_rfc-design-not-version.md
372
+ - docs/changelogs/20260907141000_advisory-lockfile-refresh.md
373
+ - docs/changelogs/20260921103000_mutation-attribute-allowlists.md
374
+ - docs/changelogs/20260921111000_release-0-3-0.md
375
+ - docs/changelogs/20260921112800_drop-standard-rubocop-direct.md
376
+ - docs/changelogs/20260921120000_graphql-floor-and-activeadmin-4.md
377
+ - docs/changelogs/20261010101500_nonconventional-id-fields.md
378
+ - docs/changelogs/20261010103800_release-0-3-1.md
379
+ - docs/changelogs/20261010110600_graphql-identity-dsl.md
380
+ - docs/changelogs/20261010111800_release-0-3-2.md
381
+ - docs/dependencies/20260907141000_json-cve-2026-71847.md
382
+ - docs/dependencies/20260907141000_sqlite3-ghsa-mwm8-39rw-8826.md
383
+ - docs/dependencies/20260921114000_rails-json-parse-kwargs.md
384
+ - docs/dependencies/20260921114500_graphql-ghsa-rmxg-5p3r-j6hh.md
385
+ - docs/dependencies/20261007161308_association-list-combobox.md
369
386
  - docs/graphql-api.md
387
+ - docs/issues/20260907141000_engineering-and-dependency-audit.md
388
+ - docs/issues/20260917123253_ci-drop-automatic-dependency-audit.md
389
+ - docs/issues/20260921103000_mutation-attribute-allowlists.md
390
+ - docs/issues/20260921104400_convention-alignment-audit.md
391
+ - docs/issues/20260921114500_dependency-audit.md
392
+ - docs/issues/20260921115500_file-length-metrics.md
393
+ - docs/issues/20261010111159_rails-composite-primary-keys-alignment.md
394
+ - docs/issues/20261010111300_partitions-and-shards-support.md
395
+ - docs/issues/20261010111500_graphql-federation-support.md
396
+ - docs/plan/20260817201500_rfc-starter.md
370
397
  - lib/active_admin/graphql.rb
371
398
  - lib/active_admin/graphql/auth_context.rb
372
399
  - lib/active_admin/graphql/engine.rb
@@ -380,10 +407,12 @@ files:
380
407
  - lib/active_admin/graphql/record_source.rb
381
408
  - lib/active_admin/graphql/resource_config.rb
382
409
  - lib/active_admin/graphql/resource_definition_dsl.rb
410
+ - lib/active_admin/graphql/resource_identity.rb
383
411
  - lib/active_admin/graphql/resource_interface.rb
384
412
  - lib/active_admin/graphql/resource_methods.rb
385
413
  - lib/active_admin/graphql/resource_query_proxy.rb
386
414
  - lib/active_admin/graphql/resource_query_proxy/controller.rb
415
+ - lib/active_admin/graphql/resource_query_proxy/identity.rb
387
416
  - lib/active_admin/graphql/run_action_mutation_config.rb
388
417
  - lib/active_admin/graphql/run_action_mutation_dsl.rb
389
418
  - lib/active_admin/graphql/run_action_payload.rb