graphql_rails 0.8.0 → 1.0.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 +13 -0
- data/Gemfile.lock +20 -23
- data/docs/README.md +21 -5
- data/docs/_sidebar.md +3 -0
- data/docs/components/controller.md +174 -17
- data/docs/components/model.md +151 -3
- data/docs/components/routes.md +28 -0
- data/docs/getting_started/quick_start.md +9 -2
- data/docs/other_tools/query_runner.md +49 -0
- data/docs/other_tools/schema_dump.md +29 -0
- data/docs/testing/testing.md +3 -1
- data/graphql_rails.gemspec +1 -1
- data/lib/generators/graphql_rails/install_generator.rb +50 -0
- data/lib/generators/graphql_rails/templates/example_users_controller.erb +19 -0
- data/lib/generators/graphql_rails/templates/graphql_application_controller.erb +8 -0
- data/lib/generators/graphql_rails/templates/graphql_controller.erb +20 -0
- data/lib/generators/graphql_rails/templates/graphql_router.erb +19 -0
- data/lib/generators/graphql_rails/templates/graphql_router_spec.erb +18 -0
- data/lib/graphql_rails.rb +2 -0
- data/lib/graphql_rails/attributes/attributable.rb +16 -13
- data/lib/graphql_rails/attributes/attribute.rb +20 -3
- data/lib/graphql_rails/attributes/input_attribute.rb +20 -10
- data/lib/graphql_rails/attributes/input_type_parser.rb +24 -46
- data/lib/graphql_rails/attributes/type_parseable.rb +128 -0
- data/lib/graphql_rails/attributes/type_parser.rb +58 -54
- data/lib/graphql_rails/concerns/service.rb +15 -0
- data/lib/graphql_rails/controller.rb +20 -16
- data/lib/graphql_rails/controller/action.rb +10 -69
- data/lib/graphql_rails/controller/action_configuration.rb +70 -34
- data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
- data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
- data/lib/graphql_rails/controller/configuration.rb +56 -5
- data/lib/graphql_rails/controller/log_controller_action.rb +4 -4
- data/lib/graphql_rails/controller/request.rb +29 -8
- data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
- data/lib/graphql_rails/decorator/relation_decorator.rb +1 -1
- data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
- data/lib/graphql_rails/errors/execution_error.rb +6 -7
- data/lib/graphql_rails/errors/system_error.rb +14 -0
- data/lib/graphql_rails/errors/validation_error.rb +1 -5
- data/lib/graphql_rails/input_configurable.rb +47 -0
- data/lib/graphql_rails/model.rb +19 -4
- data/lib/graphql_rails/model/build_connection_type.rb +48 -0
- data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +4 -4
- data/lib/graphql_rails/model/build_enum_type.rb +39 -10
- data/lib/graphql_rails/model/build_graphql_input_type.rb +7 -3
- data/lib/graphql_rails/model/build_graphql_type.rb +23 -7
- data/lib/graphql_rails/model/call_graphql_model_method.rb +59 -0
- data/lib/graphql_rails/model/configuration.rb +2 -6
- data/lib/graphql_rails/model/input.rb +10 -6
- data/lib/graphql_rails/query_runner.rb +68 -0
- data/lib/graphql_rails/railtie.rb +10 -0
- data/lib/graphql_rails/router.rb +40 -13
- data/lib/graphql_rails/router/resource_routes_builder.rb +2 -1
- data/lib/graphql_rails/router/route.rb +25 -6
- data/lib/graphql_rails/router/schema_builder.rb +26 -11
- data/lib/graphql_rails/rspec_controller_helpers.rb +4 -2
- data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
- data/lib/graphql_rails/tasks/schema.rake +14 -0
- data/lib/graphql_rails/version.rb +1 -1
- metadata +29 -9
- data/lib/graphql_rails/controller/controller_function.rb +0 -50
- data/lib/graphql_rails/controller/format_results.rb +0 -36
@@ -4,9 +4,9 @@ module GraphqlRails
|
|
4
4
|
module Model
|
5
5
|
# stores information about model specific config, like attributes and types
|
6
6
|
class BuildGraphqlInputType
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
require 'graphql_rails/concerns/service'
|
8
|
+
|
9
|
+
include ::GraphqlRails::Service
|
10
10
|
|
11
11
|
def initialize(name:, description: nil, attributes:)
|
12
12
|
@name = name
|
@@ -26,6 +26,10 @@ module GraphqlRails
|
|
26
26
|
type_attributes.each_value do |type_attribute|
|
27
27
|
argument(*type_attribute.input_argument_args)
|
28
28
|
end
|
29
|
+
|
30
|
+
def self.inspect
|
31
|
+
"#{GraphQL::Schema::InputObject}(#{graphql_name})"
|
32
|
+
end
|
29
33
|
end
|
30
34
|
end
|
31
35
|
|
@@ -4,9 +4,12 @@ module GraphqlRails
|
|
4
4
|
module Model
|
5
5
|
# stores information about model specific config, like attributes and types
|
6
6
|
class BuildGraphqlType
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
require 'graphql_rails/concerns/service'
|
8
|
+
require 'graphql_rails/model/call_graphql_model_method'
|
9
|
+
|
10
|
+
include ::GraphqlRails::Service
|
11
|
+
|
12
|
+
PAGINATION_KEYS = %i[before after first last].freeze
|
10
13
|
|
11
14
|
def initialize(name:, description: nil, attributes:)
|
12
15
|
@name = name
|
@@ -19,19 +22,32 @@ module GraphqlRails
|
|
19
22
|
type_description = description
|
20
23
|
type_attributes = attributes
|
21
24
|
|
22
|
-
GraphQL::
|
23
|
-
|
25
|
+
Class.new(GraphQL::Schema::Object) do
|
26
|
+
graphql_name(type_name)
|
24
27
|
description(type_description)
|
25
28
|
|
26
29
|
type_attributes.each_value do |attribute|
|
27
|
-
field(*attribute.field_args)
|
30
|
+
field(*attribute.field_args) do
|
31
|
+
attribute.attributes.values.each do |arg_attribute|
|
32
|
+
argument(*arg_attribute.input_argument_args)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
define_method attribute.property do |**kwargs|
|
37
|
+
CallGraphqlModelMethod.call(
|
38
|
+
model: object,
|
39
|
+
attribute_config: attribute,
|
40
|
+
method_keyword_arguments: kwargs,
|
41
|
+
graphql_context: context
|
42
|
+
)
|
43
|
+
end
|
28
44
|
end
|
29
45
|
end
|
30
46
|
end
|
31
47
|
|
32
48
|
private
|
33
49
|
|
34
|
-
attr_reader :
|
50
|
+
attr_reader :attributes, :name, :description
|
35
51
|
end
|
36
52
|
end
|
37
53
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphqlRails
|
4
|
+
module Model
|
5
|
+
# Executes model method and adds additional meta data if needed
|
6
|
+
class CallGraphqlModelMethod
|
7
|
+
require 'graphql_rails/concerns/service'
|
8
|
+
|
9
|
+
include ::GraphqlRails::Service
|
10
|
+
|
11
|
+
PAGINATION_KEYS = %i[before after first last].freeze
|
12
|
+
|
13
|
+
def initialize(model:, method_keyword_arguments:, graphql_context:, attribute_config:)
|
14
|
+
@model = model
|
15
|
+
@method_keyword_arguments = method_keyword_arguments
|
16
|
+
@graphql_context = graphql_context
|
17
|
+
@attribute_config = attribute_config
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
with_graphql_context do
|
22
|
+
run_method
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :model, :attribute_config, :graphql_context, :method_keyword_arguments
|
29
|
+
|
30
|
+
def run_method
|
31
|
+
if custom_keyword_arguments.empty?
|
32
|
+
model.send(method_name)
|
33
|
+
else
|
34
|
+
model.send(method_name, **custom_keyword_arguments)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def method_name
|
39
|
+
attribute_config.property
|
40
|
+
end
|
41
|
+
|
42
|
+
def paginated?
|
43
|
+
attribute_config.paginated?
|
44
|
+
end
|
45
|
+
|
46
|
+
def custom_keyword_arguments
|
47
|
+
return method_keyword_arguments unless paginated?
|
48
|
+
|
49
|
+
method_keyword_arguments.except(*PAGINATION_KEYS)
|
50
|
+
end
|
51
|
+
|
52
|
+
def with_graphql_context
|
53
|
+
return yield unless model.respond_to?(:with_graphql_context)
|
54
|
+
|
55
|
+
model.with_graphql_context(graphql_context) { yield }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -5,7 +5,7 @@ require 'graphql_rails/model/build_graphql_type'
|
|
5
5
|
require 'graphql_rails/model/build_enum_type'
|
6
6
|
require 'graphql_rails/model/input'
|
7
7
|
require 'graphql_rails/model/configurable'
|
8
|
-
require 'graphql_rails/model/
|
8
|
+
require 'graphql_rails/model/build_connection_type'
|
9
9
|
|
10
10
|
module GraphqlRails
|
11
11
|
module Model
|
@@ -60,11 +60,7 @@ module GraphqlRails
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def connection_type
|
63
|
-
@connection_type ||=
|
64
|
-
graphql_type.define_connection do
|
65
|
-
field :total, types.Int, resolve: CountItems
|
66
|
-
end
|
67
|
-
end
|
63
|
+
@connection_type ||= BuildConnectionType.call(graphql_type)
|
68
64
|
end
|
69
65
|
|
70
66
|
private
|
@@ -14,6 +14,11 @@ module GraphqlRails
|
|
14
14
|
@input_name_suffix = input_name_suffix
|
15
15
|
end
|
16
16
|
|
17
|
+
def initialize_copy(other)
|
18
|
+
super
|
19
|
+
@attributes = other.instance_variable_get(:@attributes)&.transform_values(&:dup)
|
20
|
+
end
|
21
|
+
|
17
22
|
def graphql_input_type
|
18
23
|
@graphql_input_type ||= BuildGraphqlInputType.call(
|
19
24
|
name: name, description: description, attributes: attributes
|
@@ -21,12 +26,11 @@ module GraphqlRails
|
|
21
26
|
end
|
22
27
|
|
23
28
|
def attribute(attribute_name, type: nil, enum: nil, **attribute_options)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
)
|
29
|
+
input_type = attribute_type(attribute_name, type: type, enum: enum, **attribute_options)
|
30
|
+
|
31
|
+
attributes[attribute_name.to_s] = Attributes::InputAttribute.new(
|
32
|
+
attribute_name, type: input_type, **attribute_options
|
33
|
+
)
|
30
34
|
end
|
31
35
|
|
32
36
|
private
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphqlRails
|
4
|
+
# executes GraphQL queries and returns json
|
5
|
+
class QueryRunner
|
6
|
+
require 'graphql_rails/router'
|
7
|
+
require 'graphql_rails/concerns/service'
|
8
|
+
|
9
|
+
include ::GraphqlRails::Service
|
10
|
+
|
11
|
+
def initialize(group: nil, params:, schema: nil, router: nil, **schema_options)
|
12
|
+
@group = group
|
13
|
+
@graphql_schema = schema
|
14
|
+
@params = params
|
15
|
+
@schema_options = schema_options
|
16
|
+
@router = router
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
graphql_schema.execute(
|
21
|
+
params[:query],
|
22
|
+
variables: variables,
|
23
|
+
operation_name: params[:operationName],
|
24
|
+
**schema_options
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :schema_options, :params, :group
|
31
|
+
|
32
|
+
def variables
|
33
|
+
ensure_hash(params[:variables])
|
34
|
+
end
|
35
|
+
|
36
|
+
def graphql_schema
|
37
|
+
@graphql_schema ||= router_schema
|
38
|
+
end
|
39
|
+
|
40
|
+
def router
|
41
|
+
@router ||= ::GraphqlRouter
|
42
|
+
end
|
43
|
+
|
44
|
+
def router_schema
|
45
|
+
router.graphql_schema(group)
|
46
|
+
end
|
47
|
+
|
48
|
+
def ensure_hash(ambiguous_param)
|
49
|
+
if ambiguous_param.blank?
|
50
|
+
{}
|
51
|
+
elsif ambiguous_param.is_a?(String)
|
52
|
+
ensure_hash(JSON.parse(ambiguous_param))
|
53
|
+
elsif kind_of_hash?(ambiguous_param)
|
54
|
+
ambiguous_param
|
55
|
+
else
|
56
|
+
raise ArgumentError, "Unexpected parameter: #{ambiguous_param.inspect}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def kind_of_hash?(object)
|
61
|
+
return true if object.is_a?(Hash)
|
62
|
+
|
63
|
+
defined?(ActionController) &&
|
64
|
+
defined?(ActionController::Parameters) &&
|
65
|
+
object.is_a?(ActionController::Parameters)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/graphql_rails/router.rb
CHANGED
@@ -11,32 +11,39 @@ module GraphqlRails
|
|
11
11
|
# graphql router that mimics Rails.application.routes
|
12
12
|
class Router
|
13
13
|
RAW_ACTION_NAMES = %i[
|
14
|
-
rescue_from query_analyzer instrument cursor_encoder default_max_page_size
|
14
|
+
use rescue_from query_analyzer instrument cursor_encoder default_max_page_size
|
15
15
|
].freeze
|
16
16
|
|
17
17
|
def self.draw(&block)
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
new.tap do |router|
|
19
|
+
router.instance_eval(&block)
|
20
|
+
end
|
21
21
|
end
|
22
22
|
|
23
23
|
attr_reader :routes, :namespace_name, :raw_graphql_actions
|
24
24
|
|
25
|
-
def initialize(module_name: '')
|
25
|
+
def initialize(module_name: '', group_names: [])
|
26
26
|
@module_name = module_name
|
27
|
+
@group_names = group_names
|
27
28
|
@routes ||= Set.new
|
28
29
|
@raw_graphql_actions ||= []
|
29
30
|
end
|
30
31
|
|
32
|
+
def group(*group_names, &block)
|
33
|
+
scoped_router = router_with(group_names: group_names)
|
34
|
+
scoped_router.instance_eval(&block)
|
35
|
+
routes.merge(scoped_router.routes)
|
36
|
+
end
|
37
|
+
|
31
38
|
def scope(**options, &block)
|
32
39
|
full_module_name = [module_name, options[:module]].reject(&:empty?).join('/')
|
33
|
-
scoped_router =
|
40
|
+
scoped_router = router_with(module_name: full_module_name)
|
34
41
|
scoped_router.instance_eval(&block)
|
35
42
|
routes.merge(scoped_router.routes)
|
36
43
|
end
|
37
44
|
|
38
45
|
def resources(name, **options, &block)
|
39
|
-
builder_options =
|
46
|
+
builder_options = full_route_options(options)
|
40
47
|
routes_builder = ResourceRoutesBuilder.new(name, **builder_options)
|
41
48
|
routes_builder.instance_eval(&block) if block
|
42
49
|
routes.merge(routes_builder.routes)
|
@@ -56,25 +63,45 @@ module GraphqlRails
|
|
56
63
|
end
|
57
64
|
end
|
58
65
|
|
59
|
-
def graphql_schema
|
60
|
-
|
66
|
+
def graphql_schema(group = nil)
|
67
|
+
@graphql_schema ||= {}
|
68
|
+
@graphql_schema[group&.to_sym] ||= SchemaBuilder.new(
|
61
69
|
queries: routes.select(&:query?),
|
62
70
|
mutations: routes.select(&:mutation?),
|
63
|
-
raw_actions: raw_graphql_actions
|
71
|
+
raw_actions: raw_graphql_actions,
|
72
|
+
group: group
|
64
73
|
).call
|
65
74
|
end
|
66
75
|
|
76
|
+
def reload_schema
|
77
|
+
@graphql_schema = nil
|
78
|
+
end
|
79
|
+
|
67
80
|
private
|
68
81
|
|
69
|
-
attr_reader :module_name
|
82
|
+
attr_reader :module_name, :group_names
|
83
|
+
|
84
|
+
def router_with(new_router_options = {})
|
85
|
+
default_options = { module_name: module_name, group_names: group_names }
|
86
|
+
full_options = default_options.merge(new_router_options)
|
87
|
+
|
88
|
+
self.class.new(full_options)
|
89
|
+
end
|
70
90
|
|
71
91
|
def add_raw_action(name, *args, &block)
|
72
92
|
raw_graphql_actions << { name: name, args: args, block: block }
|
73
93
|
end
|
74
94
|
|
75
95
|
def build_route(route_builder, name, **options)
|
76
|
-
|
77
|
-
|
96
|
+
route_builder.new(name, full_route_options(options))
|
97
|
+
end
|
98
|
+
|
99
|
+
def full_route_options(extra_options)
|
100
|
+
extra_groups = Array(extra_options[:group]) + Array(extra_options[:groups])
|
101
|
+
extra_options = extra_options.except(:group, :groups)
|
102
|
+
groups = (group_names + extra_groups).uniq
|
103
|
+
|
104
|
+
default_route_options.merge(extra_options).merge(groups: groups)
|
78
105
|
end
|
79
106
|
|
80
107
|
def default_route_options
|
@@ -70,8 +70,9 @@ module GraphqlRails
|
|
70
70
|
end
|
71
71
|
|
72
72
|
action_options = options.merge(custom_options).merge(on: on)
|
73
|
+
controller_method_name = action.to_s.underscore
|
73
74
|
action_name = [prefix, resource_name(on), suffix_name].map(&:to_s).reject(&:empty?).join('_')
|
74
|
-
builder.new(action_name, to: "#{name}##{
|
75
|
+
builder.new(action_name, to: "#{name}##{controller_method_name}", **action_options)
|
75
76
|
end
|
76
77
|
# rubocop:enable Metrics/ParameterLists
|
77
78
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../controller/
|
3
|
+
require_relative '../controller/build_controller_action_resolver'
|
4
4
|
|
5
5
|
module GraphqlRails
|
6
6
|
class Router
|
@@ -8,10 +8,11 @@ module GraphqlRails
|
|
8
8
|
class Route
|
9
9
|
attr_reader :name, :module_name, :on, :relative_path
|
10
10
|
|
11
|
-
def initialize(name, to: '', on:, **options)
|
11
|
+
def initialize(name, to: '', on:, groups: nil, **options)
|
12
12
|
@name = name.to_s.camelize(:lower)
|
13
13
|
@module_name = options[:module].to_s
|
14
14
|
@function = options[:function]
|
15
|
+
@groups = groups
|
15
16
|
@relative_path = to
|
16
17
|
@on = on.to_sym
|
17
18
|
end
|
@@ -26,12 +27,30 @@ module GraphqlRails
|
|
26
27
|
on == :collection
|
27
28
|
end
|
28
29
|
|
29
|
-
def
|
30
|
-
|
30
|
+
def show_in_group?(group_name)
|
31
|
+
return true if groups.nil? || groups.empty?
|
32
|
+
|
33
|
+
groups.include?(group_name&.to_sym)
|
31
34
|
end
|
32
35
|
|
33
|
-
def
|
34
|
-
|
36
|
+
def field_args
|
37
|
+
options = {}
|
38
|
+
|
39
|
+
if function
|
40
|
+
options[:function] = function
|
41
|
+
else
|
42
|
+
options[:resolver] = resolver
|
43
|
+
end
|
44
|
+
|
45
|
+
[name, options]
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
attr_reader :function, :groups
|
51
|
+
|
52
|
+
def resolver
|
53
|
+
@resolver ||= Controller::BuildControllerActionResolver.call(route: self)
|
35
54
|
end
|
36
55
|
end
|
37
56
|
end
|
@@ -8,34 +8,49 @@ module GraphqlRails
|
|
8
8
|
|
9
9
|
attr_reader :queries, :mutations, :raw_actions
|
10
10
|
|
11
|
-
def initialize(queries:, mutations:, raw_actions:)
|
11
|
+
def initialize(queries:, mutations:, raw_actions:, group: nil)
|
12
12
|
@queries = queries
|
13
13
|
@mutations = mutations
|
14
14
|
@raw_actions = raw_actions
|
15
|
+
@group = group
|
15
16
|
end
|
16
17
|
|
17
18
|
def call
|
18
|
-
query_type =
|
19
|
-
mutation_type =
|
19
|
+
query_type = build_group_type('Query', queries)
|
20
|
+
mutation_type = build_group_type('Mutation', mutations)
|
20
21
|
raw = raw_actions
|
21
22
|
|
22
|
-
GraphQL::Schema
|
23
|
+
Class.new(GraphQL::Schema) do
|
23
24
|
cursor_encoder(Router::PlainCursorEncoder)
|
24
25
|
raw.each { |action| send(action[:name], *action[:args], &action[:block]) }
|
25
26
|
|
26
|
-
query(query_type)
|
27
|
-
mutation(mutation_type)
|
27
|
+
query(query_type) if query_type
|
28
|
+
mutation(mutation_type) if mutation_type
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
31
32
|
private
|
32
33
|
|
33
|
-
|
34
|
-
GraphQL::ObjectType.define do
|
35
|
-
name type_name
|
34
|
+
attr_reader :group
|
36
35
|
|
37
|
-
|
38
|
-
|
36
|
+
def build_group_type(type_name, routes)
|
37
|
+
group_name = group
|
38
|
+
group_routes = routes.select { |route| route.show_in_group?(group_name) }
|
39
|
+
return if group_routes.empty?
|
40
|
+
|
41
|
+
build_type(type_name, group_routes)
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_type(type_name, group_routes)
|
45
|
+
Class.new(GraphQL::Schema::Object) do
|
46
|
+
graphql_name(type_name)
|
47
|
+
|
48
|
+
group_routes.each do |route|
|
49
|
+
field(*route.field_args)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.inspect
|
53
|
+
"#{GraphQL::Schema::Object}(#{graphql_name})"
|
39
54
|
end
|
40
55
|
end
|
41
56
|
end
|