graphql_rails 0.7.0 → 1.2.1

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 (82) 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 +35 -0
  7. data/Gemfile +3 -2
  8. data/Gemfile.lock +181 -71
  9. data/docs/README.md +40 -8
  10. data/docs/_sidebar.md +5 -0
  11. data/docs/components/controller.md +295 -9
  12. data/docs/components/decorator.md +69 -0
  13. data/docs/components/model.md +267 -6
  14. data/docs/components/routes.md +28 -0
  15. data/docs/getting_started/quick_start.md +10 -3
  16. data/docs/index.html +1 -1
  17. data/docs/logging_and_monitoring/logging_and_monitoring.md +35 -0
  18. data/docs/other_tools/query_runner.md +49 -0
  19. data/docs/other_tools/schema_dump.md +29 -0
  20. data/docs/testing/testing.md +3 -1
  21. data/graphql_rails.gemspec +5 -4
  22. data/lib/generators/graphql_rails/install_generator.rb +50 -0
  23. data/lib/generators/graphql_rails/templates/example_users_controller.erb +19 -0
  24. data/lib/generators/graphql_rails/templates/graphql_application_controller.erb +8 -0
  25. data/lib/generators/graphql_rails/templates/graphql_controller.erb +20 -0
  26. data/lib/generators/graphql_rails/templates/graphql_router.erb +19 -0
  27. data/lib/generators/graphql_rails/templates/graphql_router_spec.erb +21 -0
  28. data/lib/graphql_rails.rb +6 -0
  29. data/lib/graphql_rails/attributes/attributable.rb +22 -17
  30. data/lib/graphql_rails/attributes/attribute.rb +67 -3
  31. data/lib/graphql_rails/attributes/attribute_name_parser.rb +4 -4
  32. data/lib/graphql_rails/attributes/input_attribute.rb +33 -15
  33. data/lib/graphql_rails/attributes/input_type_parser.rb +62 -0
  34. data/lib/graphql_rails/attributes/type_name_info.rb +38 -0
  35. data/lib/graphql_rails/attributes/type_parseable.rb +132 -0
  36. data/lib/graphql_rails/attributes/type_parser.rb +59 -53
  37. data/lib/graphql_rails/concerns/service.rb +19 -0
  38. data/lib/graphql_rails/controller.rb +42 -21
  39. data/lib/graphql_rails/controller/action.rb +12 -67
  40. data/lib/graphql_rails/controller/action_configuration.rb +70 -28
  41. data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
  42. data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
  43. data/lib/graphql_rails/controller/configuration.rb +56 -3
  44. data/lib/graphql_rails/controller/log_controller_action.rb +71 -0
  45. data/lib/graphql_rails/controller/request.rb +29 -8
  46. data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
  47. data/lib/graphql_rails/decorator.rb +41 -0
  48. data/lib/graphql_rails/decorator/relation_decorator.rb +75 -0
  49. data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
  50. data/lib/graphql_rails/errors/execution_error.rb +6 -7
  51. data/lib/graphql_rails/errors/system_error.rb +14 -0
  52. data/lib/graphql_rails/errors/validation_error.rb +1 -5
  53. data/lib/graphql_rails/input_configurable.rb +47 -0
  54. data/lib/graphql_rails/integrations.rb +19 -0
  55. data/lib/graphql_rails/integrations/lograge.rb +39 -0
  56. data/lib/graphql_rails/integrations/sentry.rb +34 -0
  57. data/lib/graphql_rails/model.rb +26 -4
  58. data/lib/graphql_rails/model/add_fields_to_graphql_type.rb +45 -0
  59. data/lib/graphql_rails/model/build_connection_type.rb +52 -0
  60. data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +5 -5
  61. data/lib/graphql_rails/model/build_enum_type.rb +39 -10
  62. data/lib/graphql_rails/model/build_graphql_input_type.rb +8 -4
  63. data/lib/graphql_rails/model/call_graphql_model_method.rb +72 -0
  64. data/lib/graphql_rails/model/configurable.rb +6 -2
  65. data/lib/graphql_rails/model/configuration.rb +30 -16
  66. data/lib/graphql_rails/model/find_or_build_graphql_type.rb +64 -0
  67. data/lib/graphql_rails/model/find_or_build_graphql_type_class.rb +46 -0
  68. data/lib/graphql_rails/model/input.rb +11 -7
  69. data/lib/graphql_rails/query_runner.rb +68 -0
  70. data/lib/graphql_rails/railtie.rb +10 -0
  71. data/lib/graphql_rails/router.rb +40 -13
  72. data/lib/graphql_rails/router/resource_routes_builder.rb +10 -9
  73. data/lib/graphql_rails/router/route.rb +21 -6
  74. data/lib/graphql_rails/router/schema_builder.rb +30 -11
  75. data/lib/graphql_rails/rspec_controller_helpers.rb +6 -4
  76. data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
  77. data/lib/graphql_rails/tasks/schema.rake +14 -0
  78. data/lib/graphql_rails/version.rb +1 -1
  79. metadata +70 -19
  80. data/lib/graphql_rails/controller/controller_function.rb +0 -50
  81. data/lib/graphql_rails/controller/format_results.rb +0 -36
  82. data/lib/graphql_rails/model/build_graphql_type.rb +0 -37
