hq-graphql 2.1.0 → 2.1.1

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: 5aefcd1b96fb05c456ebdef007f9bcad196e7aba2cd864d6dd7c83f3b3fbc95c
4
+ data.tar.gz: 0173c91a781e4872cbf65c597f3266030850c8e0ba31b05d8f9b578f11263216
5
5
  SHA512:
6
- metadata.gz: 2f643af54cb80d2325dc8829c620f9c4d0c48a950aced92cb0313b2aacba52a3b797f42deb0c1f8848b9454cf70d014c4795f64d5e34444d87cc4fdbe36702c8
7
- data.tar.gz: df55f4034030bed0f035a170d581bfe030f62a2545b2f4b7d15d48838c9777a7e4aa357e5f86618ee7bb692ba001c263df9973f8e546f8a1a902d0e8f61d4821
6
+ metadata.gz: 8a7b74ef1631a0d98004beb6bec61ea6898994c3ff25366afbc1fa5ba47bc60e36061ea3875c655ecfc9ac088b5f809b28fb411c05bfaeeda4e7256742345d5e
7
+ data.tar.gz: 3f6ecdc50f8a59d0e4c5c204fe4ed5967064e82f0f4a9aa961277532d6a49be5fb534204fa03a928777133e1b9541ec86fd2f9b196c383ff9da2ce1c94736cdc
@@ -64,7 +64,6 @@ module HQ
64
64
  end
65
65
  end
66
66
 
67
- require "hq/graphql/active_record_extensions"
68
67
  require "hq/graphql/association_loader"
69
68
  require "hq/graphql/scalars"
70
69
  require "hq/graphql/comparator"
@@ -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,36 @@ module HQ
46
62
  @authorized_action ||= :read
47
63
  end
48
64
 
49
- def field_from_association(association, auto_nil:)
65
+ def field_from_association(association, auto_nil:, internal_association: false, &block)
50
66
  # The PaginationAssociationLoader doesn't support through associations yet
51
67
  return if association.through_reflection? && ::HQ::GraphQL.use_experimental_associations?
52
68
 
53
69
  association_klass = association.klass
54
- type = ::HQ::GraphQL::Types[association_klass]
55
- name = association.name
70
+ name = association.name
71
+ klass = model_klass
72
+ type = Types[association_klass]
56
73
  case association.macro
57
74
  when :has_many
58
75
  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
76
+ if ::HQ::GraphQL.use_experimental_associations?
77
+ extension FieldExtension::PaginatedArguments, klass: association_klass
78
+ extension FieldExtension::PaginatedLoader, klass: klass, association: name, internal_association: internal_association
79
+ else
80
+ extension FieldExtension::AssociationLoaderExtension, klass: klass
64
81
  end
82
+ instance_eval(&block) if block
65
83
  end
66
84
  else
67
- field name, type, null: !auto_nil || !association_required?(association), klass: model_name
85
+ field name, type, null: !auto_nil || !association_required?(association), klass: model_name do
86
+ extension FieldExtension::AssociationLoaderExtension, klass: klass
87
+ end
68
88
  end
69
- rescue ::HQ::GraphQL::Types::Error
89
+ rescue Types::Error
70
90
  nil
71
91
  end
72
92
 
73
93
  def field_from_column(column, auto_nil:)
74
- field column.name, ::HQ::GraphQL::Types.type_from_column(column), null: !auto_nil || column.null
94
+ field column.name, Types.type_from_column(column), null: !auto_nil || column.null
75
95
  end
76
96
 
77
97
  def association_required?(association)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HQ
