graphsql 0.4.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6cf0933b75d22d60adbda095d91985202111a72476a1d418b9b6b6c19a9e26c3
4
+ data.tar.gz: dc5823be1f0741913aa1166be8c4826bd7744623d5fd578457a1cc252ec0b58d
5
+ SHA512:
6
+ metadata.gz: d55b842c33c1bebdfdaec2cf0fe8939f2ac7daebac8d61e4c1c85cd57b10120fec5acb7e937ffbfb50c73cc23055e94a2df3ecd009ab40dcd8a88d5f9b4c0356
7
+ data.tar.gz: 76c2e45232b2ee3788cb313394f8263be8a51f94e9bc257a9f0275ac1e2e1e3aaaee3386173217632d0923adbcb407ff33827e0f4a09e5efe2e5f92ec3b39f80
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dot Matrix Consulting
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # GraphSQL
2
+
3
+ GraphSQL maps GraphQL type fields to ActiveRecord columns and associations,
4
+ then applies a GraphQL lookahead to an ActiveRecord relation. It keeps query
5
+ types declarative while avoiding unnecessary SQL columns and common N+1 reads.
6
+
7
+ ## Usage
8
+
9
+ ```ruby
10
+ class Types::PersonaType < Types::BaseObject
11
+ include GraphSQL::Mapping
12
+
13
+ graphsql_model Persona
14
+ graphsql_column :handle
15
+
16
+ field :handle, String, null: false
17
+ end
18
+
19
+ class Types::GameType < Types::BaseObject
20
+ include GraphSQL::Mapping
21
+
22
+ graphsql_model Game
23
+ graphsql_column :slug
24
+ graphsql_column :name
25
+ graphsql_association :owner, to: Types::PersonaType
26
+
27
+ field :slug, String, null: false
28
+ field :name, String, null: false
29
+ field :owner, Types::PersonaType, null: false
30
+ end
31
+
32
+ class Types::QueryType < Types::BaseObject
33
+ field :games, [Types::GameType], null: false, extras: [:lookahead]
34
+
35
+ def games(lookahead:)
36
+ GraphSQL.resolve(Game.all, lookahead: lookahead, type: Types::GameType)
37
+ end
38
+ end
39
+ ```
40
+
41
+ `GraphSQL.resolve` always selects the model primary key, adds requested mapped
42
+ columns, retains foreign keys required by requested `belongs_to` associations,
43
+ and preloads requested mapped associations. For STI models it also retains the
44
+ inheritance-discriminator column (`type` by default), so rows still
45
+ instantiate as the correct subclass. Unmapped GraphQL fields remain the
46
+ responsibility of their normal resolvers.
47
+
48
+ Mappings may expose a database attribute or association under another GraphQL
49
+ field name:
50
+
51
+ ```ruby
52
+ graphsql_column :display_name, as: :name
53
+ graphsql_association :organization_memberships, as: :memberships
54
+ ```
55
+
56
+ `graphsql_column`/`required_columns:` must name a real column on the mapped
57
+ model — not a Ruby method, not a virtual `attribute` with no DB backing.
58
+ `GraphSQL.resolve` raises `GraphSQL::UnknownColumnError` naming the type,
59
+ field, and column the moment it's requested, rather than letting a bad name
60
+ reach Arel and fail later as an opaque `ActiveRecord::StatementInvalid`.
61
+
62
+ ## Nested column-limiting (`to:`)
63
+
64
+ When an association's `to:` target type is itself GraphSQL-mapped, column
65
+ selection recurses: only the fields actually requested on the *nested* object
66
+ get selected on its preload query too, at any depth (`owner { organization {
67
+ name } }` limits columns on both `owner` and its own `organization`).
68
+
69
+ `to:` accepts a `-> { ... }` proc instead of a bare class for mutually
70
+ associated types that would otherwise have a load-order problem (a `Game`
71
+ type pointing at an `Organization` type that itself points back at `Game`):
72
+
73
+ ```ruby
74
+ graphsql_association :games, to: -> { Types::GameType }
75
+ ```
76
+
77
+ Without a resolvable `to:` (omitted, a class that turns out not to match the
78
+ association's actual target, a polymorphic `belongs_to`, or a `:through`
79
+ association), that one association just falls back to a plain full-row
80
+ preload — the optimization is best-effort per-association; a query mixing a
81
+ nestable and a non-nestable association preloads both correctly, just with
82
+ only the nestable one getting the narrower `SELECT`.
83
+
84
+ Mapping the *same* underlying association under two different, both-nestable
85
+ GraphQL fields on one type (e.g. `owner`/`employer` both -> `belongs_to
86
+ :organization`) and requesting both in one query raises
87
+ `GraphSQL::AliasedAssociationError` rather than silently applying only one
88
+ alias's column-limited scope to both — the two aliases share a single
89
+ association proxy on the record, so there's no way to preload them
90
+ independently. Only one of the aliased fields, not both, can be requested in
91
+ the same query.
92
+
93
+ ## Pagination / `.count`
94
+
95
+ `GraphSQL.resolve` always applies a `.select()` covering every mapped column.
96
+ Rails' default `.count` column inference joins every `select_value` into
97
+ `COUNT(col1, col2, ...)`, invalid SQL outside Postgres — so a **bare**
98
+ `.count` on a relation with more than one selected column would normally
99
+ raise `ActiveRecord::StatementInvalid`. A relation returned by
100
+ `GraphSQL.resolve` avoids this: a bare `.count` (no column, no block) counts
101
+ by the primary key instead, since it's always among the selected columns and
102
+ always safe to count. `.count(:some_column)` and `.count { |r| ... }` still
103
+ pass straight through unchanged.
104
+
105
+ ```ruby
106
+ relation.count # counts by primary key, safe by default
107
+ relation.count(:id) # explicit column still works as normal
108
+ ```
109
+
110
+ This only applies to the `ActiveRecord::Relation` GraphSQL returns directly.
111
+ `GraphSQL.resolve` returns a plain `Array` instead whenever the query also
112
+ selects a nested GraphSQL-mapped association (see "Nested column-limiting"
113
+ above) — `Array#count` has no SQL to generate and is unaffected either way.
114
+
115
+ Pagination (`.limit`/`.offset`, `geared_pagination`, Kaminari, etc.) still
116
+ belongs on the *pre-resolve* relation, not chained onto `GraphSQL.resolve`'s
117
+ return value — `GraphSQL.resolve` is meant to be the terminal step right
118
+ before returning the field's value.
119
+
120
+ ## Development
121
+
122
+ ```bash
123
+ bundle install
124
+ bundle exec rake test
125
+ ```
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphSQL
4
+ # Raised by Resolver#nested_associations when a query requests the same
5
+ # underlying AR association under two different GraphQL field names (e.g.
6
+ # `owner`/`employer` both mapped to `belongs_to :organization`), both
7
+ # resolving to a nestable (`to:`-mapped) target. Both aliases share one
8
+ # association proxy on the record, and ActiveRecord::Associations::Preloader
9
+ # skips an association it's already marked loaded — so only the first
10
+ # alias's column-limited scope actually takes effect, and the second
11
+ # alias's requested fields silently go missing (up to and including a
12
+ # MissingAttributeError). Raising here turns that into a loud, immediate
13
+ # failure instead of a silent one.
14
+ class AliasedAssociationError < StandardError
15
+ def self.for_duplicate(type:, association:, fields:)
16
+ new("#{type} maps association `#{association}` under multiple requested GraphQL fields " \
17
+ "(#{fields.map(&:inspect).join(', ')}) — GraphQL.resolve can only apply one column-limited " \
18
+ "preload scope per underlying association per query. Request only one of these fields, or " \
19
+ "don't map `#{association}` under more than one GraphQL field name.")
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphSQL
4
+ module Mapping
5
+ Association = Data.define(:name, :field_name, :type) do
6
+ # `type:` may be a class or a `-> { ... }` proc (for forward references
7
+ # between mutually-associated types, e.g. Game <-> Organization).
8
+ def resolved_type
9
+ type.respond_to?(:call) ? type.call : type
10
+ end
11
+ end
12
+
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+
17
+ module ClassMethods
18
+ def graphsql_model(model = nil)
19
+ return @graphsql_model = model if model
20
+
21
+ @graphsql_model || graphsql_mapping_ancestor&.graphsql_model
22
+ end
23
+
24
+ def graphsql_column(name, as: name)
25
+ (@graphsql_columns ||= {})[as.to_sym] = name.to_sym
26
+ end
27
+
28
+ def graphsql_association(name, as: name, to: nil)
29
+ (@graphsql_associations ||= {})[as.to_sym] = Association.new(
30
+ name: name.to_sym,
31
+ field_name: as.to_sym,
32
+ type: to
33
+ )
34
+ end
35
+
36
+ def graphsql_columns
37
+ inherited = graphsql_mapping_ancestor&.graphsql_columns || {}
38
+ inherited.merge(@graphsql_columns || {})
39
+ end
40
+
41
+ def graphsql_associations
42
+ inherited = graphsql_mapping_ancestor&.graphsql_associations || {}
43
+ inherited.merge(@graphsql_associations || {})
44
+ end
45
+
46
+ private
47
+
48
+ def graphsql_mapping_ancestor
49
+ superclass if superclass.respond_to?(:graphsql_model)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphSQL
4
+ class Resolver
5
+ # A requested association whose target type also declares GraphSQL
6
+ # mappings, paired with the reflection needed to preload it safely and
7
+ # the nested lookahead needed to know which of ITS fields were selected.
8
+ NestedAssociation = Data.define(:mapping, :reflection, :target_type, :lookahead)
9
+
10
+ # select_columns always applies a multi-column .select() (primary key +
11
+ # every mapped column), which makes a *bare* `.count` ambiguous: Rails'
12
+ # default column inference joins every select_value into `COUNT(col1,
13
+ # col2, ...)`, invalid SQL outside Postgres. The primary key is always
14
+ # among those columns and is always a safe, correct thing to count, so a
15
+ # bare `.count`/`.count(&block)` defaults to it instead of failing —
16
+ # while `.count(:some_column)` still passes straight through untouched.
17
+ module CountsByPrimaryKey
18
+ def count(column_name = nil, &block)
19
+ return super if column_name || block
20
+
21
+ super(klass.primary_key)
22
+ end
23
+ end
24
+
25
+ def initialize(relation:, lookahead:, type:, required_associations: [], required_columns: [])
26
+ @relation = relation
27
+ @lookahead = lookahead
28
+ @type = type
29
+ @required_associations = required_associations
30
+ @required_columns = required_columns
31
+ end
32
+
33
+ def resolve
34
+ return relation unless supported?(relation, type, lookahead)
35
+
36
+ resolve_relation(relation, type, lookahead)
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :relation, :lookahead, :type, :required_associations, :required_columns
42
+
43
+ def resolve_relation(source, source_type, source_lookahead)
44
+ selected = select_columns(source, source_type, source_lookahead, required: required_columns)
45
+ nested = nested_associations(source, source_type, source_lookahead)
46
+ nested_names = nested.map { |assoc| assoc.mapping.name }
47
+ plain_names = association_mappings(source_type, source_lookahead).map(&:name).uniq - nested_names
48
+ eager_names = (plain_names + required_associations).uniq
49
+
50
+ return selected if nested.empty? && eager_names.empty?
51
+ return selected.includes(*eager_names) if nested.empty?
52
+
53
+ # Preloading a *scoped* (column-limited) association requires real
54
+ # record instances (ActiveRecord::Associations::Preloader operates on
55
+ # an array, not a relation) — this forces the load a step earlier than
56
+ # a plain `.includes` would, only when nested optimization applies.
57
+ records = selected.to_a
58
+ ActiveRecord::Associations::Preloader.new(records: records, associations: eager_names).call if eager_names.any?
59
+ preload_nested!(records, nested)
60
+ records
61
+ end
62
+
63
+ def supported?(source, source_type, source_lookahead)
64
+ source.is_a?(ActiveRecord::Relation) &&
65
+ source_type.respond_to?(:graphsql_model) &&
66
+ source_type.graphsql_model == source.klass &&
67
+ source_lookahead.respond_to?(:selections)
68
+ end
69
+
70
+ def field_names(source_lookahead)
71
+ source_lookahead.selections.filter_map do |selection|
72
+ selection.name.to_sym if selection.respond_to?(:name)
73
+ end.uniq
74
+ end
75
+
76
+ def association_mappings(source_type, source_lookahead)
77
+ source_type.graphsql_associations.slice(*field_names(source_lookahead)).values
78
+ end
79
+
80
+ # Requested associations whose target type is itself GraphSQL-mapped and
81
+ # matches the reflection's actual class, with a real nested lookahead to
82
+ # drive column selection. Anything else (no `to:`, mismatched `to:`,
83
+ # polymorphic, `:through`) safely falls back to a plain full-row preload —
84
+ # optimization is best-effort, correctness never depends on it applying.
85
+ def nested_associations(source, source_type, source_lookahead)
86
+ nested = association_mappings(source_type, source_lookahead).filter_map do |mapping|
87
+ reflection = source.klass.reflect_on_association(mapping.name)
88
+ next unless reflection
89
+ next if reflection.polymorphic? || reflection.through_reflection
90
+
91
+ target_type = mapping.resolved_type
92
+ next unless target_type.respond_to?(:graphsql_model)
93
+ next unless target_type.graphsql_model == reflection.klass
94
+
95
+ nested_lookahead = source_lookahead.selection(mapping.field_name)
96
+ next unless nested_lookahead.respond_to?(:selections)
97
+
98
+ NestedAssociation.new(mapping:, reflection:, target_type:, lookahead: nested_lookahead)
99
+ end
100
+
101
+ validate_no_aliased_duplicates!(nested, source_type)
102
+ nested
103
+ end
104
+
105
+ # Two GraphQL fields mapped to the same underlying association (e.g.
106
+ # `owner`/`employer` both -> `belongs_to :organization`), both nestable,
107
+ # both requested in the same query, would otherwise silently share one
108
+ # preloaded (and column-limited) association proxy — see
109
+ # AliasedAssociationError for why that loses data instead of erroring.
110
+ def validate_no_aliased_duplicates!(nested, source_type)
111
+ nested.group_by { |assoc| assoc.reflection.name }.each_value do |group|
112
+ next if group.size == 1
113
+
114
+ raise AliasedAssociationError.for_duplicate(
115
+ type: source_type,
116
+ association: group.first.reflection.name,
117
+ fields: group.map { |assoc| assoc.mapping.field_name }
118
+ )
119
+ end
120
+ end
121
+
122
+ def select_columns(source, source_type, source_lookahead, required: [])
123
+ names = field_names(source_lookahead)
124
+ mapped_fields = source_type.graphsql_columns.slice(*names)
125
+ validate_columns!(mapped_fields, required, source.klass, source_type)
126
+ mapped = mapped_fields.values
127
+ fks = association_mappings(source_type, source_lookahead).filter_map do |mapping|
128
+ reflection = source.klass.reflect_on_association(mapping.name)
129
+ reflection.foreign_key if reflection&.belongs_to? && !reflection.polymorphic?
130
+ end
131
+ required_fks = required_associations.filter_map do |assoc_name|
132
+ reflection = source.klass.reflect_on_association(assoc_name)
133
+ reflection.foreign_key if reflection&.belongs_to? && !reflection.polymorphic?
134
+ end
135
+ # STI models need their type-discriminator column present in the
136
+ # SELECT to instantiate the correct subclass — without it, every row
137
+ # silently comes back as the base class (no error, wrong class).
138
+ sti_column = source.klass.inheritance_column if source.klass.columns_hash.key?(source.klass.inheritance_column)
139
+ columns = [source.klass.primary_key, sti_column, *mapped, *fks, *required_fks, *required].compact.map(&:to_s).uniq
140
+ table = source.klass.arel_table
141
+
142
+ source.select(*columns.map { |column| table[column] }).extend(CountsByPrimaryKey)
143
+ end
144
+
145
+ # Fails fast and legibly instead of letting a nonexistent column reach
146
+ # Arel, where it would only surface once the query executes as a raw
147
+ # ActiveRecord::StatementInvalid with no mention of GraphSQL, the
148
+ # GraphQL type, or which field caused it.
149
+ def validate_columns!(mapped_fields, required, model, source_type)
150
+ known = model.column_names
151
+
152
+ mapped_fields.each do |field, column|
153
+ next if known.include?(column.to_s)
154
+
155
+ raise UnknownColumnError.for_mapped_column(type: source_type, field: field, column: column, model: model)
156
+ end
157
+
158
+ required.each do |column|
159
+ next if known.include?(column.to_s)
160
+
161
+ raise UnknownColumnError.for_required_column(column: column, model: model)
162
+ end
163
+ end
164
+
165
+ def preload_nested!(records, nested)
166
+ return if records.empty?
167
+
168
+ nested.each do |assoc|
169
+ scope = nested_scope(assoc)
170
+
171
+ ActiveRecord::Associations::Preloader.new(
172
+ records: records,
173
+ associations: assoc.mapping.name,
174
+ scope: scope
175
+ ).call
176
+
177
+ child_records = records.flat_map { |record| Array(record.public_send(assoc.mapping.name)) }
178
+ preload_nested!(child_records, nested_associations(scope, assoc.target_type, assoc.lookahead))
179
+ end
180
+ end
181
+
182
+ # belongs_to's join key lives on the *requesting* side (already selected
183
+ # by select_columns' `fks`, above) so the child scope needs nothing extra.
184
+ # has_many/has_one's join key lives on the *child* table — the preloader
185
+ # groups loaded children by that column, so it must be selected even
186
+ # though it's never GraphQL-mapped, or every association silently
187
+ # resolves empty (confirmed: this is not a hypothetical — see the
188
+ # has_many test coverage for the exact failure mode without it).
189
+ def nested_scope(assoc)
190
+ required = assoc.reflection.belongs_to? ? [] : [assoc.reflection.foreign_key]
191
+ select_columns(assoc.target_type.graphsql_model.all, assoc.target_type, assoc.lookahead, required: required)
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphSQL
4
+ # Raised by Resolver#select_columns when a graphsql_column/required_columns:
5
+ # entry names something that isn't an actual column on the model — a plain
6
+ # Ruby method, a virtual `attribute` with no DB backing, or a typo. Without
7
+ # this check the same mistake surfaced as a raw
8
+ # ActiveRecord::StatementInvalid ("no such column: ...") from deep inside
9
+ # Arel/the SQL driver, with nothing pointing back at the offending
10
+ # graphsql_column call.
11
+ class UnknownColumnError < StandardError
12
+ def self.for_mapped_column(type:, field:, column:, model:)
13
+ new("#{type}'s graphsql_column #{field.inspect} maps to `#{column}`, " \
14
+ "but #{model} has no such column")
15
+ end
16
+
17
+ def self.for_required_column(column:, model:)
18
+ new("required_columns: referenced `#{column}`, but #{model} has no such column")
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphSQL
4
+ VERSION = "0.4.0"
5
+ end
data/lib/graphsql.rb ADDED
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require "active_support/core_ext/hash/slice"
5
+
6
+ require_relative "graphsql/version"
7
+ require_relative "graphsql/mapping"
8
+ require_relative "graphsql/unknown_column_error"
9
+ require_relative "graphsql/aliased_association_error"
10
+ require_relative "graphsql/resolver"
11
+
12
+ module GraphSQL
13
+ # `required_associations:`/`required_columns:` name associations/columns
14
+ # the caller needs loaded regardless of what the client requested (e.g. a
15
+ # model's #discussion reading title/body and persona.handle to
16
+ # find_or_open a thread, even when the query didn't ask for those
17
+ # fields). Passing these through here, rather than chaining
18
+ # `.select`/`.includes` onto the return value, is required for
19
+ # correctness: #resolve returns a plain Array (not a Relation — see
20
+ # Resolver#resolve_relation) whenever the query also requests a nested
21
+ # GraphQL-mapped association, and a caller-side `.select`/`.includes`
22
+ # chained after the call silently breaks the moment that happens.
23
+ def self.resolve(relation, lookahead:, type:, required_associations: [], required_columns: [])
24
+ Resolver.new(
25
+ relation: relation, lookahead: lookahead, type: type,
26
+ required_associations: required_associations, required_columns: required_columns
27
+ ).resolve
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: graphsql
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Dot Matrix
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '6.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: activesupport
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '6.1'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '6.1'
40
+ - !ruby/object:Gem::Dependency
41
+ name: graphql
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '1.13'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '1.13'
54
+ - !ruby/object:Gem::Dependency
55
+ name: minitest
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '5.25'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '5.25'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rake
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: sqlite3
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ description: Selects mapped ActiveRecord columns and preloads mapped associations
97
+ from GraphQL lookaheads.
98
+ email:
99
+ - matrix9180@proton.me
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - LICENSE.txt
105
+ - README.md
106
+ - lib/graphsql.rb
107
+ - lib/graphsql/aliased_association_error.rb
108
+ - lib/graphsql/mapping.rb
109
+ - lib/graphsql/resolver.rb
110
+ - lib/graphsql/unknown_column_error.rb
111
+ - lib/graphsql/version.rb
112
+ homepage: https://gitlab.com/maqstaq/graphsql
113
+ licenses:
114
+ - MIT
115
+ metadata:
116
+ source_code_uri: https://gitlab.com/maqstaq/graphsql
117
+ bug_tracker_uri: https://gitlab.com/maqstaq/graphsql/-/issues
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: 3.2.0
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubygems_version: 4.0.16
133
+ specification_version: 4
134
+ summary: GraphQL lookahead mapping for ActiveRecord relations.
135
+ test_files: []