graphql_rails 0.6.0 → 1.2.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.
- checksums.yaml +4 -4
- data/.hound.yml +1 -0
- data/.rubocop.yml +3 -3
- data/.ruby-version +1 -1
- data/.travis.yml +2 -2
- data/CHANGELOG.md +38 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +181 -71
- data/docs/README.md +48 -9
- data/docs/_sidebar.md +5 -0
- data/docs/components/controller.md +294 -8
- data/docs/components/decorator.md +69 -0
- data/docs/components/model.md +349 -11
- data/docs/components/routes.md +43 -1
- data/docs/getting_started/quick_start.md +9 -2
- data/docs/index.html +1 -1
- data/docs/logging_and_monitoring/logging_and_monitoring.md +35 -0
- data/docs/other_tools/query_runner.md +49 -0
- data/docs/other_tools/schema_dump.md +29 -0
- data/docs/testing/testing.md +3 -1
- data/graphql_rails.gemspec +5 -4
- data/lib/generators/graphql_rails/install_generator.rb +50 -0
- data/lib/generators/graphql_rails/templates/example_users_controller.erb +19 -0
- data/lib/generators/graphql_rails/templates/graphql_application_controller.erb +8 -0
- data/lib/generators/graphql_rails/templates/graphql_controller.erb +20 -0
- data/lib/generators/graphql_rails/templates/graphql_router.erb +19 -0
- data/lib/generators/graphql_rails/templates/graphql_router_spec.erb +21 -0
- data/lib/graphql_rails.rb +7 -1
- data/lib/graphql_rails/attributes.rb +13 -0
- data/lib/graphql_rails/{attribute → attributes}/attributable.rb +25 -20
- data/lib/graphql_rails/attributes/attribute.rb +94 -0
- data/lib/graphql_rails/{attribute → attributes}/attribute_name_parser.rb +2 -2
- data/lib/graphql_rails/attributes/input_attribute.rb +65 -0
- data/lib/graphql_rails/attributes/input_type_parser.rb +62 -0
- data/lib/graphql_rails/attributes/type_name_info.rb +38 -0
- data/lib/graphql_rails/attributes/type_parseable.rb +128 -0
- data/lib/graphql_rails/attributes/type_parser.rb +121 -0
- data/lib/graphql_rails/concerns/service.rb +19 -0
- data/lib/graphql_rails/controller.rb +42 -21
- data/lib/graphql_rails/controller/action.rb +12 -67
- data/lib/graphql_rails/controller/action_configuration.rb +71 -31
- data/lib/graphql_rails/controller/build_controller_action_resolver.rb +52 -0
- data/lib/graphql_rails/controller/build_controller_action_resolver/controller_action_resolver.rb +28 -0
- data/lib/graphql_rails/controller/configuration.rb +56 -4
- data/lib/graphql_rails/controller/log_controller_action.rb +71 -0
- data/lib/graphql_rails/controller/request.rb +29 -8
- data/lib/graphql_rails/controller/request/format_errors.rb +58 -0
- data/lib/graphql_rails/decorator.rb +41 -0
- data/lib/graphql_rails/decorator/relation_decorator.rb +75 -0
- data/lib/graphql_rails/errors/custom_execution_error.rb +22 -0
- data/lib/graphql_rails/errors/execution_error.rb +8 -7
- data/lib/graphql_rails/errors/system_error.rb +14 -0
- data/lib/graphql_rails/errors/validation_error.rb +1 -5
- data/lib/graphql_rails/input_configurable.rb +47 -0
- data/lib/graphql_rails/integrations.rb +19 -0
- data/lib/graphql_rails/integrations/lograge.rb +39 -0
- data/lib/graphql_rails/integrations/sentry.rb +34 -0
- data/lib/graphql_rails/model.rb +26 -4
- data/lib/graphql_rails/model/add_fields_to_graphql_type.rb +45 -0
- data/lib/graphql_rails/model/build_connection_type.rb +52 -0
- data/lib/graphql_rails/model/{configuration → build_connection_type}/count_items.rb +5 -5
- data/lib/graphql_rails/model/build_enum_type.rb +68 -0
- data/lib/graphql_rails/model/{graphql_input_type_builder.rb → build_graphql_input_type.rb} +10 -2
- data/lib/graphql_rails/model/call_graphql_model_method.rb +72 -0
- data/lib/graphql_rails/model/configurable.rb +6 -2
- data/lib/graphql_rails/model/configuration.rb +34 -19
- data/lib/graphql_rails/model/find_or_build_graphql_type.rb +64 -0
- data/lib/graphql_rails/model/find_or_build_graphql_type_class.rb +46 -0
- data/lib/graphql_rails/model/input.rb +25 -11
- data/lib/graphql_rails/query_runner.rb +68 -0
- data/lib/graphql_rails/railtie.rb +10 -0
- data/lib/graphql_rails/router.rb +40 -13
- data/lib/graphql_rails/router/resource_routes_builder.rb +19 -11
- data/lib/graphql_rails/router/route.rb +21 -6
- data/lib/graphql_rails/router/schema_builder.rb +36 -11
- data/lib/graphql_rails/rspec_controller_helpers.rb +6 -4
- data/lib/graphql_rails/tasks/dump_graphql_schema.rb +57 -0
- data/lib/graphql_rails/tasks/schema.rake +14 -0
- data/lib/graphql_rails/version.rb +1 -1
- metadata +78 -26
- data/README.md +0 -194
- data/lib/graphql_rails/attribute.rb +0 -28
- data/lib/graphql_rails/attribute/type_parser.rb +0 -115
- data/lib/graphql_rails/controller/controller_function.rb +0 -50
- data/lib/graphql_rails/controller/format_results.rb +0 -36
- data/lib/graphql_rails/model/graphql_type_builder.rb +0 -33
- 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.
|
8
|
-
end
|
9
|
-
|
10
|
-
def type
|
11
|
-
'system_error'
|
9
|
+
super.merge(extra_graphql_data)
|
12
10
|
end
|
13
11
|
|
14
|
-
def
|
15
|
-
|
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
|
data/lib/graphql_rails/model.rb
CHANGED
@@ -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
|
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
|
-
|
10
|
-
|
11
|
-
|
9
|
+
require 'graphql_rails/concerns/service'
|
10
|
+
|
11
|
+
include ::GraphqlRails::Service
|
12
12
|
|
13
|
-
def initialize(graphql_object
|
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
|