ar_lazy_preload 0.4.0 → 0.6.1

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: 302e5031cc0d1046c7ef7400dca6767fb13e4f8e68ba98a7cfa20b648f64d60a
4
- data.tar.gz: 40cc089d2e4863636d21f13bc9273f0c1341c209275d9162e6ce3ecad05c7e24
3
+ metadata.gz: 46e7c82ea00d2d4f0da30a522644d91b54f277a7e915d576c7e7fd959f5f3a12
4
+ data.tar.gz: c255f0fd18c255dc42bf4d321c598d4967960e5e4cb0370385e00b6b7b5963ea
5
5
  SHA512:
6
- metadata.gz: 00b77bb2a441ad78f74d96776d36073dbdcc29d811d98de5430ec750217c4f6d8bbf103465debd67c67fd3d86a296f8ab39d2d3e098f193fb348657d0d38c30f
7
- data.tar.gz: 0e06520adf62f0bb9f0a1bb7b6d9d667b4478b206a4ffcb1e7c40efd2314d7d0205e72cc27bbb214a1a93e6a3d4a5cd5c5def11d458dfcb3054bd4240162c818
6
+ metadata.gz: fe5b062187652784cfd99ff7c201906400247204d77b91742d2350bf487f9542415f360808d6ad2dcc78f7f56aa496c1064781cebfd4122d6b6c1e706e079769
7
+ data.tar.gz: 7cc876e17f94ac05518654b9483a6a70c62b37e4226653ed323435aefa3d4544c6b722e82f3ecdba0af2b96513044d4ee02863d4b4b270d12579a6f3b7405ed8
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:
@@ -16,6 +16,7 @@ module ArLazyPreload
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
22
  subtree = association_tree_builder.subtree_for(reflection.name)
@@ -3,7 +3,7 @@
3
3
  module ArLazyPreload
4
4
  # ActiveRecord::CollectionAssociation patch with a hook for lazy preloading
5
5
  module CollectionAssociation
6
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
6
+ # rubocop:disable Metrics/AbcSize
7
7
  def ids_reader
8
8
  return super if owner.lazy_preload_context.blank?
9
9
 
@@ -16,6 +16,6 @@ module ArLazyPreload
16
16
  @association_ids ||= reader.map(&primary_key)
17
17
  end
18
18
  end
19
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
19
+ # rubocop:enable Metrics/AbcSize
20
20
  end
21
21
  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
@@ -8,8 +8,8 @@ module ArLazyPreload
8
8
  # the associated records based on the parent association tree.
9
9
  class AssociatedContextBuilder
10
10
  # Initiates lazy preload context the records loaded lazily
11
- def self.prepare(*args)
12
- new(*args).perform
11
+ def self.prepare(**args)
12
+ new(**args).perform
13
13
  end
14
14
 
15
15
  attr_reader :parent_context, :association_name
@@ -23,12 +23,15 @@ 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
- def perform # rubocop:disable Metrics/MethodLength
26
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
27
+ def perform
27
28
  associated_records = parent_context.records.flat_map do |record|
28
29
  next if record.nil?
29
30
 
30
- record_association = record.association(association_name)
31
31
  reflection = reflection_cache[record.class]
32
+ next if reflection.nil?
33
+
34
+ record_association = record.association(association_name)
32
35
  reflection.collection? ? record_association.target : record_association.reader
33
36
  end
34
37
 
@@ -38,6 +41,7 @@ module ArLazyPreload
38
41
  auto_preload: parent_context.auto_preload?
39
42
  )
40
43
  end
44
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
41
45
 
42
46
  private
43
47
 
@@ -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.4.0"
4
+ VERSION = "0.6.1"
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.4.0
4
+ version: 0.6.1
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-24 00:00:00.000000000 Z
11
+ date: 2021-03-04 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
@@ -54,6 +54,20 @@ dependencies:
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.81.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.81.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: db-query-matchers
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - ">="
@@ -67,7 +81,7 @@ dependencies:
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
- name: db-query-matchers
84
+ name: simplecov
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - ">="
@@ -81,7 +95,7 @@ dependencies:
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
- name: coveralls
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.0.3
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: []