hq-graphql 2.0.0 → 2.0.1

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: 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