graphql_rails 0.5.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) 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 +179 -71
  9. data/docs/README.md +48 -9
  10. data/docs/_sidebar.md +5 -0
  11. data/docs/components/controller.md +294 -8
  12. data/docs/components/decorator.md +69 -0
  13. data/docs/components/model.md +349 -11
  14. data/docs/components/routes.md +43 -1
  15. data/docs/getting_started/quick_start.md +9 -2
  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 +7 -1
  29. data/lib/graphql_rails/attributes.rb +13 -0
  30. data/lib/graphql_rails/{attribute → attributes}/attributable.rb +25 -20
  31. data/lib/graphql_rails/attributes/attribute.rb +94 -0
  32. data/lib/graphql_rails/{attribute → attributes}/attribute_name_parser.rb +2 -2
  33. data/lib/graphql_rails/attributes/input_attribute.rb +65 -0
  34. data/lib/graphql_rails/attributes/input_type_parser.rb +62 -0
  35. data/lib/graphql_rails/attributes/type_name_info.rb +38 -0
  36. data/lib/graphql_rails/attributes/type_parseable.rb +128 -0
  37. data/lib/graphql_rails/attributes/type_parser.rb +121 -0
  38. data/lib/graphql_rails/concerns/service.rb +19 -0
  39. data/lib/graphql_rails/controller.rb +44 -22
  40. data/lib/graphql_rails/controller/action.rb +12 -67
  41. data/lib/graphql_rails/controller/action_configuration.rb +71 -31
  42. data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
  43. data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
  44. data/lib/graphql_rails/controller/configuration.rb +58 -4
  45. data/lib/graphql_rails/controller/log_controller_action.rb +66 -0
  46. data/lib/graphql_rails/controller/request.rb +29 -8
  47. data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
  48. data/lib/graphql_rails/decorator.rb +41 -0
  49. data/lib/graphql_rails/decorator/relation_decorator.rb +79 -0
  50. data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
  51. data/lib/graphql_rails/errors/execution_error.rb +8 -7
  52. data/lib/graphql_rails/errors/system_error.rb +14 -0
  53. data/lib/graphql_rails/errors/validation_error.rb +1 -5
  54. data/lib/graphql_rails/input_configurable.rb +47 -0
  55. data/lib/graphql_rails/integrations.rb +19 -0
  56. data/lib/graphql_rails/integrations/lograge.rb +39 -0
  57. data/lib/graphql_rails/integrations/sentry.rb +34 -0
  58. data/lib/graphql_rails/model.rb +26 -4
  59. data/lib/graphql_rails/model/add_fields_to_graphql_type.rb +45 -0
  60. data/lib/graphql_rails/model/build_connection_type.rb +48 -0
  61. data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +4 -4
  62. data/lib/graphql_rails/model/build_enum_type.rb +68 -0
  63. data/lib/graphql_rails/model/{graphql_input_type_builder.rb → build_graphql_input_type.rb} +10 -2
  64. data/lib/graphql_rails/model/call_graphql_model_method.rb +72 -0
  65. data/lib/graphql_rails/model/configurable.rb +6 -2
  66. data/lib/graphql_rails/model/configuration.rb +34 -19
  67. data/lib/graphql_rails/model/find_or_build_graphql_type.rb +64 -0
  68. data/lib/graphql_rails/model/find_or_build_graphql_type_class.rb +46 -0
  69. data/lib/graphql_rails/model/input.rb +25 -11
  70. data/lib/graphql_rails/query_runner.rb +68 -0
  71. data/lib/graphql_rails/railtie.rb +10 -0
  72. data/lib/graphql_rails/router.rb +40 -13
  73. data/lib/graphql_rails/router/plain_cursor_encoder.rb +16 -0
  74. data/lib/graphql_rails/router/resource_routes_builder.rb +19 -11
  75. data/lib/graphql_rails/router/route.rb +21 -6
  76. data/lib/graphql_rails/router/schema_builder.rb +29 -11
  77. data/lib/graphql_rails/rspec_controller_helpers.rb +25 -12
  78. data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
  79. data/lib/graphql_rails/tasks/schema.rake +14 -0
  80. data/lib/graphql_rails/version.rb +1 -1
  81. metadata +75 -22
  82. data/README.md +0 -194
  83. data/lib/graphql_rails/attribute.rb +0 -28
  84. data/lib/graphql_rails/attribute/type_parser.rb +0 -115
  85. data/lib/graphql_rails/controller/controller_function.rb +0 -50
  86. data/lib/graphql_rails/controller/format_results.rb +0 -36
  87. data/lib/graphql_rails/model/graphql_type_builder.rb +0 -33
  88. data/lib/graphql_rails/model/input_attribute.rb +0 -47
