ar_lazy_preload 0.5.0 → 0.6.2

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: 122262e10bed3d8a2793e62f24ee39e7a01453933c68d8faffce8945b794a522
4
- data.tar.gz: e50652dd855cbbbe39b292b20e07fca7f43ba660fd2f28f0c41de1fa0595c281
3
+ metadata.gz: d3b1c23e853fd6612e6d7f4f063e4cac5577c591a9a3b7be3b04c4a6801d1291
4
+ data.tar.gz: 66a30a3b81d996ebbb9324dfedf4f874498da1b202f66dcab17385fc8596bde3
5
5
  SHA512:
6
- metadata.gz: 97a5044a80f021ea4f4c0bd0b27b0881858371bb60051014f885c791570346bafae8c2e4110f5f492817cbe38d44c551a4045d886a67e1ede30a5af5079afdac
7
- data.tar.gz: '0458891f44c12464f6ce33295c6ef4e20bf55df63dc81a46ad840401cb47f1a782c466ee87f4c44c965b5090bc1c02ed2bead8a90f9b5031f9665803ade8e0a6'
6
+ metadata.gz: 7fae1e071f6f199d8a881a2bf0a60a4b5d53e1467428d07e39d67d98539a20a9b8b319ac4aac7ba73675367c648db441a7eb24bcf1f252c0becddd7905150d9c
7
+ data.tar.gz: 7c5107b91f3671cc4fa16ae38af7a8b54832c31afc10e337c76eb3b4f24440eff87d5202577bbbcef38628f3266af057966a4120097c8cf887524a8c8ce03ba3
data/README.md CHANGED
@@ -60,6 +60,19 @@ posts = User.preload_associations_lazily.flat_map(&:posts)
60
60
  # => SELECT * FROM posts WHERE user_id in (...)
61
61
  ```
62
62
 
63
+ ## Gotchas
64
+
65
+ 1. Lazy preloading [does not work](https://github.com/DmitryTsepelev/ar_lazy_preload/pull/40/files) for ActiveRecord < 6 when `.includes` is called earlier:
66
+
67
+ ```ruby
68
+ Post.includes(:user).preload_associations_lazily.each do |p|
69
+ p.user.comments.load
70
+ end
71
+ ```
72
+
73
+ 2. When `#size` is called on association (e.g., `User.lazy_preload(:posts).map { |u| u.posts.size }`), lazy preloading won't happen, because `#size` method performs `SELECT COUNT()` database request instead of loading the association when association haven't been loaded yet ([here](https://github.com/DmitryTsepelev/ar_lazy_preload/pull/42) is the issue, and [here](https://blazarblogs.wordpress.com/2019/07/27/activerecord-size-vs-count-vs-length/) is the explanation article about `size`, `length` and `count`).
74
+
75
+
63
76
  ## Installation
64
77
 
65
78
  Add this line to your application's Gemfile, and you're all set:
@@ -9,16 +9,17 @@ module ArLazyPreload
9
9
  setup_preloading_context unless ArLazyPreload.config.auto_preload?
10
10
  end
11
11
 
12
- delegate :owner, :reflection, to: :proxy_association
13
- delegate :lazy_preload_context, to: :owner
12
+ delegate :owner, :reflection, to: :proxy_association, prefix: true
13
+ delegate :lazy_preload_context, to: :proxy_association_owner
14
14
 
15
15
  private
16
16
 
17
17
  def setup_preloading_context
18
18
  return if lazy_preload_context.nil?
19
+ return if lazy_preload_context.association_tree.nil?
19
20
 
20
21
  association_tree_builder = AssociationTreeBuilder.new(lazy_preload_context.association_tree)
21
- subtree = association_tree_builder.subtree_for(reflection.name)
22
+ subtree = association_tree_builder.subtree_for(proxy_association_reflection.name)
22
23
 
23
24
  lazy_preload!(subtree)
24
25
  end
@@ -1,12 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "ar_lazy_preload/context"
4
+ require "ar_lazy_preload/contexts/temporary_preload_config"
5
+ require "ar_lazy_preload/preloaded_records_converter"
4
6
 
5
7
  module ArLazyPreload
6
8
  # ActiveRecord::Relation patch with lazy preloading support
7
9
  module Relation
8
10
  attr_writer :preloads_associations_lazily
9
11
 
12
+ def preload_associations(records)
13
+ preload = preload_values
14
+ preload += includes_values unless eager_loading?
15
+ preloader = nil
16
+ preload.each do |associations|
17
+ preloader ||= build_preloader
18
+ preloader_associations = preloader.preload records, associations
19
+ preloader_associations.each do |preloader_association|
20
+ handle_preloaded_records(preloader_association.preloaded_records)
21
+ end
22
+ end
23
+ end
24
+
10
25
  # Enhanced #load method will check if association has not been loaded yet and add a context
11
26
  # for lazy preloading to loaded each record
12
27
  def load
@@ -75,6 +90,20 @@ module ArLazyPreload
75
90
  @preloads_associations_lazily ||= false
76
91
  end
77
92
 
93
+ def handle_preloaded_records(preloaded_records)
94
+ return unless Contexts::TemporaryPreloadConfig.enabled? || preloads_associations_lazily?
95
+
96
+ records_array = PreloadedRecordsConverter.call(preloaded_records)
97
+
98
+ return unless records_array&.any?
99
+
100
+ Context.register(
101
+ records: records_array,
102
+ association_tree: lazy_preload_values,
103
+ auto_preload: true
104
+ )
105
+ end
106
+
78
107
  attr_writer :lazy_preload_values
79
108
  end
80
109
  end
@@ -28,8 +28,10 @@ module ArLazyPreload
28
28
  associated_records = parent_context.records.flat_map do |record|
