graphql_rails 0.8.0 → 1.2.2

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 (75) 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 +31 -0
  7. data/Gemfile +3 -2
  8. data/Gemfile.lock +147 -124
  9. data/docs/README.md +24 -8
  10. data/docs/_sidebar.md +3 -0
  11. data/docs/components/controller.md +194 -21
  12. data/docs/components/model.md +193 -5
  13. data/docs/components/routes.md +28 -0
  14. data/docs/getting_started/quick_start.md +10 -3
  15. data/docs/index.html +1 -1
  16. data/docs/other_tools/query_runner.md +49 -0
  17. data/docs/other_tools/schema_dump.md +29 -0
  18. data/docs/testing/testing.md +3 -1
  19. data/graphql_rails.gemspec +5 -5
  20. data/lib/generators/graphql_rails/install_generator.rb +50 -0
  21. data/lib/generators/graphql_rails/templates/example_users_controller.erb +19 -0
  22. data/lib/generators/graphql_rails/templates/graphql_application_controller.erb +8 -0
  23. data/lib/generators/graphql_rails/templates/graphql_controller.erb +20 -0
  24. data/lib/generators/graphql_rails/templates/graphql_router.erb +19 -0
  25. data/lib/generators/graphql_rails/templates/graphql_router_spec.erb +21 -0
  26. data/lib/graphql_rails.rb +2 -0
  27. data/lib/graphql_rails/attributes/attributable.rb +20 -21
  28. data/lib/graphql_rails/attributes/attribute.rb +41 -4
  29. data/lib/graphql_rails/attributes/attribute_name_parser.rb +4 -4
  30. data/lib/graphql_rails/attributes/input_attribute.rb +24 -10
  31. data/lib/graphql_rails/attributes/input_type_parser.rb +24 -46
  32. data/lib/graphql_rails/attributes/type_parseable.rb +132 -0
  33. data/lib/graphql_rails/attributes/type_parser.rb +58 -54
  34. data/lib/graphql_rails/concerns/service.rb +19 -0
  35. data/lib/graphql_rails/controller.rb +26 -22
  36. data/lib/graphql_rails/controller/action.rb +12 -67
  37. data/lib/graphql_rails/controller/action_configuration.rb +70 -34
  38. data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
  39. data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
  40. data/lib/graphql_rails/controller/configuration.rb +56 -5
  41. data/lib/graphql_rails/controller/log_controller_action.rb +11 -6
  42. data/lib/graphql_rails/controller/request.rb +29 -8
  43. data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
  44. data/lib/graphql_rails/decorator/relation_decorator.rb +1 -5
  45. data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
  46. data/lib/graphql_rails/errors/execution_error.rb +6 -7
  47. data/lib/graphql_rails/errors/system_error.rb +14 -0
  48. data/lib/graphql_rails/errors/validation_error.rb +1 -5
  49. data/lib/graphql_rails/input_configurable.rb +47 -0
  50. data/lib/graphql_rails/model.rb +19 -4
  51. data/lib/graphql_rails/model/add_fields_to_graphql_type.rb +45 -0
  52. data/lib/graphql_rails/model/build_connection_type.rb +52 -0
  53. data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +5 -5
  54. data/lib/graphql_rails/model/build_enum_type.rb +39 -10
  55. data/lib/graphql_rails/model/build_graphql_input_type.rb +8 -4
  56. data/lib/graphql_rails/model/call_graphql_model_method.rb +72 -0
  57. data/lib/graphql_rails/model/configurable.rb +6 -2
  58. data/lib/graphql_rails/model/configuration.rb +11 -10
  59. data/lib/graphql_rails/model/find_or_build_graphql_type.rb +64 -0
  60. data/lib/graphql_rails/model/find_or_build_graphql_type_class.rb +46 -0
  61. data/lib/graphql_rails/model/input.rb +10 -6
  62. data/lib/graphql_rails/query_runner.rb +68 -0
  63. data/lib/graphql_rails/railtie.rb +10 -0
  64. data/lib/graphql_rails/router.rb +40 -13
  65. data/lib/graphql_rails/router/resource_routes_builder.rb +10 -9
  66. data/lib/graphql_rails/router/route.rb +21 -6
  67. data/lib/graphql_rails/router/schema_builder.rb +30 -11
  68. data/lib/graphql_rails/rspec_controller_helpers.rb +6 -4
  69. data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
  70. data/lib/graphql_rails/tasks/schema.rake +14 -0
  71. data/lib/graphql_rails/version.rb +1 -1
  72. metadata +48 -21
  73. data/lib/graphql_rails/controller/controller_function.rb +0 -50
  74. data/lib/graphql_rails/controller/format_results.rb +0 -36
  75. 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,37 +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'
9
8
  require 'graphql_rails/controller/log_controller_action'
9
+ require 'graphql_rails/errors/system_error'
10
10
 
11
11
  module GraphqlRails
