activeadmin-graphql 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '00998f8445b9a5f9a7d4a939029d6e6460527432d658711b713df1ad293abc9e'
4
- data.tar.gz: fd5f948edd2277b99e68ee384ccf7d31c8ffa8c890bf2f277837106213498652
3
+ metadata.gz: 3347160c32fcf8dea19214ff0bdac83b9c9650855dd58de9035b153c37e0b708
4
+ data.tar.gz: 757c6bcf6d659569aee20ec9a54f094795c4c0cee4700db967a294a26779ea1b
5
5
  SHA512:
6
- metadata.gz: fb9a627765be0dfb70171b6c2b9be70828e35a86fb0be2c79df237b0dbadc0559376e176b04ab6a2de49d22404c9773a4f9bee1427d1007b139dc71114afb991
7
- data.tar.gz: 7c2d075e817702fd05648cf0f6dee228938d7d09400be066f99d4d50b23eb4d301f8ee0ac1632974ea57317f8a2ee7bcee81f5282481e9c477c9e8fd312e5db1
6
+ metadata.gz: 383ff1b8d22b9ee6e6f70aa60284ac9c832d69436bf5ee10bfa9b25ce17089aad965481f0ca7149b3742e26aef24e0b2f1b24bb9b84ab26b1076b6c4d39e855a
7
+ data.tar.gz: 8f6b647f0201d15d2dfe4e23befad9b9c05a22b226cd9d6257678eeb36d4ef39a53de4372961e6b765fe2acc2e4bc0475f835015f24bb867a0564e58b17b4824
data/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.0 (2026-09-21)
6
+
7
+ - BREAKING: omit `created_at` and `updated_at` from default create and update inputs. List them on `permit_params` to keep them writable.
8
+ - BREAKING: require graphql-ruby 2.6.9 or newer (parser cache Marshal fix).
9
+ - Add `permit_params` on the per-resource `graphql` block, with nested `create` / `update` `permit_params` and `resolve` (RFC 0006).
10
+ - Seed GraphQL mutation inputs from a static HTML `permit_params` list when graphql `permit_params` is unset.
11
+ - Add `destroy_*` as an alias of `delete_*`.
12
+ - Add `graphql_name` as an alias of `type_name`.
13
+ - Put ActiveRecord validation messages on GraphQL error `extensions.errors` when create or update fails.
14
+ - Log GraphQL execute with namespace, operation name, and error count.
15
+ - Test ActiveAdmin 4.0.0.beta23 on Rails 8. Keep ActiveAdmin 3.5 on Rails 7.2.
16
+ - Add `rfcs/` starter pack: process (RFC 0001), positioning (RFC 0002), and Standards Track design RFCs 0003–0005 for HTTP endpoint, schema DSL, and authorization.
17
+
5
18
  ## 0.2.1 (2026-07-14)
6
19
 
7
20
  - Add RBS type signatures to the published gem for Steep and other Ruby type checkers
data/CODE_OF_CONDUCT.md CHANGED
@@ -1,31 +1,26 @@
1
1
  # Code of Conduct
2
2
 
3
- ## Our Pledge
3
+ ## Pledge
4
4
 
5
- We pledge to make participation in the activeadmin-graphql community a harassment-free experience for everyone. We operate on principles of mutual respect, privacy, and authentic engagement. We value substantive contributions and clarity on intentions.
5
+ Participation is a harassment-free experience. Treat each other with respect. Keep private context private.
6
6
 
7
- ## Our Standards
7
+ ## Expected behavior
8
8
 
9
- Examples of behavior that contributes to a positive environment include:
9
+ - Engage with genuine curiosity. Admit uncertainty.
10
+ - Own every line you submit, including generated code.
11
+ - Correct errors politely.
12
+ - Use language that welcomes people and respects identity and privacy.
10
13
 
11
- - Authenticity: Engaging with genuine curiosity and admitting uncertainty rather than feigning knowledge.
12
- - Responsible innovation: Taking full responsibility for any content or code contributed, whether manually written or generated by automation tools.
13
- - Gentle correction: Responding politely to errors. We view mistakes as opportunities for learning, provided they are addressed with humility.
14
- - Inclusive language: Using language that welcomes diverse perspectives and respects the privacy and identity of all participants.
14
+ ## Unacceptable behavior
15
15
 
16
- Examples of unacceptable behavior include:
16
+ - Harassment, trolling, or insulting comments, public or private.
17
+ - Using jargon or volume, including automated spam, to silence others.
18
+ - Sharing others' data or personal context without permission.
17
19
 
18
- - Harassment: Public or private harassment, trolling, or insulting comments.
19
- - Weaponized complexity: Using jargon or overwhelming volume (including automated spam) to silence others.
20
- - Publishing private information: Sharing others' data or personal context without explicit permission.
20
+ ## Automation
21
21
 
22
- ## Artificial Intelligence & Automation
23
-
24
- In accordance with our commitment to collective awareness:
25
-
26
- - Contributors are responsible for the accuracy and security of any AI-generated artifacts they submit.
27
- - "The AI wrote it" is not a valid excuse for introducing bugs, security vulnerabilities, or bias.
22
+ You remain responsible for bugs, security holes, and bias in generated artifacts you submit.
28
23
 
29
24
  ## Enforcement
30
25
 
