graphql_rails 0.8.0 → 1.0.0

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/Gemfile.lock +20 -23
  4. data/docs/README.md +21 -5
  5. data/docs/_sidebar.md +3 -0
  6. data/docs/components/controller.md +174 -17
  7. data/docs/components/model.md +151 -3
  8. data/docs/components/routes.md +28 -0
  9. data/docs/getting_started/quick_start.md +9 -2
  10. data/docs/other_tools/query_runner.md +49 -0
  11. data/docs/other_tools/schema_dump.md +29 -0
  12. data/docs/testing/testing.md +3 -1
  13. data/graphql_rails.gemspec +1 -1
  14. data/lib/generators/graphql_rails/install_generator.rb +50 -0
  15. data/lib/generators/graphql_rails/templates/example_users_controller.erb +19 -0
  16. data/lib/generators/graphql_rails/templates/graphql_application_controller.erb +8 -0
  17. data/lib/generators/graphql_rails/templates/graphql_controller.erb +20 -0
  18. data/lib/generators/graphql_rails/templates/graphql_router.erb +19 -0
  19. data/lib/generators/graphql_rails/templates/graphql_router_spec.erb +18 -0
  20. data/lib/graphql_rails.rb +2 -0
  21. data/lib/graphql_rails/attributes/attributable.rb +16 -13
  22. data/lib/graphql_rails/attributes/attribute.rb +20 -3
  23. data/lib/graphql_rails/attributes/input_attribute.rb +20 -10
  24. data/lib/graphql_rails/attributes/input_type_parser.rb +24 -46
  25. data/lib/graphql_rails/attributes/type_parseable.rb +128 -0
  26. data/lib/graphql_rails/attributes/type_parser.rb +58 -54
  27. data/lib/graphql_rails/concerns/service.rb +15 -0
  28. data/lib/graphql_rails/controller.rb +20 -16
  29. data/lib/graphql_rails/controller/action.rb +10 -69
  30. data/lib/graphql_rails/controller/action_configuration.rb +70 -34
  31. data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
  32. data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
  33. data/lib/graphql_rails/controller/configuration.rb +56 -5
  34. data/lib/graphql_rails/controller/log_controller_action.rb +4 -4
  35. data/lib/graphql_rails/controller/request.rb +29 -8
  36. data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
  37. data/lib/graphql_rails/decorator/relation_decorator.rb +1 -1
  38. data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
  39. data/lib/graphql_rails/errors/execution_error.rb +6 -7
  40. data/lib/graphql_rails/errors/system_error.rb +14 -0
  41. data/lib/graphql_rails/errors/validation_error.rb +1 -5
  42. data/lib/graphql_rails/input_configurable.rb +47 -0
  43. data/lib/graphql_rails/model.rb +19 -4
  44. data/lib/graphql_rails/model/build_connection_type.rb +48 -0
  45. data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +4 -4
  46. data/lib/graphql_rails/model/build_enum_type.rb +39 -10
  47. data/lib/graphql_rails/model/build_graphql_input_type.rb +7 -3
  48. data/lib/graphql_rails/model/build_graphql_type.rb +23 -7
  49. data/lib/graphql_rails/model/call_graphql_model_method.rb +59 -0
  50. data/lib/graphql_rails/model/configuration.rb +2 -6
  51. data/lib/graphql_rails/model/input.rb +10 -6
  52. data/lib/graphql_rails/query_runner.rb +68 -0
  53. data/lib/graphql_rails/railtie.rb +10 -0
  54. data/lib/graphql_rails/router.rb +40 -13
  55. data/lib/graphql_rails/router/resource_routes_builder.rb +2 -1
  56. data/lib/graphql_rails/router/route.rb +25 -6
  57. data/lib/graphql_rails/router/schema_builder.rb +26 -11
  58. data/lib/graphql_rails/rspec_controller_helpers.rb +4 -2
  59. data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
  60. data/lib/graphql_rails/tasks/schema.rake +14 -0
  61. data/lib/graphql_rails/version.rb +1 -1
  62. metadata +29 -9
  63. data/lib/graphql_rails/controller/controller_function.rb +0 -50
  64. data/lib/graphql_rails/controller/format_results.rb +0 -36
