hq-graphql 2.1.0 → 2.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 05c40fc2e6eaccc1277c1b6f36b19983af507721b011d6d25c9010f877c18db7
4
- data.tar.gz: cf67ac6f3d02f3c6a5c5b0f1124e8aacd706d1e30b0077af69cad336d5dc1475
3
+ metadata.gz: 212005e9fc2e97777c5853273618ff5608c310db175324ac75a2e594cb3fa862
4
+ data.tar.gz: 2b656d56fee16a199bc865370fcdaa4f3460414909b07b2086d9df82c56d2738
5
5
  SHA512:
6
- metadata.gz: 2f643af54cb80d2325dc8829c620f9c4d0c48a950aced92cb0313b2aacba52a3b797f42deb0c1f8848b9454cf70d014c4795f64d5e34444d87cc4fdbe36702c8
7
- data.tar.gz: df55f4034030bed0f035a170d581bfe030f62a2545b2f4b7d15d48838c9777a7e4aa357e5f86618ee7bb692ba001c263df9973f8e546f8a1a902d0e8f61d4821
6
+ metadata.gz: 65e69ef87752fcd9322b06001fb8bd5dd91e7e6e234c43738776681c96d333d7729d3de88118ac1e7829b24d2c139aa71cb3affb055def6a5a63a63af3379079
7
+ data.tar.gz: 42016b3a7db045b5a768abb5217b9e68baf6b1d8b29e25b758f25c2c559190d2569671a297c0bf423518dae1f20130541904f87b74608105279a237a3677b8ad
@@ -8,6 +8,10 @@ require "hq/graphql/config"
8
8
 
9
9
  module HQ
10
10
  module GraphQL
11
+ class << self
12
+ delegate :default_object_class, to: :config
13
+ end
14
+
11
15
  def self.config
12
16
  @config ||= ::HQ::GraphQL::Config.new
13
17
  end
@@ -64,7 +68,6 @@ module HQ
64
68
  end
65
69
  end
66
70
 
67
- require "hq/graphql/active_record_extensions"
68
71
  require "hq/graphql/association_loader"
69
72
  require "hq/graphql/scalars"
70
73
  require "hq/graphql/comparator"
@@ -27,7 +27,7 @@ module HQ
27
27
  end
28
28
 
29
29
  def lazy_load!
30
- lazy_load.map(&:call)
30
+ lazy_load.each(&:call)
31
31
  @lazy_load = []
32
32
  end
33
33
 
@@ -5,6 +5,7 @@ module HQ
5
5
  class Config < Struct.new(
6
6
  :authorize,
7
7
  :authorize_field,
8
+ :default_object_class,
8
9
  :default_scope,
9
10
  :extract_class,
10
11
  :resource_lookup,
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "hq/graphql/types"
4
+
3
5
  module HQ::GraphQL
4
6
  class Enum < ::GraphQL::Schema::Enum
5
7
  ## Auto generate enums from the database using ActiveRecord
@@ -73,6 +75,3 @@ module HQ::GraphQL
73
75
  end
74
76
  end
75
77
  end
76
-
77
- require "hq/graphql/enum/sort_by"
78
- require "hq/graphql/enum/sort_order"
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "hq/graphql/enum"
4
+
3
5
  module HQ
4
6
  class GraphQL::Enum::SortBy < ::HQ::GraphQL::Enum
5
7
  value "CreatedAt", value: :created_at
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "hq/graphql/enum"
4
+
3
5
  module HQ
4
6
  class GraphQL::Enum::SortOrder < ::HQ::GraphQL::Enum
5
7
  value "ASC", value: :asc
@@ -9,7 +9,15 @@ module HQ
9
9
  super(*args, **options, &block)
10
10
  @authorize_action = authorize_action
11
11
  @authorize = authorize
12
- @class_name = klass
12
+ @klass_or_string = klass
13
+ end
14
+
15
+ def scope(&block)
16
+ if block
17
+ @scope = block
18
+ else
19
+ @scope
20
+ end
13
21
  end
14
22
 
15
23
  def authorized?(object, ctx)
@@ -18,35 +26,8 @@ module HQ
18
26
  ::HQ::GraphQL.authorize_field(authorize_action, self, object, ctx)
19
27
  end
20
28
 
21
- def resolve_field(object, args, ctx)
22
- if klass.present? && !!::GraphQL::Batch::Executor.current && object.object
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)
43
- else
44
- super
45
- end
46
- end
47
-
48
29
  def klass
