hq-graphql 2.0.0 → 2.0.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: b4f7f2421a64474e031ddd0a97ef63d36c03218fdfc51bf759f0f1150e1ae5cb
4
- data.tar.gz: 19addd42ac2c2f4bae1b985cacbda91998a3616b71f2e9923e19e89e8937d111
3
+ metadata.gz: e2508bf165d504937a9ea21ecc4b463e98e75c4e0d1ae12156df59ed649e4c2d
4
+ data.tar.gz: 07c40cac0b734c307953864ba415700b0a5dad4769d48e168361449cbe3e3a8e
5
5
  SHA512:
6
- metadata.gz: '0777580cb64e038700a3d16e6a20ef1883fe5d39575a7d7db13df5e64950bf263946581463546b6dfe53ec4aab262a07cc9114896c7755c1aaf2428e3a81c22a'
7
- data.tar.gz: e51c0b4555486894697b81420264f6abd515bb81eeeff5df4869b0a3ebb46430e6a20a3c5179e4a6d1b32e930e7253e2706377dc127394b8a66d53831986a3da
6
+ metadata.gz: 8c7d4b1e507b25c0288a55387c183079e2dfbe8fec512a15c09d47f1352ed14655dd2507f86f996848a7bf4513969d59fa16f6df8b873ce74488867b31a9832e
7
+ data.tar.gz: 86c8781c2e074dc2d6882144d8604f33d11cd943f1e3effe0949c5efb8ee26ee9b687c697e2e30e146a7651b41f31252b7ff7bfeb237ff331a917a8aba02e798
@@ -0,0 +1,27 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module HQ
5
+ module GraphQL
6
+ module Field
7
+ class AssociationLoader < ::GraphQL::Schema::Field
8
+ attr_reader :loader_klass
9
+
10
+ def initialize(*args, loader_klass: nil, **options, &block)
11
+ super(*args, **options, &block)
12
+ @loader_klass = loader_klass
13
+ end
14
+
15
+ def resolve_field(object, args, ctx)
16
+ if loader_klass.present? && !!::GraphQL::Batch::Executor.current && object.object
17
+ Loaders::Association.for(loader_klass.constantize, original_name).load(object.object).then do
18
+ super
19
+ end
20
+ else
21
+ super
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # typed: strict
4
+ require "hq/graphql/field/association_loader"
@@ -0,0 +1,52 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module HQ
5
+ module GraphQL
6
+ module Loaders
7
+ class Association < ::GraphQL::Batch::Loader
8
+ def initialize(model, association_name)
9
+ @model = model
10
+ @association_name = association_name
11
+ validate
12
+ end
13
+
14
+ def load(record)
15
+ raise TypeError, "#{@model} loader can't load association for #{record.class}" unless record.is_a?(@model)
16
+ return Promise.resolve(read_association(record)) if association_loaded?(record)
17
+ super
18
+ end
19
+
20
+ # We want to load the associations on all records, even if they have the same id
21
+ def cache_key(record)
22
+ record.object_id
23
+ end
24
+
25
+ def perform(records)
26
+ preload_association(records)
27
+ records.each { |record| fulfill(record, read_association(record)) }
28
+ end
29
+
30
+ private
31
+
32
+ def validate
33
+ unless @model.reflect_on_association(@association_name)
34
+ raise ArgumentError, "No association #{@association_name} on #{@model}"
35
+ end
36
+ end
37
+
38
+ def preload_association(records)
39
+ ::ActiveRecord::Associations::Preloader.new.preload(records, @association_name)
40
+ end
41
+
42
+ def read_association(record)
43
+ record.public_send(@association_name)
44
+ end
45
+
46
+ def association_loaded?(record)
47
+ record.association(@association_name).loaded?
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ # typed: strict
4
+ require "hq/graphql/loaders/association"
@@ -7,18 +7,20 @@ module HQ
7
7
  include Scalars
8
8
  include ::HQ::GraphQL::ActiveRecordExtensions
9
9
 
10
- def self.with_model(model_name, attributes: true, associations: true)
10
+ field_class ::HQ::GraphQL::Field::AssociationLoader
11
+
12
+ def self.with_model(model_name, attributes: true, associations: true, auto_nil: true)
11
13
  self.model_name = model_name
12
14
  self.auto_load_attributes = attributes
13
15
  self.auto_load_associations = associations