31
- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@kiskolabs.com. All complaints will be reviewed and investigated promptly and fairly.
26
+ Report abusive or harassing behavior to contact@kiskolabs.com. Reports are reviewed and investigated.
data/CONTRIBUTING.md CHANGED
@@ -1,27 +1,23 @@
1
1
  # Contributing Guidelines
2
2
 
3
- Thank you for your interest in contributing to activeadmin-graphql.
3
+ ## Automation authorship
4
4
 
5
- ## How to Contribute
5
+ You may use AI and automation tools. You remain the author of every line you submit: review, debug, and understand the change.
6
6
 
7
- ### 1. Reporting Issues
7
+ Keep pull requests small enough to review. Annotate non-obvious logic.
8
8
 
9
- - Verify accuracy: Before posting, verify your information. Avoid generalizations.
10
- - Use structured inputs: Provide clear goals, constraints, and reproduction steps.
9
+ Never send project secrets or private context to public models.
11
10
 
12
- ### 2. Pull Request Process
11
+ ## How to contribute
13
12
 
14
- - Scope: Keep PRs focused on a single goal.
15
- - Context: Explain why the change is necessary.
16
- - Testing: Run the RSpec suite (see the README for Appraisal and parallel test commands).
13
+ ### Reporting issues
17
14
 
18
- ### 3. Review Process
15
+ Verify the facts before filing. Use issue templates for goals, constraints, and reproduction steps.
19
16
 
20
- - We encourage productive friction. Expect questions about your approach.
21
- - If a reviewer suggests a change, view it as mutual aid, not criticism.
17
+ ### Pull requests
22
18
 
23
- ### Artificial Intelligence & Automation
19
+ Keep one pull request to one goal. Explain why the change is needed. Run smoke tests and regression checks locally before opening the request.
24
20
 
25
- 1. You are the author: You act as the responsible agent for any code you submit. You must review, debug, and understand every line.
26
- 2. Manage cognitive load: Do not submit massive, unreviewed automated dumps.
27
- 3. Security: Never feed project secrets or private context into public AI models.
21
+ ### Review
22
+
23
+ Reviewers will ask about approach and trade-offs. Treat suggested changes as part of the work.
data/README.md CHANGED
@@ -2,14 +2,18 @@
2
2
 
