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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/Gemfile.lock +20 -23
  4. data/docs/README.md +21 -5
  5. data/docs/_sidebar.md +3 -0
  6. data/docs/components/controller.md +174 -17
  7. data/docs/components/model.md +151 -3
  8. data/docs/components/routes.md +28 -0
  9. data/docs/getting_started/quick_start.md +9 -2
  10. data/docs/other_tools/query_runner.md +49 -0
  11. data/docs/other_tools/schema_dump.md +29 -0
  12. data/docs/testing/testing.md +3 -1
  13. data/graphql_rails.gemspec +1 -1
  14. data/lib/generators/graphql_rails/install_generator.rb +50 -0
  15. data/lib/generators/graphql_rails/templates/example_users_controller.erb +19 -0
  16. data/lib/generators/graphql_rails/templates/graphql_application_controller.erb +8 -0
  17. data/lib/generators/graphql_rails/templates/graphql_controller.erb +20 -0
  18. data/lib/generators/graphql_rails/templates/graphql_router.erb +19 -0
  19. data/lib/generators/graphql_rails/templates/graphql_router_spec.erb +18 -0
  20. data/lib/graphql_rails.rb +2 -0
  21. data/lib/graphql_rails/attributes/attributable.rb +16 -13
  22. data/lib/graphql_rails/attributes/attribute.rb +20 -3
  23. data/lib/graphql_rails/attributes/input_attribute.rb +20 -10
  24. data/lib/graphql_rails/attributes/input_type_parser.rb +24 -46
  25. data/lib/graphql_rails/attributes/type_parseable.rb +128 -0
  26. data/lib/graphql_rails/attributes/type_parser.rb +58 -54
  27. data/lib/graphql_rails/concerns/service.rb +15 -0
  28. data/lib/graphql_rails/controller.rb +20 -16
  29. data/lib/graphql_rails/controller/action.rb +10 -69
  30. data/lib/graphql_rails/controller/action_configuration.rb +70 -34
  31. data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
  32. data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
  33. data/lib/graphql_rails/controller/configuration.rb +56 -5
  34. data/lib/graphql_rails/controller/log_controller_action.rb +4 -4
  35. data/lib/graphql_rails/controller/request.rb +29 -8
  36. data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
  37. data/lib/graphql_rails/decorator/relation_decorator.rb +1 -1
  38. data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
  39. data/lib/graphql_rails/errors/execution_error.rb +6 -7
  40. data/lib/graphql_rails/errors/system_error.rb +14 -0
  41. data/lib/graphql_rails/errors/validation_error.rb +1 -5
  42. data/lib/graphql_rails/input_configurable.rb +47 -0
  43. data/lib/graphql_rails/model.rb +19 -4
  44. data/lib/graphql_rails/model/build_connection_type.rb +48 -0
  45. data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +4 -4
  46. data/lib/graphql_rails/model/build_enum_type.rb +39 -10
  47. data/lib/graphql_rails/model/build_graphql_input_type.rb +7 -3
  48. data/lib/graphql_rails/model/build_graphql_type.rb +23 -7
  49. data/lib/graphql_rails/model/call_graphql_model_method.rb +59 -0
  50. data/lib/graphql_rails/model/configuration.rb +2 -6
  51. data/lib/graphql_rails/model/input.rb +10 -6
  52. data/lib/graphql_rails/query_runner.rb +68 -0
  53. data/lib/graphql_rails/railtie.rb +10 -0
  54. data/lib/graphql_rails/router.rb +40 -13
  55. data/lib/graphql_rails/router/resource_routes_builder.rb +2 -1
  56. data/lib/graphql_rails/router/route.rb +25 -6
  57. data/lib/graphql_rails/router/schema_builder.rb +26 -11
  58. data/lib/graphql_rails/rspec_controller_helpers.rb +4 -2
  59. data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
  60. data/lib/graphql_rails/tasks/schema.rake +14 -0
  61. data/lib/graphql_rails/version.rb +1 -1
  62. metadata +29 -9
  63. data/lib/graphql_rails/controller/controller_function.rb +0 -50
  64. data/lib/graphql_rails/controller/format_results.rb +0 -36
