ar_lazy_preload 0.3.2 → 0.6.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: 69e0b3d4a8e8ac0c7c3824058347406f2ede573183f085b40acfd288e2b43014
4
- data.tar.gz: d47fa44acb9b48181ac97c4c77138977375e1f593b4eaee4673314895836220f
3
+ metadata.gz: 3d54ffe605f60682678626dc9fb88be7490cccc9d9d8e9d012e2118c97806531
4
+ data.tar.gz: d88d6afa1cd40385645771b6b8117df04e57af4753ab062c92a0dbe231836dc9
5
5
  SHA512:
6
- metadata.gz: 73f6f5dd9b745e262d5e2e1e6532f266c65eda63282938e2b08838cbb1bf4e161df8a38d208c436714967d13ef2579a6398c492b3690f98d5d9a082169d93065
7
- data.tar.gz: 5f084968e4c8aacdf438ab08f94e2768f9090185e91c1ce4e9f7841558d0541b42f8e3b9926df85191d634b01cbcb1aab9e7702b77a2eb438cf0b732017e9322
6
+ metadata.gz: b0865957982e3b35ab825b0a800c391a27989026865ca8efcd7c4b2f664e4054b2dcd92926ee3ed75ca81f2e4bc2d38fb61aa75f635f72256554fffd944eef93
7
+ data.tar.gz: b06ed1f0e44254b1d2e4660f0aa63e257c1dba54383e14d480c7852d92f3912a356e67f236f3e1107621db1a3b80a8abaf8ebbf31413f50f870ceb8c586aadfe
data/README.md CHANGED
@@ -50,6 +50,29 @@ If you want to turn automatic preload off for a specific record, you can call `.
50
50
  users.first.skip_preload.posts # => SELECT * FROM posts WHERE user_id = ?
51
51
  ```
52
52
 
53
+ ### Relation auto preloading
54
+
55
+ Another alternative for auto preloading is using relation `#preload_associations_lazily` method
56
+
57
+ ```ruby
58
+ posts = User.preload_associations_lazily.flat_map(&:posts)
59
+ # => SELECT * FROM users LIMIT 10
60
+ # => SELECT * FROM posts WHERE user_id in (...)
61
+ ```
62
+
63
+ ## Gotchas
64
+
65
+ 1. Lazy preloading [does not work](https://github.com/DmitryTsepelev/ar_lazy_preload/pull/40/files) 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
+
53
76
  ## Installation
54
77
 
55
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)
@@ -5,6 +5,7 @@ module ArLazyPreload
5
5
  module Base
6
6
  def self.included(base)
7
7
  base.class.delegate :lazy_preload, to: :all
8
+ base.class.delegate :preload_associations_lazily, to: :all
8
9
  end
9
10
 
10
11
  attr_accessor :lazy_preload_context
@@ -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
@@ -5,6 +5,8 @@ require "ar_lazy_preload/context"
5
5
  module ArLazyPreload
6
6
  # ActiveRecord::Relation patch with lazy preloading support
7
7
  module Relation
8
+ attr_writer :preloads_associations_lazily
9
+
8
10
  # Enhanced #load method will check if association has not been loaded yet and add a context
9
11
  # for lazy preloading to loaded each record
10
12
  def load
@@ -13,12 +15,25 @@ module ArLazyPreload
13
15
  if need_context
14
16
  Context.register(
15
17
  records: ar_lazy_preload_records,
16
- association_tree: lazy_preload_values
18
+ association_tree: lazy_preload_values,
19
+ auto_preload: preloads_associations_lazily?
17
20
  )
18
21
  end
19
22
  result
20
23
  end
21
24
 
25
+ # Lazily autoloads all associations. For example:
26
+ #
27
+ # users = User.preload_associations_lazily
28
+ # users.each do |user|
29
+ # user.posts.flat_map {|post| post.comments.map(&:id)}
30
+ # end
31
+ #
32
+ # Same effect can be achieved by User.lazy_preload(posts: :comments)
33
+ def preload_associations_lazily
34
+ spawn.tap { |relation| relation.preloads_associations_lazily = true }
35
+ end
36
+
22
37
  # Specify relationships to be loaded lazily when association is loaded for the first time. For
