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
@@ -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
@@ -4,35 +4,46 @@ 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'
8
+ require 'graphql_rails/controller/log_controller_action'
9
+ require 'graphql_rails/errors/system_error'
9
10
 
10
11
  module GraphqlRails
11
12
  # base class for all graphql_rails controllers
12
13
  class Controller
13
14
  class << self
14
- def inherited(sublass)
15
- 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)
16
19
  end
17
20
 
18
- def before_action(*args, &block)
19
- 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)
20
23
  end
21
24
 
22
- def around_action(*args, &block)
23
- 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)
24
27
  end
25
28
 
26
- def after_action(*args, &block)
27
- 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)
28
31
  end
29
32
 
30
33
  def action(action_name)
31
34
  controller_configuration.action(action_name)
32
35
  end
33
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
+
34
45
  def controller_configuration
35
- @controller_configuration ||= Controller::Configuration.new
46
+ @controller_configuration ||= Controller::Configuration.new(self)
36
47
  end
37
48
  end
38
49
 
@@ -44,14 +55,10 @@ module GraphqlRails
44
55
 
45
56
  def call(method_name)
46
57
  @action_name = method_name
47
- call_with_rendering(method_name)
48
-
49
- FormatResults.new(
50
- graphql_request.object_to_return,
51
- action_config: self.class.action(method_name),
52
- params: params,
53
- graphql_context: graphql_request.context
54
- ).call
58
+ with_controller_action_logging do
59
+ call_with_rendering
60
+ graphql_request.object_to_return
61
+ end
55
62
  ensure
56
63
  @action_name = nil
57
64
  end
@@ -74,13 +81,17 @@ module GraphqlRails
74
81
 
75
82
  private
76
83
 
77
- def call_with_rendering(action_name)
84
+ def call_with_rendering
78
85
  hooks_runner = ActionHooksRunner.new(action_name: action_name, controller: self)
79
86
  response = hooks_runner.call { public_send(action_name) }
80
87
 
81
88
  render response if graphql_request.no_object_to_return?
82
89
  rescue StandardError => error
83
- 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
84
95
  end
85
96
 
86
97
  def graphql_errors_from_render_params(rendering_params)
@@ -88,7 +99,17 @@ module GraphqlRails
88
99
  return [] if rendering_params.keys.count != 1
89
100
 
90
101
  errors = rendering_params[:error] || rendering_params[:errors]
91
- 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
+ )
92
113
  end
93
114
  end
94
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
@@ -2,39 +2,54 @@
2
2
 
3
3
  require 'active_support/core_ext/string/filters'
4
4
  require 'graphql_rails/attributes'
5
+ require 'graphql_rails/input_configurable'
6
+ require 'graphql_rails/errors/error'
5
7
 
6
8
  module GraphqlRails
7
9
  class Controller
8
10
  # stores all graphql_rails contoller specific config
9
11
  class ActionConfiguration
10
- 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
11
18
 
12
19
  def initialize_copy(other)
13
20
  super
14
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)
15
24
  end
16
25
 
17
- def initialize
26
+ def initialize(name:, controller:)
27
+ @name = name
28
+ @controller = controller
18
29
  @attributes = {}
19
30
  @action_options = {}
20
- @can_return_nil = false
21
31
  end
22
32
 
23
- def options(input_format:)
24
- @action_options[:input_format] = input_format
25
- 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
26
39
  end
27
40
 
28
- def permit(*no_type_attributes, **typed_attributes)
29
- no_type_attributes.each { |attribute| permit_attribute(attribute) }
30
- 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
+
31
47
  self
32
48
  end
33
49
 
34
- def paginated(pagination_options = {})
50
+ def paginated(*args)
35
51
  @return_type = nil
36
- @pagination_options = pagination_options
37
- permit(:before, :after, first: :int, last: :int)
52
+ super
38
53
  end
39
54
 
40
55
  def description(new_description = nil)
@@ -46,35 +61,51 @@ module GraphqlRails
46
61
  end
47
62
  end
48
63
 
49
- def can_return_nil
50
- @can_return_nil = true
51
- self
52
- end
53
-
54
64
  def returns(custom_return_type)
55
65
  @return_type = nil
56
66
  @custom_return_type = custom_return_type
57
67
  self
58
68
  end
59
69
 
60
- def can_return_nil?
61
- @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
62
77
  end
63
78
 
64
- def paginated?
65
- !!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)
66
93
  end
67
94
 
68
95
  def return_type
69
96
  @return_type ||= build_return_type
70
97
  end
71
98
 
99
+ def type_parser
100
+ @type_parser ||= Attributes::TypeParser.new(custom_return_type, paginated: paginated?)
101
+ end
102
+
72
103
  private
73
104
 
74
- attr_reader :custom_return_type, :action_options
105
+ attr_reader :custom_return_type
75
106
 
76
107
  def build_return_type
77
- return nil if custom_return_type.nil?
108
+ return raise_deprecation_error if custom_return_type.nil?
78
109
 
79
110
  if paginated?
80
111
  type_parser.graphql_model ? type_parser.graphql_model.graphql.connection_type : nil
@@ -83,13 +114,24 @@ module GraphqlRails
83
114
  end
84
115
  end
85
116
 
86
- def type_parser
87
- Attributes::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
88
124
  end
89
125
 
90
- def permit_attribute(name, type = nil)
91
- field_name = name.to_s.remove(/!\Z/)
92
- attributes[field_name] = Attributes::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
93
135
  end
94
136
  end
95
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