@@ -0,0 +1,19 @@
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, **kwargs, &block)
11
+ if kwargs.present?
12
+ new(*args, **kwargs).call(&block)
13
+ else
14
+ new(*args).call(&block)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,37 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/hash_with_indifferent_access'
4
+ require 'active_support/core_ext/hash'
4
5
  require 'graphql_rails/controller/configuration'
5
6
  require 'graphql_rails/controller/request'
6
- require 'graphql_rails/controller/format_results'
7
7
  require 'graphql_rails/controller/action_hooks_runner'
8
+ require 'graphql_rails/controller/log_controller_action'
9
+ require 'graphql_rails/errors/system_error'
8
10
 
9
11
  module GraphqlRails
10
12
  # base class for all graphql_rails controllers
11
13
  class Controller
12
14
  class << self
13
- def inherited(sublass)
14
- sublass.instance_variable_set(:@controller_configuration, controller_configuration.dup)
15
+ def inherited(subclass)
16
+ super
17
+ new_config = controller_configuration.dup_with(controller: subclass)
18
+ subclass.instance_variable_set(:@controller_configuration, new_config)
15
19
  end
16
20
 
17
- def before_action(*args, &block)
18
- controller_configuration.add_action_hook(:before, *args, &block)
21
+ def before_action(*args, **kwargs, &block)
22
+ controller_configuration.add_action_hook(:before, *args, **kwargs, &block)
19
23
  end
20
24
 
21
- def around_action(*args, &block)
22
- controller_configuration.add_action_hook(:around, *args, &block)
25
+ def around_action(*args, **kwargs, &block)
26
+ controller_configuration.add_action_hook(:around, *args, **kwargs, &block)
23
27
  end
24
28
 
25
- def after_action(*args, &block)
26
- controller_configuration.add_action_hook(:after, *args, &block)
29
+ def after_action(*args, **kwargs, &block)
30
+ controller_configuration.add_action_hook(:after, *args, **kwargs, &block)
27
31
  end
28
32
 
29
33
  def action(action_name)
30
34
  controller_configuration.action(action_name)
31
35
  end
32
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
+
33
45
  def controller_configuration
34
- @controller_configuration ||= Controller::Configuration.new
46
+ @controller_configuration ||= Controller::Configuration.new(self)
35
47
  end
36
48
  end
37
49
 
@@ -43,14 +55,10 @@ module GraphqlRails
43
55
 
44
56
  def call(method_name)
45
57
  @action_name = method_name
46
- call_with_rendering(method_name)
47
-
48
- FormatResults.new(
49
- graphql_request.object_to_return,
50
- action_config: self.class.action(method_name),
51
- params: params,
52
- graphql_context: graphql_request.context
53
- ).call
58
+ with_controller_action_logging do
59
+ call_with_rendering
60
+ graphql_request.object_to_return
61
+ end
54
62
  ensure
55
63
  @action_name = nil
56
64
  end
@@ -68,18 +76,22 @@ module GraphqlRails
68
76
  end
69
77
 
70
78
  def params
71
- @params = HashWithIndifferentAccess.new(graphql_request.params)
79
+ @params ||= graphql_request.params.deep_transform_keys { |key| key.to_s.underscore }.with_indifferent_access
72
80
  end
73
81
 
74
82
  private
75
83
 
