ar_lazy_preload 2.1.0 → 2.1.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: 82d5aeed661b9ab42f1d15f78cb8db84cc8b426ddfe26ae1442f297800ec0707
4
- data.tar.gz: 1c89c0b2f8025143186551e06629c40a37bf8c207b7605c71481b02c158f89f5
3
+ metadata.gz: 21e63192d8ab41a89c7fa7fe49300123e7f8759d7e3270fdf4d821bc9865db8b
4
+ data.tar.gz: 28485e31dd75c9c93949ff9cc7a5a284857d347892a58a050cf754f50204beff
5
5
  SHA512:
6
- metadata.gz: acbedff3a73e2b26ee0a905c9b5059e9e27297abccd2a16c0a68c3bd67001a4d45721b83aaad21738b36d6c7bce8d41ea6756bd0f1bd54973f339ab2cc26ea68
7
- data.tar.gz: 5285e4116f8ba49bc7aa2cbfc1962bfff112785ebd7dbed823ebadf606e9eef1f0772f7f30312344a00cbe8c76af37fc0378d422933073d8758bebe5ae6bcd85
6
+ metadata.gz: 34932cd853c5344aa865da00c1210a0680e0b18e3ac4994a9908d16683d97905deaaf56b4cd2963ef01c20352df63f80fd4021d97a1831a4691129c5382c4a35
7
+ data.tar.gz: d40a3cf3c8ef27508e385649a4373fe84fca14796c9cd44fdb7b6df55123f9b09f9bf00d9fcfb255ba9b3c8844633e05968a50297cc6867f1ce21664e4a45944
data/README.md CHANGED
@@ -13,6 +13,11 @@ Used in production by:
13
13
  - Toptal
14
14
  - _Want to be here? Let me know_ 🙂
15
15
 
16
+ ## Professional Support
17
+
18
+ Dealing with performance issues in your Rails app beyond N+1 queries?
19
+ I'm available for consulting — [get in touch](https://dmitrytsepelev.dev/consulting).
20
+
16
21
  ## Why should I use it?
17
22
 
18
23
  Lazy loading is super helpful when the list of associations to load is determined dynamically. For instance, in GraphQL this list comes from the API client, and you'll have to inspect the selection set to find out what associations are going to be used.
@@ -62,6 +67,10 @@ posts = User.preload_associations_lazily.flat_map(&:posts)
62
67
  # => SELECT * FROM posts WHERE user_id in (...)
63
68
  ```
64
69
 
70
+ ## Companion gem for non-association N+1s
71
+
72
+ [`N1Loader`](https://github.com/djezzzl/n1_loader) complements ArLazyPreload for batched lazy loading in places where regular ActiveRecord associations are not available (for example, computed values, custom loaders, service/API calls, and GraphQL fields). ArLazyPreload can stay focused on association loading while N1Loader handles the remaining N+1-prone derived data.
73
+
65
74
  ## Gotchas
66
75
 
67
76
  1. Lazy preloading [does not work](https://github.com/DmitryTsepelev/ar_lazy_preload/pull/40/files) for ActiveRecord < 6 when `.includes` is called earlier:
@@ -76,6 +85,8 @@ posts = User.preload_associations_lazily.flat_map(&:posts)
76
85
 
77
86
  3. Lazy preloading for **ActiveStorage** variants is not working automatically because of the way how it is implemented ([here](https://github.com/DmitryTsepelev/ar_lazy_preload/pull/70) is the issue). You can preload them manually by calling `#with_all_variant_records`/`#with_attached_#{attachment_name}` on association. Example: `User.with_attached_avatar.first(10).map { |u| u.avatar.variant(:small).processed.url }`
78
87
 