29
29
  next if record.nil?
30
30
 
31
- record_association = record.association(association_name)
32
31
  reflection = reflection_cache[record.class]
32
+ next if reflection.nil?
33
+
34
+ record_association = record.association(association_name)
33
35
  reflection.collection? ? record_association.target : record_association.reader
34
36
  end
35
37
 
@@ -3,6 +3,7 @@
3
3
  require "ar_lazy_preload/contexts/base_context"
4
4
  require "ar_lazy_preload/contexts/auto_preload_context"
5
5
  require "ar_lazy_preload/contexts/lazy_preload_context"
6
+ require "ar_lazy_preload/contexts/temporary_preload_config"
6
7
 
7
8
  module ArLazyPreload
8
9
  class Context
@@ -19,6 +19,9 @@ module ArLazyPreload
19
19
  @records.each { |record| record.lazy_preload_context = self }
20
20
  end
21
21
 
22
+ # @api
23
+ def association_tree; nil; end
24
+
22
25
  # This method checks if the association should be loaded and preloads it for all
23
26
  # objects in the context it if needed.
24
27
  def try_preload_lazily(association_name)
@@ -41,7 +44,11 @@ module ArLazyPreload
41
44
  private
42
45
 
43
46
  def perform_preloading(association_name)
44
- preloader.preload(records, association_name)
47
+ filtered_records = records.select do |record|
48
+ reflection_names_cache[record.class].include?(association_name)
49
+ end
50
+
51
+ preload_records(association_name, filtered_records)
45
52
  loaded_association_names.add(association_name)
46
53
 
47
54
  AssociatedContextBuilder.prepare(
@@ -50,6 +57,16 @@ module ArLazyPreload
50
57
  )
51
58
  end
52
59
 
60
+ # Method preloads associations for the specific sets of the records
61
+ # and provides automatically provides context for the records
62
+ # loaded using `includes` inside Relation#preload_associations with the
63
+ # help of the TemporaryPreloadConfig
64
+ def preload_records(association_name, records)
65
+ TemporaryPreloadConfig.within_context do
66
+ preloader.preload(records, association_name)
67
+ end
68
+ end
69
+
53
70
  def association_loaded?(association_name)
54
71
  loaded_association_names.include?(association_name)
55
72
  end
@@ -61,6 +78,12 @@ module ArLazyPreload
61
78
  def preloader
62
79
  @preloader ||= ActiveRecord::Associations::Preloader.new
63
80
  end
81
+
82
+ def reflection_names_cache
83
+ @reflection_names_cache ||= Hash.new do |hash, klass|
84
+ hash[klass] = klass.reflect_on_all_associations.map(&:name)
85
+ end
86
+ end
64
87
  end
65
88
  end
66
89
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArLazyPreload
4
+ module Contexts
5
+ # Preload config that used to enable preloading only for specfic part of the application
6
+ class TemporaryPreloadConfig
7
+ THREAD_KEY = "temporary_preload_context_enabled"
8
+
9
+ class << self
10
+ def enabled?
11
+ Thread.current[THREAD_KEY] == true
12
+ end
13
+
14
+ def within_context
15
+ Thread.current[THREAD_KEY] = true
16
+ yield
17
+ ensure
18
+ Thread.current[THREAD_KEY] = nil
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArLazyPreload
4
+ class PreloadedRecordsConverter
5
+ # For different versions of rails we have different records class
6
+ # for ~> 6.1.0 it returns plain array
7
+ # for ~> 6.0.0 it returns ActiveRecord::Relation
8
+ def self.call(preloaded_records)
9
+ case preloaded_records
10
+ when Array
11
+ preloaded_records
12
+ when ::ActiveRecord::Relation
13
+ raise(ArgumentError, "The relation is not preloaded") unless preloaded_records.loaded?
14
+
15
+ preloaded_records.to_a
16
+ else
17
+ raise(ArgumentError, "Unsupported class for preloaded records")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ArLazyPreload
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar_lazy_preload
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - DmitryTsepelev
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-27 00:00:00.000000000 Z
11
+ date: 2021-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.2'
19
+ version: '5.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '4.2'
26
+ version: '5.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -81,7 +81,21 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: coveralls
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov-lcov
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
101
  - - ">="
@@ -150,6 +164,20 @@ dependencies:
150
164
  - - ">="
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: pry
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
153
181
  description: lazy_preload implementation for ActiveRecord models
154
182
  email:
155
183
  - dmitry.a.tsepelev@gmail.com
@@ -175,13 +203,15 @@ files:
175
203
  - lib/ar_lazy_preload/contexts/auto_preload_context.rb
176
204
  - lib/ar_lazy_preload/contexts/base_context.rb
177
205
  - lib/ar_lazy_preload/contexts/lazy_preload_context.rb
206
+ - lib/ar_lazy_preload/contexts/temporary_preload_config.rb
207
+ - lib/ar_lazy_preload/preloaded_records_converter.rb
178
208
  - lib/ar_lazy_preload/railtie.rb
179
209
  - lib/ar_lazy_preload/version.rb
180
210
  homepage: https://github.com/DmitryTsepelev/ar_lazy_preload
181
211
  licenses:
182
212
  - MIT
183
213
  metadata: {}
184
- post_install_message:
214
+ post_install_message:
185
215
  rdoc_options: []
186
216
  require_paths:
187
217
  - lib
@@ -196,8 +226,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
196
226
  - !ruby/object:Gem::Version
197
227
  version: '0'
198
228
  requirements: []
199
- rubygems_version: 3.1.2
200
- signing_key:
229
+ rubygems_version: 3.1.1
230
+ signing_key:
201
231
  specification_version: 4
202
232
  summary: lazy_preload implementation for ActiveRecord models
203
233
  test_files: []