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,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql_rails/controller/request'
4
+
5
+ module GraphqlRails
6
+ class Controller
7
+ class BuildControllerActionResolver
8
+ # Resolver which includes controller specific methods.
9
+ # Used to simplify resolver build for each controller action
10
+ class ControllerActionResolver < GraphQL::Schema::Resolver
11
+ def self.controller(controller_class = nil)
12
+ @controller = controller_class if controller_class
13
+ @controller
14
+ end
15
+
16
+ def self.controller_action_name(name = nil)
17
+ @controller_action_name = name if name
18
+ @controller_action_name
19
+ end
20
+
21
+ def resolve(**inputs)
22
+ request = Request.new(object, inputs, context)
23
+ self.class.controller.new(request).call(self.class.controller_action_name)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -3,14 +3,20 @@
3
3
  require 'active_support/core_ext/string/inflections'
4
4
  require 'graphql_rails/controller/action_configuration'
5
5
  require 'graphql_rails/controller/action_hook'
6
+ require 'graphql_rails/errors/error'
6
7
 
7
8
  module GraphqlRails
8
9
  class Controller
9
10
  # stores all graphql_rails contoller specific config
10
11
  class Configuration
12
+ class InvalidActionConfiguration < GraphqlRails::Error; end
13
+
14
+ LIB_REGEXP = %r{/graphql_rails/lib/}
15
+
11
16
  attr_reader :action_by_name
12
17
 
13
- def initialize
18
+ def initialize(controller)
19
+ @controller = controller
14
20
  @hooks = {
15
21
  before: {},
16
22
  after: {},
@@ -18,6 +24,7 @@ module GraphqlRails
18
24
  }
19
25
 
20
26
  @action_by_name = {}
27
+ @action_default = nil
21
28
  end
22
29
 
23
30
  def initialize_copy(other)
@@ -31,6 +38,12 @@ module GraphqlRails
31
38
  end
32
39
  end
33
40
 
41
+ def dup_with(controller:)
42
+ dup.tap do |new_config|
43
+ new_config.instance_variable_set(:@controller, controller)
44
+ end
45
+ end
46
+
34
47
  def action_hooks_for(hook_type, action_name)
35
48
  hooks[hook_type].values.select { |hook| hook.applicable_for?(action_name) }
36
49
  end
@@ -43,15 +56,53 @@ module GraphqlRails
43
56
  ActionHook.new(name: hook_name, **options, &block)
44
57
  end
45
58
 
59
+ def action_default
60
+ @action_default ||= ActionConfiguration.new(name: :default, controller: nil)
61
+ yield(@action_default) if block_given?
62
+ @action_default
63
+ end
64
+
46
65
  def action(method_name)
47
- @action_by_name[method_name.to_s] ||= ActionConfiguration.new
48
- yield(@action_by_name[method_name.to_s]) if block_given?
49
- @action_by_name[method_name.to_s]
66
+ action_name = method_name.to_s.underscore
67
+ @action_by_name[action_name] ||= action_default.dup_with(
68
+ name: action_name,
69
+ controller: controller,
70
+ defined_at: dynamic_source_location
71
+ )
72
+ yield(@action_by_name[action_name]) if block_given?
73
+ @action_by_name[action_name]
74
+ end
75
+
76
+ def action_config(method_name)
77
+ action_name = method_name.to_s.underscore
78
+ @action_by_name.fetch(action_name) { raise_invalid_config_error(action_name) }
79
+ end
80
+
81
+ def model(model = nil)
82
+ action_default.model(model)
50
83
  end
51
84
 
52
85
  private
53
86
 
54
- attr_reader :hooks
87
+ attr_reader :hooks, :controller
88
+
89
+ def dynamic_source_location
90
+ project_trace = \
91
+ caller
92
+ .dup
93
+ .drop_while { |path| !path.match?(LIB_REGEXP) }
94
+ .drop_while { |path| path.match?(LIB_REGEXP) }
95
+
96
+ project_trace.first
97
+ end
98
+
99
+ def raise_invalid_config_error(action_name)
100
+ error_message = \
101
+ "Missing action configuration for #{controller}##{action_name}. " \
102
+ "Please define it with `action(:#{action_name})`."
103
+
104
+ raise InvalidActionConfiguration, error_message
105
+ end
55
106
  end
56
107
  end
57
108
  end
@@ -6,13 +6,13 @@ module GraphqlRails
6
6
  class Controller
7
7
  # logs controller start and end times
8
8
  class LogControllerAction
9
+ require 'graphql_rails/concerns/service'
10
+
11
+ include ::GraphqlRails::Service
12
+
9
13
  START_PROCESSING_KEY = 'start_processing.graphql_action_controller'