76
- def call_with_rendering(action_name)
84
+ def call_with_rendering
77
85
  hooks_runner = ActionHooksRunner.new(action_name: action_name, controller: self)
78
86
  response = hooks_runner.call { public_send(action_name) }
79
87
 
80
88
  render response if graphql_request.no_object_to_return?
81
89
  rescue StandardError => error
82
- 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
83
95
  end
84
96
 
85
97
  def graphql_errors_from_render_params(rendering_params)
@@ -87,7 +99,17 @@ module GraphqlRails
87
99
  return [] if rendering_params.keys.count != 1
88
100
 
89
101
  errors = rendering_params[:error] || rendering_params[:errors]
90
- Array(errors)
102
+ errors.is_a?(Enumerable) ? errors : Array(errors)
103
+ end
104
+
105
+ def with_controller_action_logging(&block)
106
+ LogControllerAction.call(
107
+ controller_name: self.class.name,
108
+ action_name: action_name,
109
+ params: params,
110
+ graphql_request: graphql_request,
111
+ &block
112
+ )
91
113
  end
92
114
  end
93
115
  end
@@ -9,14 +9,14 @@ module GraphqlRails
9
9
  class Controller
10
10
  # analyzes route and extracts controller action related data
11
11
  class Action
12
- class MissingConfigurationError < GraphqlRails::Error; end
12
+ delegate :relative_path, to: :route
13
13
 
14
14
  def initialize(route)
15
15
  @route = route
16
16
  end
17
17
 
18
18
  def return_type
19
- action_config.return_type || default_type
19
+ action_config.return_type
20
20
  end
21
21
 
22
22
  def arguments
@@ -28,56 +28,29 @@ module GraphqlRails
28
28
  end
29
29
 
30
30
  def name
31
- @name ||= action_relative_path.split('#').last
31
+ @name ||= relative_path.split('#').last
32
32
  end
33
33
 
34
34
  def description
35
35
  action_config.description
36
36
  end
37
37
 
38
- private
39
-
40
- attr_reader :route
41
-
42
- def default_inner_return_type
43
- raise_missing_return_type_error if model_graphql_type.nil?
44
-
45
- if action_config.can_return_nil?
46
- model_graphql_type
47
- else
48
- model_graphql_type.to_non_null_type
49
- end
38
+ def type_args
39
+ [type_parser.type_arg]
50
40
  end
51
41
 
52
- def raise_missing_return_type_error
53
- error_message = \
54
- "Return type for #{route.path.inspect} is not defined. " \
55
- "To do so, add `action(:#{name}).returns(YourType)` in #{controller.name} " \
56
- "or make sure that you have model named #{namespaced_model_name}"
57
-
58
- raise MissingConfigurationError, error_message
42
+ def type_options
43
+ { null: !type_parser.required? }
59
44
  end
60
45
 
61
- def default_type
62
- return default_collection_type if route.collection?
63
-
64
- default_inner_return_type
65
- end
46
+ private
66
47
 
67
- def default_collection_type
68
- if action_config.paginated?
69
- action_model.graphql.connection_type
70
- else
71
- default_inner_return_type.to_list_type.to_non_null_type
72
- end
73
- end
48
+ attr_reader :route
74
49
 
75
- def action_relative_path
76
- route.relative_path
77
- end
50
+ delegate :type_parser, to: :action_config
78
51
 
79
52
  def action_config
80
- controller.controller_configuration.action(name)
53
+ controller.controller_configuration.action_config(name)
81
54
  end
82
55
 
83
56
  def namespaced_controller_name
@@ -85,40 +58,12 @@ module GraphqlRails
85
58
  end
86
59
 
87
60
  def controller_name
88
- @controller_name ||= action_relative_path.split('#').first
89
- end
90
-
91
- def action_model
92
- namespace = namespaced_model_name.split('::')
93
- model_name = namespace.pop
94
- model = nil
95
-
96
- while model.nil? && !namespace.empty?
97
- model = namespaced_model(namespace, model_name)
98
- namespace.pop
99
- end
100
-
101
- model || namespaced_model(namespace, model_name)
102
- end
103
-
104
- def namespaced_model(namespace, model_name)
105
- [namespace, model_name].join('::').constantize
106
- rescue NameError => err
107
- raise unless err.message.match?(/uninitialized constant/)
108
-
109
- nil
61
+ @controller_name ||= relative_path.split('#').first
110
62
  end
