ar_lazy_preload 0.6.0 → 1.0.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: 3d54ffe605f60682678626dc9fb88be7490cccc9d9d8e9d012e2118c97806531
4
- data.tar.gz: d88d6afa1cd40385645771b6b8117df04e57af4753ab062c92a0dbe231836dc9
3
+ metadata.gz: 853f4f5f10ba5f9aa5e6530eb767f9d4063d62745b2b332776c7ebb44c753700
4
+ data.tar.gz: ff2f6a626e8e944734dcab46eef9677bda77582027f1e687b27b616a5cdac0cb
5
5
  SHA512:
6
- metadata.gz: b0865957982e3b35ab825b0a800c391a27989026865ca8efcd7c4b2f664e4054b2dcd92926ee3ed75ca81f2e4bc2d38fb61aa75f635f72256554fffd944eef93
7
- data.tar.gz: b06ed1f0e44254b1d2e4660f0aa63e257c1dba54383e14d480c7852d92f3912a356e67f236f3e1107621db1a3b80a8abaf8ebbf31413f50f870ceb8c586aadfe
6
+ metadata.gz: f873a03ba3abc319042f601e512b48b64d638f3b6358e722768e92745992c654f4aef229e792690c0c250563c9f28ae2d69fd77931c9af27db9a09f719f6341f
7
+ data.tar.gz: 521c1bf8d19bc8bc2c5e71c8fd2918ac400102368016a9d6448b19426bcefc569b51fda0e13b205d86a872553f08a619a436fb3c7821bb4b866056c771065121
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # ArLazyPreload [![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](https://cultofmartians.com/tasks/activerecord-lazy-preload.html) [![Gem Version](https://badge.fury.io/rb/ar_lazy_preload.svg)](https://rubygems.org/gems/ar_lazy_preload) [![Build Status](https://travis-ci.org/DmitryTsepelev/ar_lazy_preload.svg?branch=master)](https://travis-ci.org/DmitryTsepelev/ar_lazy_preload) [![Maintainability](https://api.codeclimate.com/v1/badges/00d04595661820dfba80/maintainability)](https://codeclimate.com/github/DmitryTsepelev/ar_lazy_preload/maintainability) [![Coverage Status](https://coveralls.io/repos/github/DmitryTsepelev/ar_lazy_preload/badge.svg?branch=master)](https://coveralls.io/github/DmitryTsepelev/ar_lazy_preload?branch=master)
1
+ # ArLazyPreload [![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](https://cultofmartians.com/tasks/activerecord-lazy-preload.html) [![Gem Version](https://badge.fury.io/rb/ar_lazy_preload.svg)](https://rubygems.org/gems/ar_lazy_preload) [![Build Status](https://github.com/DmitryTsepelev/ar_lazy_preload/actions/workflows/rspec.yml/badge.svg)](https://github.com/DmitryTsepelev/ar_lazy_preload/actions/workflows/rspec.yml) [![Maintainability](https://api.codeclimate.com/v1/badges/00d04595661820dfba80/maintainability)](https://codeclimate.com/github/DmitryTsepelev/ar_lazy_preload/maintainability) [![Coverage Status](https://coveralls.io/repos/github/DmitryTsepelev/ar_lazy_preload/badge.svg?branch=master)](https://coveralls.io/github/DmitryTsepelev/ar_lazy_preload?branch=master)
2
2
 
3
3
  **ArLazyPreload** is a gem that brings association lazy load functionality to your Rails applications. There is a number of built-in methods to solve [N+1 problem](https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations), but sometimes a list of associations to preload is not obvious–this is when you can get most of this gem.
4
4
 
5
5
  - **Simple**. The only thing you need to change is to use `#lazy_preload` instead of `#includes`, `#eager_load` or `#preload`
