ar_lazy_preload 0.5.2 → 0.7.0
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 +4 -4
- data/README.md +13 -0
- data/lib/ar_lazy_preload/active_record/association_relation.rb +3 -3
- data/lib/ar_lazy_preload/active_record/relation.rb +29 -0
- data/lib/ar_lazy_preload/context.rb +1 -0
- data/lib/ar_lazy_preload/contexts/base_context.rb +34 -5
- data/lib/ar_lazy_preload/contexts/temporary_preload_config.rb +23 -0
- data/lib/ar_lazy_preload/preloaded_records_converter.rb +21 -0
- data/lib/ar_lazy_preload/version.rb +1 -1
- metadata +39 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 780b04da1bdc2de0822bcf78d9ac51d7e24f5bf66aee70d42e81a0a0cb662a21
|
4
|
+
data.tar.gz: bceb49a3525fcfcac85d8662485992bc81e37015a9100a4332f3aaae1c0bd59e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d32ee8fe923b29451f032ebb050168d6eaec8d5aec0b3136917a8196284198323ed804061bf64c7b36e104b95a43ced12372baa39a79976a1f69ae22a44c190c
|
7
|
+
data.tar.gz: d104a956772f064cb255db1f6743edd312da87da0526d2eff669976ce6f8537fb78a49cb302fa818942a4bb2bf1f0f0ae4226053c696e4575981aef774c356a9
|
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,8 +9,8 @@ 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: :
|
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
|
|
@@ -19,7 +19,7 @@ module ArLazyPreload
|
|
19
19
|
return if lazy_preload_context.association_tree.nil?
|
20
20
|
|
21
21
|
association_tree_builder = AssociationTreeBuilder.new(lazy_preload_context.association_tree)
|
22
|
-
subtree = association_tree_builder.subtree_for(
|
22
|
+
subtree = association_tree_builder.subtree_for(proxy_association_reflection.name)
|
23
23
|
|
24
24
|
lazy_preload!(subtree)
|
25
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
|
@@ -45,10 +45,10 @@ module ArLazyPreload
|
|
45
45
|
|
46
46
|
def perform_preloading(association_name)
|
47
47
|
filtered_records = records.select do |record|
|
48
|
-
|
48
|
+
preloadable_record?(association_name, record)
|
49
49
|
end
|
50
|
-
preloader.preload(filtered_records, association_name)
|
51
50
|
|
51
|
+
preload_records(association_name, filtered_records)
|
52
52
|
loaded_association_names.add(association_name)
|
53
53
|
|
54
54
|
AssociatedContextBuilder.prepare(
|
@@ -57,6 +57,16 @@ module ArLazyPreload
|
|
57
57
|
)
|
58
58
|
end
|
59
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
|
+
|
60
70
|
def association_loaded?(association_name)
|
61
71
|
loaded_association_names.include?(association_name)
|
62
72
|
end
|
@@ -69,11 +79,30 @@ module ArLazyPreload
|
|
69
79
|
@preloader ||= ActiveRecord::Associations::Preloader.new
|
70
80
|
end
|
71
81
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
82
|
+
def preloadable_record?(association_name, record)
|
83
|
+
preloadable_reflections_cache.dig(record.class, association_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def preloadable_reflections_cache
|
87
|
+
@preloadable_reflections_cache ||= Hash.new do |hash, klass|
|
88
|
+
associations = klass.reflect_on_all_associations
|
89
|
+
|
90
|
+
hash[klass] = associations.each_with_object({}) do |reflection, cache|
|
91
|
+
cache[reflection.name] = preloadable_reflection?(klass, reflection)
|
92
|
+
end
|
75
93
|
end
|
76
94
|
end
|
95
|
+
|
96
|
+
def preloadable_reflection?(klass, reflection)
|
97
|
+
scope = reflection.scope
|
98
|
+
preloadable_scope = scope&.arity&.zero?
|
99
|
+
through_reflection =
|
100
|
+
reflection.options[:through] && klass.reflect_on_association(reflection.options[:through])
|
101
|
+
preloadable_through_reflection =
|
102
|
+
through_reflection && preloadable_reflection?(klass, through_reflection)
|
103
|
+
|
104
|
+
(!scope || preloadable_scope) && (!through_reflection || preloadable_through_reflection)
|
105
|
+
end
|
77
106
|
end
|
78
107
|
end
|
79
108
|
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
|
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.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DmitryTsepelev
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-10-21 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: '
|
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: '
|
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:
|
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.
|
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: []
|