3
3
  GraphQL HTTP API for [ActiveAdmin](https://activeadmin.info), built with [graphql-ruby](https://graphql-ruby.org). Register resources as usual, optionally add a `graphql do ... end` block, enable the endpoint per namespace, and get a schema with queries and mutations aligned with ActiveAdmin authorization and filters.
4
4
 
5
+ Design changes go through `rfcs/` (RFC 0001).
6
+
5
7
  ## Setup
6
8
 
7
9
  ```ruby
8
10
  # Gemfile (graphql-ruby is pulled in by activeadmin-graphql)
9
- gem "activeadmin"
11
+ gem "activeadmin" # 3.5, or pin 4.0.0.beta23
10
12
  gem "activeadmin-graphql"
11
13
  ```
12
14
 
15
+ ActiveAdmin 4.0 is still a prerelease. Pin it explicitly, for example `gem "activeadmin", "4.0.0.beta23"`. Bundler will not select a prerelease from this gem's `>= 3.2` floor. graphql-ruby must be 2.6.9 or newer.
16
+
13
17
  ```ruby
14
18
  # config/initializers/active_admin.rb
15
19
  ActiveAdmin.setup do |config|
@@ -38,12 +42,25 @@ rake rubocop
38
42
  rake spec
39
43
  ```
40
44
 
41
- Matrixed Rails versions use [Appraisal](https://github.com/thoughtbot/appraisal): `gemfiles/rails72.gemfile`, `rails8ruby34.gemfile`, and `rails8truffleruby.gemfile` pin Rails 7.2 / 8.1 (integration tests follow the `spec/dummy` app, which is tested from 7.2 upward). Run `bundle exec appraisal rspec` to execute RSpec in each gemfile context, or `bundle exec parallel_rspec spec` locally for faster multi-process runs on the current bundle.
45
+ Matrixed Rails versions use [Appraisal](https://github.com/thoughtbot/appraisal): `gemfiles/rails72.gemfile` pins Rails 7.2 with ActiveAdmin 3.5; `rails8ruby34.gemfile` and `rails8ruby4.gemfile` pin Rails 8.1 with ActiveAdmin 4.0.0.beta23 (integration tests follow the `spec/dummy` app). Run `bundle exec appraisal rspec` to execute RSpec in each gemfile context, or `bundle exec parallel_rspec spec` locally for faster multi-process runs on the current bundle.
42
46
 
43
47
  [Trunk](https://docs.trunk.io) config lives in `.trunk/`; CI runs `trunk` via `.github/workflows/trunk.yml`. Releases: `usr/bin/release.rb` (RuboCop, Appraisal RSpec across gemfiles, `gem build` / `gem push`, git tag, `gh release`).
44
48
 
45
49
  No local ActiveAdmin checkout is required; the dummy app depends on the published `activeadmin` gem like a normal host app.
46
50
 
51
+ ## Links
52
+
53
+ - [GitHub](https://github.com/amkisko/activeadmin-graphql.rb)
54
+ - [GitLab](https://gitlab.com/amkisko/activeadmin-graphql.rb)
55
+ - [RubyGems](https://rubygems.org/gems/activeadmin-graphql)
56
+ - [Versions Atom](https://rubygems.org/gems/activeadmin-graphql/versions.atom) (feed id `10803975489574`)
57
+ - [libraries.io](https://libraries.io/rubygems/activeadmin-graphql)
58
+ - [Deps.dev](https://deps.dev/rubygems/activeadmin-graphql)
59
+ - [SonarCloud](https://sonarcloud.io/project/overview?id=amkisko_activeadmin-graphql.rb)
60
+ - [Snyk](https://snyk.io/test/github/amkisko/activeadmin-graphql.rb)
61
+ - [Codecov](https://app.codecov.io/github/amkisko/activeadmin-graphql.rb)
62
+ - [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/amkisko/activeadmin-graphql.rb)
63
+
47
64
  ## License
48
65
 
49
66
  MIT — see [LICENSE.md](LICENSE.md).
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.description = "Exposes ActiveAdmin resources and pages as a graphql-ruby schema with an HTTP endpoint per namespace."
12
12
  spec.license = "MIT"
13
13
  spec.platform = Gem::Platform::RUBY
14
- spec.required_ruby_version = ">= 3.2"
14
+ spec.required_ruby_version = ">= 3.4"
15
15
 
16
16
  repository_url = "https://github.com/amkisko/activeadmin-graphql.rb"
17
17
 
@@ -43,27 +43,28 @@ Gem::Specification.new do |spec|
43
43
  spec.require_paths = ["lib"]
44
44
 
45
45
  spec.add_runtime_dependency "activeadmin", ">= 3.2"
46
- spec.add_runtime_dependency "graphql", ">= 2.3"
46
+ spec.add_runtime_dependency "graphql", ">= 2.6.9"
47
47
 
48
48
  spec.add_development_dependency "appraisal", "~> 2"
49
49
  spec.add_development_dependency "bigdecimal"
50
50
  spec.add_development_dependency "bundler", ">= 2"
51
51
  spec.add_development_dependency "devise", ">= 4.9"
52
- spec.add_development_dependency "parallel_tests", "~> 4.7"
52
+ spec.add_development_dependency "json", ">= 2.21.2", "< 3"
53
+ spec.add_development_dependency "parallel_tests", "~> 5.7"
53
54
  spec.add_development_dependency "rails", ">= 6.1"
54
55
  spec.add_development_dependency "rspec", "~> 3"
55
56
  spec.add_development_dependency "polyrun", ">= 2.2.0"
56
57
  spec.add_development_dependency "prosopite", "~> 2.0"
57
58
  spec.add_development_dependency "rspec-rails", ">= 6"
59
+ spec.add_development_dependency "rubocop", "~> 1.91"
58
60
  spec.add_development_dependency "rubocop-rails", "~> 2.34"
59
61
  spec.add_development_dependency "rubocop-rspec", "~> 3.8"
60
62
  spec.add_development_dependency "rubocop-thread_safety", "~> 0.7"
61
63
  spec.add_development_dependency "sprockets-rails", ">= 3.4"
62
- spec.add_development_dependency "sqlite3", ">= 1"
63
- spec.add_development_dependency "standard", "~> 1.52"
64
+ spec.add_development_dependency "sqlite3", ">= 2.9.6"
64
65
  spec.add_development_dependency "standard-custom", "~> 1.0"
65
66
  spec.add_development_dependency "standard-performance", "~> 1.8"
66
67
  spec.add_development_dependency "standard-rails", "~> 1.5"
67
68
  spec.add_development_dependency "standard-rspec", "~> 0.3"
68
- spec.add_development_dependency "rbs", "~> 3"
69
+ spec.add_development_dependency "rbs", "~> 4"
69
70
  end
@@ -167,6 +167,7 @@ module ActiveAdmin
167
167
  return if performed?
168
168
 
169
169
  results = schema.multiplex(payloads)
170
+ log_graphql_result(operation_name: "multiplex", error_count: results.count { |result| Array(result.to_h["errors"]).any? })
170
171
  render json: results.map(&:to_h), status: :ok
171
172
  end
172
173
 
@@ -182,6 +183,7 @@ module ActiveAdmin
182
183
  operation_name: operation_name,
183
184
  context: graphql_context
184
185
  )
186
+ log_graphql_result(operation_name: operation_name, error_count: Array(result.to_h["errors"]).size)
185
187
  render json: result.to_h, status: :ok
186
188
  end
187
189
 
@@ -216,5 +218,12 @@ module ActiveAdmin
216
218
  }
217
219
  end
218
220
  end
221
+
222
+ def log_graphql_result(operation_name:, error_count:)
223
+ Rails.logger.info(
224
+ "[activeadmin-graphql] namespace=#{active_admin_namespace.name} " \
225
+ "operation=#{operation_name.presence || "anonymous"} error_count=#{error_count}"
226
+ )
227
+ end
219
228
  end
220
229
  end
data/docs/graphql-api.md CHANGED
@@ -10,10 +10,12 @@ Add the extension to your application (it pulls in `activeadmin` and `graphql` a
10
10
 
11
11
  ```ruby
12
12
  # Gemfile
13
- gem "activeadmin"
13
+ gem "activeadmin" # 3.5, or pin 4.0.0.beta23
14
14
  gem "activeadmin-graphql"
15
15
  ```
16
16
 
17
+ ActiveAdmin 4.0 is still a prerelease. Pin it in the host Gemfile (`gem "activeadmin", "4.0.0.beta23"`). Bundler will not pick a prerelease from this gem's `>= 3.2` floor. graphql-ruby must be 2.6.9 or newer.
18
+
17
19
  Bundling loads `activeadmin/graphql`, which installs the Router/Resource integration and calls `ActiveAdmin::GraphQL.load!`, so graphql-ruby is loaded at boot whenever the gem is in your bundle. The schema class for a namespace is built on demand (first request or introspection) via `ActiveAdmin::GraphQL.schema_for(namespace)`.
18
20
 
19
21
  If `graphql = true` is set for a namespace but Bundler cannot satisfy the `graphql` gem, boot fails with an ActiveAdmin dependency error until you run `bundle install`.
@@ -214,9 +216,12 @@ The `Mutation` type is present when at least one operation exists: per-resource
214
216
  resource defines the corresponding batch, member, or collection actions.
215
217
 
216
218
  Object type names default to the model name (for example `Post`). Typed input
217
- objects mirror `attributes_for_graphql` (see the per-resource section below):
219
+ objects use assignable columns (see the per-resource section below):
218
220
  `PostCreateInput`, `PostUpdateInput`, `PostListFilterInput`, and `PostWhereInput`
219
- when the GraphQL name is `Post`.
221
+ when the GraphQL name is `Post`. `only` / `except` cap both object fields and
222
+ mutation keys. `permit_params` and nested `create` / `update` blocks narrow
223
+ create and update inputs without hiding query fields. Default mutation inputs
224
+ omit `created_at` and `updated_at`.
220
225
 
221
226
  ### Composite primary keys (Rails 7.1+)
222
227
 
@@ -270,9 +275,11 @@ These align with REST index parameters where possible:
270
275
  Typical names for a `Post` resource:
271
276
 
272
277
  * `create_post(input: PostCreateInput!)` — attribute fields match assignable
273
- columns (and the nested parent param when `belongs_to` is configured).
278
+ columns minus `created_at` / `updated_at` unless listed in `permit_params`
279
+ (and the nested parent param when `belongs_to` is configured).
274
280
  * `update_post(where: PostWhereInput!, input: PostUpdateInput!)`
275
- * `delete_post(where: PostWhereInput!)`
281
+ * `delete_post(where: PostWhereInput!)` — kept for existing clients.
282
+ * `destroy_post(where: PostWhereInput!)` — Rails / ActiveAdmin action name; same resolver as `delete_post`.
276
283
 
277
284
  Nested resources require the parent id on `where`, `input`, or list `filter`
278
285
  when the association is required.
@@ -339,9 +346,16 @@ Inside `ActiveAdmin.register` you can narrow the GraphQL surface:
339
346
  ActiveAdmin.register Post do
340
347
  graphql do
341
348
  disable! # omit this resource from the schema
342
- type_name "BlogPost" # GraphQL object / mutation type basename
343
- only :title, :body, :published_at # expose only these attributes
349
+ type_name "BlogPost" # GraphQL object / mutation type basename (`graphql_name` alias)
350
+ only :title, :body, :published_at # expose only these attributes on queries
344
351
  exclude :internal_score # or `except` / `exclude`
352
+ permit_params :title, :body # create and update inputs (query fields stay)
353
+ create do
354
+ permit_params :title, :body # replace the shared list for create
355
+ end
356
+ update do
357
+ permit_params :title # replace the shared list for update
358
+ end
345
359
  configure do
346
360
  # graphql-ruby field DSL on the object type class
347
361
  field :computed, GraphQL::Types::String, null: true
@@ -352,10 +366,21 @@ end
352
366
  ```
353
367
 
354
368
  * `disable!` — resource is not included in Query or Mutation.
355
- * `type_name` — overrides the default GraphQL type name derived from the model
356
- (object types, mutations, and Rails enum GraphQL types use this basename).
357
- * `only` / `except` (or `exclude`) — restrict columns on object types and
358
- assignable mutation keys (the primary key remains readable as `id`).
369
+ * `type_name` (or `graphql_name`) — overrides the default GraphQL type name
370
+ derived from the model (object types, mutations, and Rails enum GraphQL
371
+ types use this basename).
372
+ * `only` / `except` (or `exclude`) restrict columns on object types. These
373
+ names also cap mutation inputs. This is not ActiveAdmin `only:` / `except:`
374
+ for controller actions (`action_item only: :show`). The primary key remains
375
+ readable as `id`.
376
+ * `permit_params` (or `permit`) — restrict create and update input keys without
377
+ hiding query fields, same name as ActiveAdmin HTML `permit_params`. Nested
378
+ `create` / `update` blocks replace that shared list for one operation and
379
+ may set `resolve`. Names outside `only` / `except` are dropped. A static HTML
380
+ `permit_params :title, :body` list seeds GraphQL when the graphql list is
381
+ unset. A request-time HTML `permit_params` block is not copied. With no list
382
+ set, mutation inputs omit `created_at` and `updated_at`. Empty `permit_params`
383
+ yields an input with no column arguments.
359
384
  * `configure` — `field` and resolver methods are evaluated on the generated
360
385
  object type class.
361
386
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "resource_definition_dsl"
4
+ require_relative "resource_methods"
4
5
 
5
6
  module ActiveAdmin
6
7
  module GraphQL
@@ -60,36 +61,34 @@ module ActiveAdmin
60
61
  end
61
62
  end
62
63
 
63
- module ResourceMethods
64
- def graphql_config
65
- @graphql_config ||= ActiveAdmin::GraphQL::ResourceConfig.new
64
+ module ResourceDSLMethods
65
+ def graphql(&block)
66
+ if block
67
+ ActiveAdmin::GraphQL::ResourceDefinitionDSL.new(config.graphql_config).instance_exec(&block)
68
+ end
69
+ config.graphql_config
66
70
  end
67
71
 
68
- def attributes_for_graphql
69
- keys = resource_attributes.keys
70
- cfg = graphql_config
71
- if cfg.only_attributes
72
- keys &= cfg.only_attributes
72
+ def permit_params(*args, &block)
73
+ unless block
74
+ config.graphql_config.html_permit_params_attributes = flatten_html_permit_params(args)
73
75
  end
74
- keys -= cfg.exclude_attributes
75
- keys
76
+ super
76
77
  end
77
78
 
78
- def graphql_assignable_attribute_names
79
- names = attributes_for_graphql.map(&:to_s)
80
- pk_cols = ActiveAdmin::PrimaryKey.columns(resource_class)
81
- return names if pk_cols.size > 1
82
-
83
- names - pk_cols
84
- end
85
- end
79
+ private
86
80
 
87
- module ResourceDSLMethods
88
- def graphql(&block)
89
- if block
90
- ActiveAdmin::GraphQL::ResourceDefinitionDSL.new(config.graphql_config).instance_exec(&block)
81
+ def flatten_html_permit_params(args)
82
+ args.flat_map do |arg|
83
+ case arg
84
+ when Symbol, String
85
+ [arg.to_sym]
86
+ when Hash
87
+ arg.keys.map(&:to_sym)
88
+ else
89
+ []
90
+ end
91
91
  end
92
- config.graphql_config
93
92
  end
94
93
  end
95
94
 
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ module MutationExecutionError
6
+ module_function
7
+
8
+ def validation(record)
9
+ ::GraphQL::ExecutionError.new(
10
+ record.errors.full_messages.to_sentence,
11
+ extensions: {"errors" => record.errors.full_messages}
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ # Create or update input options set from +graphql { create do … }+ / +update do … }+.
6
+ class MutationInputConfig
7
+ attr_accessor :permit_params_attributes
8
+ attr_accessor :resolve_proc
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ # Nested DSL for +graphql { create do … }+ and +update do … }+, pairing
6
+ # ActiveAdmin +permit_params+ with graphql-ruby-style +resolve+.
7
+ class MutationInputDefinitionDSL
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ def permit_params(*attrs)
13
+ @config.permit_params_attributes = attrs.flatten.map(&:to_sym)
14
+ end
15
+ alias_method :permit, :permit_params
16
+
17
+ def resolve(&block)
18
+ @config.resolve_proc = block
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "run_action_mutation_config"
4
+ require_relative "mutation_input_config"
4
5
 
5
6
  module ActiveAdmin
6
7
  module GraphQL
@@ -10,14 +11,16 @@ module ActiveAdmin
10
11
  attr_accessor :graphql_type_name
11
12
  attr_accessor :only_attributes
12
13
  attr_accessor :exclude_attributes
14
+ attr_accessor :permit_params_attributes
15
+ attr_accessor :html_permit_params_attributes
13
16
  attr_accessor :extension_block
14
17
 
15
18
  # Optional resolver overrides (set from +graphql do+). SchemaBuilder still owns field names,
16
19
  # arguments, and types; procs replace only the Ruby resolution body.
17
20
  attr_accessor :resolve_index_proc
18
21
  attr_accessor :resolve_show_proc
19
- attr_accessor :resolve_create_proc
20
- attr_accessor :resolve_update_proc
22
+ attr_writer :resolve_create_proc
23
+ attr_writer :resolve_update_proc
21
24
  attr_accessor :resolve_destroy_proc
22
25
 
23
26
  # Default return type for run-action mutations (+batch+, +member+, +collection+) when a kind-specific
@@ -33,6 +36,22 @@ module ActiveAdmin
33
36
  !enabled
34
37
  end
35
38
 
39
+ def create_input
40
+ @create_input ||= MutationInputConfig.new
41
+ end
42
+
43
+ def update_input
44
+ @update_input ||= MutationInputConfig.new
45
+ end
46
+
47
+ def resolve_create_proc
48
+ @resolve_create_proc || create_input.resolve_proc
49
+ end
50
+
51
+ def resolve_update_proc
52
+ @resolve_update_proc || update_input.resolve_proc
53
+ end
54
+
36
55
  def batch_run_action
37
56
  @batch_run_action ||= RunActionMutationConfig.new
38
57
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "run_action_mutation_config"
4
4
  require_relative "run_action_mutation_dsl"
5
+ require_relative "mutation_input_definition_dsl"
5
6
 
6
7
  module ActiveAdmin
7
8
  module GraphQL
@@ -18,6 +19,7 @@ module ActiveAdmin
18
19
  def type_name(name)
19
20
  @config.graphql_type_name = name.to_s
20
21
  end
22
+ alias_method :graphql_name, :type_name
21
23
 
22
24
  def only(*attrs)
23
25
  @config.only_attributes = attrs.flatten.map(&:to_sym)
@@ -28,6 +30,23 @@ module ActiveAdmin
28
30
  end
29
31
  alias_method :exclude, :except
30
32
 
33
+ def permit_params(*attrs)
34
+ @config.permit_params_attributes = attrs.flatten.map(&:to_sym)
35
+ end
36
+ alias_method :permit, :permit_params
37
+
38
+ def create(&block)
39
+ return unless block
40
+
41
+ MutationInputDefinitionDSL.new(@config.create_input).instance_exec(&block)
42
+ end
43
+
44
+ def update(&block)
45
+ return unless block
46
+
47
+ MutationInputDefinitionDSL.new(@config.update_input).instance_exec(&block)
48
+ end
49
+
31
50
  def configure(&block)
32
51
  @config.extension_block = block
33
52
  end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveAdmin
4
+ module GraphQL
5
+ module Integration
6
+ module ResourceMethods
7
+ SERVER_OWNED_TIMESTAMP_COLUMNS = %w[created_at updated_at].freeze
8
+
9
+ def graphql_config
10
+ @graphql_config ||= ActiveAdmin::GraphQL::ResourceConfig.new
11
+ end
12
+
13
+ def attributes_for_graphql
14
+ keys = resource_attributes.keys
15
+ cfg = graphql_config
16
+ if cfg.only_attributes
17
+ keys &= cfg.only_attributes
18
+ end
19
+ keys -= cfg.exclude_attributes
20
+ keys
21
+ end
22
+
23
+ def graphql_assignable_attribute_names
24
+ names = attributes_for_graphql.map(&:to_s)
25
+ pk_cols = ActiveAdmin::PrimaryKey.columns(resource_class)
26
+ return names if pk_cols.size > 1
27
+
28
+ names - pk_cols
29
+ end
30
+
31
+ def graphql_create_attribute_names
32
+ mutation_permit_params_names(
33
+ graphql_config.create_input.permit_params_attributes || graphql_config.permit_params_attributes
34
+ )
35
+ end
36
+
37
+ def graphql_update_attribute_names
38
+ mutation_permit_params_names(
39
+ graphql_config.update_input.permit_params_attributes || graphql_config.permit_params_attributes
40
+ )
41
+ end
42
+
43
+ private
44
+
45
+ def mutation_permit_params_names(permit_list)
46
+ names = graphql_assignable_attribute_names.map(&:to_s)
47
+ permit_list = graphql_config.html_permit_params_attributes if permit_list.nil?
48
+ return names - SERVER_OWNED_TIMESTAMP_COLUMNS if permit_list.nil?
49
+
50
+ names & permit_list.map(&:to_s)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -28,9 +28,9 @@ module ActiveAdmin
28
28
  h
29
29
  end
30
30
 
31
- def assignable_slice_from_input(aa_res, input)
31
+ def assignable_slice_from_input(aa_res, input, attribute_names: nil)
32
32
  blob = input.to_h.stringify_keys
33
- names = aa_res.graphql_assignable_attribute_names.map(&:to_s)
33
+ names = (attribute_names || aa_res.graphql_assignable_attribute_names).map(&:to_s)
34
34
  if (btc = aa_res.belongs_to_config)
35
35
  names -= [btc.to_param.to_s]
36
36
  end
@@ -29,7 +29,11 @@ module ActiveAdmin
29
29
  namespace: ns,
30
30
  graph_params: sb.graph_params_from_input(aa_res, input)
31
31
  )
32
- attrs = sb.assignable_slice_from_input(aa_res, input)
32
+ attrs = sb.assignable_slice_from_input(
33
+ aa_res,
34
+ input,
35
+ attribute_names: aa_res.graphql_create_attribute_names
36
+ )
33
37
  record = if (hook = aa_res.graphql_config.resolve_create_proc)
34
38
  hook.call(
35
39
  proxy: proxy,
@@ -46,7 +50,7 @@ module ActiveAdmin
46
50
  end
47
51
 
48
52
  unless r.save
49
- raise ::GraphQL::ExecutionError, r.errors.full_messages.to_sentence
53
+ raise MutationExecutionError.validation(r)
50
54
  end
51
55
  r
52
56
  end
@@ -42,7 +42,11 @@ module ActiveAdmin
42
42
  raise ::GraphQL::ExecutionError, "not authorized to update this record"
43
43
  end
44
44
 
45
- attrs = sb.assignable_slice_from_input(aa_res, input)
45
+ attrs = sb.assignable_slice_from_input(
46
+ aa_res,
47
+ input,
48
+ attribute_names: aa_res.graphql_update_attribute_names
49
+ )
46
50
  record = if (hook = aa_res.graphql_config.resolve_update_proc)
47
51
  hook.call(
48
52
  proxy: proxy,
@@ -54,9 +58,9 @@ module ActiveAdmin
54
58
  aa_resource: aa_res
55
59
  )
56
60
  else
57
- attrs = attrs.stringify_keys.slice(*aa_res.graphql_assignable_attribute_names)
61
+ attrs = attrs.stringify_keys.slice(*aa_res.graphql_update_attribute_names)
58
62
  unless record.update(attrs)
59
- raise ::GraphQL::ExecutionError, record.errors.full_messages.to_sentence
63
+ raise MutationExecutionError.validation(record)
60
64
  end
61
65
  record
62
66
  end
@@ -66,17 +70,22 @@ module ActiveAdmin
66
70
  end
67
71
 
68
72
  def mutation_destroy_field(sb, ns, aa_res, model)
69
- fname = "delete_#{aa_res.resource_name.route_key.singularize.tr("-", "_")}"
73
+ stem = aa_res.resource_name.route_key.singularize.tr("-", "_")
74
+ delete_name = "delete_#{stem}"
75
+ destroy_name = "destroy_#{stem}"
70
76
  find_type = @find_input_types[model]
77
+ type_basename = sb.send(:graphql_type_name_for, aa_res)
71
78
 
72
79
  proc do
73
- field fname.to_sym, ::GraphQL::Types::Boolean, null: false, camelize: false,
74
- visibility: {kind: :mutation_delete, graphql_type_name: sb.send(:graphql_type_name_for, aa_res), resource: aa_res},
75
- description: "Delete #{model.name}" do
76
- argument :where, find_type, required: true, camelize: false
80
+ [delete_name, destroy_name].each do |fname|
81
+ field fname.to_sym, ::GraphQL::Types::Boolean, null: false, camelize: false,
82
+ visibility: {kind: :mutation_delete, graphql_type_name: type_basename, resource: aa_res},
83
+ description: "Destroy #{model.name}" do
84
+ argument :where, find_type, required: true, camelize: false
85
+ end
77
86
  end
78
87
 
79
- define_method(fname.to_sym) do |where:, **|
88
+ define_method(delete_name.to_sym) do |where:, **|
80
89
  auth = context[:auth]
81
90
  blob = where.to_h.stringify_keys
82
91
  graph = sb.graph_params_from_find_blob(aa_res, blob)
@@ -112,6 +121,7 @@ module ActiveAdmin
112
121
  end
113
122
  true
114
123
  end
124
+ alias_method destroy_name.to_sym, delete_name.to_sym
115
125
  end
116
126
  end
117
127
  end
@@ -6,10 +6,16 @@ module ActiveAdmin
6
6
  module TypesInputs
7
7
  private
8
8
 
9
- def define_input_assignable_arguments!(input_class, aa_res, required:)
9
+ def mark_empty_input_object!(input_class)
10
+ return if input_class.any_arguments?
11
+
12
+ input_class.has_no_arguments(true)
13
+ end
14
+
15
+ def define_input_assignable_arguments!(input_class, aa_res, required:, attribute_names:)
10
16
  model = aa_res.resource_class
11
17
  cols_by_name = model.columns.index_by(&:name)
12
- gassign = aa_res.graphql_assignable_attribute_names.map(&:to_s)
18
+ gassign = attribute_names.map(&:to_s)
13
19
  if (btc = aa_res.belongs_to_config)
14
20
  gassign -= [btc.to_param.to_s]
15
21
  end
@@ -33,12 +39,19 @@ module ActiveAdmin
33
39
  graphql_name "#{gname}CreateInput"
34
40
  description "Attributes (and nested route params) for creating #{model.name}"
35
41
 
36
- builder.send(:define_input_assignable_arguments!, self, aa_res, required: false)
42
+ builder.send(
43
+ :define_input_assignable_arguments!,
44
+ self,
45
+ aa_res,
46
+ required: false,
47
+ attribute_names: aa_res.graphql_create_attribute_names
48
+ )
37
49
 
38
50
  if btc
39
51
  argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: btc.required?, camelize: false
40
52
  end
41
53
  end
54
+ mark_empty_input_object!(klass)
42
55
  attach_input_object_visibility!(klass, "#{gname}CreateInput", aa_res, :create_input)
43
56
  klass
44
57
  end
@@ -53,12 +66,19 @@ module ActiveAdmin
53
66
  graphql_name "#{gname}UpdateInput"
54
67
  description "Partial attributes (and nested route params) for updating #{model.name}"
55
68
 
56
- builder.send(:define_input_assignable_arguments!, self, aa_res, required: false)
69
+ builder.send(
70
+ :define_input_assignable_arguments!,
71
+ self,
72
+ aa_res,
73
+ required: false,
74
+ attribute_names: aa_res.graphql_update_attribute_names
75
+ )
57
76
 
58
77
  if btc
59
78
  argument btc.to_param.to_sym, ::GraphQL::Types::ID, required: false, camelize: false
60
79
  end
61
80
  end
81
+ mark_empty_input_object!(klass)
62
82
  attach_input_object_visibility!(klass, "#{gname}UpdateInput", aa_res, :update_input)
63
83
  klass
64
84
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveAdmin
4
4
  module GraphQL
5
- VERSION = "0.2.1"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -77,6 +77,7 @@ module ActiveAdmin
77
77
  graphql/run_action_mutation_dsl
78
78
  graphql/key_value_pair_input
79
79
  graphql/policy_set_cache
80
+ graphql/mutation_execution_error
80
81
  graphql/schema_builder
81
82
  ].each { |path| require_relative path }
82
83
  end
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.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '2.3'
32
+ version: 2.6.9
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: '2.3'
39
+ version: 2.6.9
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: appraisal
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -93,20 +93,40 @@ dependencies:
93
93
  - - ">="
94
94
  - !ruby/object:Gem::Version
95
95
  version: '4.9'
96
+ - !ruby/object:Gem::Dependency
97
+ name: json
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 2.21.2
103
+ - - "<"
104
+ - !ruby/object:Gem::Version
105
+ version: '3'
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 2.21.2
113
+ - - "<"
114
+ - !ruby/object:Gem::Version
115
+ version: '3'
96
116
  - !ruby/object:Gem::Dependency
97
117
  name: parallel_tests
98
118
  requirement: !ruby/object:Gem::Requirement
99
119
  requirements:
100
120
  - - "~>"
101
121
  - !ruby/object:Gem::Version
102
- version: '4.7'
122
+ version: '5.7'
103
123
  type: :development
104
124
  prerelease: false
105
125
  version_requirements: !ruby/object:Gem::Requirement
106
126
  requirements:
107
127
  - - "~>"
108
128
  - !ruby/object:Gem::Version
109
- version: '4.7'
129
+ version: '5.7'
110
130
  - !ruby/object:Gem::Dependency
111
131
  name: rails
112
132
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +197,20 @@ dependencies:
177
197
  - - ">="
178
198
  - !ruby/object:Gem::Version
179
199
  version: '6'
200
+ - !ruby/object:Gem::Dependency
201
+ name: rubocop
202
+ requirement: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - "~>"
205
+ - !ruby/object:Gem::Version
206
+ version: '1.91'
207
+ type: :development
208
+ prerelease: false
209
+ version_requirements: !ruby/object:Gem::Requirement
210
+ requirements:
211
+ - - "~>"
212
+ - !ruby/object:Gem::Version
213
+ version: '1.91'
180
214
  - !ruby/object:Gem::Dependency
181
215
  name: rubocop-rails
182
216
  requirement: !ruby/object:Gem::Requirement
@@ -239,28 +273,14 @@ dependencies:
239
273
  requirements:
240
274
  - - ">="
241
275
  - !ruby/object:Gem::Version
242
- version: '1'
276
+ version: 2.9.6
243
277
  type: :development
244
278
  prerelease: false
245
279
  version_requirements: !ruby/object:Gem::Requirement
246
280
  requirements:
247
281
  - - ">="
248
282
  - !ruby/object:Gem::Version
249
- version: '1'
250
- - !ruby/object:Gem::Dependency
251
- name: standard
252
- requirement: !ruby/object:Gem::Requirement
253
- requirements:
254
- - - "~>"
255
- - !ruby/object:Gem::Version
256
- version: '1.52'
257
- type: :development
258
- prerelease: false
259
- version_requirements: !ruby/object:Gem::Requirement
260
- requirements:
261
- - - "~>"
262
- - !ruby/object:Gem::Version
263
- version: '1.52'
283
+ version: 2.9.6
264
284
  - !ruby/object:Gem::Dependency
265
285
  name: standard-custom
266
286
  requirement: !ruby/object:Gem::Requirement
@@ -323,14 +343,14 @@ dependencies:
323
343
  requirements:
324
344
  - - "~>"
325
345
  - !ruby/object:Gem::Version
326
- version: '3'
346
+ version: '4'
327
347
  type: :development
328
348
  prerelease: false
329
349
  version_requirements: !ruby/object:Gem::Requirement
330
350
  requirements:
331
351
  - - "~>"
332
352
  - !ruby/object:Gem::Version
333
- version: '3'
353
+ version: '4'
334
354
  description: Exposes ActiveAdmin resources and pages as a graphql-ruby schema with
335
355
  an HTTP endpoint per namespace.
336
356
  email:
@@ -352,12 +372,16 @@ files:
352
372
  - lib/active_admin/graphql/engine.rb
353
373
  - lib/active_admin/graphql/integration.rb
354
374
  - lib/active_admin/graphql/key_value_pair_input.rb
375
+ - lib/active_admin/graphql/mutation_execution_error.rb
376
+ - lib/active_admin/graphql/mutation_input_config.rb
377
+ - lib/active_admin/graphql/mutation_input_definition_dsl.rb
355
378
  - lib/active_admin/graphql/policy_set_cache.rb
356
379
  - lib/active_admin/graphql/railtie.rb
357
380
  - lib/active_admin/graphql/record_source.rb
358
381
  - lib/active_admin/graphql/resource_config.rb
359
382
  - lib/active_admin/graphql/resource_definition_dsl.rb
360
383
  - lib/active_admin/graphql/resource_interface.rb
384
+ - lib/active_admin/graphql/resource_methods.rb
361
385
  - lib/active_admin/graphql/resource_query_proxy.rb
362
386
  - lib/active_admin/graphql/resource_query_proxy/controller.rb
363
387
  - lib/active_admin/graphql/run_action_mutation_config.rb
@@ -407,7 +431,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
407
431
  requirements:
408
432
  - - ">="
409
433
  - !ruby/object:Gem::Version
410
- version: '3.2'
434
+ version: '3.4'
411
435
  required_rubygems_version: !ruby/object:Gem::Requirement
412
436
  requirements:
413
437
  - - ">="