@@ -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
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ # allows to enable various integrations
5
+ module Integrations
6
+ def self.enable(*integrations)
7
+ @enabled_integrations ||= []
8
+
9
+ to_be_enabled_integrations = integrations.map(&:to_s) - @enabled_integrations
10
+
11
+ to_be_enabled_integrations.each do |integration|
12
+ require_relative "./integrations/#{integration}"
13
+ Integrations.const_get(integration.classify).enable
14
+ end
15
+
16
+ @enabled_integrations += to_be_enabled_integrations
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ module Integrations
5
+ # lograge integration
6
+ #
7
+ # usage:
8
+ # add `GraphqlRails::Integrations::Lograge.enable` in your initializers
9
+ module Lograge
10
+ require 'lograge'
11
+
12
+ # lograge subscriber for graphql_rails controller events
13
+ class GraphqlActionControllerSubscriber < ::Lograge::LogSubscribers::Base
14
+ def process_action(event)
15
+ process_main_event(event)
16
+ end
17
+
18
+ private
19
+
20
+ def initial_data(payload)
21
+ {
22
+ controller: payload[:controller],
23
+ action: payload[:action]
24
+ }
25
+ end
26
+ end
27
+
28
+ def self.enable
29
+ return unless active?
30
+
31
+ GraphqlActionControllerSubscriber.attach_to :graphql_action_controller
32
+ end
33
+
34
+ def self.active?
35
+ !defined?(Rails) || Rails.configuration&.lograge&.enabled
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ module Integrations
5
+ # sentry integration
6
+ module Sentry
7
+ require 'active_support/concern'
8
+
9
+ # controller extension which logs errors to sentry
10
+ module SentryLogger
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ around_action :log_to_sentry
15
+
16
+ protected
17
+
18
+ def log_to_sentry
19
+ Raven.context.transaction.pop
20
+ Raven.context.transaction.push "#{self.class}##{action_name}"
21
+ yield
22
+ rescue Exception => error # rubocop:disable Lint/RescueException
23
+ Raven.capture_exception(error) unless error.is_a?(GraphQL::ExecutionError)
24
+ raise error
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.enable
30
+ GraphqlRails::Controller.include(SentryLogger)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -17,17 +17,39 @@ 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
22
+ def inherited(subclass)
23
+ super
24
+ subclass.instance_variable_set(:@graphql, graphql.dup)
25
+ subclass.graphql.instance_variable_set(:@model_class, self)
26
+ subclass.graphql.instance_variable_set(:@graphql_type, nil)
27
+ end
28
+
26
29
  def graphql
27
30
  @graphql ||= Model::Configuration.new(self)
28
31
  yield(@graphql) if block_given?
29
32
  @graphql
30
33
  end
31
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
32
54
  end
33
55
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlRails
4
+ module Model
5
+ # Adds graphql attributes as graphql fields to given graphql schema object.
6
+ class AddFieldsToGraphqlType
7
+ require 'graphql_rails/concerns/service'
8
+ require 'graphql_rails/model/call_graphql_model_method'
9
+
10
+ include ::GraphqlRails::Service
11
+
12
+ def initialize(klass:, attributes:)
13
+ @klass = klass
14
+ @attributes = attributes
15
+ end
16
+
17
+ def call
18
+ attributes.each { |attribute| define_graphql_field(attribute) }
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :attributes, :klass
24
+
25
+ def define_graphql_field(attribute) # rubocop:disable Metrics/MethodLength)
26
+ klass.class_eval do
27
+ field(*attribute.field_args, **attribute.field_options) do
28
+ attribute.attributes.values.each do |arg_attribute|
29
+ argument(*arg_attribute.input_argument_args, **arg_attribute.input_argument_options)
30
+ end
31
+ end
32
+
33
+ define_method(attribute.field_name) do |**kwargs|
34
+ CallGraphqlModelMethod.call(
35
+ model: object,
36
+ attribute_config: attribute,
37
+ method_keyword_arguments: kwargs,
38
+ graphql_context: context
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,52 @@
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
34
+
35
+ def total
36
+ CountItems.call(self)
37
+ end
38
+ end
39
+ end
40
+
41
+ def build_edge_type
42
+ type = initial_type
43
+
44
+ Class.new(GraphQL::Types::Relay::BaseEdge) do
45
+ graphql_name("#{type.graphql_name}Edge")
46
+
47
+ node_type(type)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -2,15 +2,15 @@
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
- def initialize(graphql_object, _args, _ctx)
13
+ def initialize(graphql_object)
14
14
  @graphql_object = graphql_object
15
15
  end
16
16
 
@@ -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
@@ -4,9 +4,9 @@ module GraphqlRails
4
4
  module Model
5
5
  # stores information about model specific config, like attributes and types
6
6
  class BuildGraphqlInputType
7
- def self.call(*args)
8
- new(*args).call
9
- end
7
+ require 'graphql_rails/concerns/service'
8
+
9
+ include ::GraphqlRails::Service
10
10
 
11
11
  def initialize(name:, description: nil, attributes:)
12
12
  @name = name
@@ -24,7 +24,11 @@ module GraphqlRails
24
24
  description(type_description)
25
25
 
26
26
  type_attributes.each_value do |type_attribute|
27
- argument(*type_attribute.input_argument_args)
27
+ argument(*type_attribute.input_argument_args, **type_attribute.input_argument_options)
28
+ end
29
+
30
+ def self.inspect
31
+ "#{GraphQL::Schema::InputObject}(#{graphql_name})"
28
32
  end
29
33
  end
30
34
  end