graphql 0.17.2 → 0.18.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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +1 -0
  3. data/lib/graphql/analysis/query_depth.rb +1 -1
  4. data/lib/graphql/base_type.rb +25 -1
  5. data/lib/graphql/define.rb +2 -0
  6. data/lib/graphql/define/assign_connection.rb +11 -0
  7. data/lib/graphql/define/assign_global_id_field.rb +11 -0
  8. data/lib/graphql/define/assign_object_field.rb +21 -20
  9. data/lib/graphql/define/defined_object_proxy.rb +2 -2
  10. data/lib/graphql/define/instance_definable.rb +13 -3
  11. data/lib/graphql/field.rb +1 -1
  12. data/lib/graphql/language/generation.rb +57 -6
  13. data/lib/graphql/language/lexer.rb +434 -212
  14. data/lib/graphql/language/lexer.rl +18 -0
  15. data/lib/graphql/language/nodes.rb +75 -0
  16. data/lib/graphql/language/parser.rb +853 -341
  17. data/lib/graphql/language/parser.y +114 -17
  18. data/lib/graphql/query.rb +15 -1
  19. data/lib/graphql/relay.rb +13 -0
  20. data/lib/graphql/relay/array_connection.rb +80 -0
  21. data/lib/graphql/relay/base_connection.rb +138 -0
  22. data/lib/graphql/relay/connection_field.rb +54 -0
  23. data/lib/graphql/relay/connection_type.rb +25 -0
  24. data/lib/graphql/relay/edge.rb +22 -0
  25. data/lib/graphql/relay/edge_type.rb +14 -0
  26. data/lib/graphql/relay/global_id_resolve.rb +15 -0
  27. data/lib/graphql/relay/global_node_identification.rb +124 -0
  28. data/lib/graphql/relay/mutation.rb +146 -0
  29. data/lib/graphql/relay/page_info.rb +13 -0
  30. data/lib/graphql/relay/relation_connection.rb +98 -0
  31. data/lib/graphql/schema.rb +3 -0
  32. data/lib/graphql/schema/printer.rb +12 -2
  33. data/lib/graphql/static_validation/message.rb +9 -5
  34. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  35. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  36. data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
  37. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +7 -7
  38. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  39. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
  40. data/lib/graphql/static_validation/rules/fields_will_merge.rb +6 -6
  41. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +17 -9
  42. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  43. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +1 -1
  44. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  45. data/lib/graphql/static_validation/rules/fragments_are_used.rb +17 -6
  46. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  47. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +2 -2
  48. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -5
  49. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
  50. data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +12 -11
  51. data/lib/graphql/static_validation/type_stack.rb +33 -2
  52. data/lib/graphql/static_validation/validation_context.rb +5 -0
  53. data/lib/graphql/version.rb +1 -1
  54. data/readme.md +16 -4
  55. data/spec/graphql/analysis/analyze_query_spec.rb +31 -2
  56. data/spec/graphql/analysis/query_complexity_spec.rb +24 -0
  57. data/spec/graphql/argument_spec.rb +1 -1
  58. data/spec/graphql/define/instance_definable_spec.rb +9 -0
  59. data/spec/graphql/field_spec.rb +1 -1
  60. data/spec/graphql/internal_representation/rewrite_spec.rb +3 -3
  61. data/spec/graphql/language/generation_spec.rb +25 -4
  62. data/spec/graphql/language/parser_spec.rb +116 -1
  63. data/spec/graphql/query_spec.rb +10 -0
  64. data/spec/graphql/relay/array_connection_spec.rb +164 -0
  65. data/spec/graphql/relay/connection_type_spec.rb +37 -0
  66. data/spec/graphql/relay/global_node_identification_spec.rb +149 -0
  67. data/spec/graphql/relay/mutation_spec.rb +55 -0
  68. data/spec/graphql/relay/page_info_spec.rb +106 -0
  69. data/spec/graphql/relay/relation_connection_spec.rb +348 -0
  70. data/spec/graphql/schema/printer_spec.rb +8 -0
  71. data/spec/graphql/schema/reduce_types_spec.rb +1 -1
  72. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +12 -6
  73. data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +8 -4
  74. data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
  75. data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
  76. data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +7 -2
  77. data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +4 -2
  78. data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
  79. data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +5 -3
  80. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
  81. data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +5 -2
  82. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +10 -2
  83. data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +6 -3
  84. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +8 -4
  85. data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
  86. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +6 -3
  87. data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +6 -3
  88. data/spec/spec_helper.rb +7 -0
  89. data/spec/support/dairy_app.rb +11 -10
  90. data/spec/support/star_wars_data.rb +65 -58
  91. data/spec/support/star_wars_schema.rb +192 -54
  92. metadata +84 -2
