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
@@ -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