ar_lazy_preload 0.5.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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: []