ar_lazy_preload 0.3.1 → 0.5.2

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: b8f6aa06d7f037bd8762f0000db03adde926f41ee536ec4faac8b1f3ca93ff1e
4
- data.tar.gz: c6a8b59093090fc898692c102acda5ed949a34846605b65a6f27b89b940d2d46
3
+ metadata.gz: f8b1ef234e5a995bfce176e47fe5526fba9330b61cc27374e6667a5f5dfca3af
4
+ data.tar.gz: 3516b8bf449802b2ba3ff7022faec299ff274fe4716db884e46da63fe47d5ac9
5
5
  SHA512:
6
- metadata.gz: 41abf78b2668b8f39c5713a72a6536a57f660bea16bdebdb4a2f1d069c86285d0455c2d193d46f2dbe12551e12d823c1187163223193826d3861154f67c03c1d
7
- data.tar.gz: 7cd15b8bf27071baa258992e99a726e9b49f25ea5d8edb2802fa9d2e30ce652bb204acfb131b8e0adff33f178a6a1d322e4717e2f8339da4224765a5da92ca30
6
+ metadata.gz: 80a0a1e05b961a6d66b20c0b5e1a32ee2824d868e7e96d25e85b4e2b3c48baa1ee9129cf934b71e73a2887245faa9f54b3ecc23cc7815d60c30efc8bca9fbb7b
7
+ data.tar.gz: bbb5f854ed701c562d31f00cf3f832eb668c42aca809e4376aed4b78c114bd3c25db15edea712d65419a23c3fbbe1dfac5d698055dad5c5ae61ea837ea6fa273
data/README.md CHANGED
@@ -44,6 +44,22 @@ ArLazyPreload.config.auto_preload = true
44
44
 
45
45
  After that there is no need to call `#lazy_preload` on the association, everything would be loaded lazily.
46
46
 
47
+ If you want to turn automatic preload off for a specific record, you can call `.skip_preload` before any associations method:
48
+
49
+ ```ruby
50
+ users.first.skip_preload.posts # => SELECT * FROM posts WHERE user_id = ?
51
+ ```
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
+
47
63
  ## Installation
48
64
 
49
65
  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,10 +5,17 @@ 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
11
12
 
12
13
  delegate :try_preload_lazily, to: :lazy_preload_context, allow_nil: true
14
+
15
+ def skip_preload
16
+ lazy_preload_context&.records&.delete(self)
17
+ self.lazy_preload_context = nil
18
+ self
19
+ end
13
20
  end
14
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.1"
4
+ VERSION = "0.5.2"
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.1
4
+ version: 0.5.2
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-10 00:00:00.000000000 Z
11
+ date: 2020-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -56,16 +56,16 @@ dependencies:
56
56
  name: rubocop
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 0.81.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - '='
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: 0.81.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: db-query-matchers
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -196,7 +196,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
196
196
  - !ruby/object:Gem::Version
197
197
  version: '0'
198
198
  requirements: []
199
- rubygems_version: 3.0.3
199
+ rubygems_version: 3.1.2
200
200
  signing_key:
201
201
  specification_version: 4
202
202
  summary: lazy_preload implementation for ActiveRecord models