graphql_rails 0.8.0 → 1.2.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.
- checksums.yaml +4 -4
- data/.hound.yml +1 -0
- data/.rubocop.yml +3 -3
- data/.ruby-version +1 -1
- data/.travis.yml +2 -2
- data/CHANGELOG.md +31 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +147 -124
- data/docs/README.md +24 -8
- data/docs/_sidebar.md +3 -0
- data/docs/components/controller.md +194 -21
- data/docs/components/model.md +193 -5
- data/docs/components/routes.md +28 -0
- data/docs/getting_started/quick_start.md +10 -3
- data/docs/index.html +1 -1
- 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 +5 -5
- 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 +21 -0
- data/lib/graphql_rails.rb +2 -0
- data/lib/graphql_rails/attributes/attributable.rb +20 -21
- data/lib/graphql_rails/attributes/attribute.rb +41 -4
- data/lib/graphql_rails/attributes/attribute_name_parser.rb +4 -4
- data/lib/graphql_rails/attributes/input_attribute.rb +24 -10
- data/lib/graphql_rails/attributes/input_type_parser.rb +24 -46
- data/lib/graphql_rails/attributes/type_parseable.rb +132 -0
- data/lib/graphql_rails/attributes/type_parser.rb +58 -54
- data/lib/graphql_rails/concerns/service.rb +19 -0
- data/lib/graphql_rails/controller.rb +26 -22
- data/lib/graphql_rails/controller/action.rb +12 -67
- 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 +11 -6
- 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 -5
- 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/add_fields_to_graphql_type.rb +45 -0
- data/lib/graphql_rails/model/build_connection_type.rb +52 -0
- data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +5 -5
- data/lib/graphql_rails/model/build_enum_type.rb +39 -10
- data/lib/graphql_rails/model/build_graphql_input_type.rb +8 -4
- data/lib/graphql_rails/model/call_graphql_model_method.rb +72 -0
- data/lib/graphql_rails/model/configurable.rb +6 -2
- data/lib/graphql_rails/model/configuration.rb +11 -10
- data/lib/graphql_rails/model/find_or_build_graphql_type.rb +64 -0
- data/lib/graphql_rails/model/find_or_build_graphql_type_class.rb +46 -0
- 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 +10 -9
- data/lib/graphql_rails/router/route.rb +21 -6
- data/lib/graphql_rails/router/schema_builder.rb +30 -11
- data/lib/graphql_rails/rspec_controller_helpers.rb +6 -4
- 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 +48 -21
- data/lib/graphql_rails/controller/controller_function.rb +0 -50
- data/lib/graphql_rails/controller/format_results.rb +0 -36
- data/lib/graphql_rails/model/build_graphql_type.rb +0 -37
data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'graphql_rails/controller/request'
|
4
|
+
|
5
|
+
module GraphqlRails
|
6
|
+
class Controller
|
7
|
+
class BuildControllerActionResolver
|
8
|
+
# Resolver which includes controller specific methods.
|
9
|
+
# Used to simplify resolver build for each controller action
|
10
|
+
class ControllerActionResolver < GraphQL::Schema::Resolver
|
11
|
+
def self.controller(controller_class = nil)
|
12
|
+
@controller = controller_class if controller_class
|
13
|
+
@controller
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.controller_action_name(name = nil)
|
17
|
+
@controller_action_name = name if name
|
18
|
+
@controller_action_name
|
19
|
+
end
|
20
|
+
|
21
|
+
def resolve(**inputs)
|
22
|
+
request = Request.new(object, inputs, context)
|
23
|
+
self.class.controller.new(request).call(self.class.controller_action_name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -3,14 +3,20 @@
|
|
3
3
|
require 'active_support/core_ext/string/inflections'
|
4
4
|
require 'graphql_rails/controller/action_configuration'
|
5
5
|
require 'graphql_rails/controller/action_hook'
|
6
|
+
require 'graphql_rails/errors/error'
|
6
7
|
|
7
8
|
module GraphqlRails
|
8
9
|
class Controller
|
9
10
|
# stores all graphql_rails contoller specific config
|
10
11
|
class Configuration
|
12
|
+
class InvalidActionConfiguration < GraphqlRails::Error; end
|
13
|
+
|
14
|
+
LIB_REGEXP = %r{/graphql_rails/lib/}
|
15
|
+
|
11
16
|
attr_reader :action_by_name
|
12
17
|
|
13
|
-
def initialize
|
18
|
+
def initialize(controller)
|
19
|
+
@controller = controller
|
14
20
|
@hooks = {
|
15
21
|
before: {},
|
16
22
|
after: {},
|
@@ -18,6 +24,7 @@ module GraphqlRails
|
|
18
24
|
}
|
19
25
|
|
20
26
|
@action_by_name = {}
|
27
|
+
@action_default = nil
|
21
28
|
end
|
22
29
|
|
23
30
|
def initialize_copy(other)
|
@@ -31,6 +38,12 @@ module GraphqlRails
|
|
31
38
|
end
|
32
39
|
end
|
33
40
|
|
41
|
+
def dup_with(controller:)
|
42
|
+
dup.tap do |new_config|
|
43
|
+
new_config.instance_variable_set(:@controller, controller)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
34
47
|
def action_hooks_for(hook_type, action_name)
|
35
48
|
hooks[hook_type].values.select { |hook| hook.applicable_for?(action_name) }
|
36
49
|
end
|
@@ -43,15 +56,53 @@ module GraphqlRails
|
|
43
56
|
ActionHook.new(name: hook_name, **options, &block)
|
44
57
|
end
|
45
58
|
|
59
|
+
def action_default
|
60
|
+
@action_default ||= ActionConfiguration.new(name: :default, controller: nil)
|
61
|
+
yield(@action_default) if block_given?
|
62
|
+
@action_default
|
63
|
+
end
|
64
|
+
|
46
65
|
def action(method_name)
|
47
|
-
|
48
|
-
|
49
|
-
|
66
|
+
action_name = method_name.to_s.underscore
|
67
|
+
@action_by_name[action_name] ||= action_default.dup_with(
|
68
|
+
name: action_name,
|
69
|
+
controller: controller,
|
70
|
+
defined_at: dynamic_source_location
|
71
|
+
)
|
72
|
+
yield(@action_by_name[action_name]) if block_given?
|
73
|
+
@action_by_name[action_name]
|
74
|
+
end
|
75
|
+
|
76
|
+
def action_config(method_name)
|
77
|
+
action_name = method_name.to_s.underscore
|
78
|
+
@action_by_name.fetch(action_name) { raise_invalid_config_error(action_name) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def model(model = nil)
|
82
|
+
action_default.model(model)
|
50
83
|
end
|
51
84
|
|
52
85
|
private
|
53
86
|
|
54
|
-
attr_reader :hooks
|
87
|
+
attr_reader :hooks, :controller
|
88
|
+
|
89
|
+
def dynamic_source_location
|
90
|
+
project_trace = \
|
91
|
+
caller
|
92
|
+
.dup
|
93
|
+
.drop_while { |path| !path.match?(LIB_REGEXP) }
|
94
|
+
.drop_while { |path| path.match?(LIB_REGEXP) }
|
95
|
+
|
96
|
+
project_trace.first
|
97
|
+
end
|
98
|
+
|
99
|
+
def raise_invalid_config_error(action_name)
|
100
|
+
error_message = \
|
101
|
+
"Missing action configuration for #{controller}##{action_name}. " \
|
102
|
+
"Please define it with `action(:#{action_name})`."
|
103
|
+
|
104
|
+
raise InvalidActionConfiguration, error_message
|
105
|
+
end
|
55
106
|
end
|
56
107
|
end
|
57
108
|
end
|
@@ -6,13 +6,13 @@ module GraphqlRails
|
|
6
6
|
class Controller
|
7
7
|
# logs controller start and end times
|
8
8
|
class LogControllerAction
|
9
|
+
require 'graphql_rails/concerns/service'
|
10
|
+
|
11
|
+
include ::GraphqlRails::Service
|
12
|
+
|
9
13
|
START_PROCESSING_KEY = 'start_processing.graphql_action_controller'
|
10
14
|
PROCESS_ACTION_KEY = 'process_action.graphql_action_controller'
|
11
15
|
|
12
|
-
def self.call(**kwargs, &block)
|
13
|
-
new(**kwargs).call(&block)
|
14
|
-
end
|
15
|
-
|
16
16
|
def initialize(controller_name:, action_name:, params:, graphql_request:)
|
17
17
|
@controller_name = controller_name
|
18
18
|
@action_name = action_name
|
@@ -51,8 +51,7 @@ module GraphqlRails
|
|
51
51
|
params
|
52
52
|
else
|
53
53
|
filter_options = Rails.configuration.filter_parameters
|
54
|
-
|
55
|
-
parametter_filter.filter(params)
|
54
|
+
parameter_filter_class.new(filter_options).filter(params)
|
56
55
|
end
|
57
56
|
end
|
58
57
|
|
@@ -61,6 +60,12 @@ module GraphqlRails
|
|
61
60
|
|
62
61
|
Rails.application.config.filter_parameters || []
|
63
62
|
end
|
63
|
+
|
64
|
+
def parameter_filter_class
|
65
|
+
return ActiveSupport::ParameterFilter if Object.const_defined?('ActiveSupport::ParameterFilter')
|
66
|
+
|
67
|
+
ActionDispatch::Http::ParameterFilter
|
68
|
+
end
|
64
69
|
end
|
65
70
|
end
|
66
71
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'graphql_rails/errors/execution_error'
|
4
|
-
|
5
3
|
module GraphqlRails
|
6
4
|
class Controller
|
7
5
|
# Contains all info related with single request to controller
|
8
6
|
class Request
|
7
|
+
require 'graphql_rails/controller/request/format_errors'
|
8
|
+
|
9
9
|
attr_accessor :object_to_return
|
10
10
|
attr_reader :errors, :context
|
11
11
|
|
@@ -16,12 +16,9 @@ module GraphqlRails
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def errors=(new_errors)
|
19
|
-
@errors = new_errors
|
19
|
+
@errors = FormatErrors.call(not_formatted_errors: new_errors)
|
20
20
|
|
21
|
-
|
22
|
-
error_message = error.is_a?(String) ? error : error.message
|
23
|
-
context.add_error(ExecutionError.new(error_message))
|
24
|
-
end
|
21
|
+
@errors.each { |error| context.add_error(error) }
|
25
22
|
end
|
26
23
|
|
27
24
|
def no_object_to_return?
|
@@ -29,12 +26,36 @@ module GraphqlRails
|
|
29
26
|
end
|
30
27
|
|
31
28
|
def params
|
32
|
-
inputs.to_h
|
29
|
+
deep_transform_values(inputs.to_h) do |val|
|
30
|
+
graphql_object_to_hash(val)
|
31
|
+
end
|
33
32
|
end
|
34
33
|
|
35
34
|
private
|
36
35
|
|
37
36
|
attr_reader :graphql_object, :inputs
|
37
|
+
|
38
|
+
def graphql_object_to_hash(object)
|
39
|
+
if object.is_a?(GraphQL::Dig)
|
40
|
+
object.to_h
|
41
|
+
elsif object.is_a?(Array)
|
42
|
+
object.map { |item| graphql_object_to_hash(item) }
|
43
|
+
else
|
44
|
+
object
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def deep_transform_values(hash, &block)
|
49
|
+
return hash unless hash.is_a?(Hash)
|
50
|
+
|
51
|
+
hash.transform_values do |val|
|
52
|
+
if val.is_a?(Hash)
|
53
|
+
deep_transform_values(val, &block)
|
54
|
+
else
|
55
|
+
yield(val)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
38
59
|
end
|
39
60
|
end
|
40
61
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'graphql_rails/concerns/service'
|
4
|
+
require 'graphql_rails/errors/execution_error'
|
5
|
+
require 'graphql_rails/errors/validation_error'
|
6
|
+
require 'graphql_rails/errors/custom_execution_error'
|
7
|
+
|
8
|
+
module GraphqlRails
|
9
|
+
class Controller
|
10
|
+
class Request
|
11
|
+
# Converts user provided free-form errors in to meaningfull graphql error classes
|
12
|
+
class FormatErrors
|
13
|
+
include Service
|
14
|
+
|
15
|
+
def initialize(not_formatted_errors:)
|
16
|
+
@not_formatted_errors = not_formatted_errors
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
if validation_errors?
|
21
|
+
formatted_validation_errors
|
22
|
+
else
|
23
|
+
not_formatted_errors.map { |error| format_error(error) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :not_formatted_errors
|
30
|
+
|
31
|
+
def validation_errors?
|
32
|
+
defined?(ActiveModel) &&
|
33
|
+
defined?(ActiveModel::Errors) &&
|
34
|
+
not_formatted_errors.is_a?(ActiveModel::Errors)
|
35
|
+
end
|
36
|
+
|
37
|
+
def formatted_validation_errors
|
38
|
+
not_formatted_errors.map do |field, message|
|
39
|
+
ValidationError.new(message, field)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def format_error(error)
|
44
|
+
if error.is_a?(String)
|
45
|
+
ExecutionError.new(error)
|
46
|
+
elsif error.is_a?(GraphQL::ExecutionError)
|
47
|
+
error
|
48
|
+
elsif CustomExecutionError.accepts?(error)
|
49
|
+
message = error[:message] || error['message']
|
50
|
+
CustomExecutionError.new(message, error.except(:message, 'message'))
|
51
|
+
elsif error.respond_to?(:message)
|
52
|
+
ExecutionError.new(error.message)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -37,7 +37,7 @@ module GraphqlRails
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def to_a
|
40
|
-
@to_a ||= relation.to_a.map { |it| decorator.new(it) }
|
40
|
+
@to_a ||= relation.to_a.map { |it| decorator.new(it, *decorator_args) }
|
41
41
|
end
|
42
42
|
|
43
43
|
private
|
@@ -71,9 +71,5 @@ module GraphqlRails
|
|
71
71
|
self.class.new(decorator: decorator, relation: new_relation, decorator_args: decorator_args)
|
72
72
|
end
|
73
73
|
end
|
74
|
-
|
75
|
-
GraphQL::Relay::BaseConnection.register_connection_implementation(
|
76
|
-
RelationDecorator, GraphQL::Relay::RelationConnection
|
77
|
-
)
|
78
74
|
end
|
79
75
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphqlRails
|
4
|
+
# base class which is returned in case something bad happens. Contains all error rendering tructure
|
5
|
+
class CustomExecutionError < ExecutionError
|
6
|
+
attr_reader :extra_graphql_data
|
7
|
+
|
8
|
+
def self.accepts?(error)
|
9
|
+
error.is_a?(Hash) &&
|
10
|
+
(error.key?(:message) || error.key?('message'))
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(message, extra_graphql_data = {})
|
14
|
+
super(message)
|
15
|
+
@extra_graphql_data = extra_graphql_data.stringify_keys
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_h
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -6,15 +6,14 @@ module GraphqlRails
|
|
6
6
|
# base class which is returned in case something bad happens. Contains all error rendering tructure
|
7
7
|
class ExecutionError < GraphQL::ExecutionError
|
8
8
|
def to_h
|
9
|
-
super.
|
9
|
+
super.merge(extra_graphql_data)
|
10
10
|
end
|
11
11
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
500
|
12
|
+
def extra_graphql_data
|
13
|
+
{}.tap do |data|
|
14
|
+
data['type'] = type if respond_to?(:type) && type
|
15
|
+
data['code'] = type if respond_to?(:code) && code
|
16
|
+
end
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphqlRails
|
4
|
+
# base class which is returned in case something bad happens. Contains all error rendering tructure
|
5
|
+
class SystemError < ExecutionError
|
6
|
+
def to_h
|
7
|
+
super.except('locations')
|
8
|
+
end
|
9
|
+
|
10
|
+
def type
|
11
|
+
'system_error'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -6,7 +6,7 @@ module GraphqlRails
|
|
6
6
|
attr_reader :short_message, :field
|
7
7
|
|
8
8
|
def initialize(short_message, field)
|
9
|
-
super([field.presence, short_message].compact.join(' '))
|
9
|
+
super([field.presence&.to_s&.humanize, short_message].compact.join(' '))
|
10
10
|
@short_message = short_message
|
11
11
|
@field = field
|
12
12
|
end
|
@@ -15,10 +15,6 @@ module GraphqlRails
|
|
15
15
|
'validation_error'
|
16
16
|
end
|
17
17
|
|
18
|
-
def http_status_code
|
19
|
-
422
|
20
|
-
end
|
21
|
-
|
22
18
|
def to_h
|
23
19
|
super.merge('field' => field, 'short_message' => short_message)
|
24
20
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphqlRails
|
4
|
+
# contains configuration options related with inputs
|
5
|
+
module InputConfigurable
|
6
|
+
def permit(*no_type_attributes, **typed_attributes)
|
7
|
+
no_type_attributes.each { |attribute| permit_input(attribute) }
|
8
|
+
typed_attributes.each { |attribute, type| permit_input(attribute, type: type) }
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def permit_input(name, **input_options)
|
13
|
+
field_name = name.to_s.remove(/!\Z/)
|
14
|
+
|
15
|
+
attributes[field_name] = build_input_attribute(name.to_s, **input_options)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def paginated(pagination_options = {})
|
20
|
+
pagination_options = {} if pagination_options == true
|
21
|
+
pagination_options = nil if pagination_options == false
|
22
|
+
|
23
|
+
@pagination_options = pagination_options
|
24
|
+
permit(:before, :after, first: :int, last: :int)
|
25
|
+
end
|
26
|
+
|
27
|
+
def paginated?
|
28
|
+
!pagination_options.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def pagination_options
|
32
|
+
@pagination_options
|
33
|
+
end
|
34
|
+
|
35
|
+
def input_attribute_options
|
36
|
+
@input_attribute_options || {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_input_attribute(name, options: {}, **other_options)
|
40
|
+
Attributes::InputAttribute.new(
|
41
|
+
name.to_s,
|
42
|
+
options: input_attribute_options.merge(options),
|
43
|
+
**other_options
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|