49
- @klass ||= @class_name&.constantize
30
+ @klass ||= @klass_or_string.is_a?(String) ? @klass_or_string.constantize : @klass_or_string
50
31
  end
51
32
  end
52
33
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hq/graphql/association_loader"
4
+
5
+ module HQ
6
+ module GraphQL
7
+ module FieldExtension
8
+ class AssociationLoaderExtension < ::GraphQL::Schema::FieldExtension
9
+ def resolve(object:, **_kwargs)
10
+ AssociationLoader.for(options[:klass], field.original_name).load(object.object)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hq/graphql/enum/sort_by"
4
+ require "hq/graphql/enum/sort_order"
5
+
6
+ module HQ
7
+ module GraphQL
8
+ module FieldExtension
9
+ class PaginatedArguments < ::GraphQL::Schema::FieldExtension
10
+ def apply
11
+ field.argument :offset, Integer, required: false
12
+ field.argument :limit, Integer, required: false
13
+ field.argument :sort_order, Enum::SortOrder, required: false
14
+
15
+ resource = ::HQ::GraphQL.lookup_resource(options[:klass])
16
+ enum = resource ? resource.sort_fields_enum : ::HQ::GraphQL::Enum::SortBy
17
+ field.argument :sort_by, enum, required: false
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hq/graphql/paginated_association_loader"
4
+
5
+ module HQ
6
+ module GraphQL
7
+ module FieldExtension
8
+ class PaginatedLoader < ::GraphQL::Schema::FieldExtension
9
+ def resolve(object:, arguments:, **_options)
10
+ limit = arguments[:limit]
11
+ offset = arguments[:offset]
12
+ sort_by = arguments[:sort_by]
13
+ sort_order = arguments[:sort_order]
14
+ scope = field.scope.call(**arguments.except(:limit, :offset, :sort_by, :sort_order)) if field.scope
15
+ loader = PaginatedAssociationLoader.for(
16
+ klass,
17
+ association,
18
+ internal_association: internal_association,
19
+ scope: scope,
20
+ limit: limit,
21
+ offset: offset,
22
+ sort_by: sort_by,
23
+ sort_order: sort_order
24
+ )
25
+
26
+ loader.load(object.object)
27
+ end
28
+
29
+ private
30
+
31
+ def association
32
+ options[:association]
33
+ end
34
+
35
+ def internal_association
36
+ options[:internal_association]
37
+ end
38
+
39
+ def klass
40
+ options[:klass]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "hq/graphql/active_record_extensions"
4
+ require "hq/graphql/inputs"
5
+ require "hq/graphql/types"
6
+
3
7
  module HQ
4
8
  module GraphQL
5
9
  class InputObject < ::GraphQL::Schema::InputObject
@@ -1,12 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "hq/graphql/active_record_extensions"
4
+ require "hq/graphql/field"
5
+ require "hq/graphql/field_extension/association_loader_extension"
6
+ require "hq/graphql/field_extension/paginated_arguments"
7
+ require "hq/graphql/field_extension/paginated_loader"
8
+ require "hq/graphql/object_association"
9
+ require "hq/graphql/types"
10
+
3
11
  module HQ
4
12
  module GraphQL
5
13
  class Object < ::GraphQL::Schema::Object
6
14
  include Scalars
7
- include ::HQ::GraphQL::ActiveRecordExtensions
15
+ include ActiveRecordExtensions
16
+ extend ObjectAssociation
8
17
 
9
- field_class ::HQ::GraphQL::Field
18
+ field_class Field
10
19
 
11
20
  def self.authorize_action(action)
12
21
  self.authorized_action = action
@@ -28,8 +37,15 @@ module HQ
28
37
  end
29
38
 
30
39
  model_associations.each do |association|
40
+ next if resource_reflections[association.name.to_s]
31
41
  field_from_association(association, auto_nil: auto_nil)
32
42
  end
43
+
44
+ resource_reflections.values.each do |resource_reflection|
45
+ reflection = resource_reflection.reflection(model_klass)
46
+ next unless reflection
47
+ field_from_association(reflection, auto_nil: auto_nil, internal_association: true, &resource_reflection.block)
48
+ end
33
49
  end
34
50
  end
35
51
 
@@ -46,32 +62,38 @@ module HQ
46
62
  @authorized_action ||= :read