6
- - **Fast**. Take a look at [benchmarks](https://travis-ci.org/DmitryTsepelev/ar_lazy_preload) (`TASK=bench` and `TASK=memory`)
6
+ - **Fast**. Take a look at [performance benchmark](https://github.com/DmitryTsepelev/ar_lazy_preload/actions/workflows/bench.yml) and [memory benchmark](https://github.com/DmitryTsepelev/ar_lazy_preload/actions/workflows/memory.yml)
7
7
  - **Perfect fit for GraphQL**. Define a list of associations to load at the top-level resolver and let the gem do its job
8
8
  - **Auto-preload support**. If you don't want to specify the association list–set `ArLazyPreload.config.auto_preload` to `true`
9
9
 
@@ -62,7 +62,7 @@ posts = User.preload_associations_lazily.flat_map(&:posts)
62
62
 
63
63
  ## Gotchas
64
64
 
65
- 1. Lazy preloading [does not work](https://github.com/DmitryTsepelev/ar_lazy_preload/pull/40/files) when `.includes` is called earlier:
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
66
 
67
67
  ```ruby
68
68
  Post.includes(:user).preload_associations_lazily.each do |p|
@@ -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,25 @@
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
+
16
+ preload.each do |associations|
17
+ ArLazyPreload::Preloader.new(records, associations).preload.each do |preloader_association|
18
+ handle_preloaded_records(preloader_association.preloaded_records)
19
+ end
20
+ end
21
+ end
22
+
10
23
  # Enhanced #load method will check if association has not been loaded yet and add a context
11
24
  # for lazy preloading to loaded each record
12
25
  def load
@@ -75,6 +88,20 @@ module ArLazyPreload
75
88
  @preloads_associations_lazily ||= false
76
89
  end
77
90
 
91
+ def handle_preloaded_records(preloaded_records)
92
+ return unless Contexts::TemporaryPreloadConfig.enabled? || preloads_associations_lazily?
93
+
94
+ records_array = PreloadedRecordsConverter.call(preloaded_records)
95
+
96
+ return unless records_array&.any?
97
+
98
+ Context.register(
99
+ records: records_array,
100
+ association_tree: lazy_preload_values,
101
+ auto_preload: true
102
+ )
103
+ end
104
+
78
105
  attr_writer :lazy_preload_values
79
106
  end
80
107
  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
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "set"
4
4
  require "ar_lazy_preload/associated_context_builder"
5
+ require "ar_lazy_preload/preloader"
5
6
 
6
7
  module ArLazyPreload
7
8
  module Contexts
@@ -45,10 +46,10 @@ module ArLazyPreload
45
46
 
46
47
  def perform_preloading(association_name)
47
48
  filtered_records = records.select do |record|
48
- reflection_names_cache[record.class].include?(association_name)
49
+ preloadable_record?(association_name, record)
49
50
  end
50
- preloader.preload(filtered_records, association_name)
51
51
 
52
+ preload_records(association_name, filtered_records)
52
53
  loaded_association_names.add(association_name)
53
54
 
54
55
  AssociatedContextBuilder.prepare(
@@ -57,6 +58,16 @@ module ArLazyPreload
57
58
  )
58
59
  end
59
60
 
61
+ # Method preloads associations for the specific sets of the records
62
+ # and provides automatically provides context for the records
63
+ # loaded using `includes` inside Relation#preload_associations with the
64
+ # help of the TemporaryPreloadConfig
65
+ def preload_records(association_name, records)
66
+ TemporaryPreloadConfig.within_context do
67
+ ArLazyPreload::Preloader.new(records, [association_name]).preload
68
+ end
69
+ end
70
+
60
71
  def association_loaded?(association_name)
61
72
  loaded_association_names.include?(association_name)
62
73
  end
@@ -65,15 +76,30 @@ module ArLazyPreload
65
76
  @loaded_association_names ||= Set.new
66
77
  end
67
78
 
68
- def preloader
69
- @preloader ||= ActiveRecord::Associations::Preloader.new
79
+ def preloadable_record?(association_name, record)
80
+ preloadable_reflections_cache.dig(record.class, association_name)
70
81
  end
71
82
 
72
- def reflection_names_cache
73
- @reflection_names_cache ||= Hash.new do |hash, klass|
74
- hash[klass] = klass.reflect_on_all_associations.map(&:name)
83
+ def preloadable_reflections_cache
84
+ @preloadable_reflections_cache ||= Hash.new do |hash, klass|
85
+ associations = klass.reflect_on_all_associations
86
+
87
+ hash[klass] = associations.each_with_object({}) do |reflection, cache|
88
+ cache[reflection.name] = preloadable_reflection?(klass, reflection)
89
+ end
75
90
  end
76
91
  end
92
+
93
+ def preloadable_reflection?(klass, reflection)
94
+ scope = reflection.scope
95
+ preloadable_scope = scope&.arity&.zero?
96
+ through_reflection =
97
+ reflection.options[:through] && klass.reflect_on_association(reflection.options[:through])
98
+ preloadable_through_reflection =
99
+ through_reflection && preloadable_reflection?(klass, through_reflection)
100
+
101
+ (!scope || preloadable_scope) && (!through_reflection || preloadable_through_reflection)
102
+ end
77
103
  end
78
104
  end
79
105
  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
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ArLazyPreload
4
+ class Preloader
5
+ def initialize(records, associations)
6
+ @records = records
7
+ @associations = associations
8
+ end
9
+
10
+ class << self
11
+ def patch_for_rails_7!
12
+ define_method(:preload) do
13
+ ActiveRecord::Associations::Preloader.new(
14
+ records: @records, associations: @associations
15
+ ).call
16
+ end
17
+ end
18
+ end
19
+
20
+ def preload
21
+ ActiveRecord::Associations::Preloader.new.preload(@records, @associations)
22
+ end
23
+ end
24
+ end
@@ -25,6 +25,8 @@ module ArLazyPreload
25
25
 
26
26
  ActiveRecord::Associations::CollectionAssociation.prepend(CollectionAssociation)
27
27
  ActiveRecord::Associations::CollectionProxy.prepend(CollectionProxy)
28
+
29
+ ArLazyPreload::Preloader.patch_for_rails_7! if ActiveRecord::VERSION::MAJOR >= 7
28
30
  end
29
31
  end
30
32
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ArLazyPreload
4
- VERSION = "0.6.0"
4
+ VERSION = "1.0.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.6.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - DmitryTsepelev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-18 00:00:00.000000000 Z
11
+ date: 2021-12-29 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.2'
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.2'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
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'
167
181
  description: lazy_preload implementation for ActiveRecord models
168
182
  email:
169
183
  - dmitry.a.tsepelev@gmail.com
@@ -189,6 +203,9 @@ files:
189
203
  - lib/ar_lazy_preload/contexts/auto_preload_context.rb
190
204
  - lib/ar_lazy_preload/contexts/base_context.rb
191
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
208
+ - lib/ar_lazy_preload/preloader.rb
192
209
  - lib/ar_lazy_preload/railtie.rb
193
210
  - lib/ar_lazy_preload/version.rb
194
211
  homepage: https://github.com/DmitryTsepelev/ar_lazy_preload
@@ -203,14 +220,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
203
220
  requirements:
204
221
  - - ">="
205
222
  - !ruby/object:Gem::Version
206
- version: '2.3'
223
+ version: '2.6'
207
224
  required_rubygems_version: !ruby/object:Gem::Requirement
208
225
  requirements:
209
226
  - - ">="
210
227
  - !ruby/object:Gem::Version
211
228
  version: '0'
212
229
  requirements: []
213
- rubygems_version: 3.0.3
230
+ rubygems_version: 3.1.4
214
231
  signing_key:
215
232
  specification_version: 4
216
233
  summary: lazy_preload implementation for ActiveRecord models