@@ -5,9 +5,13 @@ require 'graphql'
5
5
  module GraphqlRails
6
6
  module Attributes
7
7
  # converts string value in to GraphQL type
8
- class InputTypeParser < TypeParser
8
+ class InputTypeParser
9
+ require_relative './type_parseable'
10
+
11
+ include TypeParseable
12
+
9
13
  def initialize(unparsed_type, subtype:)
10
- super(unparsed_type)
14
+ @unparsed_type = unparsed_type
11
15
  @subtype = subtype
12
16
  end
13
17
 
@@ -17,68 +21,42 @@ module GraphqlRails
17
21
  partly_parsed_type || parsed_type
18
22
  end
19
23
 
20
- def nullable_type
21
- return nil if unparsed_type.nil?
22
-
23
- partly_parsed_type || parsed_nullable_type
24
- end
25
-
26
- protected
27
-
28
- def partly_parsed_type
29
- return unparsed_type if raw_graphql_type?
30
- return unparsed_type.graphql_input_type if unparsed_type.is_a?(GraphqlRails::Model::Input)
31
- end
32
-
33
- def parsed_nullable_type
34
- if list?
35
- parsed_inner_type.to_list_type
36
- else
37
- type_by_name
38
- end
39
- end
40
-
41
- def parsed_type
24
+ def input_type_arg
42
25
  if list?
43
- parsed_list_type
26
+ list_type_arg
44
27
  else
45
- parsed_inner_type
28
+ unwrapped_type
46
29
  end
47
30
  end
48
31
 
49
- def raw_graphql_type?
50
- unparsed_type.is_a?(GraphQL::InputObjectType) || super
51
- end
32
+ private
52
33
 
53
- def dynamicly_defined_type
54
- type_class = graphql_model
55
- return unless type_class
34
+ attr_reader :unparsed_type, :subtype
56
35
 
57
- type_class.graphql.input(*subtype).graphql_input_type
36
+ def unwrapped_type
37
+ raw_unwrapped_type || unwrapped_scalar_type || unwrapped_model_input_type || raise_not_supported_type_error
58
38
  end
59
39
 
60
- def parsed_list_type
61
- list_type = parsed_inner_type.to_list_type
40
+ def raw_unwrapped_type
41
+ return nil unless raw_graphql_type?
62
42
 
63
- if required_list?
64
- list_type = list_type.to_graphql if list_type.respond_to?(:to_graphql)
65
- list_type.to_non_null_type
66
- else
67
- list_type
68
- end
43
+ unwrap_type(unparsed_type)
69
44
  end
70
45
 
71
- def parsed_inner_type
46
+ def list_type_arg
72
47
  if required_inner_type?
73
- type_by_name.to_non_null_type
48
+ [unwrapped_type]
74
49
  else
75
- type_by_name
50
+ [unwrapped_type, null: true]
76
51
  end
77
52
  end
78
53
 
79
- private
54
+ def unwrapped_model_input_type
55
+ type_class = graphql_model
56
+ return unless type_class
80
57
 
81
- attr_reader :subtype
58
+ type_class.graphql.input(subtype).graphql_input_type
59
+ end
82
60
  end
83
61
  end
84
62
  end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ module Attributes
