graphql_rails 0.7.0 → 1.2.1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.hound.yml +1 -0
  3. data/.rubocop.yml +3 -3
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +2 -2
  6. data/CHANGELOG.md +35 -0
  7. data/Gemfile +3 -2
  8. data/Gemfile.lock +181 -71
  9. data/docs/README.md +40 -8
  10. data/docs/_sidebar.md +5 -0
  11. data/docs/components/controller.md +295 -9
  12. data/docs/components/decorator.md +69 -0
  13. data/docs/components/model.md +267 -6
  14. data/docs/components/routes.md +28 -0
  15. data/docs/getting_started/quick_start.md +10 -3
  16. data/docs/index.html +1 -1
  17. data/docs/logging_and_monitoring/logging_and_monitoring.md +35 -0
  18. data/docs/other_tools/query_runner.md +49 -0
  19. data/docs/other_tools/schema_dump.md +29 -0
  20. data/docs/testing/testing.md +3 -1
  21. data/graphql_rails.gemspec +5 -4
  22. data/lib/generators/graphql_rails/install_generator.rb +50 -0
  23. data/lib/generators/graphql_rails/templates/example_users_controller.erb +19 -0
  24. data/lib/generators/graphql_rails/templates/graphql_application_controller.erb +8 -0
  25. data/lib/generators/graphql_rails/templates/graphql_controller.erb +20 -0
  26. data/lib/generators/graphql_rails/templates/graphql_router.erb +19 -0
  27. data/lib/generators/graphql_rails/templates/graphql_router_spec.erb +21 -0
  28. data/lib/graphql_rails.rb +6 -0
  29. data/lib/graphql_rails/attributes/attributable.rb +22 -17
  30. data/lib/graphql_rails/attributes/attribute.rb +67 -3
  31. data/lib/graphql_rails/attributes/attribute_name_parser.rb +4 -4
  32. data/lib/graphql_rails/attributes/input_attribute.rb +33 -15
  33. data/lib/graphql_rails/attributes/input_type_parser.rb +62 -0
  34. data/lib/graphql_rails/attributes/type_name_info.rb +38 -0
  35. data/lib/graphql_rails/attributes/type_parseable.rb +132 -0
  36. data/lib/graphql_rails/attributes/type_parser.rb +59 -53
  37. data/lib/graphql_rails/concerns/service.rb +19 -0
  38. data/lib/graphql_rails/controller.rb +42 -21
  39. data/lib/graphql_rails/controller/action.rb +12 -67
  40. data/lib/graphql_rails/controller/action_configuration.rb +70 -28
  41. data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
  42. data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
  43. data/lib/graphql_rails/controller/configuration.rb +56 -3
  44. data/lib/graphql_rails/controller/log_controller_action.rb +71 -0
  45. data/lib/graphql_rails/controller/request.rb +29 -8
  46. data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
  47. data/lib/graphql_rails/decorator.rb +41 -0
  48. data/lib/graphql_rails/decorator/relation_decorator.rb +75 -0
  49. data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
  50. data/lib/graphql_rails/errors/execution_error.rb +6 -7
  51. data/lib/graphql_rails/errors/system_error.rb +14 -0
  52. data/lib/graphql_rails/errors/validation_error.rb +1 -5
  53. data/lib/graphql_rails/input_configurable.rb +47 -0
  54. data/lib/graphql_rails/integrations.rb +19 -0
  55. data/lib/graphql_rails/integrations/lograge.rb +39 -0
  56. data/lib/graphql_rails/integrations/sentry.rb +34 -0
  57. data/lib/graphql_rails/model.rb +26 -4
  58. data/lib/graphql_rails/model/add_fields_to_graphql_type.rb +45 -0
  59. data/lib/graphql_rails/model/build_connection_type.rb +52 -0
  60. data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +5 -5
  61. data/lib/graphql_rails/model/build_enum_type.rb +39 -10
  62. data/lib/graphql_rails/model/build_graphql_input_type.rb +8 -4
  63. data/lib/graphql_rails/model/call_graphql_model_method.rb +72 -0
  64. data/lib/graphql_rails/model/configurable.rb +6 -2
  65. data/lib/graphql_rails/model/configuration.rb +30 -16
  66. data/lib/graphql_rails/model/find_or_build_graphql_type.rb +64 -0
  67. data/lib/graphql_rails/model/find_or_build_graphql_type_class.rb +46 -0
  68. data/lib/graphql_rails/model/input.rb +11 -7
  69. data/lib/graphql_rails/query_runner.rb +68 -0
  70. data/lib/graphql_rails/railtie.rb +10 -0
  71. data/lib/graphql_rails/router.rb +40 -13
  72. data/lib/graphql_rails/router/resource_routes_builder.rb +10 -9
  73. data/lib/graphql_rails/router/route.rb +21 -6
  74. data/lib/graphql_rails/router/schema_builder.rb +30 -11
  75. data/lib/graphql_rails/rspec_controller_helpers.rb +6 -4
  76. data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
  77. data/lib/graphql_rails/tasks/schema.rake +14 -0
  78. data/lib/graphql_rails/version.rb +1 -1
  79. metadata +70 -19
  80. data/lib/graphql_rails/controller/controller_function.rb +0 -50
  81. data/lib/graphql_rails/controller/format_results.rb +0 -36
  82. data/lib/graphql_rails/model/build_graphql_type.rb +0 -37