14
16
 
15
17
  lazy_load do
16
18
  model_columns.each do |column|
17
- field_from_column(column)
19
+ field_from_column(column, auto_nil: auto_nil)
18
20
  end
19
21
 
20
22
  model_associations.each do |association|
21
- field_from_association association
23
+ field_from_association(association, auto_nil: auto_nil)
22
24
  end
23
25
  end
24
26
  end
@@ -31,21 +33,28 @@ module HQ
31
33
  class << self
32
34
  private
33
35
 
34
- def field_from_association(association)
36
+ def field_from_association(association, auto_nil:)
35
37
  type = ::HQ::GraphQL::Types[association.klass]
36
38
  name = association.name
37
39
  case association.macro
38
40
  when :has_many
39
- field name, [type], null: false
41
+ field name, [type], null: false, loader_klass: model_name
40
42
  else
41
- field name, type, null: true
43
+ field name, type, null: !auto_nil || !association_required?(association), loader_klass: model_name
42
44
  end
43
45
  rescue ::HQ::GraphQL::Types::Error
44
46
  nil
45
47
  end
46
48
 
47
- def field_from_column(column)
48
- field column.name, ::HQ::GraphQL::Types.type_from_column(column), null: true
49
+ def field_from_column(column, auto_nil:)
50
+ field column.name, ::HQ::GraphQL::Types.type_from_column(column), null: !auto_nil || column.null
51
+ end
52
+
53
+ def association_required?(association)
54
+ !association.options[:optional] || model_klass.validators.any? do |validation|
55
+ next unless validation.class == ActiveRecord::Validations::PresenceValidator
56
+ validation.attributes.any? { |a| a.to_s == association.name.to_s }
57
+ end
49
58
  end
50
59
  end
51
60
  end
@@ -5,13 +5,13 @@ module HQ
5
5
  module GraphQL
6
6
  module Resource
7
7
  module Mutation
8
- def self.build(model_name, graphql_name:, require_primary_key: false, &block)
8
+ def self.build(model_name, graphql_name:, require_primary_key: false, show_copy: false, &block)
9
9
  Class.new(::HQ::GraphQL::Mutation) do
10
10
  graphql_name graphql_name
11
11
 
12
12
  lazy_load do
13
13
  field :errors, ::HQ::GraphQL::Types::Object, null: false
14
- field :resource, ::HQ::GraphQL::Types[model_name], null: true
14
+ field :resource, ::HQ::GraphQL::Types[model_name, show_copy], null: true
15
15
  end
16
16
 
17
17
  instance_eval(&block)
@@ -53,6 +53,10 @@ module HQ
53
53
  @input_klass ||= build_input_object
54
54
  end
55
55
 
56
+ def input_result_klass
57
+ @input_result_klass ||= build_graphql_object(name: "#{graphql_name}Copy", auto_nil: false)
58
+ end
59
+
56
60
  def query_klass
57
61
  @query_klass ||= build_graphql_object
58
62
  end
@@ -67,6 +71,10 @@ module HQ
67
71
  @input_klass = build_input_object(**options, &block)
68
72
  end
69
73
 
74
+ def input_result(**options, &block)
75
+ @input_result_klass = build_graphql_object(name: "#{graphql_name}Copy", **options, auto_nil: false, &block)
76
+ end
77
+
70
78
  def mutations(create: true, copy: true, update: true, destroy: true)
71
79
  scoped_graphql_name = graphql_name
72
80
  scoped_model_name = model_name
@@ -102,7 +110,8 @@ module HQ
102
110
  copy_mutation = ::HQ::GraphQL::Resource::Mutation.build(
103
111
  model_name,
104
112
  graphql_name: "#{scoped_graphql_name}Copy",
105
- require_primary_key: true
113
+ require_primary_key: true,
114
+ show_copy: true
106
115
  ) do
107
116
  define_method(:resolve) do |**args|
108
117
  resource = scoped_self.find_record(args, context)
@@ -251,8 +260,8 @@ module HQ
251
260
 
252
261
  private
253
262
 
254
- def build_graphql_object(**options, &block)
255
- scoped_graphql_name = graphql_name
263
+ def build_graphql_object(name: graphql_name, **options, &block)
264
+ scoped_graphql_name = name
256
265
  scoped_model_name = model_name
257
266
  Class.new(::HQ::GraphQL::Object) do