12
12
  # base class for all graphql_rails controllers
13
13
  class Controller
14
14
  class << self
15
- def inherited(sublass)
15
+ def inherited(subclass)
16
16
  super
17
- sublass.instance_variable_set(:@controller_configuration, controller_configuration.dup)
17
+ new_config = controller_configuration.dup_with(controller: subclass)
18
+ subclass.instance_variable_set(:@controller_configuration, new_config)
18
19
  end
19
20
 
20
- def before_action(*args, &block)
21
- 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)
22
23
  end
23
24
 
24
- def around_action(*args, &block)
25
- 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)
26
27
  end
27
28
 
28
- def after_action(*args, &block)
29
- 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)
30
31
  end
31
32
 
32
33
  def action(action_name)
33
34
  controller_configuration.action(action_name)
34
35
  end
35
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
+
36
45
  def controller_configuration
37
- @controller_configuration ||= Controller::Configuration.new
46
+ @controller_configuration ||= Controller::Configuration.new(self)
38
47
  end
39
48
  end
40
49
 
@@ -48,7 +57,7 @@ module GraphqlRails
48
57
  @action_name = method_name
49
58
  with_controller_action_logging do
50
59
  call_with_rendering
51
- format_controller_results
60
+ graphql_request.object_to_return
52
61
  end
53
62
  ensure
54
63
  @action_name = nil
@@ -78,7 +87,11 @@ module GraphqlRails
78
87
 
79
88
  render response if graphql_request.no_object_to_return?
80
89
  rescue StandardError => error
81
- 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
82
95
  end
83
96
 
84
97
  def graphql_errors_from_render_params(rendering_params)
@@ -86,7 +99,7 @@ module GraphqlRails
86
99
  return [] if rendering_params.keys.count != 1
87
100
 
88
101
  errors = rendering_params[:error] || rendering_params[:errors]
89
- Array(errors)
102
+ errors.is_a?(Enumerable) ? errors : Array(errors)
90
103
  end
91
104
 
92
105
  def with_controller_action_logging(&block)
@@ -98,14 +111,5 @@ module GraphqlRails
98
111
  &block
99
112
  )
100
113
  end
101
-
102
- def format_controller_results
103
- FormatResults.new(
104
- graphql_request.object_to_return,
105
- action_config: self.class.action(action_name),
106
- params: params,
107
- graphql_context: graphql_request.context
108
- ).call
109
- end
110
114
  end
111
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,50 +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_input(attribute) }
30
- typed_attributes.each { |attribute, type| permit_input(attribute, type: type) }
31
- self
32
- end
41
+ def options(action_options = nil)
42
+ @input_attribute_options ||= {}
43
+ return @input_attribute_options if action_options.nil?
33
44
 
34
- def permit_input(name, type: nil, options: {}, **input_options)
35
- field_name = name.to_s.remove(/!\Z/)
45
+ @input_attribute_options[:input_format] = action_options[:input_format] if action_options[:input_format]
36
46
 
37
- attributes[field_name] = Attributes::InputAttribute.new(
38
- name.to_s, type,
39
- options: action_options.merge(options),
40
- **input_options
41
- )
42
47
  self
43
48
  end
44
49
 
45
- def paginated(pagination_options = {})
50
+ def paginated(*args)
46
51
  @return_type = nil
47
- @pagination_options = pagination_options
48
- permit(:before, :after, first: :int, last: :int)
52
+ super
49
53
  end
50
54
 
51
55
  def description(new_description = nil)
@@ -57,35 +61,51 @@ module GraphqlRails
57
61
  end
58
62
  end
59
63
 
60
- def can_return_nil
61
- @can_return_nil = true
62
- self
63
- end
64
-
65
64
  def returns(custom_return_type)
66
65
  @return_type = nil
67
66
  @custom_return_type = custom_return_type
68
67
  self
69
68
  end
70
69
 
71
- def can_return_nil?
72
- @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
73
77
  end
74
78
 
75
- def paginated?
76
- !!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)
77
93
  end
78
94
 
79
95
  def return_type
80
96
  @return_type ||= build_return_type
81
97
  end
82
98
 
99
+ def type_parser
100
+ @type_parser ||= Attributes::TypeParser.new(custom_return_type, paginated: paginated?)
101
+ end
102
+
83
103
  private
84
104
 
85
- attr_reader :custom_return_type, :action_options
105
+ attr_reader :custom_return_type
86
106
 
87
107
  def build_return_type
88
- return nil if custom_return_type.nil?
108
+ return raise_deprecation_error if custom_return_type.nil?
89
109
 
90
110
  if paginated?
91
111
  type_parser.graphql_model ? type_parser.graphql_model.graphql.connection_type : nil
@@ -94,8 +114,24 @@ module GraphqlRails
94
114
  end
95
115
  end
96
116
 
97
- def type_parser
98
- 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
124
+ end
125
+
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
99
135
  end
100
136
  end
101
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