@@ -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
@@ -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(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
@@ -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
@@ -17,10 +17,6 @@ module GraphqlRails
17
17
  #
18
18
  # YourModel.new.graphql_type # => type with [:id, :title] attributes
19
19
  module Model
20
- def self.included(base)
21
- base.extend(ClassMethods)
22
- end
23
-
24
20
  # static methods for GraphqlRails::Model
25
21
  module ClassMethods
26
22
  def inherited(subclass)
@@ -36,5 +32,24 @@ module GraphqlRails
36
32
  @graphql
37
33
  end
38
34
  end
35
+
36
+ def self.included(base)
37
+ base.extend(ClassMethods)
38
+ end
39
+
40
+ def graphql_context
41
+ @graphql_context
42
+ end
43
+
44
+ def graphql_context=(value)
45
+ @graphql_context = value
46
+ end
47
+
48
+ def with_graphql_context(graphql_context)
49
+ self.graphql_context = graphql_context
50
+ yield(self)
51
+ ensure
52
+ self.graphql_context = nil
53
+ end
39
54
  end
40
55
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+ require 'graphql_rails/model/build_connection_type/count_items'
5
+
6
+ module GraphqlRails
7
+ module Model
8
+ # builds connection type from graphql type with some extra attributes
9
+ class BuildConnectionType
10
+ require 'graphql_rails/concerns/service'
11
+
12
+ include ::GraphqlRails::Service
13
+
14
+ attr_reader :initial_type
15
+
16
+ def initialize(initial_type)
17
+ @initial_type = initial_type
18
+ end
19
+
20
+ def call
21
+ build_connection_type
22
+ end
23
+
24
+ private
25
+
26
+ def build_connection_type
27
+ edge_type = build_edge_type
28
+ type = initial_type
29
+ Class.new(GraphQL::Types::Relay::BaseConnection) do
30
+ graphql_name("#{type.graphql_name}Connection")
31
+ edge_type(edge_type)
32
+
33
+ field :total, Integer, null: false, resolve: CountItems
34
+ end
35
+ end
36
+
37
+ def build_edge_type
38
+ type = initial_type
39
+
40
+ Class.new(GraphQL::Types::Relay::BaseEdge) do
41
+ graphql_name("#{type.graphql_name}Edge")
42
+
43
+ node_type(type)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -2,13 +2,13 @@
2
2
 
3
3
  module GraphqlRails
4
4
  module Model
5
- class Configuration
5
+ class BuildConnectionType
6
6
  # Used when generating ConnectionType.
7
7
  # It handles all the logic which is related with counting total items
8
8
  class CountItems
9
- def self.call(*args)
10
- new(*args).call
11
- end
9
+ require 'graphql_rails/concerns/service'
10
+
11
+ include ::GraphqlRails::Service
12
12
 
13
13
  def initialize(graphql_object, _args, _ctx)
14
14
  @graphql_object = graphql_object
@@ -7,9 +7,10 @@ module GraphqlRails
7
7
  module Model
8
8
  # contains info about single graphql attribute
9
9
  class BuildEnumType
10
- def self.call(*args)
11
- new(*args).call
12
- end
10
+ class InvalidEnum < GraphqlRails::Error; end
11
+ require 'graphql_rails/concerns/service'
12
+
13
+ include ::GraphqlRails::Service
13
14
 
14
15
  def initialize(name, allowed_values:, description: nil)
15
16
  @name = name
@@ -18,22 +19,50 @@ module GraphqlRails
18
19
  end
19
20
 
20
21
  def call
21
- allowed_values = self.allowed_values
22
- enum_name = name.to_s.camelize
23
- enum_description = description
22
+ validate
23
+ build_enum
24
+ end
25
+
26
+ protected
27
+
28
+ attr_reader :name, :allowed_values, :description
29
+
30
+ def validate
31
+ return if allowed_values.is_a?(Array) && !allowed_values.empty?
32
+
33
+ validate_enum_type
34
+ validate_enum_content
35
+ end
36
+
37
+ def validate_enum_type
38
+ return if allowed_values.is_a?(Array)
39
+
40
+ raise InvalidEnum, "Enum must be instance of Array, but instance of #{allowed_values.class} was given"
41
+ end
42
+
43
+ def validate_enum_content
44
+ return unless allowed_values.empty?
45
+
46
+ raise InvalidEnum, 'At lest one enum option must be given'
47
+ end
48
+
49
+ def formatted_name
50
+ name.to_s.camelize
51
+ end
24
52
 
53
+ def build_enum(allowed_values: self.allowed_values, enum_name: formatted_name, enum_description: description)
25
54
  Class.new(GraphQL::Schema::Enum) do
26
55
  allowed_values.each do |allowed_value|
27
56
  graphql_name(enum_name)
28
57
  description(enum_description) if enum_description
29
58
  value(allowed_value.to_s.underscore.upcase, value: allowed_value)
30
59
  end
60
+
61
+ def self.inspect
62
+ "#{GraphQL::Schema::Enum}(#{graphql_name})"
63
+ end
31
64
  end
32
65
  end
33
-
34
- protected
35
-
36
- attr_reader :name, :allowed_values, :description
37
66
  end
38
67
  end
39
68
  end