258
267
  graphql_name scoped_graphql_name
@@ -1,4 +1,4 @@
1
- # typed: strong
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "hq/graphql/types/object"
@@ -8,11 +8,12 @@ module HQ
8
8
  MISSING_TYPE_MSG = "The GraphQL type for `%{klass}` is missing."
9
9
  end
10
10
 
11
- def self.[](key)
12
- @types ||= Hash.new do |hash, klass|
13
- hash[klass] = klass_for(klass)
11
+ def self.[](key, *args)
12
+ @types ||= Hash.new do |hash, options|
13
+ klass, show_copy = Array(options)
14
+ hash[klass] = show_copy ? copy_klass(klass) : klass_for(klass)
14
15
  end
15
- @types[key]
16
+ @types[[key, *args]]
16
17
  end
17
18
 
18
19
  def self.type_from_column(column)
@@ -43,10 +44,18 @@ module HQ
43
44
  class << self
44
45
  private
45
46
 
47
+ def copy_klass(klass_or_string)
48
+ find_klass(klass_or_string, :input_result_klass)
49
+ end
50
+
46
51
  def klass_for(klass_or_string)
52
+ find_klass(klass_or_string, :query_klass)
53
+ end
54
+
55
+ def find_klass(klass_or_string, method)
47
56
  klass = klass_or_string.is_a?(String) ? klass_or_string.constantize : klass_or_string
48
- ::HQ::GraphQL.types.detect { |t| t.model_klass == klass }&.query_klass ||
49
- ::HQ::GraphQL.types.detect { |t| t.model_klass == klass.base_class }&.query_klass ||
57
+ ::HQ::GraphQL.types.detect { |t| t.model_klass == klass }&.send(method) ||
58
+ ::HQ::GraphQL.types.detect { |t| t.model_klass == klass.base_class }&.send(method) ||
50
59
  raise(Error, Error::MISSING_TYPE_MSG % { klass: klass.name })
51
60
  end
52
61
  end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module HQ
5
5
  module GraphQL
6
- VERSION = "2.0.0"
6
+ VERSION = "2.0.1"
7
7
  end
8
8
  end
data/lib/hq/graphql.rb CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  require "rails"
5
5
  require "graphql"
6
+ require "graphql/batch"
6
7
  require "sorbet-runtime"
7
8
  require "hq/graphql/config"
8
9
 
@@ -49,8 +50,10 @@ end
49
50
  require "hq/graphql/active_record_extensions"
50
51
  require "hq/graphql/scalars"
51
52
 
53
+ require "hq/graphql/field"
52
54
  require "hq/graphql/inputs"
53
55
  require "hq/graphql/input_object"
56
+ require "hq/graphql/loaders"
54
57
  require "hq/graphql/mutation"
55
58
  require "hq/graphql/object"
56
59
  require "hq/graphql/resource"
data/lib/hq-graphql.rb CHANGED
@@ -2,6 +2,6 @@
2
2
  # rubocop:enable Naming/FileName
3
3
  # frozen_string_literal: true
4
4
 
5
- # typed: strong
5
+ # typed: strict
6
6
 
7
7
  require "hq/graphql"
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.0.0
4
+ version: 2.0.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: 2019-09-04 00:00:00.000000000 Z
11
+ date: 2019-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -44,6 +44,20 @@ dependencies:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: 1.9.6
47
+ - !ruby/object:Gem::Dependency
48
+ name: graphql-batch
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.4'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.4'
47
61
  - !ruby/object:Gem::Dependency
48
62
  name: pg
49
63
  requirement: !ruby/object:Gem::Requirement
@@ -241,8 +255,12 @@ files:
241
255
  - lib/hq/graphql/active_record_extensions.rb
242
256
  - lib/hq/graphql/config.rb
243
257
  - lib/hq/graphql/engine.rb
258
+ - lib/hq/graphql/field.rb
259
+ - lib/hq/graphql/field/association_loader.rb
244
260
  - lib/hq/graphql/input_object.rb
245
261
  - lib/hq/graphql/inputs.rb
262
+ - lib/hq/graphql/loaders.rb
263
+ - lib/hq/graphql/loaders/association.rb
246
264
  - lib/hq/graphql/mutation.rb
247
265
  - lib/hq/graphql/object.rb
248
266
  - lib/hq/graphql/resource.rb