4
+ module GraphQL
5
+ module ObjectAssociation
6
+ class ResourceReflection
7
+ attr_reader :name, :scope, :options, :macro, :block
8
+
9
+ def initialize(name, scope, options, macro, block)
10
+ @name = name
11
+ @scope = scope
12
+ @options = options
13
+ @macro = macro
14
+ @block = block
15
+ end
16
+
17
+ def reflection(model_klass)
18
+ if macro == :has_many
19
+ ::ActiveRecord::Associations::Builder::HasMany.create_reflection(model_klass, name, scope, options)
20
+ elsif macro == :belongs_to
21
+ ::ActiveRecord::Associations::Builder::BelongsTo.create_reflection(model_klass, name, scope, options)
22
+ end
23
+ end
24
+ end
25
+
26
+ def reflect_on_association(association)
27
+ resource_reflections[association.to_s]&.reflection(model_klass)
28
+ end
29
+
30
+ def belongs_to(name, scope = nil, **options, &block)
31
+ add_reflection(name, scope, options, :belongs_to, block)
32
+ end
33
+
34
+ def has_many(name, scope = nil, through: nil, **options, &block)
35
+ raise TypeError, "has_many through is unsupported" if through
36
+ add_reflection(name, scope, options, :has_many, block)
37
+ end
38
+
39
+ private
40
+
41
+ def resource_reflections
42
+ @resource_reflections ||= {}
43
+ end
44
+
45
+ def add_reflection(name, scope, options, macro, block)
46
+ resource_reflections[name.to_s] = ResourceReflection.new(name, scope, options, macro, block)
47
+ end
48
+ end
49
+ end
50
+ 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 || :updated_at
27
+ @sort_order = normalize_sort_order(sort_order)
13
28
 
14
29
  validate!
15
30
  end
@@ -95,6 +110,7 @@ module HQ
95
110
  scope = association_class
96
111
  scope = association.scopes.reduce(scope, &:merge)
97
112
  scope = association_class.default_scopes.reduce(scope, &:merge)
113
+ scope = scope.merge(@scope) if @scope
98
114
  scope
99
115
  end
100
116
 
@@ -107,7 +123,11 @@ module HQ
107
123
  end
108
124
 
109
125
  def association
110
- @model.reflect_on_association(@association_name)
126
+ if @internal_association
127
+ Types[@model].reflect_on_association(@association_name)
128
+ else
129
+ @model.reflect_on_association(@association_name)
130
+ end
111
131
  end
112
132
 
113
133
  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)
@@ -80,6 +67,10 @@ module HQ
80
67
  @query_klass ||= build_graphql_object
81
68
  end
82
69
 
70
+ def sort_fields_enum
71
+ @sort_fields_enum || ::HQ::GraphQL::Enum::SortBy
72
+ end
73
+
83
74
  protected
84
75
 
85
76
  def default_scope(&block)
@@ -101,6 +92,10 @@ module HQ
101
92
  @query_klass = build_graphql_object(**options, &block)
102
93
  end
103
94
 
95
+ def sort_fields(*fields)
96
+ self.sort_fields_enum = fields
97
+ end
98
+
104
99
  def def_root(field_name, is_array: false, null: true, &block)
105
100
  resource = self
106
101
  resolver = -> {
@@ -134,12 +129,7 @@ module HQ
134
129
 
135
130
  if find_all
136
131
  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
132
+ extension FieldExtension::PaginatedArguments, klass: scoped_self.model_klass if pagination
143
133
 
144
134
  define_method(:resolve) do |limit: nil, offset: nil, sort_by: nil, sort_order: nil, **_attrs|
145
135
  scope = scoped_self.scope(context).all
@@ -186,6 +176,16 @@ module HQ
186
176
  class_eval(&block) if block
187
177
  end
188
178
  end
179
+
180
+ def sort_fields_enum=(fields)
181
+ @sort_fields_enum ||= Class.new(::HQ::GraphQL::Enum::SortBy).tap do |c|
182
+ c.graphql_name "#{graphql_name}Sort"
183
+ end
184
+
185
+ Array(fields).each do |field|
186
+ @sort_fields_enum.value field.to_s.classify, value: field
187
+ end
188
+ end
189
189
  end
190
190
  end
191
191
  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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module HQ
4
4
  module GraphQL
5
- VERSION = "2.1.0"
5
+ VERSION = "2.1.1"
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.1
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-06-22 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