graphql_rails 0.8.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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