88
+ 4. Lazy preloading does not work for association object creation methods, so it is better if you separate the logic for writing and reading objects and use lazy preloading for read only, more information with example [here](https://github.com/DmitryTsepelev/ar_lazy_preload/issues/75)
89
+
79
90
  ## Installation
80
91
 
81
92
  Add this line to your application's Gemfile, and you're all set:
@@ -44,7 +44,7 @@ module ArLazyPreload
44
44
  #
45
45
  # Same effect can be achieved by User.lazy_preload(posts: :comments)
46
46
  def preload_associations_lazily
47
- spawn.tap { |relation| relation.preloads_associations_lazily = true }
47
+ tap { |relation| relation.preloads_associations_lazily = true }
48
48
  end
49
49
 
50
50
  # Specify relationships to be loaded lazily when association is loaded for the first time. For
@@ -23,7 +23,7 @@ module ArLazyPreload
23
23
 
24
24
  # Takes all the associated records for the records, attached to the :parent_context and creates
25
25
  # a preloading context for them
26
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
26
+ # rubocop:disable Metrics/MethodLength
27
27
  def perform
28
28
  associated_records = parent_context.records.flat_map do |record|
29
29
  next if record.nil?
@@ -32,7 +32,7 @@ module ArLazyPreload
32
32
  next if reflection.nil?
33
33
 
34
34
  record_association = record.association(association_name)
35
- reflection.collection? ? record_association.target : record_association.reader
35
+ record_association.target
36
36
  end
37
37
 
38
38
  Context.register(
@@ -41,7 +41,7 @@ module ArLazyPreload
41
41
  auto_preload: parent_context.auto_preload?
42
42
  )
43
43
  end
44
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
44
+ # rubocop:enable Metrics/MethodLength
45
45
 
46
46
  private
47
47
 
@@ -52,12 +52,65 @@ module ArLazyPreload
52
52
  preload_records(association_name, filtered_records)
53
53
  loaded_association_names.add(association_name)
54
54
 
55
+ # Prepare context for intermediate through associations
56
+ prepare_through_association_contexts(association_name, filtered_records)
57
+
55
58
  AssociatedContextBuilder.prepare(
56
59
  parent_context: self,
57
60
  association_name: association_name
58
61
  )
59
62
  end
60
63
 
64
+ # Prepare context for intermediate associations in through chains
65
+ def prepare_through_association_contexts(association_name, filtered_records)
66
+ filtered_records.group_by(&:class).each do |klass, klass_records|
67
+ chain = through_association_chain(association_name, klass)
68
+ next if chain.empty?
69
+
70
+ chain.each do |intermediate_name|
71
+ register_intermediate_context(klass, klass_records, intermediate_name)
72
+ end
73
+ end
74
+ end
75
+
76
+ def register_intermediate_context(klass, klass_records, intermediate_name)
77
+ return if association_loaded?(intermediate_name)
78
+
79
+ reflection = klass.reflect_on_association(intermediate_name)
80
+ return if reflection.nil?
81
+
82
+ loaded_association_names.add(intermediate_name)
83
+
84
+ intermediate_records = collect_intermediate_records(klass_records, reflection)
85
+ return if intermediate_records.empty?
86
+
87
+ ArLazyPreload::Context.register(records: intermediate_records, auto_preload: true)
88
+ end
89
+
90
+ def collect_intermediate_records(klass_records, reflection)
91
+ klass_records.flat_map do |record|
92
+ record_association = record.association(reflection.name)
93
+ record_association.target
94
+ end.compact
95
+ end
96
+
97
+ # Extract chain of intermediate association names for through associations
98
+ def through_association_chain(association_name, klass)
99
+ reflection = klass.reflect_on_association(association_name)
100
+ return [] unless reflection&.options&.key?(:through)
101
+
102
+ chain = []
103
+ current_reflection = reflection
104
+
105
+ while current_reflection&.options&.key?(:through)
106
+ through_name = current_reflection.options[:through]
107
+ chain.unshift(through_name)
108
+ current_reflection = klass.reflect_on_association(through_name)
109
+ end
110
+
111
+ chain
112
+ end
113
+
61
114
  # Method preloads associations for the specific sets of the records
62
115
  # and provides automatically provides context for the records
63
116
  # loaded using `includes` inside Relation#preload_associations with the
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ArLazyPreload
4
- VERSION = "2.1.0"
4
+ VERSION = "2.1.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar_lazy_preload
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - DmitryTsepelev
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-03-02 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '6.1'
18
+ version: '7.0'
20
19
  type: :development
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: '6.1'
25
+ version: '7.0'
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: rspec
29
28
  requirement: !ruby/object:Gem::Requirement
@@ -109,19 +108,19 @@ dependencies:
109
108
  - !ruby/object:Gem::Version
110
109
  version: '0'
111
110
  - !ruby/object:Gem::Dependency
112
- name: database_cleaner
111
+ name: database_cleaner-active_record
113
112
  requirement: !ruby/object:Gem::Requirement
114
113
  requirements:
115
- - - ">="
114
+ - - '='
116
115
  - !ruby/object:Gem::Version
117
- version: '0'
116
+ version: 2.2.1
118
117
  type: :development
119
118
  prerelease: false
120
119
  version_requirements: !ruby/object:Gem::Requirement
121
120
  requirements:
122
- - - ">="
121
+ - - '='
123
122
  - !ruby/object:Gem::Version
124
- version: '0'
123
+ version: 2.2.1
125
124
  - !ruby/object:Gem::Dependency
126
125
  name: factory_bot
127
126
  requirement: !ruby/object:Gem::Requirement
@@ -178,6 +177,34 @@ dependencies:
178
177
  - - ">="
179
178
  - !ruby/object:Gem::Version
180
179
  version: '0'
180
+ - !ruby/object:Gem::Dependency
181
+ name: benchmark
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ type: :development
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ - !ruby/object:Gem::Dependency
195
+ name: ostruct
196
+ requirement: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ type: :development
202
+ prerelease: false
203
+ version_requirements: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
181
208
  description: lazy_preload implementation for ActiveRecord models
182
209
  email:
183
210
  - dmitry.a.tsepelev@gmail.com
@@ -212,7 +239,6 @@ homepage: https://github.com/DmitryTsepelev/ar_lazy_preload
212
239
  licenses:
213
240
  - MIT
214
241
  metadata: {}
215
- post_install_message:
216
242
  rdoc_options: []
217
243
  require_paths:
218
244
  - lib
@@ -227,8 +253,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
227
253
  - !ruby/object:Gem::Version
228
254
  version: '0'
229
255
  requirements: []
230
- rubygems_version: 3.4.10
231
- signing_key:
256
+ rubygems_version: 4.0.3
232
257
  specification_version: 4
233
258
  summary: lazy_preload implementation for ActiveRecord models
234
259
  test_files: []