graphql-preload 1.0.4 → 2.0.0

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
  SHA1:
3
- metadata.gz: b779bc0472181687609ba597178bd337c1653dfc
4
- data.tar.gz: cdb4a938912f4337650e9f9f710083f8f2fe33f1
3
+ metadata.gz: c9cba006dfca0e52d2cbd40c5364594f358a3ac6
4
+ data.tar.gz: 3086dde1fc427ba144b721b9ae0d10cdca6f0f2d
5
5
  SHA512:
6
- metadata.gz: 21b41bf9c3c001560f37598d0040a2651812fd298cd692108dacb35750992cab9d5172ae7f84164225c5621ffdd1c50d5e06a5ad192e1c295ce723747581d663
7
- data.tar.gz: d9f543bf8f0a4d30024da23e4baf620c546fcd3db253b9076b98da793c8a232b4281c473805d608c6b152c115cec2e1f740fda54594812d56ada7316ef27d759
6
+ metadata.gz: d1490ee109e5d08f0bd5c8a5d84ba29981487a0a7950942fbab89936272dbe73180e135406b748c297991381ea867fea8b5a5a2ac2d684e3ab29d8945562bc94
7
+ data.tar.gz: 7de182aa0dba1117bbfb15482e7a218407f4022bdc62d8336f1133cac9c2149d424be4fa43f35e23dcc4ccab28692a154ce4e0f6ff8c5e73ccdf3cffbc862122
data/README.md CHANGED
@@ -49,6 +49,27 @@ Call `preload` when defining your field:
49
49
  end
50
50
  end
51
51
 
