graphql_rails 0.6.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) 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 +38 -0
  7. data/Gemfile +3 -2
  8. data/Gemfile.lock +181 -71
  9. data/docs/README.md +48 -9
  10. data/docs/_sidebar.md +5 -0
  11. data/docs/components/controller.md +294 -8
  12. data/docs/components/decorator.md +69 -0
  13. data/docs/components/model.md +349 -11
  14. data/docs/components/routes.md +43 -1
  15. data/docs/getting_started/quick_start.md +9 -2
  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 +7 -1
  29. data/lib/graphql_rails/attributes.rb +13 -0
  30. data/lib/graphql_rails/{attribute → attributes}/attributable.rb +25 -20
  31. data/lib/graphql_rails/attributes/attribute.rb +94 -0
  32. data/lib/graphql_rails/{attribute → attributes}/attribute_name_parser.rb +2 -2
  33. data/lib/graphql_rails/attributes/input_attribute.rb +65 -0
  34. data/lib/graphql_rails/attributes/input_type_parser.rb +62 -0
  35. data/lib/graphql_rails/attributes/type_name_info.rb +38 -0
  36. data/lib/graphql_rails/attributes/type_parseable.rb +128 -0
  37. data/lib/graphql_rails/attributes/type_parser.rb +121 -0
  38. data/lib/graphql_rails/concerns/service.rb +19 -0
  39. data/lib/graphql_rails/controller.rb +42 -21
  40. data/lib/graphql_rails/controller/action.rb +12 -67
  41. data/lib/graphql_rails/controller/action_configuration.rb +71 -31
  42. data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
  43. data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
  44. data/lib/graphql_rails/controller/configuration.rb +56 -4
  45. data/lib/graphql_rails/controller/log_controller_action.rb +71 -0
  46. data/lib/graphql_rails/controller/request.rb +29 -8
  47. data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
  48. data/lib/graphql_rails/decorator.rb +41 -0
  49. data/lib/graphql_rails/decorator/relation_decorator.rb +75 -0
  50. data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
  51. data/lib/graphql_rails/errors/execution_error.rb +8 -7
  52. data/lib/graphql_rails/errors/system_error.rb +14 -0
  53. data/lib/graphql_rails/errors/validation_error.rb +1 -5
  54. data/lib/graphql_rails/input_configurable.rb +47 -0
  55. data/lib/graphql_rails/integrations.rb +19 -0
  56. data/lib/graphql_rails/integrations/lograge.rb +39 -0
  57. data/lib/graphql_rails/integrations/sentry.rb +34 -0
  58. data/lib/graphql_rails/model.rb +26 -4
  59. data/lib/graphql_rails/model/add_fields_to_graphql_type.rb +45 -0
  60. data/lib/graphql_rails/model/build_connection_type.rb +52 -0
  61. data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +5 -5
  62. data/lib/graphql_rails/model/build_enum_type.rb +68 -0
  63. data/lib/graphql_rails/model/{graphql_input_type_builder.rb → build_graphql_input_type.rb} +10 -2
  64. data/lib/graphql_rails/model/call_graphql_model_method.rb +72 -0
  65. data/lib/graphql_rails/model/configurable.rb +6 -2
  66. data/lib/graphql_rails/model/configuration.rb +34 -19
  67. data/lib/graphql_rails/model/find_or_build_graphql_type.rb +64 -0
  68. data/lib/graphql_rails/model/find_or_build_graphql_type_class.rb +46 -0
  69. data/lib/graphql_rails/model/input.rb +25 -11
  70. data/lib/graphql_rails/query_runner.rb +68 -0
  71. data/lib/graphql_rails/railtie.rb +10 -0
  72. data/lib/graphql_rails/router.rb +40 -13
  73. data/lib/graphql_rails/router/resource_routes_builder.rb +19 -11
  74. data/lib/graphql_rails/router/route.rb +21 -6
  75. data/lib/graphql_rails/router/schema_builder.rb +36 -11
  76. data/lib/graphql_rails/rspec_controller_helpers.rb +6 -4
  77. data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
  78. data/lib/graphql_rails/tasks/schema.rake +14 -0
  79. data/lib/graphql_rails/version.rb +1 -1
  80. metadata +78 -26
  81. data/README.md +0 -194
  82. data/lib/graphql_rails/attribute.rb +0 -28
  83. data/lib/graphql_rails/attribute/type_parser.rb +0 -115
  84. data/lib/graphql_rails/controller/controller_function.rb +0 -50
  85. data/lib/graphql_rails/controller/format_results.rb +0 -36
  86. data/lib/graphql_rails/model/graphql_type_builder.rb +0 -33
  87. data/lib/graphql_rails/model/input_attribute.rb +0 -47
@@ -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
@@ -1,18 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GraphqlRails
4
+ require 'graphql'
5
+
4
6
  # base class which is returned in case something bad happens. Contains all error rendering tructure
5
7
  class ExecutionError < GraphQL::ExecutionError
6
8
  def to_h
7
- super.except('locations').merge('type' => type, 'http_status_code' => http_status_code)
8
- end
9
-
10
- def type
11
- 'system_error'
9
+ super.merge(extra_graphql_data)
12
10
  end
13
11
 
14
- def http_status_code
15
- 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
16
17
  end
17
18
  end
18
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
 
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphql'
4
+ require 'graphql_rails/attributes/attributable'
5
+
6
+ module GraphqlRails
7
+ module Model
8
+ # contains info about single graphql attribute
9
+ class BuildEnumType
10
+ class InvalidEnum < GraphqlRails::Error; end
11
+ require 'graphql_rails/concerns/service'
12
+
13
+ include ::GraphqlRails::Service
14
+
15
+ def initialize(name, allowed_values:, description: nil)
16
+ @name = name
17
+ @allowed_values = allowed_values
18
+ @description = description
19
+ end
20
+
21
+ def call
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
52
+
53
+ def build_enum(allowed_values: self.allowed_values, enum_name: formatted_name, enum_description: description)
54
+ Class.new(GraphQL::Schema::Enum) do
55
+ allowed_values.each do |allowed_value|
56
+ graphql_name(enum_name)
57
+ description(enum_description) if enum_description
58
+ value(allowed_value.to_s.underscore.upcase, value: allowed_value)
59
+ end
60
+
61
+ def self.inspect
62
+ "#{GraphQL::Schema::Enum}(#{graphql_name})"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end