ar_lazy_preload 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +63 -0
- data/Rakefile +25 -0
- data/lib/ar_lazy_preload.rb +12 -0
- data/lib/ar_lazy_preload/active_record/association.rb +11 -0
- data/lib/ar_lazy_preload/active_record/association_relation.rb +26 -0
- data/lib/ar_lazy_preload/active_record/base.rb +20 -0
- data/lib/ar_lazy_preload/active_record/collection_association.rb +21 -0
- data/lib/ar_lazy_preload/active_record/collection_proxy.rb +12 -0
- data/lib/ar_lazy_preload/active_record/merger.rb +37 -0
- data/lib/ar_lazy_preload/active_record/relation.rb +61 -0
- data/lib/ar_lazy_preload/associated_context_builder.rb +53 -0
- data/lib/ar_lazy_preload/association_tree_builder.rb +34 -0
- data/lib/ar_lazy_preload/configuration.rb +17 -0
- data/lib/ar_lazy_preload/context.rb +23 -0
- data/lib/ar_lazy_preload/contexts/auto_preload_context.rb +14 -0
- data/lib/ar_lazy_preload/contexts/base_context.rb +62 -0
- data/lib/ar_lazy_preload/contexts/lazy_preload_context.rb +33 -0
- data/lib/ar_lazy_preload/railtie.rb +31 -0
- data/lib/ar_lazy_preload/version.rb +5 -0
- metadata +203 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 69e0b3d4a8e8ac0c7c3824058347406f2ede573183f085b40acfd288e2b43014
|
4
|
+
data.tar.gz: d47fa44acb9b48181ac97c4c77138977375e1f593b4eaee4673314895836220f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 73f6f5dd9b745e262d5e2e1e6532f266c65eda63282938e2b08838cbb1bf4e161df8a38d208c436714967d13ef2579a6398c492b3690f98d5d9a082169d93065
|
7
|
+
data.tar.gz: 5f084968e4c8aacdf438ab08f94e2768f9090185e91c1ce4e9f7841558d0541b42f8e3b9926df85191d634b01cbcb1aab9e7702b77a2eb438cf0b732017e9322
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 DmitryTsepelev
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
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)
|
2
|
+
|
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
|
+
|
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`)
|
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
|
+
- **Auto-preload support**. If you don't want to specify the association list–set `ArLazyPreload.config.auto_preload` to `true`
|
9
|
+
|
10
|
+
<p align="center">
|
11
|
+
<a href="https://evilmartians.com/?utm_source=ar_lazy_preload">
|
12
|
+
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54">
|
13
|
+
</a>
|
14
|
+
</p>
|
15
|
+
|
16
|
+
## Why should I use it?
|
17
|
+
|
18
|
+
Lazy loading is super helpful when the list of associations to load is determined dynamically. For instance, in GraphQL this list comes from the API client, and you'll have to inspect the selection set to find out what associations are going to be used.
|
19
|
+
|
20
|
+
This gem uses a different approach: it won't load anything until the association is called for a first time. When it happens–it loads all the associated records for all records from the initial relation in a single query.
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Let's try `#lazy_preload` in action! The following code will perform a single SQL request (because we've never accessed posts):
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
users = User.lazy_preload(:posts).limit(10) # => SELECT * FROM users LIMIT 10
|
28
|
+
users.map(&:first_name)
|
29
|
+
```
|
30
|
+
|
31
|
+
However, when we try to load posts, there will be one more request for posts:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
users.map(&:posts) # => SELECT * FROM posts WHERE user_id in (...)
|
35
|
+
```
|
36
|
+
|
37
|
+
## Auto preloading
|
38
|
+
|
39
|
+
If you want the gem to be even lazier–you can configure it to load all the associations lazily without specifying them explicitly. To do that you'll need to change the configuration in the following way:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
ArLazyPreload.config.auto_preload = true
|
43
|
+
```
|
44
|
+
|
45
|
+
After that there is no need to call `#lazy_preload` on the association, everything would be loaded lazily.
|
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
|
+
## Installation
|
54
|
+
|
55
|
+
Add this line to your application's Gemfile, and you're all set:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
gem "ar_lazy_preload"
|
59
|
+
```
|
60
|
+
|
61
|
+
## License
|
62
|
+
|
63
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "appraisal"
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
require "rubocop/rake_task"
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
RuboCop::RakeTask.new
|
8
|
+
|
9
|
+
if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
|
10
|
+
task :default do
|
11
|
+
sh "rubocop && appraisal install && rake appraisal spec"
|
12
|
+
end
|
13
|
+
else
|
14
|
+
task default: [:rubocop, :spec]
|
15
|
+
end
|
16
|
+
|
17
|
+
task :bench do
|
18
|
+
cmd = %w[bundle exec ruby benchmark/main.rb]
|
19
|
+
exit system(*cmd)
|
20
|
+
end
|
21
|
+
|
22
|
+
task :memory do
|
23
|
+
cmd = %w[bundle exec ruby benchmark/memory.rb]
|
24
|
+
exit system(*cmd)
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ArLazyPreload
|
4
|
+
# ActiveRecord::AssociationRelation patch for setting up lazy_preload_values based on
|
5
|
+
# owner context
|
6
|
+
module AssociationRelation
|
7
|
+
def initialize(*args)
|
8
|
+
super(*args)
|
9
|
+
setup_preloading_context unless ArLazyPreload.config.auto_preload?
|
10
|
+
end
|
11
|
+
|
12
|
+
delegate :owner, :reflection, to: :proxy_association
|
13
|
+
delegate :lazy_preload_context, to: :owner
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def setup_preloading_context
|
18
|
+
return if lazy_preload_context.nil?
|
19
|
+
|
20
|
+
association_tree_builder = AssociationTreeBuilder.new(lazy_preload_context.association_tree)
|
21
|
+
subtree = association_tree_builder.subtree_for(reflection.name)
|
22
|
+
|
23
|
+
lazy_preload!(subtree)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ArLazyPreload
|
4
|
+
# ActiveRecord::Base patch with lazy preloading support
|
5
|
+
module Base
|
6
|
+
def self.included(base)
|
7
|
+
base.class.delegate :lazy_preload, to: :all
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :lazy_preload_context
|
11
|
+
|
12
|
+
delegate :try_preload_lazily, to: :lazy_preload_context, allow_nil: true
|
13
|
+
|
14
|
+
def skip_preload
|
15
|
+
lazy_preload_context&.records&.delete(self)
|
16
|
+
self.lazy_preload_context = nil
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ArLazyPreload
|
4
|
+
# ActiveRecord::CollectionAssociation patch with a hook for lazy preloading
|
5
|
+
module CollectionAssociation
|
6
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
7
|
+
def ids_reader
|
8
|
+
return super if owner.lazy_preload_context.blank?
|
9
|
+
|
10
|
+
primary_key = reflection.association_primary_key.to_sym
|
11
|
+
if loaded?
|
12
|
+
target.map(&primary_key)
|
13
|
+
elsif !target.empty?
|
14
|
+
load_target.map(&primary_key)
|
15
|
+
else
|
16
|
+
@association_ids ||= reader.map(&primary_key)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ArLazyPreload
|
4
|
+
# ActiveRecord::Relation::Merger patch implementing merge functionality
|
5
|
+
# for lazy preloadable relations
|
6
|
+
module Merger
|
7
|
+
# Enhanced #merge implements merging lazy_preload_values
|
8
|
+
def merge
|
9
|
+
result = super
|
10
|
+
|
11
|
+
if other.lazy_preload_values.any?
|
12
|
+
if other.klass == relation.klass
|
13
|
+
merge_lazy_preloads
|
14
|
+
else
|
15
|
+
reflect_and_merge_lazy_preloads
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def merge_lazy_preloads
|
25
|
+
relation.lazy_preload!(*other.lazy_preload_values)
|
26
|
+
end
|
27
|
+
|
28
|
+
def reflect_and_merge_lazy_preloads
|
29
|
+
reflection = relation.klass.reflect_on_all_associations.find do |r|
|
30
|
+
r.class_name == other.klass.name
|
31
|
+
end
|
32
|
+
return unless reflection
|
33
|
+
|
34
|
+
relation.lazy_preload!(reflection.name => other.lazy_preload_values)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ar_lazy_preload/context"
|
4
|
+
|
5
|
+
module ArLazyPreload
|
6
|
+
# ActiveRecord::Relation patch with lazy preloading support
|
7
|
+
module Relation
|
8
|
+
# Enhanced #load method will check if association has not been loaded yet and add a context
|
9
|
+
# for lazy preloading to loaded each record
|
10
|
+
def load
|
11
|
+
need_context = !loaded?
|
12
|
+
result = super
|
13
|
+
if need_context
|
14
|
+
Context.register(
|
15
|
+
records: ar_lazy_preload_records,
|
16
|
+
association_tree: lazy_preload_values
|
17
|
+
)
|
18
|
+
end
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
# Specify relationships to be loaded lazily when association is loaded for the first time. For
|
23
|
+
# example:
|
24
|
+
#
|
25
|
+
# users = User.lazy_preload(:posts)
|
26
|
+
# users.each do |user|
|
27
|
+
# user.first_name
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# will cause only one SQL request to load users, while
|
31
|
+
#
|
32
|
+
# users = User.lazy_preload(:posts)
|
33
|
+
# users.each do |user|
|
34
|
+
# user.posts.map(&:id)
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# will make an additional query.
|
38
|
+
def lazy_preload(*args)
|
39
|
+
check_if_method_has_arguments!(:lazy_preload, args)
|
40
|
+
spawn.lazy_preload!(*args)
|
41
|
+
end
|
42
|
+
|
43
|
+
def lazy_preload!(*args)
|
44
|
+
args.flatten!
|
45
|
+
self.lazy_preload_values += args
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def lazy_preload_values
|
50
|
+
@lazy_preload_values ||= []
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def ar_lazy_preload_records
|
56
|
+
@records
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_writer :lazy_preload_values
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ar_lazy_preload/association_tree_builder"
|
4
|
+
|
5
|
+
module ArLazyPreload
|
6
|
+
# This class is responsible for building context for associated records. Given a list of records
|
7
|
+
# belonging to the same context and association name it will create and attach a new context to
|
8
|
+
# the associated records based on the parent association tree.
|
9
|
+
class AssociatedContextBuilder
|
10
|
+
# Initiates lazy preload context the records loaded lazily
|
11
|
+
def self.prepare(*args)
|
12
|
+
new(*args).perform
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :parent_context, :association_name
|
16
|
+
|
17
|
+
# :parent_context - root context
|
18
|
+
# :association_name - lazily preloaded association name
|
19
|
+
def initialize(parent_context:, association_name:)
|
20
|
+
@parent_context = parent_context
|
21
|
+
@association_name = association_name
|
22
|
+
end
|
23
|
+
|
24
|
+
# Takes all the associated records for the records, attached to the :parent_context and creates
|
25
|
+
# a preloading context for them
|
26
|
+
def perform
|
27
|
+
associated_records = parent_context.records.flat_map do |record|
|
28
|
+
next if record.nil?
|
29
|
+
|
30
|
+
record_association = record.association(association_name)
|
31
|
+
reflection = reflection_cache[record.class]
|
32
|
+
reflection.collection? ? record_association.target : record_association.reader
|
33
|
+
end
|
34
|
+
|
35
|
+
Context.register(records: associated_records, association_tree: child_association_tree)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def child_association_tree
|
41
|
+
# `association_tree` is unnecessary when auto preload is enabled
|
42
|
+
return nil if ArLazyPreload.config.auto_preload?
|
43
|
+
|
44
|
+
AssociationTreeBuilder.new(parent_context.association_tree).subtree_for(association_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def reflection_cache
|
48
|
+
@reflection_cache ||= Hash.new do |hash, klass|
|
49
|
+
hash[klass] = klass.reflect_on_association(association_name)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ArLazyPreload
|
4
|
+
# This class is responsible for building association subtrees from a given association tree
|
5
|
+
# For instance, given a following tree `[:users, { users: :comments }]`,
|
6
|
+
# #subtree_for will build a subtree `[:comments]` when :users argument is passed
|
7
|
+
class AssociationTreeBuilder
|
8
|
+
attr_reader :association_tree
|
9
|
+
|
10
|
+
def initialize(association_tree)
|
11
|
+
@association_tree =
|
12
|
+
case association_tree
|
13
|
+
when Array
|
14
|
+
association_tree
|
15
|
+
when Hash
|
16
|
+
[association_tree]
|
17
|
+
else
|
18
|
+
raise ArgumentError, "unexpected association_tree with class #{association_tree.class}"
|
19
|
+
end.select { |node| node.is_a?(Hash) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def subtree_for(association)
|
23
|
+
subtree_cache[association]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def subtree_cache
|
29
|
+
@subtree_cache ||= Hash.new do |hash, association|
|
30
|
+
hash[association] = association_tree.flat_map { |node| node[association] }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ArLazyPreload
|
4
|
+
# ArLazyPreload configuration:
|
5
|
+
#
|
6
|
+
# - `auto_preload` - load all the associations lazily without
|
7
|
+
# an explicit lazy_preload call
|
8
|
+
class Configuration
|
9
|
+
attr_accessor :auto_preload
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@auto_preload = false
|
13
|
+
end
|
14
|
+
|
15
|
+
alias auto_preload? auto_preload
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ar_lazy_preload/contexts/base_context"
|
4
|
+
require "ar_lazy_preload/contexts/auto_preload_context"
|
5
|
+
require "ar_lazy_preload/contexts/lazy_preload_context"
|
6
|
+
|
7
|
+
module ArLazyPreload
|
8
|
+
class Context
|
9
|
+
# Initiates lazy preload context for given records
|
10
|
+
def self.register(records:, association_tree:)
|
11
|
+
return if records.empty?
|
12
|
+
|
13
|
+
if ArLazyPreload.config.auto_preload?
|
14
|
+
Contexts::AutoPreloadContext.new(records: records)
|
15
|
+
elsif association_tree.any?
|
16
|
+
Contexts::LazyPreloadContext.new(
|
17
|
+
records: records,
|
18
|
+
association_tree: association_tree
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ArLazyPreload
|
4
|
+
module Contexts
|
5
|
+
# This class is responsible for automatic association preloading
|
6
|
+
class AutoPreloadContext < BaseContext
|
7
|
+
protected
|
8
|
+
|
9
|
+
def association_needs_preload?(_association_name)
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require "ar_lazy_preload/associated_context_builder"
|
5
|
+
|
6
|
+
module ArLazyPreload
|
7
|
+
module Contexts
|
8
|
+
# This is a base context class, which is responsible for holding a connection between a list of
|
9
|
+
# ActiveRecord::Base objects which have been loaded by the same instance of
|
10
|
+
# ActiveRecord::Relation.
|
11
|
+
class BaseContext
|
12
|
+
attr_reader :records
|
13
|
+
|
14
|
+
# :records - array of ActiveRecord instances
|
15
|
+
def initialize(records:)
|
16
|
+
@records = records.dup
|
17
|
+
@records.compact!
|
18
|
+
@records.uniq!
|
19
|
+
@records.each { |record| record.lazy_preload_context = self }
|
20
|
+
end
|
21
|
+
|
22
|
+
# This method checks if the association should be loaded and preloads it for all
|
23
|
+
# objects in the context it if needed.
|
24
|
+
def try_preload_lazily(association_name)
|
25
|
+
return if association_loaded?(association_name) ||
|
26
|
+
!association_needs_preload?(association_name)
|
27
|
+
|
28
|
+
perform_preloading(association_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def association_needs_preload?(_association_name)
|
34
|
+
raise NotImplementedError
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def perform_preloading(association_name)
|
40
|
+
preloader.preload(records, association_name)
|
41
|
+
loaded_association_names.add(association_name)
|
42
|
+
|
43
|
+
AssociatedContextBuilder.prepare(
|
44
|
+
parent_context: self,
|
45
|
+
association_name: association_name
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def association_loaded?(association_name)
|
50
|
+
loaded_association_names.include?(association_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def loaded_association_names
|
54
|
+
@loaded_association_names ||= Set.new
|
55
|
+
end
|
56
|
+
|
57
|
+
def preloader
|
58
|
+
@preloader ||= ActiveRecord::Associations::Preloader.new
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ArLazyPreload
|
4
|
+
module Contexts
|
5
|
+
# This class is responsible for lazy preloading. It contains a tree of associations, which were
|
6
|
+
# requested to be loaded lazily.
|
7
|
+
class LazyPreloadContext < BaseContext
|
8
|
+
attr_reader :association_tree
|
9
|
+
|
10
|
+
# :records - array of ActiveRecord instances
|
11
|
+
# :association_tree - list of symbols or hashes representing a tree of preloadable
|
12
|
+
# associations
|
13
|
+
def initialize(records:, association_tree:)
|
14
|
+
@association_tree = association_tree
|
15
|
+
|
16
|
+
super(records: records)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def association_needs_preload?(association_name)
|
22
|
+
association_tree.any? do |node|
|
23
|
+
case node
|
24
|
+
when Symbol
|
25
|
+
node == association_name
|
26
|
+
when Hash
|
27
|
+
node.key?(association_name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ar_lazy_preload/active_record/base"
|
4
|
+
require "ar_lazy_preload/active_record/relation"
|
5
|
+
require "ar_lazy_preload/active_record/association"
|
6
|
+
require "ar_lazy_preload/active_record/collection_association"
|
7
|
+
require "ar_lazy_preload/active_record/merger"
|
8
|
+
require "ar_lazy_preload/active_record/association_relation"
|
9
|
+
require "ar_lazy_preload/active_record/collection_proxy"
|
10
|
+
|
11
|
+
module ArLazyPreload
|
12
|
+
class Railtie < Rails::Railtie
|
13
|
+
config.to_prepare do |_app|
|
14
|
+
ActiveSupport.on_load(:active_record) do
|
15
|
+
ActiveRecord::Base.include(Base)
|
16
|
+
|
17
|
+
ActiveRecord::Relation.prepend(Relation)
|
18
|
+
ActiveRecord::AssociationRelation.prepend(AssociationRelation)
|
19
|
+
ActiveRecord::Relation::Merger.prepend(Merger)
|
20
|
+
|
21
|
+
[
|
22
|
+
ActiveRecord::Associations::CollectionAssociation,
|
23
|
+
ActiveRecord::Associations::Association
|
24
|
+
].each { |klass| klass.prepend(Association) }
|
25
|
+
|
26
|
+
ActiveRecord::Associations::CollectionAssociation.prepend(CollectionAssociation)
|
27
|
+
ActiveRecord::Associations::CollectionProxy.prepend(CollectionProxy)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ar_lazy_preload
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- DmitryTsepelev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-07-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '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'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: db-query-matchers
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: coveralls
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: database_cleaner
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: factory_bot
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: appraisal
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: memory_profiler
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
description: lazy_preload implementation for ActiveRecord models
|
154
|
+
email:
|
155
|
+
- dmitry.a.tsepelev@gmail.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- MIT-LICENSE
|
161
|
+
- README.md
|
162
|
+
- Rakefile
|
163
|
+
- lib/ar_lazy_preload.rb
|
164
|
+
- lib/ar_lazy_preload/active_record/association.rb
|
165
|
+
- lib/ar_lazy_preload/active_record/association_relation.rb
|
166
|
+
- lib/ar_lazy_preload/active_record/base.rb
|
167
|
+
- lib/ar_lazy_preload/active_record/collection_association.rb
|
168
|
+
- lib/ar_lazy_preload/active_record/collection_proxy.rb
|
169
|
+
- lib/ar_lazy_preload/active_record/merger.rb
|
170
|
+
- lib/ar_lazy_preload/active_record/relation.rb
|
171
|
+
- lib/ar_lazy_preload/associated_context_builder.rb
|
172
|
+
- lib/ar_lazy_preload/association_tree_builder.rb
|
173
|
+
- lib/ar_lazy_preload/configuration.rb
|
174
|
+
- lib/ar_lazy_preload/context.rb
|
175
|
+
- lib/ar_lazy_preload/contexts/auto_preload_context.rb
|
176
|
+
- lib/ar_lazy_preload/contexts/base_context.rb
|
177
|
+
- lib/ar_lazy_preload/contexts/lazy_preload_context.rb
|
178
|
+
- lib/ar_lazy_preload/railtie.rb
|
179
|
+
- lib/ar_lazy_preload/version.rb
|
180
|
+
homepage: https://github.com/DmitryTsepelev/ar_lazy_preload
|
181
|
+
licenses:
|
182
|
+
- MIT
|
183
|
+
metadata: {}
|
184
|
+
post_install_message:
|
185
|
+
rdoc_options: []
|
186
|
+
require_paths:
|
187
|
+
- lib
|
188
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '2.3'
|
193
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - ">="
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
requirements: []
|
199
|
+
rubygems_version: 3.0.3
|
200
|
+
signing_key:
|
201
|
+
specification_version: 4
|
202
|
+
summary: lazy_preload implementation for ActiveRecord models
|
203
|
+
test_files: []
|