@@ -2,29 +2,93 @@
2
2
 
3
3
  require 'graphql'
4
4
  require 'graphql_rails/attributes/attributable'
5
+ require 'graphql_rails/input_configurable'
5
6
 
6
7
  module GraphqlRails
7
8
  module Attributes
8
9
  # contains info about single graphql attribute
9
10
  class Attribute
10
11
  include Attributable
12
+ include InputConfigurable
11
13
 
12
- attr_reader :property, :description
14
+ attr_reader :attributes
13
15
 
14
- def initialize(name, type = nil, description: nil, property: name)
16
+ # rubocop:disable Metrics/ParameterLists
17
+ def initialize(name, type = nil, description: nil, property: name, required: nil, options: {})
15
18
  @initial_type = type
16
19
  @initial_name = name
20
+ @options = options
17
21
  @description = description
18
22
  @property = property.to_s
23
+ @required = required
24
+ @attributes ||= {}
25
+ end
26
+ # rubocop:enable Metrics/ParameterLists
27
+
28
+ def type(new_type = nil)
29
+ return @initial_type if new_type.nil?
30
+
31
+ @initial_type = new_type
32
+ self
33
+ end
34
+
35
+ def description(new_description = nil)
36
+ return @description if new_description.nil?
37
+
38
+ @description = new_description
39
+ self
40
+ end
41
+
42
+ def property(new_property = nil)
43
+ return @property if new_property.nil?
44
+
45
+ @property = new_property.to_s
46
+ self
47
+ end
48
+
49
+ def options(new_options = {})
50
+ return @options if new_options.blank?
51
+
52
+ @options = new_options
53
+ self
19
54
  end
20
55
 
21
56
  def field_args
22
- [field_name, graphql_field_type, { property: property.to_sym, description: description }]
57
+ [
58
+ field_name,
59
+ type_parser.type_arg,
60
+ *description
61
+ ]
62
+ end
63
+
64
+ def field_options
65
+ {
66
+ method: property.to_sym,
67
+ null: optional?,
68
+ camelize: camelize?
69
+ }
70
+ end
71
+
72
+ def argument_args
73
+ [
74
+ field_name,
75
+ type_parser.type_arg,
76
+ {
77
+ description: description,
78
+ required: required?
79
+ }
80
+ ]
23
81
  end
24
82
 
25
83
  protected
26
84
 
27
85
  attr_reader :initial_type, :initial_name
86
+
87
+ private
88
+
89
+ def camelize?
90
+ options[:input_format] != :original && options[:attribute_name_format] != :original
91
+ end
28
92
  end
29
93
  end
30
94
  end
@@ -27,11 +27,11 @@ module GraphqlRails
27
27
  @graphql_type ||= \
28
28
  case name
29
29
  when 'id', /_id\Z/
30
- GraphQL::ID_TYPE
30
+ GraphQL::Types::ID
31
31
  when /\?\Z/
32
- GraphQL::BOOLEAN_TYPE
32
+ GraphQL::Types::Boolean
33
33
  else
34
- GraphQL::STRING_TYPE
34
+ GraphQL::Types::String
35
35
  end
36
36
  end
37
37
 
