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
@@ -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
|
@@ -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(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
|
@@ -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
|
data/lib/graphql_rails/model.rb
CHANGED
@@ -17,10 +17,6 @@ module GraphqlRails
|
|
17
17
|
#
|
18
18
|
# YourModel.new.graphql_type # => type with [:id, :title] attributes
|
19
19
|
module Model
|
20
|
-
def self.included(base)
|
21
|
-
base.extend(ClassMethods)
|
22
|
-
end
|
23
|
-
|
24
20
|
# static methods for GraphqlRails::Model
|
25
21
|
module ClassMethods
|
26
22
|
def inherited(subclass)
|
@@ -36,5 +32,24 @@ module GraphqlRails
|
|
36
32
|
@graphql
|
37
33
|
end
|
38
34
|
end
|
35
|
+
|
36
|
+
def self.included(base)
|
37
|
+
base.extend(ClassMethods)
|
38
|
+
end
|
39
|
+
|
40
|
+
def graphql_context
|
41
|
+
@graphql_context
|
42
|
+
end
|
43
|
+
|
44
|
+
def graphql_context=(value)
|
45
|
+
@graphql_context = value
|
46
|
+
end
|
47
|
+
|
48
|
+
def with_graphql_context(graphql_context)
|
49
|
+
self.graphql_context = graphql_context
|
50
|
+
yield(self)
|
51
|
+
ensure
|
52
|
+
self.graphql_context = nil
|
53
|
+
end
|
39
54
|
end
|
40
55
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'graphql'
|
4
|
+
require 'graphql_rails/model/build_connection_type/count_items'
|
5
|
+
|
6
|
+
module GraphqlRails
|
7
|
+
module Model
|
8
|
+
# builds connection type from graphql type with some extra attributes
|
9
|
+
class BuildConnectionType
|
10
|
+
require 'graphql_rails/concerns/service'
|
11
|
+
|
12
|
+
include ::GraphqlRails::Service
|
13
|
+
|
14
|
+
attr_reader :initial_type
|
15
|
+
|
16
|
+
def initialize(initial_type)
|
17
|
+
@initial_type = initial_type
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
build_connection_type
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def build_connection_type
|
27
|
+
edge_type = build_edge_type
|
28
|
+
type = initial_type
|
29
|
+
Class.new(GraphQL::Types::Relay::BaseConnection) do
|
30
|
+
graphql_name("#{type.graphql_name}Connection")
|
31
|
+
edge_type(edge_type)
|
32
|
+
|
33
|
+
field :total, Integer, null: false, resolve: CountItems
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_edge_type
|
38
|
+
type = initial_type
|
39
|
+
|
40
|
+
Class.new(GraphQL::Types::Relay::BaseEdge) do
|
41
|
+
graphql_name("#{type.graphql_name}Edge")
|
42
|
+
|
43
|
+
node_type(type)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
module GraphqlRails
|
4
4
|
module Model
|
5
|
-
class
|
5
|
+
class BuildConnectionType
|
6
6
|
# Used when generating ConnectionType.
|
7
7
|
# It handles all the logic which is related with counting total items
|
8
8
|
class CountItems
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
require 'graphql_rails/concerns/service'
|
10
|
+
|
11
|
+
include ::GraphqlRails::Service
|
12
12
|
|
13
13
|
def initialize(graphql_object, _args, _ctx)
|
14
14
|
@graphql_object = graphql_object
|
@@ -7,9 +7,10 @@ module GraphqlRails
|
|
7
7
|
module Model
|
8
8
|
# contains info about single graphql attribute
|
9
9
|
class BuildEnumType
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
class InvalidEnum < GraphqlRails::Error; end
|
11
|
+
require 'graphql_rails/concerns/service'
|
12
|
+
|
13
|
+
include ::GraphqlRails::Service
|
13
14
|
|
14
15
|
def initialize(name, allowed_values:, description: nil)
|
15
16
|
@name = name
|
@@ -18,22 +19,50 @@ module GraphqlRails
|
|
18
19
|
end
|
19
20
|
|
20
21
|
def call
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
validate
|
23
|
+
build_enum
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
attr_reader :name, :allowed_values, :description
|
29
|
+
|
30
|
+
def validate
|
31
|
+
return if allowed_values.is_a?(Array) && !allowed_values.empty?
|
32
|
+
|
33
|
+
validate_enum_type
|
34
|
+
validate_enum_content
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_enum_type
|
38
|
+
return if allowed_values.is_a?(Array)
|
39
|
+
|
40
|
+
raise InvalidEnum, "Enum must be instance of Array, but instance of #{allowed_values.class} was given"
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_enum_content
|
44
|
+
return unless allowed_values.empty?
|
45
|
+
|
46
|
+
raise InvalidEnum, 'At lest one enum option must be given'
|
47
|
+
end
|
48
|
+
|
49
|
+
def formatted_name
|
50
|
+
name.to_s.camelize
|
51
|
+
end
|
24
52
|
|
53
|
+
def build_enum(allowed_values: self.allowed_values, enum_name: formatted_name, enum_description: description)
|
25
54
|
Class.new(GraphQL::Schema::Enum) do
|
26
55
|
allowed_values.each do |allowed_value|
|
27
56
|
graphql_name(enum_name)
|
28
57
|
description(enum_description) if enum_description
|
29
58
|
value(allowed_value.to_s.underscore.upcase, value: allowed_value)
|
30
59
|
end
|
60
|
+
|
61
|
+
def self.inspect
|
62
|
+
"#{GraphQL::Schema::Enum}(#{graphql_name})"
|
63
|
+
end
|
31
64
|
end
|
32
65
|
end
|
33
|
-
|
34
|
-
protected
|
35
|
-
|
36
|
-
attr_reader :name, :allowed_values, :description
|
37
66
|
end
|
38
67
|
end
|
39
68
|
end
|