10
14
  PROCESS_ACTION_KEY = 'process_action.graphql_action_controller'
11
15
 
12
- def self.call(**kwargs, &block)
13
- new(**kwargs).call(&block)
14
- end
15
-
16
16
  def initialize(controller_name:, action_name:, params:, graphql_request:)
17
17
  @controller_name = controller_name
18
18
  @action_name = action_name
@@ -51,8 +51,7 @@ module GraphqlRails
51
51
  params
52
52
  else
53
53
  filter_options = Rails.configuration.filter_parameters
54
- parametter_filter = ActionDispatch::Http::ParameterFilter.new(filter_options)
55
- parametter_filter.filter(params)
54
+ parameter_filter_class.new(filter_options).filter(params)
56
55
  end
57
56
  end
58
57
 
@@ -61,6 +60,12 @@ module GraphqlRails
61
60
 
62
61
  Rails.application.config.filter_parameters || []
63
62
  end
63
+
64
+ def parameter_filter_class
65
+ return ActiveSupport::ParameterFilter if Object.const_defined?('ActiveSupport::ParameterFilter')
66
+
67
+ ActionDispatch::Http::ParameterFilter
68
+ end
64
69
  end
65
70
  end
66
71
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'graphql_rails/errors/execution_error'
4
-
5
3
  module GraphqlRails
6
4
  class Controller
7
5
  # Contains all info related with single request to controller
8
6
  class Request
7
+ require 'graphql_rails/controller/request/format_errors'
8
+
9
9
  attr_accessor :object_to_return
10
10
  attr_reader :errors, :context
11
11
 
@@ -16,12 +16,9 @@ module GraphqlRails
16
16
  end
17
17
 
18
18
  def errors=(new_errors)
19
- @errors = new_errors
19
+ @errors = FormatErrors.call(not_formatted_errors: new_errors)
20
20
 
21
- new_errors.each do |error|
22
- error_message = error.is_a?(String) ? error : error.message
23
- context.add_error(ExecutionError.new(error_message))
24
- end
21
+ @errors.each { |error| context.add_error(error) }
25
22
  end
26
23
 
27
24
  def no_object_to_return?
@@ -29,12 +26,36 @@ module GraphqlRails
29
26
  end
30
27
 
31
28
  def params
32
- inputs.to_h
29
+ deep_transform_values(inputs.to_h) do |val|
30
+ graphql_object_to_hash(val)
31
+ end
33
32
  end
34
33
 
35
34
  private
36
35
 
37
36
  attr_reader :graphql_object, :inputs
37
+
38
+ def graphql_object_to_hash(object)
39
+ if object.is_a?(GraphQL::Dig)
40
+ object.to_h
41
+ elsif object.is_a?(Array)
42
+ object.map { |item| graphql_object_to_hash(item) }
43
+ else
44
+ object
45
+ end
46
+ end
47
+
48
+ def deep_transform_values(hash, &block)
49
+ return hash unless hash.is_a?(Hash)
50
+
51
+ hash.transform_values do |val|
52
+ if val.is_a?(Hash)
53
+ deep_transform_values(val, &block)
54
+ else
55
+ yield(val)
56
+ end
57
+ end
58
+ end
38
59
  end
39
60
  end
40
61
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql_rails/concerns/service'
4
+ require 'graphql_rails/errors/execution_error'
5
+ require 'graphql_rails/errors/validation_error'
6
+ require 'graphql_rails/errors/custom_execution_error'
7
+
8
+ module GraphqlRails
9
+ class Controller
10
+ class Request
11
+ # Converts user provided free-form errors in to meaningfull graphql error classes
12
+ class FormatErrors
13
+ include Service
14
+
15
+ def initialize(not_formatted_errors:)
16
+ @not_formatted_errors = not_formatted_errors
17
+ end
18
+
19
+ def call
20
+ if validation_errors?
21
+ formatted_validation_errors
22
+ else
23
+ not_formatted_errors.map { |error| format_error(error) }
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :not_formatted_errors
30
+
31
+ def validation_errors?
32
+ defined?(ActiveModel) &&
33
+ defined?(ActiveModel::Errors) &&
34
+ not_formatted_errors.is_a?(ActiveModel::Errors)
35
+ end
36
+
37
+ def formatted_validation_errors
38
+ not_formatted_errors.map do |field, message|
39
+ ValidationError.new(message, field)
40
+ end
41
+ end
42
+
43
+ def format_error(error)
44
+ if error.is_a?(String)
45
+ ExecutionError.new(error)
46
+ elsif error.is_a?(GraphQL::ExecutionError)
47
+ error
48
+ elsif CustomExecutionError.accepts?(error)
49
+ message = error[:message] || error['message']
50
+ CustomExecutionError.new(message, error.except(:message, 'message'))
51
+ elsif error.respond_to?(:message)
52
+ ExecutionError.new(error.message)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -37,7 +37,7 @@ module GraphqlRails
37
37
  end