@@ -44,7 +44,7 @@ module GraphqlRails
44
44
  attr_reader :options
45
45
 
46
46
  def original_format?
47
- options[:input_format] == :original
47
+ options[:input_format] == :original || options[:attribute_name_format] == :original
48
48
  end
49
49
 
50
50
  def preprocesed_name
@@ -4,43 +4,61 @@ module GraphqlRails
4
4
  module Attributes
5
5
  # contains info about single graphql input attribute
6
6
  class InputAttribute
7
+ require_relative './input_type_parser'
8
+ require_relative './attribute_name_parser'
7
9
  include Attributable
8
10
 
9
11
  attr_reader :description
10
12
 
11
- def initialize(name, type = nil, description: nil, options: {})
13
+ # rubocop:disable Metrics/ParameterLists
14
+ def initialize(name, type: nil, description: nil, subtype: nil, required: nil, options: {})
12
15
  @initial_name = name
13
16
  @initial_type = type
14
17
  @description = description
15
18
  @options = options
19
+ @subtype = subtype
20
+ @required = required
16
21
  end
22
+ # rubocop:enable Metrics/ParameterLists
17
23
 
18
- def function_argument_args
19
- [field_name, graphql_input_type, { description: description }]
24
+ def input_argument_args
25
+ type = raw_input_type || input_type_parser.input_type_arg
26
+
27
+ [field_name, type]
20
28
  end
21
29
 
22
- def input_argument_args
23
- type = raw_input_type || model_input_type || nullable_type
24
- [field_name, type, { required: required?, description: description }]
30
+ def input_argument_options
31
+ { required: required?, description: description, camelize: false }
25
32
  end
26
33
 
27
- def graphql_input_type
28
- raw_input_type || model_input_type || graphql_field_type
34
+ def paginated?
35
+ false
29
36
  end
30
37
 
31
38
  private
32
39
 
33
- attr_reader :initial_name, :initial_type, :options
40
+ attr_reader :initial_name, :initial_type, :options, :subtype
34
41
 
35
- def raw_input_type
36
- return initial_type if initial_type.is_a?(GraphQL::InputObjectType)
37
- return initial_type.graphql_input_type if initial_type.is_a?(Model::Input)
42
+ def attribute_name_parser
43
+ @attribute_name_parser ||= AttributeNameParser.new(
44
+ initial_name, options: attribute_naming_options
45
+ )
38
46
  end
39
47
 
40
- def model_input_type
41
- return unless graphql_model
48
+ def attribute_naming_options
49
+ options.slice(:input_format)
50
+ end
51
+
52
+ def input_type_parser
53
+ @input_type_parser ||= begin
54
+ initial_parseable_type = initial_type || attribute_name_parser.graphql_type
55
+ InputTypeParser.new(initial_parseable_type, subtype: subtype)
56
+ end
57
+ end
42
58
 
43
- graphql_model.graphql.input.graphql_input_type
59
+ def raw_input_type
60
+ return initial_type if initial_type.is_a?(GraphQL::InputObjectType)
61
+ return initial_type.graphql_input_type if initial_type.is_a?(Model::Input)
44
62
  end
45
63
  end