5
+ # Contains shared parsing logic.
6
+ # Expects that including class has:
7
+ # * method "unparsed_type" which might be Instance of String, Symbol, GraphQL type or so
8
+ module TypeParseable
9
+ require_relative './type_name_info'
10
+
11
+ class UnknownTypeError < ArgumentError; end
12
+
13
+ TYPE_MAPPING = {
14
+ 'id' => GraphQL::ID_TYPE,
15
+
16
+ 'int' => GraphQL::INT_TYPE,
17
+ 'integer' => GraphQL::INT_TYPE,
18
+
19
+ 'string' => GraphQL::STRING_TYPE,
20
+ 'str' => GraphQL::STRING_TYPE,
21
+ 'text' => GraphQL::STRING_TYPE,
22
+ 'time' => GraphQL::STRING_TYPE,
23
+ 'date' => GraphQL::STRING_TYPE,
24
+
25
+ 'bool' => GraphQL::BOOLEAN_TYPE,
26
+ 'boolean' => GraphQL::BOOLEAN_TYPE,
27
+
28
+ 'float' => GraphQL::FLOAT_TYPE,
29
+ 'double' => GraphQL::FLOAT_TYPE,
30
+ 'decimal' => GraphQL::FLOAT_TYPE
31
+ }.freeze
32
+
33
+ WRAPPER_TYPES = [
34
+ GraphQL::Schema::List,
35
+ GraphQL::Schema::NonNull,
36
+ GraphQL::NonNullType,
37
+ GraphQL::ListType
38
+ ].freeze
39
+
40
+ GRAPHQL_BASE_TYPES = [
41
+ GraphQL::BaseType,
42
+ GraphQL::ObjectType,
43
+ GraphQL::InputObjectType
44
+ ].freeze
45
+
46
+ RAW_GRAPHQL_TYPES = (WRAPPER_TYPES + GRAPHQL_BASE_TYPES).freeze
47
+
48
+ def unwrapped_scalar_type
49
+ TYPE_MAPPING[nullable_inner_name.downcase.downcase]
50
+ end
51
+
52
+ def raw_graphql_type?
53
+ return true if RAW_GRAPHQL_TYPES.detect { |raw_type| unparsed_type.is_a?(raw_type) }
54
+
55
+ defined?(GraphQL::Schema::Member) &&
56
+ unparsed_type.is_a?(Class) &&
57
+ unparsed_type < GraphQL::Schema::Member
58
+ end
59
+
60
+ def graphql_model
61
+ type_class = \
62
+ if unparsed_type.is_a?(Class) && unparsed_type < GraphqlRails::Model
63
+ unparsed_type
64
+ else
65
+ nullable_inner_name.safe_constantize
66
+ end
67
+
68
+ return if type_class.nil?
69
+ return unless type_class < GraphqlRails::Model
70
+
71
+ type_class
72
+ end
73
+
74
+ protected
75
+
76
+ def unwrap_type(type)
77
+ unwrappable = type
78
+ unwrappable = unwrappable.of_type while wrapped_type?(unwrappable)
79
+ unwrappable
80
+ end
81
+
82
+ def wrapped_type?(type)
83
+ WRAPPER_TYPES.any? { |wrapper| type.is_a?(wrapper) }
84
+ end
85
+
86
+ def nullable_inner_name
87
+ type_name_info.nullable_inner_name
88
+ end
89
+
90
+ def list?
91
+ type_name_info.list?
92
+ end
93
+
94
+ def required_inner_type?
95
+ type_name_info.required_inner_type?
96
+ end
97
+
98
+ def required_list?
99
+ type_name_info.required_list?
100
+ end
101
+
102
+ def required?
103
+ type_name_info.required?
104
+ end
105
+
106
+ def type_name_info
107
+ @type_name_info ||= begin
108
+ type_name = \
109
+ if unparsed_type.respond_to?(:to_type_signature)
110
+ unparsed_type.to_type_signature
111
+ else
112
+ unparsed_type.to_s
113
+ end
114
+ TypeNameInfo.new(type_name)
115
+ end
116
+ end
117
+
118
+ def raise_not_supported_type_error
119
+ error_message = \
120
+ "Type #{unparsed_type.inspect} is not supported. " \
121
+ "Supported scalar types are: #{TypeParseable::TYPE_MAPPING.keys}. " \
122
+ 'All the classes that includes `GraphqlRails::Model` are also supported as types.'
123
+
124
+ raise UnknownTypeError, error_message
125
+ end
126
+ end
127
+ end
128
+ end
@@ -1,44 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'graphql'
4
+ require 'graphql_rails/model/build_connection_type'
5
+ require 'graphql_rails/errors/error'
4
6
 
5
7
  module GraphqlRails
6
8
  module Attributes
7
9
  # converts string value in to GraphQL type
8
10
  class TypeParser
9
11
  require_relative './type_name_info'
12
+ require_relative './type_parseable'
10
13
 
11
- class UnknownTypeError < ArgumentError; end
14
+ class NotSupportedFeature < GraphqlRails::Error; end
12
15
 
