ar_lazy_preload 0.3.1 → 0.5.2

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