111
63
 
112
64
  def namespaced_model_name
113
65
  namespaced_controller_name.singularize.classify
114
66
  end
115
-
116
- def model_graphql_type
117
- return unless action_model
118
- return unless action_model < GraphqlRails::Model
119
-
120
- action_model.graphql.graphql_type
121
- end
122
67
  end
123
68
  end
124
69
  end
@@ -1,42 +1,55 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/core_ext/string/filters'
4
- require 'graphql_rails/attribute/type_parser'
5
- require 'graphql_rails/attribute'
6
- require 'graphql_rails/model/input_attribute'
4
+ require 'graphql_rails/attributes'
5
+ require 'graphql_rails/input_configurable'
6
+ require 'graphql_rails/errors/error'
7
7
 
8
8
  module GraphqlRails
9
9
  class Controller
10
10
  # stores all graphql_rails contoller specific config
11
11
  class ActionConfiguration
12
- attr_reader :attributes, :pagination_options
12
+ class MissingConfigurationError < GraphqlRails::Error; end
13
+ class DeprecatedDefaultModelError < GraphqlRails::Error; end
14
+
15
+ include InputConfigurable
16
+
17
+ attr_reader :attributes, :pagination_options, :name, :controller, :defined_at
13
18
 
14
19
  def initialize_copy(other)
15
20
  super
16
21
  @attributes = other.instance_variable_get(:@attributes).dup.transform_values(&:dup)
22
+ @action_options = other.instance_variable_get(:@action_options).dup.transform_values(&:dup)
23
+ @pagination_options = other.instance_variable_get(:@pagination_options)&.dup&.transform_values(&:dup)
17
24
  end
18
25
 
19
- def initialize
26
+ def initialize(name:, controller:)
27
+ @name = name
28
+ @controller = controller
20
29
  @attributes = {}
21
30
  @action_options = {}
22
- @can_return_nil = false
23
31
  end
24
32
 
25
- def options(input_format:)
26
- @action_options[:input_format] = input_format
27
- self
33
+ def dup_with(name:, controller:, defined_at:)
34
+ dup.tap do |new_action|
35
+ new_action.instance_variable_set(:@defined_at, defined_at)
36
+ new_action.instance_variable_set(:@name, name)
37
+ new_action.instance_variable_set(:@controller, controller)
38
+ end
28
39
  end
29
40
 
30
- def permit(*no_type_attributes, **typed_attributes)
31
- no_type_attributes.each { |attribute| permit_attribute(attribute) }
32
- typed_attributes.each { |attribute, type| permit_attribute(attribute, type) }
41
+ def options(action_options = nil)
42
+ @input_attribute_options ||= {}
43
+ return @input_attribute_options if action_options.nil?
44
+
45
+ @input_attribute_options[:input_format] = action_options[:input_format] if action_options[:input_format]
46
+
33
47
  self
34
48
  end
35
49
 
36
- def paginated(pagination_options = {})
50
+ def paginated(*args)
37
51
  @return_type = nil
38
- @pagination_options = pagination_options
39
- permit(:before, :after, first: :int, last: :int)
52
+ super
40
53
  end
41
54
 
42
55
  def description(new_description = nil)
@@ -48,35 +61,51 @@ module GraphqlRails
48
61
  end
49
62
  end
50
63
 
51
- def can_return_nil
52
- @can_return_nil = true
53
- self
54
- end
55
-
56
64
  def returns(custom_return_type)
57
65
  @return_type = nil
58
66
  @custom_return_type = custom_return_type
59
67
  self
60
68
  end
61
69
 