13
- TYPE_MAPPING = {
14
- 'id' => GraphQL::ID_TYPE,
16
+ include TypeParseable
15
17
 
16
- 'int' => GraphQL::INT_TYPE,
17
- 'integer' => GraphQL::INT_TYPE,
18
+ delegate :list?, :required_inner_type?, :required_list?, :required?, to: :type_name_info
18
19
 
19
- 'string' => GraphQL::STRING_TYPE,
20
- 'str' => GraphQL::STRING_TYPE,
21
- 'text' => GraphQL::STRING_TYPE,
22
- 'time' => GraphQL::STRING_TYPE,
23
- 'date' => GraphQL::STRING_TYPE,
24
-
25
- 'bool' => GraphQL::BOOLEAN_TYPE,
26
- 'boolean' => GraphQL::BOOLEAN_TYPE,
27
-
28
- 'float' => GraphQL::FLOAT_TYPE,
29
- 'double' => GraphQL::FLOAT_TYPE,
30
- 'decimal' => GraphQL::FLOAT_TYPE
31
- }.freeze
32
-
33
- RAW_GRAPHQL_TYPES = [
34
- GraphQL::Schema::List,
35
- GraphQL::BaseType,
36
- GraphQL::ObjectType,
37
- GraphQL::InputObjectType
38
- ].freeze
39
-
40
- def initialize(unparsed_type)
20
+ def initialize(unparsed_type, paginated: false)
41
21
  @unparsed_type = unparsed_type
22
+ @paginated = paginated
23
+ end
24
+
25
+ def paginated?
26
+ @paginated
42
27
  end
43
28
 
44
29
  def graphql_type
@@ -51,25 +36,43 @@ module GraphqlRails
51
36
  end
52
37
  end
53
38
 
54
- def graphql_model
55
- type_class = nullable_inner_name.safe_constantize
56
- return unless type_class.respond_to?(:graphql)
57
-
58
- type_class
39
+ def type_arg
40
+ if paginated?
41
+ paginated_type_arg
42
+ elsif list?
43
+ list_type_arg
44
+ else
45
+ raw_unwrapped_type
46
+ end
59
47
  end
60
48
 
61
49
  protected
62
50
 
63
- def dynamicly_defined_type
64
- type_class = graphql_model
65
- return unless type_class
51
+ def paginated_type_arg
52
+ return graphql_model.graphql.connection_type if graphql_model
66
53
 
67
- type_class.graphql.graphql_type
54
+ raise NotSupportedFeature, 'pagination is only supported for models which include GraphqlRails::Model'
68
55
  end
69
56
 
70
- private
57
+ def list_type_arg
58
+ if required_inner_type?
59
+ [raw_unwrapped_type]
60
+ else
61
+ [raw_unwrapped_type, null: true]
62
+ end
63
+ end
71
64
 
72
- delegate :list?, :required_inner_type?, :required_list?, :nullable_inner_name, to: :type_name_info
65
+ def parsed_type
66
+ return unparsed_type if raw_graphql_type?
67
+
68
+ type_by_name
69
+ end
70
+
71
+ def raw_unwrapped_type
72
+ @raw_unwrapped_type ||= unwrap_type(parsed_type)
73
+ end
74
+
75
+ private
73
76
 
74
77
  attr_reader :unparsed_type
75
78
 
@@ -91,26 +94,27 @@ module GraphqlRails
91
94
  end
92
95
  end
93
96
 
94
- def raw_graphql_type?
95
- return true if RAW_GRAPHQL_TYPES.detect { |raw_type| unparsed_type.is_a?(raw_type) }
96
-
97
- defined?(GraphQL::Schema::Member) &&
98
- unparsed_type.is_a?(Class) &&
99
- unparsed_type < GraphQL::Schema::Member
100
- end
101
-
102
97
  def type_name_info
103
- @type_name_info ||= TypeNameInfo.new(unparsed_type.to_s)
98
+ @type_name_info ||= begin
99
+ type_name = \
100
+ if unparsed_type.respond_to?(:to_type_signature)
101
+ unparsed_type.to_type_signature
102
+ else
103
+ unparsed_type.to_s
104
+ end
105
+ TypeNameInfo.new(type_name)
106
+ end
104
107
  end