@@ -0,0 +1,54 @@
1
+ module GraphQL
2
+ module Relay
3
+ # Provided a GraphQL field which returns a collection of items,
4
+ # `ConnectionField.create` modifies that field to expose those items
5
+ # as a collection.
6
+ #
7
+ # The original resolve proc is used to fetch items,
8
+ # then a connection implementation is fetched with {BaseConnection.connection_for_items}.
9
+ class ConnectionField
10
+ ARGUMENT_DEFINITIONS = [
11
+ ["first", GraphQL::INT_TYPE, "Returns the first _n_ elements from the list."],
12
+ ["after", GraphQL::STRING_TYPE, "Returns the elements in the list that come after the specified global ID."],
13
+ ["last", GraphQL::INT_TYPE, "Returns the last _n_ elements from the list."],
14
+ ["before", GraphQL::STRING_TYPE, "Returns the elements in the list that come before the specified global ID."],
15
+ ]
16
+
17
+ DEFAULT_ARGUMENTS = ARGUMENT_DEFINITIONS.reduce({}) do |memo, arg_defn|
18
+ argument = GraphQL::Argument.new
19
+ name, type, description = arg_defn
20
+ argument.name = name
21
+ argument.type = type
22
+ argument.description = description
23
+ memo[argument.name.to_s] = argument
24
+ memo
25
+ end
26
+
27
+ # Turn A GraphQL::Field into a connection by:
28
+ # - Merging in the default arguments
29
+ # - Transforming its resolve function to return a connection object
30
+ # @param [GraphQL::Field] A field which returns items to be wrapped as a connection
31
+ # @param max_page_size [Integer] The maximum number of items which may be requested (if a larger page is requested, it is limited to this number)
32
+ # @return [GraphQL::Field] A field which serves a connections
33
+ def self.create(underlying_field, max_page_size: nil)
34
+ underlying_field.arguments = DEFAULT_ARGUMENTS.merge(underlying_field.arguments)
35
+ original_resolve = underlying_field.resolve_proc
36
+ underlying_field.resolve = get_connection_resolve(underlying_field.name, original_resolve, max_page_size: max_page_size)
37
+ underlying_field
38
+ end
39
+
40
+ private
41
+
42
+ # Wrap the original resolve proc
43
+ # so you capture its value, then wrap it in a
44
+ # connection implementation
45
+ def self.get_connection_resolve(field_name, underlying_resolve, max_page_size: nil)
46
+ -> (obj, args, ctx) {
47
+ items = underlying_resolve.call(obj, args, ctx)
48
+ connection_class = GraphQL::Relay::BaseConnection.connection_for_items(items)
49
+ connection_class.new(items, args, max_page_size: max_page_size, parent: obj)
50
+ }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,25 @@
1
+ module GraphQL
2
+ module Relay
3
+ module ConnectionType
4
+ # Create a connection which exposes edges of this type
5
+ def self.create_type(wrapped_type, edge_type: nil, edge_class: nil, &block)
6
+ edge_type ||= wrapped_type.edge_type
7
+ edge_class ||= GraphQL::Relay::Edge
8
+ connection_type_name = "#{wrapped_type.name}Connection"
9
+
10
+ connection_type = ObjectType.define do
11
+ name(connection_type_name)
12
+ field :edges, types[edge_type] do
13
+ resolve -> (obj, args, ctx) {
14
+ obj.edge_nodes.map { |item| edge_class.new(item, obj) }
15
+ }
16
+ end
17
+ field :pageInfo, !PageInfo, property: :page_info
18
+ block && instance_eval(&block)
19
+ end
20
+
21
+ connection_type
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ module GraphQL
2
+ module Relay
3
+ # Mostly an internal concern.
4
+ #
5
+ # Wraps an object as a `node`, and exposes a connection-specific `cursor`.
6
+ class Edge < GraphQL::ObjectType
7
+ attr_reader :node, :connection
8
+ def initialize(node, connection)
9
+ @node = node
10
+ @connection = connection
11
+ end
12
+
13
+ def cursor
14
+ @cursor ||= connection.cursor_from_node(node)
15
+ end
16
+
17
+ def parent
18
+ @parent ||= connection.parent
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module GraphQL
2
+ module Relay
3
+ module EdgeType
4
+ def self.create_type(wrapped_type, name: nil, &block)
5
+ GraphQL::ObjectType.define do
6
+ name("#{wrapped_type.name}Edge")
7
+ field :node, wrapped_type
8
+ field :cursor, !types.String
9
+ block && instance_eval(&block)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module GraphQL
2
+ module Relay
3
+ class GlobalIdResolve
4
+ def initialize(type_name:, property:)
5
+ @property = property
6
+ @type_name = type_name
7
+ end
8
+
9
+ def call(obj, args, ctx)
10
+ id_value = obj.public_send(@property)
11
+ ctx.query.schema.node_identification.to_global_id(@type_name, id_value)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,124 @@
1
+ require 'singleton'
2
+ module GraphQL
3
+ module Relay
4
+ # This object provides helpers for working with global IDs.
5
+ # It's assumed you'll only have 1!
6
+ #
7
+ # GlobalIdField depends on that, since it calls class methods
8
+ # which delegate to the singleton instance.
9
+ #
10
+ class GlobalNodeIdentification
11
+ include GraphQL::Define::InstanceDefinable
12
+ accepts_definitions(:object_from_id, :type_from_object, :to_global_id, :from_global_id, :description)
13
+ lazy_defined_attr_accessor :description
14
+
15
+ class << self
16
+ attr_accessor :id_separator
17
+ end
18
+
19
+ self.id_separator = "-"
20
+
21
+ def initialize
22
+ @to_global_id_proc = DEFAULT_TO_GLOBAL_ID
23
+ @from_global_id_proc = DEFAULT_FROM_GLOBAL_ID
24
+ end
25
+
26
+ # Returns `NodeInterface`, which all Relay types must implement
27
+ def interface
28
+ @interface ||= begin
29
+ ensure_defined
30
+ ident = self
31
+ GraphQL::InterfaceType.define do
32
+ name "Node"
33
+ field :id, !types.ID
34
+ resolve_type -> (obj, schema) {
35
+ ident.type_from_object(obj)
36
+ }
37
+ end
38
+ end
39
+ end
40
+
41
+ # Returns a field for finding objects from a global ID, which Relay needs
42
+ def field
43
+ ensure_defined
44
+ ident = self
45
+ GraphQL::Field.define do
46
+ type(ident.interface)
47
+ argument :id, !types.ID
48
+ resolve -> (obj, args, ctx) {
49
+ ctx.query.schema.node_identification.object_from_id(args[:id], ctx)
50
+ }
51
+ description ident.description
52
+ end
53
+ end
54
+
55
+ DEFAULT_TO_GLOBAL_ID = -> (type_name, id) {
56
+ id_str = id.to_s
57
+ if type_name.include?(self.id_separator) || id_str.include?(self.id_separator)
58
+ raise "to_global_id(#{type_name}, #{id}) contains reserved characters `#{self.id_separator}`"
59
+ end
60
+ Base64.strict_encode64([type_name, id_str].join(self.id_separator))
61
+ }
62
+
63
+ DEFAULT_FROM_GLOBAL_ID = -> (global_id) {
64
+ Base64.decode64(global_id).split(self.id_separator)
65
+ }
66
+
67
+ # Create a global ID for type-name & ID
68
+ # (This is an opaque transform)
69
+ def to_global_id(type_name, id)
70
+ ensure_defined
71
+ @to_global_id_proc.call(type_name, id)
72
+ end
73
+
74
+ def to_global_id=(proc)
75
+ ensure_defined
76
+ @to_global_id_proc = proc
77
+ end
78
+
79
+ # Get type-name & ID from global ID
80
+ # (This reverts the opaque transform)
81
+ def from_global_id(global_id)
82
+ ensure_defined
83
+ @from_global_id_proc.call(global_id)
84
+ end
85
+
86
+ def from_global_id=(proc)
87
+ ensure_defined
88
+ @from_global_id_proc = proc
89
+ end
90
+
91
+ # Use the provided config to
92
+ # get a type for a given object
93
+ def type_from_object(object)
94
+ ensure_defined
95
+ type_result = @type_from_object_proc.call(object)
96
+ if type_result.nil?
97
+ nil
98
+ elsif !type_result.is_a?(GraphQL::BaseType)
99
+ type_str = "#{type_result} (#{type_result.class.name})"
100
+ raise "type_from_object(#{object}) returned #{type_str}, but it should return a GraphQL type"
101
+ else
102
+ type_result
103
+ end
104
+ end
105
+
106
+ def type_from_object=(proc)
107
+ ensure_defined
108
+ @type_from_object_proc = proc
109
+ end
110
+
111
+ # Use the provided config to
112
+ # get an object from a UUID
113
+ def object_from_id(id, ctx)
114
+ ensure_defined
115
+ @object_from_id_proc.call(id, ctx)
116
+ end
117
+
118
+ def object_from_id=(proc)
119
+ ensure_defined
120
+ @object_from_id_proc = proc
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,146 @@
1
+ module GraphQL
2
+ module Relay
3
+ # Define a Relay mutation:
4
+ # - give it a name (used for derived inputs & outputs)
5
+ # - declare its inputs
6
+ # - declare its outputs
7
+ # - declare the mutation procedure
8
+ #
9
+ # `resolve` should return a hash with a key for each of the `return_field`s
10
+ #
11
+ # Inputs may also contain a `clientMutationId`
12
+ #
13
+ # @example Updating the name of an item
14
+ # UpdateNameMutation = GraphQL::Relay::Mutation.define do
15
+ # name "UpdateName"
16
+ #
17
+ # input_field :name, !types.String
18
+ # input_field :itemId, !types.ID
19
+ #
20
+ # return_field :item, ItemType
21
+ #
22
+ # resolve -> (inputs, ctx) {
23
+ # item = Item.find_by_id(inputs[:id])
24
+ # item.update(name: inputs[:name])
25
+ # {item: item}
26
+ # }
27
+ # end
28
+ #
29
+ # MutationType = GraphQL::ObjectType.define do
30
+ # # The mutation object exposes a field:
31
+ # field :updateName, field: UpdateNameMutation.field
32
+ # end
33
+ #
34
+ # # Then query it:
35
+ # query_string = %|
36
+ # mutation updateName {
37
+ # updateName(input: {itemId: 1, name: "new name", clientMutationId: "1234"}) {
38
+ # item { name }
39
+ # clientMutationId
40
+ # }|
41
+ #
42
+ # GraphQL::Query.new(MySchema, query_string).result
43
+ # # {"data" => {
44
+ # # "updateName" => {
45
+ # # "item" => { "name" => "new name"},
46
+ # # "clientMutationId" => "1234"
47
+ # # }
48
+ # # }}
49
+ #
50
+ class Mutation
51
+ include GraphQL::Define::InstanceDefinable
52
+ accepts_definitions(
53
+ :name, :description, :resolve,
54
+ input_field: GraphQL::Define::AssignArgument,
55
+ return_field: GraphQL::Define::AssignObjectField,
56
+ )
57
+ lazy_defined_attr_accessor :name, :description
58
+ lazy_defined_attr_accessor :fields, :arguments
59
+
60
+ # For backwards compat, but do we need this separate API?
61
+ alias :return_fields :fields
62
+ alias :input_fields :arguments
63
+
64
+ def initialize
65
+ @fields = {}
66
+ @arguments = {}
67
+ end
68
+
69
+ def resolve=(proc)
70
+ ensure_defined
71
+ @resolve_proc = proc
72
+ end
73
+
74
+ def field
75
+ @field ||= begin
76
+ ensure_defined
77
+ field_return_type = self.return_type
78
+ field_input_type = self.input_type
79
+ field_resolve_proc = -> (obj, args, ctx){
80
+ results_hash = @resolve_proc.call(args[:input], ctx)
81
+ Result.new(arguments: args, result: results_hash)
82
+ }
83
+ GraphQL::Field.define do
84
+ type(field_return_type)
85
+ argument :input, !field_input_type
86
+ resolve(field_resolve_proc)
87
+ end
88
+ end
89
+ end
90
+
91
+ def return_type
92
+ @return_type ||= begin
93
+ ensure_defined
94
+ mutation_name = name
95
+ type_name = "#{mutation_name}Payload"
96
+ type_fields = return_fields
97
+ GraphQL::ObjectType.define do
98
+ name(type_name)
99
+ description("Autogenerated return type of #{mutation_name}")
100
+ field :clientMutationId, types.String, "A unique identifier for the client performing the mutation."
101
+ type_fields.each do |name, field_obj|
102
+ field name, field: field_obj
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ def input_type
109
+ @input_type ||= begin
110
+ ensure_defined
111
+ mutation_name = name
112
+ type_name = "#{mutation_name}Input"
113
+ type_fields = input_fields
114
+ GraphQL::InputObjectType.define do
115
+ name(type_name)
116
+ description("Autogenerated input type of #{mutation_name}")
117
+ input_field :clientMutationId, types.String, "A unique identifier for the client performing the mutation."
118
+ type_fields.each do |name, field_obj|
119
+ input_field name, field_obj.type, field_obj.description, default_value: field_obj.default_value
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ class Result
126
+ attr_reader :arguments, :result
127
+ def initialize(arguments:, result:)
128
+ @arguments = arguments
129
+ @result = result
130
+ end
131
+
132
+ def clientMutationId
133
+ arguments[:input][:clientMutationId]
134
+ end
135
+
136
+ def method_missing(name, *args, &block)
137
+ if result.key?(name)
138
+ result[name]
139
+ else
140
+ super
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,13 @@
1
+ module GraphQL
2
+ module Relay
3
+ # Wrap a Connection and expose its page info
4
+ PageInfo = GraphQL::ObjectType.define do
5
+ name("PageInfo")
6
+ description("Metadata about a connection")
7
+ field :hasNextPage, !types.Boolean, "Indicates if there are more pages to fetch", property: :has_next_page
8
+ field :hasPreviousPage, !types.Boolean, "Indicates if there are any pages prior to the current page", property: :has_previous_page
9
+ field :startCursor, types.String, "When paginating backwards, the cursor to continue", property: :start_cursor
10
+ field :endCursor, types.String, "When paginating forwards, the cursor to continue", property: :end_cursor
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,98 @@
1
+ module GraphQL
2
+ module Relay
3
+ # A connection implementation to expose SQL collection objects.
4
+ # It works for:
5
+ # - `ActiveRecord::Relation`
6
+ # - `Sequel::Dataset`
7
+ class RelationConnection < BaseConnection
8
+ def cursor_from_node(item)
9
+ offset = starting_offset + paged_nodes_array.index(item) + 1
10
+ Base64.strict_encode64(offset.to_s)
11
+ end
12
+
13
+ def has_next_page
14
+ !!(first && sliced_nodes.limit(limit + 1).count > limit)
15
+ end
16
+
17
+ def has_previous_page
18
+ !!(last && starting_offset > 0)
19
+ end
20
+
21
+ private
22
+
23
+ # apply first / last limit results
24
+ def paged_nodes
25
+ @paged_nodes ||= begin
26
+ items = sliced_nodes
27
+ items.limit(limit)
28
+ end
29
+ end
30
+
31
+ # Apply cursors to edges
32
+ def sliced_nodes
33
+ @sliced_nodes ||= begin
34
+ items = object
35
+ items.offset(starting_offset)
36
+ end
37
+ end
38
+
39
+ def offset_from_cursor(cursor)
40
+ Base64.decode64(cursor).to_i
41
+ end
42
+
43
+ def starting_offset
44
+ @starting_offset ||= begin
45
+ if before
46
+ [previous_offset, 0].max
47
+ else
48
+ previous_offset
49
+ end
50
+ end
51
+ end
52
+
53
+ # Offset from the previous selection, if there was one
54
+ # Otherwise, zero
55
+ def previous_offset
56
+ @previous_offset ||= if after
57
+ offset_from_cursor(after)
58
+ elsif before
59
+ prev_page_size = [max_page_size, last].compact.min || 0
60
+ offset_from_cursor(before) - prev_page_size - 1
61
+ else
62
+ 0
63
+ end
64
+ end
65
+
66
+ # Limit to apply to this query:
67
+ # - find a value from the query
68
+ # - don't exceed max_page_size
69
+ # - otherwise, don't limit
70
+ def limit
71
+ @limit ||= begin
72
+ limit_from_arguments = if first
73
+ first
74
+ else
75
+ if previous_offset < 0
76
+ previous_offset + (last ? last : 0)
77
+ else
78
+ last
79
+ end
80
+ end
81
+ [limit_from_arguments, max_page_size].compact.min
82
+ end
83
+ end
84
+
85
+ def paged_nodes_array
86
+ @paged_nodes_array ||= paged_nodes.to_a
87
+ end
88
+ end
89
+
90
+
91
+ if defined?(ActiveRecord::Relation)
92
+ BaseConnection.register_connection_implementation(ActiveRecord::Relation, RelationConnection)
93
+ end
94
+ if defined?(Sequel::Dataset)
95
+ BaseConnection.register_connection_implementation(Sequel::Dataset, RelationConnection)
96
+ end
97
+ end
98
+ end