graphql_rails 0.8.0 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
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