46
64
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+
5
+ module GraphqlRails
6
+ module Attributes
7
+ # converts string value in to GraphQL type
8
+ class InputTypeParser
9
+ require_relative './type_parseable'
10
+
11
+ include TypeParseable
12
+
13
+ def initialize(unparsed_type, subtype:)
14
+ @unparsed_type = unparsed_type
15
+ @subtype = subtype
16
+ end
17
+
18
+ def graphql_type
19
+ return nil if unparsed_type.nil?
20
+
21
+ partly_parsed_type || parsed_type
22
+ end
23
+
24
+ def input_type_arg
25
+ if list?
26
+ list_type_arg
27
+ else
28
+ unwrapped_type
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :unparsed_type, :subtype
35
+
36
+ def unwrapped_type
37
+ raw_unwrapped_type || unwrapped_scalar_type || unwrapped_model_input_type || raise_not_supported_type_error
38
+ end
39
+
40
+ def raw_unwrapped_type
41
+ return nil unless raw_graphql_type?
42
+
43
+ unwrap_type(unparsed_type)
44
+ end
45
+
46
+ def list_type_arg
47
+ if required_inner_type?
48
+ [unwrapped_type]
49
+ else
50
+ [unwrapped_type, null: true]
51
+ end
52
+ end
53
+
54
+ def unwrapped_model_input_type
55
+ type_class = graphql_model
56
+ return unless type_class
57
+
58
+ type_class.graphql.input(subtype).graphql_input_type
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ module Attributes
5
+ # checks various attributes based on graphql type name
6
+ class TypeNameInfo
7
+ attr_reader :name
8
+
9
+ def initialize(name)
10
+ @name = name
11
+ end
12
+
13
+ def nullable_inner_name
14
+ inner_name[/[^!]+/]
15
+ end
16
+
17
+ def inner_name
18
+ name[/[^!\[\]]+!?/]
19
+ end
20
+
21
+ def required_inner_type?
22
+ inner_name.include?('!')
23
+ end
24
+
25
+ def list?
26
+ name.include?(']')
27
+ end
28
+
29
+ def required?
30
+ name.end_with?('!')
31
+ end
32
+
33
+ def required_list?
34
+ required? && list?
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,132 @@
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::Types::ID,
15
+
16
+ 'int' => GraphQL::Types::Int,
17
+ 'integer' => GraphQL::Types::Int,
18
+
19
+ 'string' => GraphQL::Types::String,
20
+ 'str' => GraphQL::Types::String,
21
+ 'text' => GraphQL::Types::String,
22
+ 'time' => GraphQL::Types::String,
23
+ 'date' => GraphQL::Types::String,
24
+
25
+ 'bool' => GraphQL::Types::Boolean,
26
+ 'boolean' => GraphQL::Types::Boolean,
27
+
28
+ 'float' => GraphQL::Types::Float,
29
+ 'double' => GraphQL::Types::Float,
30
+ 'decimal' => GraphQL::Types::Float
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 core_scalar_type?
61
+ unwrapped_scalar_type.in?(TYPE_MAPPING.values)
62
+ end
63
+
64
+ def graphql_model
65
+ type_class = \
66
+ if unparsed_type.is_a?(Class) && unparsed_type < GraphqlRails::Model
67
+ unparsed_type
68
+ else
69
+ nullable_inner_name.safe_constantize
70
+ end
71
+
72
+ return if type_class.nil?
73
+ return unless type_class < GraphqlRails::Model
74
+
75
+ type_class
76
+ end
77
+
78
+ protected
79
+
80
+ def unwrap_type(type)
81
+ unwrappable = type
82
+ unwrappable = unwrappable.of_type while wrapped_type?(unwrappable)
83
+ unwrappable
84
+ end
85
+
86
+ def wrapped_type?(type)
87
+ WRAPPER_TYPES.any? { |wrapper| type.is_a?(wrapper) }
88
+ end
89
+
90
+ def nullable_inner_name
91
+ type_name_info.nullable_inner_name
92
+ end
93
+
94
+ def list?
95
+ type_name_info.list?
96
+ end
97
+
98
+ def required_inner_type?
99
+ type_name_info.required_inner_type?
100
+ end
101
+
102
+ def required_list?
103
+ type_name_info.required_list?
104
+ end
105
+
106
+ def required?
107
+ type_name_info.required?
108
+ end
109
+
110
+ def type_name_info
111
+ @type_name_info ||= begin
112
+ type_name = \
113
+ if unparsed_type.respond_to?(:to_type_signature)
114
+ unparsed_type.to_type_signature
115
+ else
116
+ unparsed_type.to_s
117
+ end
118
+ TypeNameInfo.new(type_name)
119
+ end
120
+ end
121
+
122
+ def raise_not_supported_type_error
123
+ error_message = \
124
+ "Type #{unparsed_type.inspect} is not supported. " \
125
+ "Supported scalar types are: #{TypeParseable::TYPE_MAPPING.keys}. " \
126
+ 'All the classes that includes `GraphqlRails::Model` are also supported as types.'
127
+
128
+ raise UnknownTypeError, error_message
129
+ end
130
+ end
131
+ end
132
+ end
@@ -1,35 +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
- class UnknownTypeError < ArgumentError; end
11
+ require_relative './type_name_info'
12
+ require_relative './type_parseable'
10
13
 