62
- def can_return_nil?
63
- @can_return_nil
70
+ def model(model_name = nil)
71
+ if model_name
72
+ @model = model_name
73
+ self
74
+ else
75
+ @model || raise_missing_config_error
76
+ end
64
77
  end
65
78
 
66
- def paginated?
67
- !!pagination_options # rubocop:disable Style/DoubleNegation
79
+ def returns_single(required: true)
80
+ model_name = model.to_s
81
+ model_name = "#{model_name}!" if required
82
+
83
+ returns(model_name)
84
+ end
85
+
86
+ def returns_list(required_inner: true, required_list: true)
87
+ model_name = model.to_s
88
+ model_name = "#{model_name}!" if required_inner
89
+ list_name = "[#{model_name}]"
90
+ list_name = "#{list_name}!" if required_list
91
+
92
+ returns(list_name)
68
93
  end
69
94
 
70
95
  def return_type
71
96
  @return_type ||= build_return_type
72
97
  end
73
98
 
99
+ def type_parser
100
+ @type_parser ||= Attributes::TypeParser.new(custom_return_type, paginated: paginated?)
101
+ end
102
+
74
103
  private
75
104
 
76
- attr_reader :custom_return_type, :action_options
105
+ attr_reader :custom_return_type
77
106
 
78
107
  def build_return_type
79
- return nil if custom_return_type.nil?
108
+ return raise_deprecation_error if custom_return_type.nil?
80
109
 
81
110
  if paginated?
82
111
  type_parser.graphql_model ? type_parser.graphql_model.graphql.connection_type : nil
@@ -85,13 +114,24 @@ module GraphqlRails
85
114
  end
86
115
  end
87
116
 
88
- def type_parser
89
- GraphqlRails::Attribute::TypeParser.new(custom_return_type)
117
+ def raise_deprecation_error
118
+ message = \
119
+ 'Default return types are deprecated. ' \
120
+ "You need to manually set something like `action(:#{name}).returns('#{suggested_model_name}')`"
121
+
122
+ full_backtrace = ([defined_at] + caller).compact
123
+ raise DeprecatedDefaultModelError, message, full_backtrace
90
124
  end
91
125
 
92
- def permit_attribute(name, type = nil)
93
- field_name = name.to_s.remove(/!\Z/)
94
- attributes[field_name] = Model::InputAttribute.new(name.to_s, type, options: action_options)
126
+ def suggested_model_name
127
+ controller&.name.to_s.demodulize.sub(/Controller$/, '').singularize
128
+ end
129
+
130
+ def raise_missing_config_error
131
+ error_message = \
132
+ 'Default model for controller is not defined. To do so add `model(YourModel)`'
133
+
134
+ raise MissingConfigurationError, error_message
95
135
  end
96
136
  end
97
137
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql_rails/controller/action'
4
+ require 'graphql_rails/concerns/service'
5
+ require 'graphql_rails/controller/action_configuration'
6
+ require 'graphql_rails/controller/build_controller_action_resolver/controller_action_resolver'
7
+
8
+ module GraphqlRails
9
+ class Controller
10
+ # graphql resolver which redirects actions to appropriate controller and controller action
11
+ class BuildControllerActionResolver
12
+ include ::GraphqlRails::Service
13
+
14
+ def initialize(route:)
15
+ @route = route
16
+ end
17
+
18
+ def call # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
19
+ action = build_action
20
+
21
+ Class.new(ControllerActionResolver) do
22
+ type(*action.type_args, **action.type_options)
23
+ description(action.description)
24
+ controller(action.controller)
25
+ controller_action_name(action.name)
26
+
27
+ action.arguments.each do |attribute|
28
+ argument(*attribute.input_argument_args, **attribute.input_argument_options)
29
+ end
30
+
31
+ def self.inspect
32
+ "ControllerActionResolver(#{controller.name}##{controller_action_name})"
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :route
40
+
41
+ def build_action
42
+ Action.new(route).tap do |action|
43
+ assert_action(action)
44
+ end
45
+ end
46
+
47
+ def assert_action(action)
48
+ action.return_type
49
+ end
50
+ end
51
+ end
52
+ end