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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8b1ef234e5a995bfce176e47fe5526fba9330b61cc27374e6667a5f5dfca3af
4
- data.tar.gz: 3516b8bf449802b2ba3ff7022faec299ff274fe4716db884e46da63fe47d5ac9
3
+ metadata.gz: 780b04da1bdc2de0822bcf78d9ac51d7e24f5bf66aee70d42e81a0a0cb662a21
4
+ data.tar.gz: bceb49a3525fcfcac85d8662485992bc81e37015a9100a4332f3aaae1c0bd59e
5
5
  SHA512:
6
- metadata.gz: 80a0a1e05b961a6d66b20c0b5e1a32ee2824d868e7e96d25e85b4e2b3c48baa1ee9129cf934b71e73a2887245faa9f54b3ecc23cc7815d60c30efc8bca9fbb7b
7
- data.tar.gz: bbb5f854ed701c562d31f00cf3f832eb668c42aca809e4376aed4b78c114bd3c25db15edea712d65419a23c3fbbe1dfac5d698055dad5c5ae61ea837ea6fa273
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: :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
 
@@ -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(reflection.name)
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
@@ -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
@@ -45,10 +45,10 @@ module ArLazyPreload
45
45
 
46
46
  def perform_preloading(association_name)
47
47
  filtered_records = records.select do |record|
48
- reflection_names_cache[record.class].include?(association_name)
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 reflection_names_cache
73
- @reflection_names_cache ||= Hash.new do |hash, klass|
74
- hash[klass] = klass.reflect_on_all_associations.map(&:name)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ArLazyPreload
4
- VERSION = "0.5.2"
4
+ VERSION = "0.7.0"
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.2
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: 2020-09-17 00:00:00.000000000 Z
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: '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: []