105
108
 
106
109
  def type_by_name
107
- TYPE_MAPPING.fetch(nullable_inner_name.downcase) do
108
- dynamicly_defined_type || raise(
109
- UnknownTypeError,
110
- "Type #{unparsed_type.inspect} is not supported. Supported scalar types are: #{TYPE_MAPPING.keys}." \
111
- ' All the classes that includes `GraphqlRails::Model` are also supported as types.'
112
- )
113
- end
110
+ unwrapped_scalar_type || unwrapped_model_type || raise_not_supported_type_error
111
+ end
112
+
113
+ def unwrapped_model_type
114
+ type_class = graphql_model
115
+ return unless type_class
116
+
117
+ type_class.graphql.graphql_type
114
118
  end
115
119
  end
116
120
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ # includes all service object related logic
5
+ module Service
6
+ require 'active_support/concern'
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ def call(*args, &block)
11
+ new(*args).call(&block)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -4,17 +4,18 @@ require 'active_support/hash_with_indifferent_access'
4
4
  require 'active_support/core_ext/hash'
5
5
  require 'graphql_rails/controller/configuration'
6
6
  require 'graphql_rails/controller/request'
7
- require 'graphql_rails/controller/format_results'
8
7
  require 'graphql_rails/controller/action_hooks_runner'
9
8
  require 'graphql_rails/controller/log_controller_action'
9
+ require 'graphql_rails/errors/system_error'
10
10
 
11
11
  module GraphqlRails
12
12
  # base class for all graphql_rails controllers
13
13
  class Controller
14
14
  class << self
15
- def inherited(sublass)
15
+ def inherited(subclass)
16
16
  super
17
- sublass.instance_variable_set(:@controller_configuration, controller_configuration.dup)
17
+ new_config = controller_configuration.dup_with(controller: subclass)
18
+ subclass.instance_variable_set(:@controller_configuration, new_config)
18
19
  end
19
20
 
20
21
  def before_action(*args, &block)
@@ -33,8 +34,16 @@ module GraphqlRails
33
34
  controller_configuration.action(action_name)
34
35
  end
35
36
 
37
+ def action_default
38
+ controller_configuration.action_default
39
+ end
40
+
41
+ def model(*args)
42
+ controller_configuration.model(*args)
43
+ end
44
+
36
45
  def controller_configuration
37
- @controller_configuration ||= Controller::Configuration.new
46
+ @controller_configuration ||= Controller::Configuration.new(self)
38
47
  end
39
48
  end
40
49
 
@@ -48,7 +57,7 @@ module GraphqlRails
48
57
  @action_name = method_name
49
58
  with_controller_action_logging do
50
59
  call_with_rendering
51
- format_controller_results
60
+ graphql_request.object_to_return
52
61
  end
53
62
  ensure
54
63
  @action_name = nil
@@ -78,7 +87,11 @@ module GraphqlRails
78
87
 
79
88
  render response if graphql_request.no_object_to_return?
80
89
  rescue StandardError => error
81
- render error: error
90
+ if error.is_a?(GraphQL::ExecutionError)
91
+ render error: error
92
+ else
93
+ render error: SystemError.new(error.message)
94
+ end
82
95
  end
83
96
 
84
97
  def graphql_errors_from_render_params(rendering_params)
@@ -86,7 +99,7 @@ module GraphqlRails
86
99
  return [] if rendering_params.keys.count != 1
87
100
 
88
101
  errors = rendering_params[:error] || rendering_params[:errors]
89
- Array(errors)
102
+ errors.is_a?(Enumerable) ? errors : Array(errors)
90
103
  end
91
104
 
92
105
  def with_controller_action_logging(&block)
@@ -98,14 +111,5 @@ module GraphqlRails
98
111
  &block
99
112
  )
100
113
  end
101
-
102
- def format_controller_results
103
- FormatResults.new(
104
- graphql_request.object_to_return,
105
- action_config: self.class.action(action_name),
106
- params: params,
107
- graphql_context: graphql_request.context
108
- ).call
109
- end
110
114
  end
111
115
  end