graphql_rails 0.7.0 → 1.2.1

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