47
63
  end
48
64
 
49
- def field_from_association(association, auto_nil:)
50
- # The PaginationAssociationLoader doesn't support through associations yet
51
- return if association.through_reflection? && ::HQ::GraphQL.use_experimental_associations?
52
-
65
+ def field_from_association(association, auto_nil:, internal_association: false, &block)
53
66
  association_klass = association.klass
54
- type = ::HQ::GraphQL::Types[association_klass]
55
- name = association.name
67
+ name = association.name.to_s
68
+ return if fields[name]
69
+
70
+ klass = model_klass
71
+ type = Types[association_klass]
56
72
  case association.macro
57
73
  when :has_many
58
74
  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
75
+ if ::HQ::GraphQL.use_experimental_associations?
76
+ extension FieldExtension::PaginatedArguments, klass: association_klass
77
+ extension FieldExtension::PaginatedLoader, klass: klass, association: name, internal_association: internal_association
78
+ else
79
+ extension FieldExtension::AssociationLoaderExtension, klass: klass
64
80
  end
81
+ instance_eval(&block) if block
65
82
  end
66
83
  else
67
- field name, type, null: !auto_nil || !association_required?(association), klass: model_name
84
+ field name, type, null: !auto_nil || !association_required?(association), klass: model_name do
85
+ extension FieldExtension::AssociationLoaderExtension, klass: klass
86
+ end
68
87
  end
69
- rescue ::HQ::GraphQL::Types::Error
88
+ rescue Types::Error
70
89
  nil
71
90
  end
72
91
 
73
92
  def field_from_column(column, auto_nil:)
74
- field column.name, ::HQ::GraphQL::Types.type_from_column(column), null: !auto_nil || column.null
93
+ name = column.name
94
+ return if fields[name]
95
+
96
+ field name, Types.type_from_column(column), null: !auto_nil || column.null
75
97
  end
76
98
 
77
99
  def association_required?(association)
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HQ
4
+ module GraphQL
5
+ module ObjectAssociation
6
+ def reflect_on_association(association)
7
+ resource_reflections[association.to_s]&.reflection(model_klass)
8
+ end
9
+
10
+ def update(name, &block)
11
+ resource_reflections[name.to_s] = UpdatedReflection.new(name, block)
12
+ end
13
+
14
+ def belongs_to(name, scope = nil, **options, &block)
15
+ add_reflection(name, scope, options, :belongs_to, block)
16
+ end
17
+
18
+ def has_many(name, scope = nil, through: nil, **options, &block)
19
+ raise TypeError, "has_many through is unsupported" if through
20
+ add_reflection(name, scope, options, :has_many, block)
21
+ end
22
+
23
+ private
24
+
25
+ def resource_reflections
26
+ @resource_reflections ||= {}
27
+ end
28
+
29
+ def add_reflection(name, scope, options, macro, block)
30
+ resource_reflections[name.to_s] = ResourceReflection.new(name, scope, options, macro, block)
31
+ end
32
+
33
+ class ResourceReflection
34
+ attr_reader :name, :scope, :options, :macro, :block
35
+
36
+ def initialize(name, scope, options, macro, block)
37
+ @name = name
38
+ @scope = scope
39
+ @options = options
40
+ @macro = macro
41
+ @block = block
42
+ end
43
+
44
+ def reflection(model_klass)
45
+ if macro == :has_many
46
+ ::ActiveRecord::Associations::Builder::HasMany.create_reflection(model_klass, name, scope, options)
47
+ elsif macro == :belongs_to
48
+ ::ActiveRecord::Associations::Builder::BelongsTo.create_reflection(model_klass, name, scope, options)
49
+ end
50
+ end
51
+ end
52
+
53
+ class UpdatedReflection
54
+ attr_reader :name, :block
55
+
56
+ def initialize(name, block)
57
+ @name = name
58
+ @block = block
59
+ end
60
+
61
+ def reflection(model_klass)
62
+ model_klass.reflect_on_association(name)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,15 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "hq/graphql/types"
4
+
3
5
  module HQ
4
6
  module GraphQL
5
7
  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)
