activeadmin-graphql 0.2.0 → 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 +4 -4
- data/CHANGELOG.md +24 -0
- data/CODE_OF_CONDUCT.md +14 -19
- data/CONTRIBUTING.md +12 -16
- data/README.md +19 -2
- data/activeadmin-graphql.gemspec +11 -7
- data/app/controllers/active_admin/graphql_controller.rb +50 -8
- data/docs/graphql-api.md +36 -11
- data/lib/active_admin/graphql/integration.rb +22 -23
- data/lib/active_admin/graphql/mutation_execution_error.rb +16 -0
- data/lib/active_admin/graphql/mutation_input_config.rb +11 -0
- data/lib/active_admin/graphql/mutation_input_definition_dsl.rb +22 -0
- data/lib/active_admin/graphql/policy_set_cache.rb +64 -0
- data/lib/active_admin/graphql/resource_config.rb +21 -2
- data/lib/active_admin/graphql/resource_definition_dsl.rb +19 -0
- data/lib/active_admin/graphql/resource_methods.rb +55 -0
- data/lib/active_admin/graphql/resource_query_proxy.rb +20 -0
- data/lib/active_admin/graphql/run_action_payload.rb +2 -1
- data/lib/active_admin/graphql/schema_builder/graph_params.rb +2 -2
- data/lib/active_admin/graphql/schema_builder/mutation_create.rb +6 -2
- data/lib/active_admin/graphql/schema_builder/mutation_update_destroy.rb +19 -9
- data/lib/active_admin/graphql/schema_builder/query_type_policies.rb +17 -5
- data/lib/active_admin/graphql/schema_builder/types_inputs.rb +24 -4
- data/lib/active_admin/graphql/schema_builder/types_object.rb +5 -2
- data/lib/active_admin/graphql/schema_builder/wire.rb +11 -1
- data/lib/active_admin/graphql/version.rb +1 -1
- data/lib/active_admin/graphql.rb +9 -1
- data/sig/active_admin/graphql.rbs +7 -0
- metadata +89 -35
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3347160c32fcf8dea19214ff0bdac83b9c9650855dd58de9035b153c37e0b708
|
|
4
|
+
data.tar.gz: 757c6bcf6d659569aee20ec9a54f094795c4c0cee4700db967a294a26779ea1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 383ff1b8d22b9ee6e6f70aa60284ac9c832d69436bf5ee10bfa9b25ce17089aad965481f0ca7149b3742e26aef24e0b2f1b24bb9b84ab26b1076b6c4d39e855a
|
|
7
|
+
data.tar.gz: 8f6b647f0201d15d2dfe4e23befad9b9c05a22b226cd9d6257678eeb36d4ef39a53de4372961e6b765fe2acc2e4bc0475f835015f24bb867a0564e58b17b4824
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
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
|
+
|
|
18
|
+
## 0.2.1 (2026-07-14)
|
|
19
|
+
|
|
20
|
+
- Add RBS type signatures to the published gem for Steep and other Ruby type checkers
|
|
21
|
+
- Add Ruby 4.0 to the supported compatibility matrix
|
|
22
|
+
- Return HTTP 400 with a clear error when the GraphQL request body or variables contain invalid JSON
|
|
23
|
+
- Return `null` for member queries when the record id does not exist
|
|
24
|
+
- Enforce read authorization on `belongs_to` association fields so denied parent records resolve as `null`
|
|
25
|
+
- Build GraphQL schemas safely when `schema_for` runs concurrently
|
|
26
|
+
|
|
3
27
|
## 0.2.0 (2026-04-29)
|
|
4
28
|
|
|
5
29
|
- Add `activeadmin_policies` GraphQL policy surfaces:
|
data/CODE_OF_CONDUCT.md
CHANGED
|
@@ -1,31 +1,26 @@
|
|
|
1
1
|
# Code of Conduct
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Pledge
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Participation is a harassment-free experience. Treat each other with respect. Keep private context private.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Expected behavior
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3
|
+
## Automation authorship
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
7
|
+
Keep pull requests small enough to review. Annotate non-obvious logic.
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
11
|
+
## How to contribute
|
|
13
12
|
|
|
14
|
-
|
|
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
|
-
|
|
15
|
+
Verify the facts before filing. Use issue templates for goals, constraints, and reproduction steps.
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
- If a reviewer suggests a change, view it as mutual aid, not criticism.
|
|
17
|
+
### Pull requests
|
|
22
18
|
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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).
|
data/activeadmin-graphql.gemspec
CHANGED
|
@@ -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.
|
|
14
|
+
spec.required_ruby_version = ">= 3.4"
|
|
15
15
|
|
|
16
16
|
repository_url = "https://github.com/amkisko/activeadmin-graphql.rb"
|
|
17
17
|
|
|
@@ -36,31 +36,35 @@ Gem::Specification.new do |spec|
|
|
|
36
36
|
].select { |f| File.file?(f) } +
|
|
37
37
|
Dir["docs/**/*.md"] +
|
|
38
38
|
Dir["app/**/*.rb"] +
|
|
39
|
-
Dir["lib/**/*.rb"]
|
|
39
|
+
Dir["lib/**/*.rb"] +
|
|
40
|
+
Dir["sig/**/*"]
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
spec.require_paths = ["lib"]
|
|
43
44
|
|
|
44
45
|
spec.add_runtime_dependency "activeadmin", ">= 3.2"
|
|
45
|
-
spec.add_runtime_dependency "graphql", ">= 2.
|
|
46
|
+
spec.add_runtime_dependency "graphql", ">= 2.6.9"
|
|
46
47
|
|
|
47
48
|
spec.add_development_dependency "appraisal", "~> 2"
|
|
48
49
|
spec.add_development_dependency "bigdecimal"
|
|
49
50
|
spec.add_development_dependency "bundler", ">= 2"
|
|
50
51
|
spec.add_development_dependency "devise", ">= 4.9"
|
|
51
|
-
spec.add_development_dependency "
|
|
52
|
+
spec.add_development_dependency "json", ">= 2.21.2", "< 3"
|
|
53
|
+
spec.add_development_dependency "parallel_tests", "~> 5.7"
|
|
52
54
|
spec.add_development_dependency "rails", ">= 6.1"
|
|
53
55
|
spec.add_development_dependency "rspec", "~> 3"
|
|
56
|
+
spec.add_development_dependency "polyrun", ">= 2.2.0"
|
|
57
|
+
spec.add_development_dependency "prosopite", "~> 2.0"
|
|
54
58
|
spec.add_development_dependency "rspec-rails", ">= 6"
|
|
59
|
+
spec.add_development_dependency "rubocop", "~> 1.91"
|
|
55
60
|
spec.add_development_dependency "rubocop-rails", "~> 2.34"
|
|
56
61
|
spec.add_development_dependency "rubocop-rspec", "~> 3.8"
|
|
57
62
|
spec.add_development_dependency "rubocop-thread_safety", "~> 0.7"
|
|
58
|
-
spec.add_development_dependency "simplecov", "~> 0.22"
|
|
59
63
|
spec.add_development_dependency "sprockets-rails", ">= 3.4"
|
|
60
|
-
spec.add_development_dependency "sqlite3", ">=
|
|
61
|
-
spec.add_development_dependency "standard", "~> 1.52"
|
|
64
|
+
spec.add_development_dependency "sqlite3", ">= 2.9.6"
|
|
62
65
|
spec.add_development_dependency "standard-custom", "~> 1.0"
|
|
63
66
|
spec.add_development_dependency "standard-performance", "~> 1.8"
|
|
64
67
|
spec.add_development_dependency "standard-rails", "~> 1.5"
|
|
65
68
|
spec.add_development_dependency "standard-rspec", "~> 0.3"
|
|
69
|
+
spec.add_development_dependency "rbs", "~> 4"
|
|
66
70
|
end
|
|
@@ -9,6 +9,10 @@ module ActiveAdmin
|
|
|
9
9
|
class GraphqlController < ApplicationController
|
|
10
10
|
protect_from_forgery with: :exception
|
|
11
11
|
|
|
12
|
+
rescue_from ActionDispatch::Http::Parameters::ParseError do
|
|
13
|
+
render json: {errors: [{message: "Invalid JSON"}]}, status: :bad_request
|
|
14
|
+
end
|
|
15
|
+
|
|
12
16
|
before_action :ensure_graphql_enabled!
|
|
13
17
|
before_action :authenticate_graphql!
|
|
14
18
|
|
|
@@ -104,6 +108,8 @@ module ActiveAdmin
|
|
|
104
108
|
|
|
105
109
|
def request_body_hash
|
|
106
110
|
json = request_body_json
|
|
111
|
+
return nil if json.nil? || json == :invalid_json
|
|
112
|
+
|
|
107
113
|
json if json.is_a?(Hash)
|
|
108
114
|
end
|
|
109
115
|
|
|
@@ -119,13 +125,21 @@ module ActiveAdmin
|
|
|
119
125
|
body.present? ? JSON.parse(body) : nil
|
|
120
126
|
end
|
|
121
127
|
rescue JSON::ParserError
|
|
122
|
-
@request_body_json =
|
|
128
|
+
@request_body_json = :invalid_json
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def invalid_request_json?
|
|
132
|
+
request_body_json == :invalid_json
|
|
123
133
|
end
|
|
124
134
|
|
|
125
135
|
def ensure_variables(raw)
|
|
126
136
|
case raw
|
|
127
137
|
when String
|
|
128
|
-
raw.
|
|
138
|
+
if raw.blank?
|
|
139
|
+
{}
|
|
140
|
+
else
|
|
141
|
+
JSON.parse(raw)
|
|
142
|
+
end
|
|
129
143
|
when Hash
|
|
130
144
|
raw
|
|
131
145
|
when ActionController::Parameters
|
|
@@ -136,25 +150,40 @@ module ActiveAdmin
|
|
|
136
150
|
{}
|
|
137
151
|
end
|
|
138
152
|
rescue JSON::ParserError
|
|
139
|
-
|
|
153
|
+
:invalid_json
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def render_invalid_json!
|
|
157
|
+
render json: {errors: [{message: "Invalid JSON"}]}, status: :bad_request
|
|
140
158
|
end
|
|
141
159
|
|
|
142
160
|
def render_multiplex(schema)
|
|
161
|
+
return render_invalid_json! if invalid_request_json?
|
|
162
|
+
|
|
143
163
|
operations = multiplex_operations
|
|
144
164
|
return render_multiplex_limit_error!(operations.size) if exceeds_multiplex_limit?(operations)
|
|
145
165
|
|
|
146
166
|
payloads = build_multiplex_payloads(operations)
|
|
167
|
+
return if performed?
|
|
168
|
+
|
|
147
169
|
results = schema.multiplex(payloads)
|
|
170
|
+
log_graphql_result(operation_name: "multiplex", error_count: results.count { |result| Array(result.to_h["errors"]).any? })
|
|
148
171
|
render json: results.map(&:to_h), status: :ok
|
|
149
172
|
end
|
|
150
173
|
|
|
151
174
|
def render_single(schema)
|
|
175
|
+
return render_invalid_json! if invalid_request_json?
|
|
176
|
+
|
|
177
|
+
variables = ensure_variables(variables_hash)
|
|
178
|
+
return render_invalid_json! if variables == :invalid_json
|
|
179
|
+
|
|
152
180
|
result = schema.execute(
|
|
153
181
|
query: query_string,
|
|
154
|
-
variables:
|
|
182
|
+
variables: variables,
|
|
155
183
|
operation_name: operation_name,
|
|
156
184
|
context: graphql_context
|
|
157
185
|
)
|
|
186
|
+
log_graphql_result(operation_name: operation_name, error_count: Array(result.to_h["errors"]).size)
|
|
158
187
|
render json: result.to_h, status: :ok
|
|
159
188
|
end
|
|
160
189
|
|
|
@@ -174,14 +203,27 @@ module ActiveAdmin
|
|
|
174
203
|
|
|
175
204
|
def build_multiplex_payloads(operations)
|
|
176
205
|
context = graphql_context
|
|
177
|
-
operations.map do |
|
|
206
|
+
operations.map do |operation|
|
|
207
|
+
variables = ensure_variables(operation[:variables])
|
|
208
|
+
if variables == :invalid_json
|
|
209
|
+
render_invalid_json!
|
|
210
|
+
return []
|
|
211
|
+
end
|
|
212
|
+
|
|
178
213
|
{
|
|
179
|
-
query:
|
|
180
|
-
variables:
|
|
181
|
-
operation_name:
|
|
214
|
+
query: operation[:query],
|
|
215
|
+
variables: variables,
|
|
216
|
+
operation_name: operation[:operation_name],
|
|
182
217
|
context: context
|
|
183
218
|
}
|
|
184
219
|
end
|
|
185
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
|
|
186
228
|
end
|
|
187
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
|
|
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
|
|
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
|
|
356
|
-
(object types, mutations, and Rails enum GraphQL
|
|
357
|
-
|
|
358
|
-
|
|
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
|
|
64
|
-
def
|
|
65
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
75
|
-
keys
|
|
76
|
+
super
|
|
76
77
|
end
|
|
77
78
|
|
|
78
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveAdmin
|
|
4
|
+
module GraphQL
|
|
5
|
+
# Request-scoped memoization for ActiveAdmin policy sets on GraphQL objects.
|
|
6
|
+
class PolicySetCache
|
|
7
|
+
class << self
|
|
8
|
+
def fetch(context, subject_owner:, subject:)
|
|
9
|
+
namespace = context[:namespace]
|
|
10
|
+
unless namespace
|
|
11
|
+
return policy_builder(context).send(
|
|
12
|
+
:build_policy_set,
|
|
13
|
+
auth: context[:auth],
|
|
14
|
+
subject_owner: subject_owner,
|
|
15
|
+
subject: subject,
|
|
16
|
+
context: context
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
cache = context[:policy_set_cache] ||= {}
|
|
21
|
+
key = cache_key(subject_owner, subject)
|
|
22
|
+
cache[key] ||= policy_builder(context).send(
|
|
23
|
+
:build_policy_set,
|
|
24
|
+
auth: context[:auth],
|
|
25
|
+
subject_owner: subject_owner,
|
|
26
|
+
subject: subject,
|
|
27
|
+
context: context
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def policy_builder(context)
|
|
32
|
+
context[:policy_schema_builder] ||= SchemaBuilder.new(context[:namespace])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def cache_key(subject_owner, subject)
|
|
36
|
+
owner_key = subject_owner_key(subject_owner)
|
|
37
|
+
"#{owner_key}:#{subject_key(subject)}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def subject_owner_key(subject_owner)
|
|
43
|
+
if subject_owner.is_a?(ActiveAdmin::Page)
|
|
44
|
+
"page-owner:#{subject_owner.name}"
|
|
45
|
+
else
|
|
46
|
+
"resource-owner:#{subject_owner.resource_class.name}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def subject_key(subject)
|
|
51
|
+
if subject.is_a?(Class)
|
|
52
|
+
"class:#{subject.name}"
|
|
53
|
+
elsif subject.is_a?(ActiveAdmin::Page)
|
|
54
|
+
"page:#{subject.name}"
|
|
55
|
+
elsif subject.is_a?(ActiveRecord::Base)
|
|
56
|
+
"record:#{subject.class.name}:#{PrimaryKey.graphql_id_value(subject)}"
|
|
57
|
+
else
|
|
58
|
+
"object:#{subject.object_id}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|