ar_lazy_preload 0.4.0 → 0.6.1

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: 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: []