52
+ ### `preload_scope`
53
+ Starting with Rails 4.1, you can scope your preloaded records by passing a valid scope to [`ActiveRecord::Associations::Preloader`](https://apidock.com/rails/v4.1.8/ActiveRecord/Associations/Preloader/preload). Scoping can improve performance by reducing the number of models to be instantiated and can help with certain business goals (e.g., only returning records that have not been soft deleted).
54
+
55
+ This functionality is surfaced through the `preload_scope` option:
56
+
57
+ PostType = GraphQL::ObjectType.define do
58
+ name 'Post'
59
+
60
+ field :comments, !types[!CommentType] do
61
+ preload :comments
62
+ preload_scope ->(args, ctx) { Comment.where(deleted_at: nil) }
63
+
64
+ # Resolves with records returned from the following query:
65
+ # SELECT "comments".*
66
+ # FROM "comments"
67
+ # WHERE "comments"."deleted_at" IS NULL
68
+ # AND "comments"."post_id" IN (1, 2, 3)
69
+ resolve ->(obj, args, ctx) { obj.comments }
70
+ end
71
+ end
72
+
52
73
  ## Development
53
74
 
54
75
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -21,12 +21,12 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ['lib']
23
23
 
24
- spec.add_runtime_dependency 'activerecord', '>= 3.2', '< 6'
24
+ spec.add_runtime_dependency 'activerecord', '>= 4.1', '< 6'
25
25
  spec.add_runtime_dependency 'graphql', '>= 1.5', '< 2'
26
26
  spec.add_runtime_dependency 'graphql-batch', '~> 0.3'
27
27
  spec.add_runtime_dependency 'promise.rb', '~> 0.7'
28
28
 
29
- spec.add_development_dependency 'bundler', '~> 1.15'
29
+ spec.add_development_dependency 'bundler', '~> 1.16'
30
30
  spec.add_development_dependency 'minitest', '~> 5.0'
31
31
  spec.add_development_dependency 'pry', '~> 0.10'
32
32
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -6,7 +6,8 @@ GraphQL::Field.accepts_definitions(
6
6
  preload: ->(type, *args) do
7
7
  type.metadata[:preload] ||= []
8
8
  type.metadata[:preload].concat(args)
9
- end
9
+ end,
10
+ preload_scope: ->(type, arg) { type.metadata[:preload_scope] = arg }
10
11
  )
11
12
 
12
13
  GraphQL::Schema.accepts_definitions(
@@ -9,7 +9,11 @@ module GraphQL
9
9
  new_resolver = ->(obj, args, ctx) do
10
10
  return old_resolver.call(obj, args, ctx) unless obj
11
11
 
12
- preload(obj, field.metadata[:preload]).then do
12
+ if field.metadata[:preload_scope]
13
+ scope = field.metadata[:preload_scope].call(args, ctx)
14
+ end
15
+
16
+ preload(obj, field.metadata[:preload], scope).then do
13
17
  old_resolver.call(obj, args, ctx)
14
18
  end
15
19
  end
@@ -19,32 +23,35 @@ module GraphQL
19
23
  end
20
24
  end
21
25
 
22
- private def preload(record, associations)
23
- raise TypeError, "Expected #{associations} to be a Symbol, not a String" if associations.is_a?(String)
24
- return preload_single_association(record, associations) if associations.is_a?(Symbol)
26
+ private def preload(record, associations, scope)
27
+ if associations.is_a?(String)
28
+ raise TypeError, "Expected #{associations} to be a Symbol, not a String"
29
+ elsif associations.is_a?(Symbol)
30
+ return preload_single_association(record, associations, scope)
31
+ end
25
32
 
26
33
  promises = []
27
34
 
28
35
  Array.wrap(associations).each do |association|
29
36
  case association
30
37
  when Symbol
31
- promises << preload_single_association(record, association)
38
+ promises << preload_single_association(record, association, scope)
32
39
  when Array
33
40
  association.each do |sub_association|
34
- promises << preload(record, sub_association)
41
+ promises << preload(record, sub_association, scope)
35
42
  end
36
43
  when Hash
37
44
  association.each do |sub_association, nested_association|
38
- promises << preload_single_association(record, sub_association).then do
45
+ promises << preload_single_association(record, sub_association, scope).then do
39
46
  associated_records = record.public_send(sub_association)
40
47
 
41
48
  case associated_records
42
49
  when ActiveRecord::Base
43
- preload(associated_records, nested_association)
50
+ preload(associated_records, nested_association, scope)
44
51
  else
45
52
  Promise.all(
46
53
  Array.wrap(associated_records).map do |associated_record|
47
- preload(associated_record, nested_association)
54
+ preload(associated_record, nested_association, scope)
48
55
  end
49
56
  )
50
57
  end
@@ -56,8 +63,19 @@ module GraphQL
56
63
  Promise.all(promises)
57
64
  end
58
65
 
59
- private def preload_single_association(record, association)
60
- GraphQL::Preload::Loader.for(record.class, association).load(record)
66
+ private def preload_single_association(record, association, scope)
67
+ # We would like to pass the `scope` (which is an `ActiveRecord::Relation`),
68
+ # directly into `Loader.for`. However, because the scope is
69
+ # created for each parent record, they are different objects and
70
+ # return different loaders, breaking batching.
71
+ # Therefore, we pass in `scope.to_sql`, which is the same for all the
72
+ # scopes and set the `scope` using an accessor. The actual scope
73
+ # object used will be the last one, which shouldn't make any difference,
74
+ # because even though they are different objects, they are all
75
+ # functionally equivalent.
76
+ loader = GraphQL::Preload::Loader.for(record.class, association, scope.try(:to_sql))
77
+ loader.scope = scope
78
+ loader.load(record)
61
79
  end
62
80
  end
63
81
  end
@@ -2,13 +2,14 @@ module GraphQL
2
2
  module Preload
3
3
  # Preloads ActiveRecord::Associations when called from the Preload::Instrument
4
4
  class Loader < GraphQL::Batch::Loader
5
+ attr_accessor :scope
5
6
  attr_reader :association, :model
6
7
 
7
8
  def cache_key(record)
8
9
  record.object_id
9
10
  end
10
11
 
11
- def initialize(model, association)
12
+ def initialize(model, association, _scope_sql)
12
13
  @association = association
13
14
  @model = model
14
15
 
@@ -34,12 +35,11 @@ module GraphQL
34
35
  end
35
36
 
36
37
  private def preload_association(records)
37
- if ((ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR >= 1) ||
38
- ActiveRecord::VERSION::MAJOR > 4)
39
- ActiveRecord::Associations::Preloader.new.preload(records, association)
40
- else
41
- ActiveRecord::Associations::Preloader.new(records, association).run
42
- end
38
+ ActiveRecord::Associations::Preloader.new.preload(records, association, preload_scope)
39
+ end
40
+
41
+ private def preload_scope
42
+ scope if scope.try(:klass) == model.reflect_on_association(association).klass
43
43
  end
44
44
 
45
45
  private def validate_association
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module Preload
3
- VERSION = '1.0.4'.freeze
3
+ VERSION = '2.0.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-preload
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Foster, Etienne Tripier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-11-03 00:00:00.000000000 Z
11
+ date: 2018-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '3.2'
19
+ version: '4.1'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
22
  version: '6'
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '3.2'
29
+ version: '4.1'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '6'
@@ -84,14 +84,14 @@ dependencies:
84
84
  requirements:
85
85
  - - "~>"
86
86
  - !ruby/object:Gem::Version
87
- version: '1.15'
87
+ version: '1.16'
88
88
  type: :development
89
89
  prerelease: false
90
90
  version_requirements: !ruby/object:Gem::Requirement
91
91
  requirements:
92
92
  - - "~>"
93
93
  - !ruby/object:Gem::Version
94
- version: '1.15'
94
+ version: '1.16'
95
95
  - !ruby/object:Gem::Dependency
96
96
  name: minitest
97
97
  requirement: !ruby/object:Gem::Requirement