8
+ def self.for(*args, scope: nil, **kwargs)
9
+ if scope
10
+ raise TypeError, "scope must be an ActiveRecord::Relation" unless scope.is_a?(::ActiveRecord::Relation)
11
+ executor = ::GraphQL::Batch::Executor.current
12
+ loader_key = loader_key_for(*args, **kwargs, scope: scope.to_sql)
13
+ executor.loader(loader_key) { new(*args, **kwargs, scope: scope) }
14
+ else
15
+ super
16
+ end
17
+ end
18
+
19
+ def initialize(model, association_name, internal_association: false, limit: nil, offset: nil, scope: nil, sort_by: nil, sort_order: nil)
20
+ @model = model
21
+ @association_name = association_name
22
+ @internal_association = internal_association
23
+ @limit = [0, limit].max if limit
24
+ @offset = [0, offset].max if offset
25
+ @scope = scope
26
+ @sort_by = sort_by || :created_at
27
+ @sort_order = normalize_sort_order(sort_order)
13
28
 
14
29
  validate!
15
30
  end
@@ -24,7 +39,8 @@ module HQ
24
39
  end
25
40
 
26
41
  def perform(records)
27
- scope =
42
+ values = records.map { |r| source_value(r) }
43
+ scope =
28
44
  if @limit || @offset
29
45
  # If a limit or offset is added, then we need to transform the query
30
46
  # into a lateral join so that we can limit on groups of data.
@@ -42,59 +58,86 @@ module HQ
42
58
  # > ) a_top ON TRUE
43
59
  # > WHERE addresses.user_id IN ($1, $2, ..., $N)
44
60
  # > ORDER BY a_top.created_at DESC
45
- inner_table = association_class.arel_table
46
- association_table = inner_table.alias("outer")
61
+ inner_table = association_class.arel_table
62
+ lateral_join_table = through_reflection? ? through_association.klass.arel_table : inner_table
63
+ from_table = lateral_join_table.alias("outer")
47
64
 
48
65
  inside_scope = default_scope.
49
66
  select(inner_table[::Arel.star]).
50
67
  from(inner_table).
51
- where(inner_table[association_key].eq(association_table[association_key])).
68
+ where(lateral_join_table[target_join_key].eq(from_table[target_join_key])).
52
69
  reorder(arel_order(inner_table)).
53
70
  limit(@limit).
54
71
  offset(@offset)
55
72
 
56
- outside_table = ::Arel::Table.new("top")
73
+ if through_reflection?
74
+ # expose the through_reflection key
75
+ inside_scope = inside_scope.select(lateral_join_table[target_join_key])
76
+ end
77
+
78
+ lateral_table = ::Arel::Table.new("top")
57
79
  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))
80
+ select(lateral_table[::Arel.star]).distinct.
81
+ from(from_table).
82
+ where(from_table[target_join_key].in(values)).
83
+ joins("INNER JOIN LATERAL (#{inside_scope.to_sql}) #{lateral_table.name} ON TRUE").
84
+ reorder(arel_order(lateral_table))
63
85
  else
64
- default_scope.
65
- reorder(arel_order(association_class.arel_table)).
66
- where(association_key => records.map { |r| join_value(r) })
86
+ scope = default_scope.reorder(arel_order(association_class.arel_table))
87
+
88
+ if through_reflection?
89
+ scope.where(through_association.name => { target_join_key => values }).
90
+ # expose the through_reflection key
91
+ select(association_class.arel_table[::Arel.star], through_association.klass.arel_table[target_join_key])
92
+ else
93
+ scope.where(target_join_key => values)
94
+ end
67
95
  end
68
96
 
69
97
  results = scope.to_a
70
98
  records.each do |record|
71
- fulfill(record, association_value(record, results)) unless fulfilled?(record)
99
+ fulfill(record, target_value(record, results)) unless fulfilled?(record)
72
100
  end
73
101
  end
74
102
 
75
103
  private
76
104
 
77
- def association_key
78
- belongs_to? ? association.association_primary_key : association.foreign_key
105
+ def source_join_key
106
+ belongs_to? ? association.foreign_key : association.association_primary_key
79
107
  end
80
108
 
81
- def association_value(record, results)
82
- enumerator = has_many? ? :select : :detect
83
- results.send(enumerator) { |r| r.send(association_key) == join_value(record) }
109
+ def source_value(record)
110
+ record.send(source_join_key)
84
111
  end
85
112
 
86
- def join_key
87
- belongs_to? ? association.foreign_key : association.association_primary_key
113
+ def target_join_key
114
+ if through_reflection?
115
+ through_association.foreign_key
116
+ elsif belongs_to?
117
+ association.association_primary_key
118
+ else
119
+ association.foreign_key
120
+ end
88
121
  end
