hq-graphql 2.0.6 → 2.1.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/README.md +28 -0
- data/lib/hq-graphql.rb +0 -2
- data/lib/hq/graphql.rb +29 -20
- data/lib/hq/graphql/active_record_extensions.rb +23 -27
- data/lib/hq/graphql/association_loader.rb +49 -0
- data/lib/hq/graphql/comparator.rb +1 -6
- data/lib/hq/graphql/config.rb +17 -11
- data/lib/hq/graphql/engine.rb +0 -1
- data/lib/hq/graphql/enum.rb +78 -0
- data/lib/hq/graphql/enum/sort_by.rb +8 -0
- data/lib/hq/graphql/enum/sort_order.rb +8 -0
- data/lib/hq/graphql/field.rb +26 -6
- data/lib/hq/graphql/input_object.rb +8 -7
- data/lib/hq/graphql/inputs.rb +4 -3
- data/lib/hq/graphql/mutation.rb +0 -1
- data/lib/hq/graphql/object.rb +15 -4
- data/lib/hq/graphql/paginated_association_loader.rb +138 -0
- data/lib/hq/graphql/resource.rb +47 -156
- data/lib/hq/graphql/resource/auto_mutation.rb +159 -0
- data/lib/hq/graphql/root_mutation.rb +1 -2
- data/lib/hq/graphql/root_query.rb +0 -1
- data/lib/hq/graphql/scalars.rb +0 -1
- data/lib/hq/graphql/schema.rb +22 -0
- data/lib/hq/graphql/types.rb +19 -8
- data/lib/hq/graphql/types/object.rb +7 -11
- data/lib/hq/graphql/types/uuid.rb +7 -14
- data/lib/hq/graphql/version.rb +1 -2
- metadata +9 -39
- data/lib/hq/graphql/loaders.rb +0 -4
- data/lib/hq/graphql/loaders/association.rb +0 -52
- data/lib/hq/graphql/resource/mutation.rb +0 -39
data/lib/hq/graphql/field.rb
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
-
# typed: false
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module HQ
|
5
4
|
module GraphQL
|
6
5
|
class Field < ::GraphQL::Schema::Field
|
7
|
-
attr_reader :authorize_action, :authorize
|
6
|
+
attr_reader :authorize_action, :authorize
|
8
7
|
|
9
8
|
def initialize(*args, authorize_action: :read, authorize: nil, klass: nil, **options, &block)
|
10
9
|
super(*args, **options, &block)
|
11
10
|
@authorize_action = authorize_action
|
12
11
|
@authorize = authorize
|
13
|
-
@
|
12
|
+
@class_name = klass
|
14
13
|
end
|
15
14
|
|
16
15
|
def authorized?(object, ctx)
|
@@ -21,13 +20,34 @@ module HQ
|
|
21
20
|
|
22
21
|
def resolve_field(object, args, ctx)
|
23
22
|
if klass.present? && !!::GraphQL::Batch::Executor.current && object.object
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
loader =
|
24
|
+
if ::HQ::GraphQL.use_experimental_associations?
|
25
|
+
limit = args[:limit]
|
26
|
+
offset = args[:offset]
|
27
|
+
sort_by = args[:sortBy]
|
28
|
+
sort_order = args[:sortOrder]
|
29
|
+
|
30
|
+
PaginatedAssociationLoader.for(
|
31
|
+
klass,
|
32
|
+
original_name,
|
33
|
+
limit: limit,
|
34
|
+
offset: offset,
|
35
|
+
sort_by: sort_by,
|
36
|
+
sort_order: sort_order
|
37
|
+
)
|
38
|
+
else
|
39
|
+
AssociationLoader.for(klass, original_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
loader.load(object.object)
|
27
43
|
else
|
28
44
|
super
|
29
45
|
end
|
30
46
|
end
|
47
|
+
|
48
|
+
def klass
|
49
|
+
@klass ||= @class_name&.constantize
|
50
|
+
end
|
31
51
|
end
|
32
52
|
end
|
33
53
|
end
|
@@ -1,10 +1,8 @@
|
|
1
|
-
# typed: true
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module HQ
|
5
4
|
module GraphQL
|
6
5
|
class InputObject < ::GraphQL::Schema::InputObject
|
7
|
-
extend T::Sig
|
8
6
|
include Scalars
|
9
7
|
include ::HQ::GraphQL::ActiveRecordExtensions
|
10
8
|
|
@@ -30,11 +28,11 @@ module HQ
|
|
30
28
|
end
|
31
29
|
|
32
30
|
#### Class Methods ####
|
33
|
-
|
34
|
-
def self.with_model(model_name, attributes: true, associations: false)
|
31
|
+
def self.with_model(model_name, attributes: true, associations: false, enums: true)
|
35
32
|
self.model_name = model_name
|
36
33
|
self.auto_load_attributes = attributes
|
37
34
|
self.auto_load_associations = associations
|
35
|
+
self.auto_load_enums = enums
|
38
36
|
|
39
37
|
lazy_load do
|
40
38
|
model_columns.each do |column|
|
@@ -62,16 +60,19 @@ module HQ
|
|
62
60
|
private
|
63
61
|
|
64
62
|
def argument_from_association(association)
|
65
|
-
|
63
|
+
is_enum = is_enum?(association)
|
64
|
+
input_or_type = is_enum ? ::HQ::GraphQL::Types[association.klass] : ::HQ::GraphQL::Inputs[association.klass]
|
66
65
|
name = association.name
|
67
66
|
|
68
67
|
case association.macro
|
69
68
|
when :has_many
|
70
|
-
argument name, [
|
69
|
+
argument name, [input_or_type], required: false
|
71
70
|
else
|
72
|
-
argument name,
|
71
|
+
argument name, input_or_type, required: false
|
73
72
|
end
|
74
73
|
|
74
|
+
return if is_enum
|
75
|
+
|
75
76
|
if !model_klass.nested_attributes_options.key?(name.to_sym)
|
76
77
|
model_klass.accepts_nested_attributes_for name, allow_destroy: true
|
77
78
|
end
|
data/lib/hq/graphql/inputs.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# typed: true
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module HQ
|
@@ -25,8 +24,10 @@ module HQ
|
|
25
24
|
|
26
25
|
def klass_for(klass_or_string)
|
27
26
|
klass = klass_or_string.is_a?(String) ? klass_or_string.constantize : klass_or_string
|
28
|
-
::HQ::GraphQL.
|
29
|
-
|
27
|
+
resource = ::HQ::GraphQL.lookup_resource(klass)
|
28
|
+
|
29
|
+
raise(Error, Error::MISSING_TYPE_MSG % { klass: klass.name }) if !resource
|
30
|
+
resource.input_klass
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
data/lib/hq/graphql/mutation.rb
CHANGED
data/lib/hq/graphql/object.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# typed: true
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module HQ
|
@@ -17,10 +16,11 @@ module HQ
|
|
17
16
|
super && ::HQ::GraphQL.authorized?(authorized_action, object, context)
|
18
17
|
end
|
19
18
|
|
20
|
-
def self.with_model(model_name, attributes: true, associations: true, auto_nil: true)
|
19
|
+
def self.with_model(model_name, attributes: true, associations: true, auto_nil: true, enums: true)
|
21
20
|
self.model_name = model_name
|
22
21
|
self.auto_load_attributes = attributes
|
23
22
|
self.auto_load_associations = associations
|
23
|
+
self.auto_load_enums = enums
|
24
24
|
|
25
25
|
lazy_load do
|
26
26
|
model_columns.each do |column|
|
@@ -47,11 +47,22 @@ module HQ
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def field_from_association(association, auto_nil:)
|
50
|
-
|
50
|
+
# The PaginationAssociationLoader doesn't support through associations yet
|
51
|
+
return if association.through_reflection? && ::HQ::GraphQL.use_experimental_associations?
|
52
|
+
|
53
|
+
association_klass = association.klass
|
54
|
+
type = ::HQ::GraphQL::Types[association_klass]
|
51
55
|
name = association.name
|
52
56
|
case association.macro
|
53
57
|
when :has_many
|
54
|
-
field name, [type], null: false, klass: model_name
|
58
|
+
field name, [type], null: false, klass: model_name do
|
59
|
+
if ::HQ::GraphQL.use_experimental_associations? && (resource = ::HQ::GraphQL.lookup_resource(association_klass))
|
60
|
+
argument :offset, Integer, required: false
|
61
|
+
argument :limit, Integer, required: false
|
62
|
+
argument :sort_by, resource.sort_fields_enum, required: false
|
63
|
+
argument :sort_order, Enum::SortOrder, required: false
|
64
|
+
end
|
65
|
+
end
|
55
66
|
else
|
56
67
|
field name, type, null: !auto_nil || !association_required?(association), klass: model_name
|
57
68
|
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HQ
|
4
|
+
module GraphQL
|
5
|
+
class PaginatedAssociationLoader < ::GraphQL::Batch::Loader
|
6
|
+
def initialize(model, association_name, limit: nil, offset: nil, sort_by: nil, sort_order: nil)
|
7
|
+
@model = model
|
8
|
+
@association_name = association_name
|
9
|
+
@limit = [0, limit].max if limit
|
10
|
+
@offset = [0, offset].max if offset
|
11
|
+
@sort_by = sort_by || :updated_at
|
12
|
+
@sort_order = normalize_sort_order(sort_order)
|
13
|
+
|
14
|
+
validate!
|
15
|
+
end
|
16
|
+
|
17
|
+
def load(record)
|
18
|
+
raise TypeError, "#{@model} loader can't load association for #{record.class}" unless record.is_a?(@model)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def cache_key(record)
|
23
|
+
record.send(primary_key)
|
24
|
+
end
|
25
|
+
|
26
|
+
def perform(records)
|
27
|
+
scope =
|
28
|
+
if @limit || @offset
|
29
|
+
# If a limit or offset is added, then we need to transform the query
|
30
|
+
# into a lateral join so that we can limit on groups of data.
|
31
|
+
#
|
32
|
+
# > SELECT * FROM addresses WHERE addresses.user_id IN ($1, $2, ..., $N) ORDER BY addresses.created_at DESC;
|
33
|
+
# ...becomes
|
34
|
+
# > SELECT DISTINCT a_top.*
|
35
|
+
# > FROM addresses
|
36
|
+
# > INNER JOIN LATERAL (
|
37
|
+
# > SELECT inner.*
|
38
|
+
# > FROM addresses inner
|
39
|
+
# > WHERE inner.user_id = addresses.user_id
|
40
|
+
# > ORDER BY inner.created_at DESC
|
41
|
+
# > LIMIT 1
|
42
|
+
# > ) a_top ON TRUE
|
43
|
+
# > WHERE addresses.user_id IN ($1, $2, ..., $N)
|
44
|
+
# > ORDER BY a_top.created_at DESC
|
45
|
+
inner_table = association_class.arel_table
|
46
|
+
association_table = inner_table.alias("outer")
|
47
|
+
|
48
|
+
inside_scope = default_scope.
|
49
|
+
select(inner_table[::Arel.star]).
|
50
|
+
from(inner_table).
|
51
|
+
where(inner_table[association_key].eq(association_table[association_key])).
|
52
|
+
reorder(arel_order(inner_table)).
|
53
|
+
limit(@limit).
|
54
|
+
offset(@offset)
|
55
|
+
|
56
|
+
outside_table = ::Arel::Table.new("top")
|
57
|
+
association_class.
|
58
|
+
select(outside_table[::Arel.star]).distinct.
|
59
|
+
from(association_table).
|
60
|
+
joins("INNER JOIN LATERAL (#{inside_scope.to_sql}) #{outside_table.name} ON TRUE").
|
61
|
+
where(association_table[association_key].in(records.map { |r| join_value(r) })).
|
62
|
+
reorder(arel_order(outside_table))
|
63
|
+
else
|
64
|
+
default_scope.
|
65
|
+
reorder(arel_order(association_class.arel_table)).
|
66
|
+
where(association_key => records.map { |r| join_value(r) })
|
67
|
+
end
|
68
|
+
|
69
|
+
results = scope.to_a
|
70
|
+
records.each do |record|
|
71
|
+
fulfill(record, association_value(record, results)) unless fulfilled?(record)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def association_key
|
78
|
+
belongs_to? ? association.association_primary_key : association.foreign_key
|
79
|
+
end
|
80
|
+
|
81
|
+
def association_value(record, results)
|
82
|
+
enumerator = has_many? ? :select : :detect
|
83
|
+
results.send(enumerator) { |r| r.send(association_key) == join_value(record) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def join_key
|
87
|
+
belongs_to? ? association.foreign_key : association.association_primary_key
|
88
|
+
end
|
89
|
+
|
90
|
+
def join_value(record)
|
91
|
+
record.send(join_key)
|
92
|
+
end
|
93
|
+
|
94
|
+
def default_scope
|
95
|
+
scope = association_class
|
96
|
+
scope = association.scopes.reduce(scope, &:merge)
|
97
|
+
scope = association_class.default_scopes.reduce(scope, &:merge)
|
98
|
+
scope
|
99
|
+
end
|
100
|
+
|
101
|
+
def belongs_to?
|
102
|
+
association.macro == :belongs_to
|
103
|
+
end
|
104
|
+
|
105
|
+
def has_many?
|
106
|
+
association.macro == :has_many
|
107
|
+
end
|
108
|
+
|
109
|
+
def association
|
110
|
+
@model.reflect_on_association(@association_name)
|
111
|
+
end
|
112
|
+
|
113
|
+
def association_class
|
114
|
+
association.klass
|
115
|
+
end
|
116
|
+
|
117
|
+
def primary_key
|
118
|
+
@model.primary_key
|
119
|
+
end
|
120
|
+
|
121
|
+
def arel_order(table)
|
122
|
+
table[@sort_by].send(@sort_order)
|
123
|
+
end
|
124
|
+
|
125
|
+
def normalize_sort_order(input)
|
126
|
+
if input.to_s.casecmp("asc").zero?
|
127
|
+
:asc
|
128
|
+
else
|
129
|
+
:desc
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def validate!
|
134
|
+
raise ArgumentError, "No association #{@association_name} on #{@model}" unless association
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/hq/graphql/resource.rb
CHANGED
@@ -1,23 +1,41 @@
|
|
1
|
-
# typed: false
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
require "hq/graphql/resource/
|
3
|
+
require "hq/graphql/resource/auto_mutation"
|
5
4
|
|
6
5
|
module HQ
|
7
6
|
module GraphQL
|
8
7
|
module Resource
|
9
|
-
extend T::Helpers
|
10
|
-
|
11
8
|
def self.included(base)
|
12
9
|
super
|
13
|
-
::HQ::GraphQL.
|
10
|
+
::HQ::GraphQL.resources << base
|
14
11
|
base.include Scalars
|
15
12
|
base.include ::GraphQL::Types
|
13
|
+
base.extend ClassMethods
|
16
14
|
end
|
17
15
|
|
18
16
|
module ClassMethods
|
17
|
+
include AutoMutation
|
18
|
+
|
19
19
|
attr_writer :graphql_name, :model_name
|
20
20
|
|
21
|
+
def sort_fields(*fields)
|
22
|
+
self.sort_fields_enum = fields
|
23
|
+
end
|
24
|
+
|
25
|
+
def sort_fields_enum
|
26
|
+
@sort_fields_enum || ::HQ::GraphQL::Enum::SortBy
|
27
|
+
end
|
28
|
+
|
29
|
+
def sort_fields_enum=(fields)
|
30
|
+
@sort_fields_enum ||= Class.new(::HQ::GraphQL::Enum::SortBy).tap do |c|
|
31
|
+
c.graphql_name "#{graphql_name}Sort"
|
32
|
+
end
|
33
|
+
|
34
|
+
Array(fields).each do |field|
|
35
|
+
@sort_fields_enum.value field.to_s.classify, value: field
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
21
39
|
def scope(context)
|
22
40
|
scope = model_klass
|
23
41
|
scope = ::HQ::GraphQL.default_scope(scope, context)
|
@@ -39,7 +57,7 @@ module HQ
|
|
39
57
|
end
|
40
58
|
|
41
59
|
def model_name
|
42
|
-
@model_name ||
|
60
|
+
@model_name || ::HQ::GraphQL.extract_class(self)
|
43
61
|
end
|
44
62
|
|
45
63
|
def model_klass
|
@@ -73,144 +91,10 @@ module HQ
|
|
73
91
|
end
|
74
92
|
|
75
93
|
def mutations(create: true, copy: true, update: true, destroy: true)
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
if create
|
81
|
-
create_mutation = ::HQ::GraphQL::Resource::Mutation.build(model_name, action: :create, graphql_name: "#{scoped_graphql_name}Create") do
|
82
|
-
define_method(:resolve) do |**args|
|
83
|
-
resource = scoped_self.new_record(context)
|
84
|
-
resource.assign_attributes(args[:attributes].format_nested_attributes)
|
85
|
-
if resource.save
|
86
|
-
{
|
87
|
-
resource: resource,
|
88
|
-
errors: {},
|
89
|
-
}
|
90
|
-
else
|
91
|
-
{
|
92
|
-
resource: nil,
|
93
|
-
errors: errors_from_resource(resource)
|
94
|
-
}
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
lazy_load do
|
99
|
-
argument :attributes, ::HQ::GraphQL::Inputs[scoped_model_name], required: true
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
mutation_klasses["create_#{scoped_graphql_name.underscore}"] = create_mutation
|
104
|
-
end
|
105
|
-
|
106
|
-
if copy
|
107
|
-
copy_mutation = ::HQ::GraphQL::Resource::Mutation.build(
|
108
|
-
model_name,
|
109
|
-
action: :copy,
|
110
|
-
graphql_name: "#{scoped_graphql_name}Copy",
|
111
|
-
require_primary_key: true,
|
112
|
-
nil_klass: true
|
113
|
-
) do
|
114
|
-
define_method(:resolve) do |**args|
|
115
|
-
resource = scoped_self.find_record(args, context)
|
116
|
-
|
117
|
-
if resource
|
118
|
-
copy = resource.copy
|
119
|
-
if copy.save
|
120
|
-
{
|
121
|
-
resource: copy,
|
122
|
-
errors: {},
|
123
|
-
}
|
124
|
-
else
|
125
|
-
{
|
126
|
-
resource: copy,
|
127
|
-
errors: errors_from_resource(copy)
|
128
|
-
}
|
129
|
-
end
|
130
|
-
else
|
131
|
-
{
|
132
|
-
resource: nil,
|
133
|
-
errors: { resource: "Unable to find #{scoped_graphql_name}" }
|
134
|
-
}
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
mutation_klasses["copy_#{scoped_graphql_name.underscore}"] = copy_mutation
|
140
|
-
end
|
141
|
-
|
142
|
-
if update
|
143
|
-
update_mutation = ::HQ::GraphQL::Resource::Mutation.build(
|
144
|
-
model_name,
|
145
|
-
action: :update,
|
146
|
-
graphql_name: "#{scoped_graphql_name}Update",
|
147
|
-
require_primary_key: true
|
148
|
-
) do
|
149
|
-
define_method(:resolve) do |**args|
|
150
|
-
resource = scoped_self.find_record(args, context)
|
151
|
-
|
152
|
-
if resource
|
153
|
-
resource.assign_attributes(args[:attributes].format_nested_attributes)
|
154
|
-
if resource.save
|
155
|
-
{
|
156
|
-
resource: resource,
|
157
|
-
errors: {},
|
158
|
-
}
|
159
|
-
else
|
160
|
-
{
|
161
|
-
resource: nil,
|
162
|
-
errors: errors_from_resource(resource)
|
163
|
-
}
|
164
|
-
end
|
165
|
-
else
|
166
|
-
{
|
167
|
-
resource: nil,
|
168
|
-
errors: { resource: "Unable to find #{scoped_graphql_name}" }
|
169
|
-
}
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
lazy_load do
|
174
|
-
argument :attributes, ::HQ::GraphQL::Inputs[scoped_model_name], required: true
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
mutation_klasses["update_#{scoped_graphql_name.underscore}"] = update_mutation
|
179
|
-
end
|
180
|
-
|
181
|
-
if destroy
|
182
|
-
destroy_mutation = ::HQ::GraphQL::Resource::Mutation.build(
|
183
|
-
model_name,
|
184
|
-
action: :destroy,
|
185
|
-
graphql_name: "#{scoped_graphql_name}Destroy",
|
186
|
-
require_primary_key: true
|
187
|
-
) do
|
188
|
-
define_method(:resolve) do |**attrs|
|
189
|
-
resource = scoped_self.find_record(attrs, context)
|
190
|
-
|
191
|
-
if resource
|
192
|
-
if resource.destroy
|
193
|
-
{
|
194
|
-
resource: resource,
|
195
|
-
errors: {},
|
196
|
-
}
|
197
|
-
else
|
198
|
-
{
|
199
|
-
resource: nil,
|
200
|
-
errors: errors_from_resource(resource)
|
201
|
-
}
|
202
|
-
end
|
203
|
-
else
|
204
|
-
{
|
205
|
-
resource: nil,
|
206
|
-
errors: { resource: "Unable to find #{scoped_graphql_name}" }
|
207
|
-
}
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
mutation_klasses["destroy_#{scoped_graphql_name.underscore}"] = destroy_mutation
|
213
|
-
end
|
94
|
+
mutation_klasses["create_#{graphql_name.underscore}"] = build_create if create
|
95
|
+
mutation_klasses["copy_#{graphql_name.underscore}"] = build_copy if copy
|
96
|
+
mutation_klasses["update_#{graphql_name.underscore}"] = build_update if update
|
97
|
+
mutation_klasses["destroy_#{graphql_name.underscore}"] = build_destroy if destroy
|
214
98
|
end
|
215
99
|
|
216
100
|
def query(**options, &block)
|
@@ -218,10 +102,10 @@ module HQ
|
|
218
102
|
end
|
219
103
|
|
220
104
|
def def_root(field_name, is_array: false, null: true, &block)
|
221
|
-
|
105
|
+
resource = self
|
222
106
|
resolver = -> {
|
223
107
|
Class.new(::GraphQL::Schema::Resolver) do
|
224
|
-
type = is_array ? [
|
108
|
+
type = is_array ? [resource.query_klass] : resource.query_klass
|
225
109
|
type type, null: null
|
226
110
|
class_eval(&block) if block
|
227
111
|
end
|
@@ -231,7 +115,7 @@ module HQ
|
|
231
115
|
}
|
232
116
|
end
|
233
117
|
|
234
|
-
def root_query(find_one: true, find_all: true, pagination:
|
118
|
+
def root_query(find_one: true, find_all: true, pagination: true, limit_max: 250)
|
235
119
|
field_name = graphql_name.underscore
|
236
120
|
scoped_self = self
|
237
121
|
|
@@ -250,18 +134,27 @@ module HQ
|
|
250
134
|
|
251
135
|
if find_all
|
252
136
|
def_root field_name.pluralize, is_array: true, null: false do
|
253
|
-
|
254
|
-
|
137
|
+
if pagination
|
138
|
+
argument :offset, Integer, required: false
|
139
|
+
argument :limit, Integer, required: false
|
140
|
+
end
|
141
|
+
argument :sort_by, scoped_self.sort_fields_enum, required: false
|
142
|
+
argument :sort_order, Enum::SortOrder, required: false
|
255
143
|
|
256
|
-
define_method(:resolve) do |
|
144
|
+
define_method(:resolve) do |limit: nil, offset: nil, sort_by: nil, sort_order: nil, **_attrs|
|
257
145
|
scope = scoped_self.scope(context).all
|
258
146
|
|
259
|
-
if pagination || page ||
|
260
|
-
|
261
|
-
limit = [
|
262
|
-
scope = scope.limit(limit).offset(
|
147
|
+
if pagination || page || limit
|
148
|
+
offset = [0, *offset].max
|
149
|
+
limit = [[limit_max, *limit].min, 0].max
|
150
|
+
scope = scope.limit(limit).offset(offset)
|
263
151
|
end
|
264
152
|
|
153
|
+
sort_by ||= :updated_at
|
154
|
+
sort_order ||= :desc
|
155
|
+
# There should be no risk for SQL injection since an enum is being used for both sort_by and sort_order
|
156
|
+
scope = scope.reorder(sort_by => sort_order)
|
157
|
+
|
265
158
|
scope
|
266
159
|
end
|
267
160
|
end
|
@@ -294,8 +187,6 @@ module HQ
|
|
294
187
|
end
|
295
188
|
end
|
296
189
|
end
|
297
|
-
|
298
|
-
mixes_in_class_methods(ClassMethods)
|
299
190
|
end
|
300
191
|
end
|
301
192
|
end
|