graphql_rails 0.8.0 → 1.0.0

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