89
122
 
90
- def join_value(record)
91
- record.send(join_key)
123
+ def target_value(record, results)
124
+ enumerator = has_many? ? :select : :detect
125
+ results.send(enumerator) { |r| r.send(target_join_key) == source_value(record) }
92
126
  end
93
127
 
94
128
  def default_scope
95
129
  scope = association_class
96
130
  scope = association.scopes.reduce(scope, &:merge)
97
131
  scope = association_class.default_scopes.reduce(scope, &:merge)
132
+ scope = scope.merge(@scope) if @scope
133
+
134
+ if through_reflection?
135
+ source = association_class.arel_table
136
+ target = through_association.klass.arel_table
137
+ join = source.join(target).on(target[association.foreign_key].eq(source[source_join_key]))
138
+ scope = scope.joins(join.join_sources)
139
+ end
140
+
98
141
  scope
99
142
  end
100
143
 
@@ -106,8 +149,20 @@ module HQ
106
149
  association.macro == :has_many
107
150
  end
108
151
 
152
+ def through_association
153
+ association.through_reflection
154
+ end
155
+
156
+ def through_reflection?
157
+ association.through_reflection?
158
+ end
159
+
109
160
  def association
110
- @model.reflect_on_association(@association_name)
161
+ if @internal_association
162
+ Types[@model].reflect_on_association(@association_name)
163
+ else
164
+ @model.reflect_on_association(@association_name)
165
+ end
111
166
  end
112
167
 
113
168
  def association_class
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "hq/graphql/enum/sort_by"
4
+ require "hq/graphql/field_extension/paginated_arguments"
5
+ require "hq/graphql/input_object"
6
+ require "hq/graphql/object"
3
7
  require "hq/graphql/resource/auto_mutation"
8
+ require "hq/graphql/scalars"
4
9
 
5
10
  module HQ
6
11
  module GraphQL
@@ -18,24 +23,6 @@ module HQ
18
23
 
19
24
  attr_writer :graphql_name, :model_name
20
25
 
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
-
39
26
  def scope(context)
40
27
  scope = model_klass
41
28
  scope = ::HQ::GraphQL.default_scope(scope, context)
@@ -72,12 +59,24 @@ module HQ
72
59
  @input_klass ||= build_input_object
73
60
  end
74
61
 
75
- def nil_query_klass
76
- @nil_query_klass ||= build_graphql_object(name: "#{graphql_name}Copy", auto_nil: false)
62
+ def nil_query_object
63
+ @nil_query_object ||= build_graphql_object(name: "#{graphql_name}Copy", auto_nil: false)
77
64
  end
78
65
 
79
- def query_klass
80
- @query_klass ||= build_graphql_object
66
+ def query_object
67
+ @query_object ||= begin
68
+ if @query_object_options
69
+ options, block = @query_object_options
70
+ @query_object_options = nil
71
+ build_graphql_object(**options, &block)
72
+ else
73
+ build_graphql_object
74
+ end
75
+ end
76
+ end
77
+
78
+ def sort_fields_enum
79
+ @sort_fields_enum || ::HQ::GraphQL::Enum::SortBy
81
80
  end
82
81
 
83
82
  protected
@@ -98,14 +97,22 @@ module HQ
98
97
  end
99
98
 
100
99
  def query(**options, &block)
101
- @query_klass = build_graphql_object(**options, &block)
100
+ @query_object_options = [options, block]
101
+ end
102
+
103
+ def query_class(klass)
104
+ @query_class = klass
105
+ end
106
+
107
+ def sort_fields(*fields)
108
+ self.sort_fields_enum = fields
102
109
  end
103
110
 
104
111
  def def_root(field_name, is_array: false, null: true, &block)
105
112
  resource = self
