hq-graphql 2.1.0 → 2.1.5

Sign up to get free protection for your applications and to get access to all the features.
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