23
38
  # example:
24
39
  #
@@ -56,6 +71,10 @@ module ArLazyPreload
56
71
  @records
57
72
  end
58
73
 
74
+ def preloads_associations_lazily?
75
+ @preloads_associations_lazily ||= false
76
+ end
77
+
59
78
  attr_writer :lazy_preload_values
60
79
  end
61
80
  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,23 +23,31 @@ 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
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
26
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
 
35
- Context.register(records: associated_records, association_tree: child_association_tree)
38
+ Context.register(
39
+ records: associated_records,
40
+ association_tree: child_association_tree,
41
+ auto_preload: parent_context.auto_preload?
42
+ )
36
43
  end
44
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
37
45
 
38
46
  private
39
47
 
40
48
  def child_association_tree
41
49
  # `association_tree` is unnecessary when auto preload is enabled
42
- return nil if ArLazyPreload.config.auto_preload?
50
+ return nil if parent_context.auto_preload?
43
51
 
44
52
  AssociationTreeBuilder.new(parent_context.association_tree).subtree_for(association_name)
45
53
  end
@@ -7,10 +7,10 @@ require "ar_lazy_preload/contexts/lazy_preload_context"
7
7
  module ArLazyPreload
8
8
  class Context
9
9
  # Initiates lazy preload context for given records
10
- def self.register(records:, association_tree:)
10
+ def self.register(records:, association_tree:, auto_preload: false)
11
11
  return if records.empty?
12
12
 
13
- if ArLazyPreload.config.auto_preload?
13
+ if ArLazyPreload.config.auto_preload? || auto_preload
14
14
  Contexts::AutoPreloadContext.new(records: records)
15
15
  elsif association_tree.any?
16
16
  Contexts::LazyPreloadContext.new(
@@ -4,6 +4,10 @@ module ArLazyPreload
4
4
  module Contexts
5
5
  # This class is responsible for automatic association preloading
6
6
  class AutoPreloadContext < BaseContext
7
+ def auto_preload?
8
+ true
9
+ end
10
+
7
11
  protected
8
12
 
9
13
  def association_needs_preload?(_association_name)
@@ -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)
@@ -28,6 +31,10 @@ module ArLazyPreload
28
31
  perform_preloading(association_name)
29
32
  end
30
33
 
34
+ def auto_preload?
35
+ false
36
+ end
37
+
31
38
  protected
32
39
 
33
40
  def association_needs_preload?(_association_name)
@@ -37,7 +44,11 @@ module ArLazyPreload
37
44
  private
38
45
 
39
46
  def perform_preloading(association_name)
40
- preloader.preload(records, association_name)
47
+ filtered_records = records.select do |record|
48
+ reflection_names_cache[record.class].include?(association_name)
49
+ end
50
+ preloader.preload(filtered_records, association_name)
51
+
41
52
  loaded_association_names.add(association_name)
42
53
 
43
54
  AssociatedContextBuilder.prepare(
@@ -57,6 +68,12 @@ module ArLazyPreload
57
68
  def preloader
58
69
  @preloader ||= ActiveRecord::Associations::Preloader.new
59
70
  end
71
+
72
+ def reflection_names_cache
73
+ @reflection_names_cache ||= Hash.new do |hash, klass|
74
+ hash[klass] = klass.reflect_on_all_associations.map(&:name)
75
+ end
76
+ end
60
77
  end
61
78
  end
62
79
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ArLazyPreload
4
- VERSION = "0.3.2"
4
+ VERSION = "0.6.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.3.2
4
+ version: 0.6.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-07-21 00:00:00.000000000 Z
11
+ date: 2020-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -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
  - - ">="