106
113
  resolver = -> {
107
114
  Class.new(::GraphQL::Schema::Resolver) do
108
- type = is_array ? [resource.query_klass] : resource.query_klass
115
+ type = is_array ? [resource.query_object] : resource.query_object
109
116
  type type, null: null
110
117
  class_eval(&block) if block
111
118
  end
@@ -134,12 +141,7 @@ module HQ
134
141
 
135
142
  if find_all
136
143
  def_root field_name.pluralize, is_array: true, null: false do
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
144
+ extension FieldExtension::PaginatedArguments, klass: scoped_self.model_klass if pagination
143
145
 
144
146
  define_method(:resolve) do |limit: nil, offset: nil, sort_by: nil, sort_order: nil, **_attrs|
145
147
  scope = scoped_self.scope(context).all
@@ -166,7 +168,8 @@ module HQ
166
168
  def build_graphql_object(name: graphql_name, **options, &block)
167
169
  scoped_graphql_name = name
168
170
  scoped_model_name = model_name
169
- Class.new(::HQ::GraphQL::Object) do
171
+ object_class = @query_class || ::HQ::GraphQL.default_object_class || ::HQ::GraphQL::Object
172
+ Class.new(object_class) do
170
173
  graphql_name scoped_graphql_name
171
174
 
172
175
  with_model scoped_model_name, **options
@@ -186,6 +189,16 @@ module HQ
186
189
  class_eval(&block) if block
187
190
  end
188
191
  end
192
+
193
+ def sort_fields_enum=(fields)
194
+ @sort_fields_enum ||= Class.new(::HQ::GraphQL::Enum::SortBy).tap do |c|
195
+ c.graphql_name "#{graphql_name}Sort"
196
+ end
197
+
198
+ Array(fields).each do |field|
199
+ @sort_fields_enum.value field.to_s.classify, value: field
200
+ end
201
+ end
189
202
  end
190
203
  end
191
204
  end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "hq/graphql/inputs"
4
+ require "hq/graphql/mutation"
5
+ require "hq/graphql/types"
6
+
3
7
  module HQ
4
8
  module GraphQL
5
9
  module Resource
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "hq/graphql/types/object"
4
+ require "hq/graphql/types/uuid"
5
+
3
6
  module HQ
4
7
  module GraphQL
5
8
  module Types
@@ -10,7 +13,7 @@ module HQ
10
13
  def self.registry
11
14
  @registry ||= Hash.new do |hash, options|
12
15
  klass, nil_klass = Array(options)
13
- hash[klass] = nil_klass ? nil_query_klass(klass) : klass_for(klass)
16
+ hash[options] = nil_klass ? nil_query_object(klass) : klass_for(klass)
14
17
  end
15
18
  end
16
19
 
@@ -39,6 +42,10 @@ module HQ
39
42
  ::GraphQL::Types::Float
40
43
  when :boolean
41
44
  ::GraphQL::Types::Boolean
45
+ when :date
46
+ ::GraphQL::Types::ISO8601Date
47
+ when :datetime
48
+ ::GraphQL::Types::ISO8601DateTime
42
49
  else
43
50
  ::GraphQL::Types::String
44
51
  end
@@ -54,12 +61,12 @@ module HQ
54
61
  class << self
55
62
  private
56
63
 
57
- def nil_query_klass(klass_or_string)
58
- find_klass(klass_or_string, :nil_query_klass)
64
+ def nil_query_object(klass_or_string)
65
+ find_klass(klass_or_string, :nil_query_object)
59
66
  end
60
67
 
61
68
  def klass_for(klass_or_string)
62
- find_klass(klass_or_string, :query_klass)
69
+ find_klass(klass_or_string, :query_object)
63
70
  end
64
71
 
65
72
  def find_klass(klass_or_string, method)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module HQ
4
4
  module GraphQL
5
- VERSION = "2.1.0"
5
+ VERSION = "2.1.5"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hq-graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danny Jones
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-17 00:00:00.000000000 Z
11
+ date: 2020-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -255,10 +255,14 @@ files:
255
255
  - lib/hq/graphql/enum/sort_by.rb
256
256
  - lib/hq/graphql/enum/sort_order.rb
257
257
  - lib/hq/graphql/field.rb
258
+ - lib/hq/graphql/field_extension/association_loader_extension.rb
259
+ - lib/hq/graphql/field_extension/paginated_arguments.rb
260
+ - lib/hq/graphql/field_extension/paginated_loader.rb
258
261
  - lib/hq/graphql/input_object.rb
259
262
  - lib/hq/graphql/inputs.rb
260
263
  - lib/hq/graphql/mutation.rb
261
264
  - lib/hq/graphql/object.rb
265
+ - lib/hq/graphql/object_association.rb
262
266
  - lib/hq/graphql/paginated_association_loader.rb
263
267
  - lib/hq/graphql/resource.rb
264
268
  - lib/hq/graphql/resource/auto_mutation.rb