11
- TYPE_MAPPING = {
12
- 'id' => GraphQL::ID_TYPE,
14
+ class NotSupportedFeature < GraphqlRails::Error; end
13
15
 
14
- 'int' => GraphQL::INT_TYPE,
15
- 'integer' => GraphQL::INT_TYPE,
16
+ include TypeParseable
16
17
 
17
- 'string' => GraphQL::STRING_TYPE,
18
- 'str' => GraphQL::STRING_TYPE,
19
- 'text' => GraphQL::STRING_TYPE,
20
- 'time' => GraphQL::STRING_TYPE,
21
- 'date' => GraphQL::STRING_TYPE,
18
+ delegate :list?, :required_inner_type?, :required_list?, :required?, to: :type_name_info
22
19
 
23
- 'bool' => GraphQL::BOOLEAN_TYPE,
24
- 'boolean' => GraphQL::BOOLEAN_TYPE,
25
-
26
- 'float' => GraphQL::FLOAT_TYPE,
27
- 'double' => GraphQL::FLOAT_TYPE,
28
- 'decimal' => GraphQL::FLOAT_TYPE
29
- }.freeze
30
-
31
- def initialize(unparsed_type)
20
+ def initialize(unparsed_type, paginated: false)
32
21
  @unparsed_type = unparsed_type
22
+ @paginated = paginated
23
+ end
24
+
25
+ def paginated?
26
+ @paginated
33
27
  end
34
28
 
35
29
  def graphql_type
@@ -42,11 +36,40 @@ module GraphqlRails
42
36
  end
43
37
  end
44
38
 
45
- def graphql_model
46
- type_class = inner_type_name.safe_constantize
47
- return unless type_class.respond_to?(:graphql)
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
47
+ end
48
+
49
+ protected
50
+
51
+ def paginated_type_arg
52
+ return graphql_model.graphql.connection_type if graphql_model
53
+
54
+ raise NotSupportedFeature, 'pagination is only supported for models which include GraphqlRails::Model'
55
+ end
56
+
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
64
+
65
+ def parsed_type
66
+ return unparsed_type if raw_graphql_type?
67
+
68
+ type_by_name
69
+ end
48
70
 
49
- type_class
71
+ def raw_unwrapped_type
72
+ @raw_unwrapped_type ||= unwrap_type(parsed_type)
50
73
  end
51
74
 
52
75
  private
@@ -56,7 +79,7 @@ module GraphqlRails
56
79
  def parsed_list_type
57
80
  list_type = parsed_inner_type.to_list_type
58
81
 
59
- if required_list_type?
82
+ if required_list?
60
83
  list_type.to_non_null_type
61
84
  else
62
85
  list_type
@@ -71,40 +94,23 @@ module GraphqlRails
71
94
  end
72
95
  end
73
96
 
74
- def required_inner_type?
75
- !!unparsed_type[/\w!/] # rubocop:disable Style/DoubleNegation
76
- end
77
-
78
- def list?
79
- unparsed_type.to_s.include?(']')
80
- end
81
-
82
- def required_list_type?
83
- unparsed_type.to_s.include?(']!')
84
- end
85
-
86
- def raw_graphql_type?
87
- unparsed_type.is_a?(GraphQL::BaseType) ||
88
- unparsed_type.is_a?(GraphQL::ObjectType) ||
89
- unparsed_type.is_a?(GraphQL::InputObjectType) ||
90
- (defined?(GraphQL::Schema::Member) && unparsed_type.is_a?(Class) && unparsed_type < GraphQL::Schema::Member)
91
- end
92
-
93
- def inner_type_name
94
- unparsed_type.to_s.tr('[]!', '')
97
+ def type_name_info
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
95
107
  end
96
108
 
97
109
  def type_by_name
98
- TYPE_MAPPING.fetch(inner_type_name.downcase) do
99
- dynamicly_defined_type || raise(
100
- UnknownTypeError,
101
- "Type #{unparsed_type.inspect} is not supported. Supported scalar types are: #{TYPE_MAPPING.keys}." \
102
- ' All the classes that includes `GraphqlRails::Model` are also supported as types.'
103
- )
104
- end
110
+ unwrapped_scalar_type || unwrapped_model_type || raise_not_supported_type_error
105
111
  end
106
112
 
107
- def dynamicly_defined_type
113
+ def unwrapped_model_type
108
114
  type_class = graphql_model
109
115
  return unless type_class
110
116