38
38
 
39
39
  def to_a
40
- @to_a ||= relation.to_a.map { |it| decorator.new(it) }
40
+ @to_a ||= relation.to_a.map { |it| decorator.new(it, *decorator_args) }
41
41
  end
42
42
 
43
43
  private
@@ -71,9 +71,5 @@ module GraphqlRails
71
71
  self.class.new(decorator: decorator, relation: new_relation, decorator_args: decorator_args)
72
72
  end
73
73
  end
74
-
75
- GraphQL::Relay::BaseConnection.register_connection_implementation(
76
- RelationDecorator, GraphQL::Relay::RelationConnection
77
- )
78
74
  end
79
75
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ # base class which is returned in case something bad happens. Contains all error rendering tructure
5
+ class CustomExecutionError < ExecutionError
6
+ attr_reader :extra_graphql_data
7
+
8
+ def self.accepts?(error)
9
+ error.is_a?(Hash) &&
10
+ (error.key?(:message) || error.key?('message'))
11
+ end
12
+
13
+ def initialize(message, extra_graphql_data = {})
14
+ super(message)
15
+ @extra_graphql_data = extra_graphql_data.stringify_keys
16
+ end
17
+
18
+ def to_h
19
+ super
20
+ end
21
+ end
22
+ end
@@ -6,15 +6,14 @@ module GraphqlRails
6
6
  # base class which is returned in case something bad happens. Contains all error rendering tructure
7
7
  class ExecutionError < GraphQL::ExecutionError
8
8
  def to_h
9
- super.except('locations').merge('type' => type, 'http_status_code' => http_status_code)
9
+ super.merge(extra_graphql_data)
10
10
  end
11
11
 
12
- def type
13
- 'system_error'
14
- end
15
-
16
- def http_status_code
17
- 500
12
+ def extra_graphql_data
13
+ {}.tap do |data|
14
+ data['type'] = type if respond_to?(:type) && type
15
+ data['code'] = type if respond_to?(:code) && code
16
+ end
18
17
  end
19
18
  end
20
19
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ # base class which is returned in case something bad happens. Contains all error rendering tructure
5
+ class SystemError < ExecutionError
6
+ def to_h
7
+ super.except('locations')
8
+ end
9
+
10
+ def type
11
+ 'system_error'
12
+ end
13
+ end
14
+ end
@@ -6,7 +6,7 @@ module GraphqlRails
6
6
  attr_reader :short_message, :field
7
7
 
8
8
  def initialize(short_message, field)
9
- super([field.presence, short_message].compact.join(' '))
9
+ super([field.presence&.to_s&.humanize, short_message].compact.join(' '))
10
10
  @short_message = short_message
11
11
  @field = field
12
12
  end
@@ -15,10 +15,6 @@ module GraphqlRails
15
15
  'validation_error'
16
16
  end
17
17
 
18
- def http_status_code
19
- 422
20
- end
21
-
22
18
  def to_h
23
19
  super.merge('field' => field, 'short_message' => short_message)
24
20
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ # contains configuration options related with inputs
5
+ module InputConfigurable
6
+ def permit(*no_type_attributes, **typed_attributes)
7
+ no_type_attributes.each { |attribute| permit_input(attribute) }
8
+ typed_attributes.each { |attribute, type| permit_input(attribute, type: type) }
9
+ self
10
+ end
11
+
12
+ def permit_input(name, **input_options)
13
+ field_name = name.to_s.remove(/!\Z/)
14
+
15
+ attributes[field_name] = build_input_attribute(name.to_s, **input_options)
16
+ self
17
+ end
18
+
19
+ def paginated(pagination_options = {})
20
+ pagination_options = {} if pagination_options == true
21
+ pagination_options = nil if pagination_options == false
22
+
23
+ @pagination_options = pagination_options
24
+ permit(:before, :after, first: :int, last: :int)
25
+ end
26
+
27
+ def paginated?
28
+ !pagination_options.nil?
29
+ end
30
+
31
+ def pagination_options
32
+ @pagination_options
33
+ end
34
+
35
+ def input_attribute_options
36
+ @input_attribute_options || {}
37
+ end
38
+
39
+ def build_input_attribute(name, options: {}, **other_options)
40
+ Attributes::InputAttribute.new(
41
+ name.to_s,
42
+ options: input_attribute_options.merge(options),
43
+ **other_options
44
+ )
45
+ end
46
+ end
47
+ end