goldiloader 0.0.9 → 2.1.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 +4 -4
- data/lib/goldiloader/active_record_patches.rb +125 -93
- data/lib/goldiloader/association_info.rb +25 -92
- data/lib/goldiloader/association_loader.rb +1 -25
- data/lib/goldiloader/association_options.rb +15 -20
- data/lib/goldiloader/compatibility.rb +21 -9
- data/lib/goldiloader/version.rb +1 -1
- metadata +52 -38
- data/.gitignore +0 -20
- data/.rspec +0 -2
- data/.travis.yml +0 -20
- data/CHANGELOG.md +0 -51
- data/Gemfile +0 -3
- data/README.md +0 -187
- data/Rakefile +0 -9
- data/goldiloader.gemspec +0 -37
- data/spec/db/database.yml +0 -3
- data/spec/db/schema.rb +0 -173
- data/spec/goldiloader/auto_include_context_spec.rb +0 -164
- data/spec/goldiloader/goldiloader_spec.rb +0 -737
- data/spec/spec_helper.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7414c0de13b4e9b5c87d8106cedfdb2028a3640
|
4
|
+
data.tar.gz: 2a0650488a2ebdc94d665fa12b3dc7d88282c791
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 46d11df02dbae98f7915b06df38937ba93937671433403ff1f161def9568b92cb1a310eb55eed6c9edb073e5851660bb890d7c324cafca12c99585d269ebd0e9
|
7
|
+
data.tar.gz: '09179806ac8786f88f059d0c3cba83c4a85acd7f9fe1794dca06ac6672da948b854264263c709911425689493c2932d7f242504a2b0d0d5f9818d0487b58da17'
|
@@ -1,11 +1,15 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
module Goldiloader
|
4
|
-
module
|
4
|
+
module BasePatch
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
8
|
attr_writer :auto_include_context
|
9
|
+
|
10
|
+
class << self
|
11
|
+
delegate :auto_include, to: :all
|
12
|
+
end
|
9
13
|
end
|
10
14
|
|
11
15
|
def initialize_copy(other)
|
@@ -22,131 +26,159 @@ module Goldiloader
|
|
22
26
|
super
|
23
27
|
end
|
24
28
|
end
|
25
|
-
|
26
|
-
|
27
|
-
ActiveRecord::Base.send(:include, Goldiloader::AutoIncludableModel)
|
29
|
+
::ActiveRecord::Base.include(::Goldiloader::BasePatch)
|
28
30
|
|
29
|
-
|
31
|
+
module RelationPatch
|
32
|
+
def exec_queries
|
33
|
+
return super if loaded? || !auto_include_value
|
30
34
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
Goldiloader::AutoIncludeContext.register_models(models, eager_load_values)
|
36
|
-
models
|
37
|
-
end
|
35
|
+
models = super
|
36
|
+
Goldiloader::AutoIncludeContext.register_models(models, eager_load_values)
|
37
|
+
models
|
38
|
+
end
|
38
39
|
|
39
|
-
|
40
|
-
|
40
|
+
def auto_include(auto_include = true)
|
41
|
+
spawn.auto_include!(auto_include)
|
42
|
+
end
|
41
43
|
|
42
|
-
|
44
|
+
def auto_include!(auto_include = true)
|
45
|
+
self.auto_include_value = auto_include
|
46
|
+
self
|
47
|
+
end
|
43
48
|
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
def auto_include_value
|
50
|
+
# Note: Don't use get_value because that doesn't work properly with defaulting boolean values
|
51
|
+
@values.fetch(:auto_include, true)
|
52
|
+
end
|
47
53
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
54
|
+
def auto_include_value=(value)
|
55
|
+
if Goldiloader::Compatibility.rails_4?
|
56
|
+
raise ::ActiveRecord::Relation::ImmutableRelation if @loaded
|
57
|
+
check_cached_relation
|
58
|
+
@values[:auto_include] = value
|
59
|
+
elsif Goldiloader::Compatibility.rails_5_0?
|
60
|
+
assert_mutability!
|
61
|
+
@values[:auto_include] = value
|
62
|
+
else
|
63
|
+
set_value(:auto_include, value)
|
64
|
+
end
|
65
|
+
end
|
52
66
|
end
|
67
|
+
::ActiveRecord::Relation.prepend(::Goldiloader::RelationPatch)
|
68
|
+
|
69
|
+
module MergerPatch
|
70
|
+
private
|
53
71
|
|
54
|
-
|
55
|
-
|
72
|
+
def merge_single_values
|
73
|
+
relation.auto_include_value = other.auto_include_value
|
74
|
+
super
|
75
|
+
end
|
56
76
|
end
|
77
|
+
ActiveRecord::Relation::Merger.prepend(::Goldiloader::MergerPatch)
|
57
78
|
|
58
|
-
|
79
|
+
module AssociationPatch
|
80
|
+
extend ActiveSupport::Concern
|
59
81
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
!association_info.group? &&
|
65
|
-
!association_info.from? &&
|
66
|
-
!association_info.finder_sql? &&
|
67
|
-
# Joins not properly eager loaded - See https://github.com/salsify/goldiloader/issues/11
|
68
|
-
!association_info.joins? &&
|
69
|
-
# Unscope not properly eager loaded - See https://github.com/salsify/goldiloader/issues/13
|
70
|
-
!association_info.unscope? &&
|
71
|
-
!association_info.instance_dependent?
|
72
|
-
end
|
82
|
+
included do
|
83
|
+
class_attribute :default_fully_load
|
84
|
+
self.default_fully_load = false
|
85
|
+
end
|
73
86
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
Goldiloader::AssociationLoader.load(owner, reflection.name)
|
79
|
-
target
|
80
|
-
else
|
81
|
-
send("#{load_method}_without_auto_include", *args)
|
87
|
+
def auto_include?
|
88
|
+
# We only auto include associations that don't have in-memory changes since the
|
89
|
+
# Rails association Preloader clobbers any in-memory changes
|
90
|
+
!loaded? && target.blank? && eager_loadable?
|
82
91
|
end
|
83
|
-
end
|
84
92
|
|
85
|
-
|
93
|
+
def fully_load?
|
94
|
+
!loaded? && options.fetch(:fully_load) { self.class.default_fully_load }
|
95
|
+
end
|
86
96
|
|
87
|
-
|
97
|
+
private
|
88
98
|
|
89
|
-
|
99
|
+
def eager_loadable?
|
100
|
+
association_info = Goldiloader::AssociationInfo.new(self)
|
101
|
+
!association_info.limit? &&
|
102
|
+
!association_info.offset? &&
|
103
|
+
(!association_info.has_one? || !association_info.order?) &&
|
104
|
+
(!association_info.group? || ::Goldiloader::Compatibility.group_eager_loadable?) &&
|
105
|
+
(!association_info.from? || ::Goldiloader::Compatibility.from_eager_loadable?) &&
|
106
|
+
!association_info.instance_dependent? &&
|
107
|
+
association_info.auto_include? &&
|
108
|
+
(!owner.destroyed? || ::Goldiloader::Compatibility.destroyed_model_associations_eager_loadable?)
|
109
|
+
end
|
90
110
|
|
91
|
-
|
92
|
-
|
111
|
+
def load_with_auto_include
|
112
|
+
if loaded? && !stale_target?
|
113
|
+
target
|
114
|
+
elsif auto_include?
|
115
|
+
Goldiloader::AssociationLoader.load(owner, reflection.name)
|
116
|
+
target
|
117
|
+
else
|
118
|
+
yield
|
119
|
+
end
|
120
|
+
end
|
93
121
|
end
|
122
|
+
::ActiveRecord::Associations::Association.include(::Goldiloader::AssociationPatch)
|
94
123
|
|
95
|
-
|
96
|
-
|
124
|
+
module SingularAssociationPatch
|
125
|
+
private
|
97
126
|
|
98
|
-
|
99
|
-
|
100
|
-
[:first, :second, :third, :fourth, :fifth, :last, :size, :ids_reader, :empty?].each do |method|
|
101
|
-
# Some of these methods were added in Rails 4
|
102
|
-
next unless method_defined?(method)
|
103
|
-
|
104
|
-
aliased_target, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
|
105
|
-
define_method("#{aliased_target}_with_fully_load#{punctuation}") do |*args, &block|
|
106
|
-
load_target if fully_load?
|
107
|
-
send("#{aliased_target}_without_fully_load#{punctuation}", *args, &block)
|
127
|
+
def find_target(*args)
|
128
|
+
load_with_auto_include { super }
|
108
129
|
end
|
109
|
-
|
110
|
-
alias_method_chain method, :fully_load
|
111
130
|
end
|
131
|
+
::ActiveRecord::Associations::SingularAssociation.prepend(::Goldiloader::SingularAssociationPatch)
|
112
132
|
|
113
|
-
|
133
|
+
module CollectionAssociationPatch
|
134
|
+
# Force these methods to load the entire association for fully_load associations
|
135
|
+
association_methods = [:size, :ids_reader, :empty?]
|
136
|
+
if Goldiloader::Compatibility::ACTIVE_RECORD_VERSION < ::Gem::Version.new('5.1')
|
137
|
+
association_methods.concat([:first, :second, :third, :fourth, :fifth, :last])
|
138
|
+
end
|
114
139
|
|
115
|
-
|
116
|
-
|
117
|
-
|
140
|
+
association_methods.each do |method|
|
141
|
+
define_method(method) do |*args, &block|
|
142
|
+
load_target if fully_load?
|
143
|
+
super(*args, &block)
|
144
|
+
end
|
145
|
+
end
|
118
146
|
|
119
|
-
|
147
|
+
def load_target(*args)
|
148
|
+
load_with_auto_include { super }
|
149
|
+
end
|
120
150
|
|
121
|
-
|
151
|
+
if Goldiloader::Compatibility::ACTIVE_RECORD_VERSION >= ::Gem::Version.new('5.1')
|
152
|
+
def find_from_target?
|
153
|
+
fully_load? || super
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
::ActiveRecord::Associations::CollectionAssociation.prepend(::Goldiloader::CollectionAssociationPatch)
|
122
158
|
|
123
|
-
|
124
|
-
klass.class_eval do
|
159
|
+
module ThroughAssociationPatch
|
125
160
|
def auto_include?
|
126
161
|
# Only auto include through associations if the target association is auto-loadable
|
127
162
|
through_association = owner.association(through_reflection.name)
|
128
163
|
through_association.auto_include? && super
|
129
164
|
end
|
130
165
|
end
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
166
|
+
::ActiveRecord::Associations::HasManyThroughAssociation.prepend(::Goldiloader::ThroughAssociationPatch)
|
167
|
+
::ActiveRecord::Associations::HasOneThroughAssociation.prepend(::Goldiloader::ThroughAssociationPatch)
|
168
|
+
|
169
|
+
module CollectionProxyPatch
|
170
|
+
# The CollectionProxy just forwards exists? to the underlying scope so we need to intercept this and
|
171
|
+
# force it to use size which handles fully_load properly.
|
172
|
+
def exists?(*args)
|
173
|
+
# We don't fully_load the association when arguments are passed to exists? since Rails always
|
174
|
+
# pushes this query into the database without any caching (and it likely not a common
|
175
|
+
# scenario worth optimizing).
|
176
|
+
if args.empty? && @association.fully_load?
|
177
|
+
size > 0
|
178
|
+
else
|
179
|
+
scope.exists?(*args)
|
180
|
+
end
|
139
181
|
end
|
140
182
|
end
|
141
|
-
|
142
|
-
|
143
|
-
# The CollectionProxy just forwards exists? to the underlying scope so we need to intercept this and
|
144
|
-
# force it to use size which handles fully_load properly.
|
145
|
-
ActiveRecord::Associations::CollectionProxy.class_eval do
|
146
|
-
def exists?(*args)
|
147
|
-
# We don't fully_load the association when arguments are passed to exists? since Rails always
|
148
|
-
# pushes this query into the database without any caching (and it likely not a common
|
149
|
-
# scenario worth optimizing).
|
150
|
-
args.empty? && @association.fully_load? ? size > 0 : super
|
151
|
-
end
|
183
|
+
::ActiveRecord::Associations::CollectionProxy.prepend(::Goldiloader::CollectionProxyPatch)
|
152
184
|
end
|
@@ -7,109 +7,42 @@ module Goldiloader
|
|
7
7
|
@association = association
|
8
8
|
end
|
9
9
|
|
10
|
-
|
11
|
-
Goldiloader::Compatibility.association_finder_sql_enabled? &&
|
12
|
-
association_options[:finder_sql].present?
|
13
|
-
end
|
10
|
+
delegate :association_scope, :reflection, to: :@association
|
14
11
|
|
15
|
-
|
16
|
-
|
12
|
+
def has_one?
|
13
|
+
reflection.has_one?
|
14
|
+
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
def offset?
|
17
|
+
association_scope && association_scope.offset_value.present?
|
18
|
+
end
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
def limit?
|
21
|
+
association_scope && association_scope.limit_value.present?
|
22
|
+
end
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
def auto_include?
|
25
|
+
association_scope.nil? || association_scope.auto_include_value
|
26
|
+
end
|
29
27
|
|
30
|
-
|
28
|
+
def from?
|
29
|
+
if ActiveRecord::VERSION::MAJOR >= 5
|
30
|
+
association_scope && association_scope.from_clause.present?
|
31
|
+
else
|
31
32
|
association_scope && association_scope.from_value.present?
|
32
33
|
end
|
34
|
+
end
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
end
|
37
|
-
|
38
|
-
def joins?
|
39
|
-
# Yuck - Through associations will always have a join for *each* 'through' table
|
40
|
-
association_scope && (association_scope.joins_values.size - num_through_joins) > 0
|
41
|
-
end
|
42
|
-
|
43
|
-
def uniq?
|
44
|
-
association_scope && association_scope.uniq_value
|
45
|
-
end
|
46
|
-
|
47
|
-
def instance_dependent?
|
48
|
-
reflection.scope.present? && reflection.scope.arity > 0
|
49
|
-
end
|
50
|
-
|
51
|
-
def unscope?
|
52
|
-
Goldiloader::Compatibility.unscope_query_method_enabled? &&
|
53
|
-
association_scope &&
|
54
|
-
association_scope.unscope_values.present?
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
def num_through_joins
|
60
|
-
association = @association
|
61
|
-
count = 0
|
62
|
-
while association.is_a?(ActiveRecord::Associations::ThroughAssociation)
|
63
|
-
count += 1
|
64
|
-
association = association.owner.association(association.through_reflection.name)
|
65
|
-
end
|
66
|
-
count
|
67
|
-
end
|
68
|
-
else
|
69
|
-
def read_only?
|
70
|
-
association_options[:readonly].present?
|
71
|
-
end
|
72
|
-
|
73
|
-
def offset?
|
74
|
-
association_options[:offset].present?
|
75
|
-
end
|
76
|
-
|
77
|
-
def limit?
|
78
|
-
association_options[:limit].present?
|
79
|
-
end
|
80
|
-
|
81
|
-
def from?
|
82
|
-
false
|
83
|
-
end
|
84
|
-
|
85
|
-
def group?
|
86
|
-
association_options[:group].present?
|
87
|
-
end
|
88
|
-
|
89
|
-
def joins?
|
90
|
-
# Rails 3 didn't support joins for associations
|
91
|
-
false
|
92
|
-
end
|
93
|
-
|
94
|
-
def uniq?
|
95
|
-
association_options[:uniq]
|
96
|
-
end
|
97
|
-
|
98
|
-
def instance_dependent?
|
99
|
-
# Rails 3 didn't support this
|
100
|
-
false
|
101
|
-
end
|
102
|
-
|
103
|
-
def unscope?
|
104
|
-
# Rails 3 didn't support this
|
105
|
-
false
|
106
|
-
end
|
36
|
+
def group?
|
37
|
+
association_scope && association_scope.group_values.present?
|
107
38
|
end
|
108
39
|
|
109
|
-
|
40
|
+
def order?
|
41
|
+
association_scope && association_scope.order_values.present?
|
42
|
+
end
|
110
43
|
|
111
|
-
def
|
112
|
-
|
44
|
+
def instance_dependent?
|
45
|
+
reflection.scope.present? && reflection.scope.arity > 0
|
113
46
|
end
|
114
47
|
end
|
115
48
|
end
|
@@ -10,36 +10,12 @@ module Goldiloader
|
|
10
10
|
end
|
11
11
|
|
12
12
|
eager_load(models, association_name)
|
13
|
-
|
14
|
-
# Workaround Rails #15853 by setting models read only
|
15
|
-
if read_only?(models, association_name)
|
16
|
-
associated_models = associated_models(models, association_name)
|
17
|
-
mark_read_only(associated_models)
|
18
|
-
end
|
19
13
|
end
|
20
14
|
|
21
15
|
private
|
22
16
|
|
23
17
|
def eager_load(models, association_name)
|
24
|
-
|
25
|
-
::ActiveRecord::Associations::Preloader.new.preload(models, [association_name])
|
26
|
-
else
|
27
|
-
::ActiveRecord::Associations::Preloader.new(models, [association_name]).run
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def mark_read_only(models)
|
32
|
-
models.each(&:readonly!)
|
33
|
-
end
|
34
|
-
|
35
|
-
def read_only?(models, association_name)
|
36
|
-
model = first_model_with_association(models, association_name)
|
37
|
-
if model.nil?
|
38
|
-
false
|
39
|
-
else
|
40
|
-
association_info = AssociationInfo.new(model.association(association_name))
|
41
|
-
association_info.read_only?
|
42
|
-
end
|
18
|
+
::ActiveRecord::Associations::Preloader.new.preload(models, [association_name])
|
43
19
|
end
|
44
20
|
|
45
21
|
def first_model_with_association(models, association_name)
|
@@ -4,33 +4,28 @@ module Goldiloader
|
|
4
4
|
module AssociationOptions
|
5
5
|
extend self
|
6
6
|
|
7
|
-
OPTIONS = [:
|
7
|
+
OPTIONS = [:fully_load].freeze
|
8
|
+
|
9
|
+
# This is only used in Rails 5+
|
10
|
+
module AssociationBuilderExtension
|
11
|
+
def self.build(model, reflection)
|
12
|
+
# We have no callbacks to register
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.valid_options
|
16
|
+
OPTIONS
|
17
|
+
end
|
18
|
+
end
|
8
19
|
|
9
20
|
def register
|
10
|
-
if ::ActiveRecord::VERSION::MAJOR >=
|
11
|
-
ActiveRecord::Associations::Builder::Association.
|
21
|
+
if ::ActiveRecord::VERSION::MAJOR >= 5
|
22
|
+
ActiveRecord::Associations::Builder::Association.extensions << AssociationBuilderExtension
|
12
23
|
else
|
13
|
-
|
14
|
-
# to register the valid option for each one.
|
15
|
-
collection_association_classes.each do |assoc_class|
|
16
|
-
assoc_class.valid_options.concat(OPTIONS)
|
17
|
-
end
|
24
|
+
ActiveRecord::Associations::Builder::Association.valid_options.concat(OPTIONS)
|
18
25
|
end
|
19
26
|
end
|
20
27
|
|
21
28
|
private
|
22
|
-
|
23
|
-
def collection_association_classes
|
24
|
-
# Association.descendants doesn't work well with lazy classloading :(
|
25
|
-
[
|
26
|
-
ActiveRecord::Associations::Builder::Association,
|
27
|
-
ActiveRecord::Associations::Builder::BelongsTo,
|
28
|
-
ActiveRecord::Associations::Builder::HasAndBelongsToMany,
|
29
|
-
ActiveRecord::Associations::Builder::HasMany,
|
30
|
-
ActiveRecord::Associations::Builder::HasOne,
|
31
|
-
ActiveRecord::Associations::Builder::SingularAssociation
|
32
|
-
]
|
33
|
-
end
|
34
29
|
end
|
35
30
|
end
|
36
31
|
|
@@ -2,21 +2,33 @@
|
|
2
2
|
|
3
3
|
module Goldiloader
|
4
4
|
module Compatibility
|
5
|
+
ACTIVE_RECORD_VERSION = ::Gem::Version.new(::ActiveRecord::VERSION::STRING)
|
6
|
+
PRE_RAILS_5_2 = ACTIVE_RECORD_VERSION < ::Gem::Version.new('5.2.0')
|
7
|
+
POST_RAILS_5_1_4 = ACTIVE_RECORD_VERSION > ::Gem::Version.new('5.1.5')
|
8
|
+
PRE_RAILS_5_1_5 = ACTIVE_RECORD_VERSION < ::Gem::Version.new('5.1.5')
|
9
|
+
FROM_EAGER_LOADABLE = ACTIVE_RECORD_VERSION >= ::Gem::Version.new('5.1.5') ||
|
10
|
+
(ACTIVE_RECORD_VERSION >= ::Gem::Version.new('5.0.7') && ACTIVE_RECORD_VERSION < ::Gem::Version.new('5.1.0'))
|
11
|
+
GROUP_EAGER_LOADABLE = FROM_EAGER_LOADABLE
|
5
12
|
|
6
|
-
|
7
|
-
|
8
|
-
|
13
|
+
def self.rails_4?
|
14
|
+
::ActiveRecord::VERSION::MAJOR == 4
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.rails_5_0?
|
18
|
+
::ActiveRecord::VERSION::MAJOR == 5 && ::ActiveRecord::VERSION::MINOR == 0
|
19
|
+
end
|
9
20
|
|
10
|
-
|
11
|
-
|
21
|
+
# See https://github.com/rails/rails/pull/32375
|
22
|
+
def self.destroyed_model_associations_eager_loadable?
|
23
|
+
PRE_RAILS_5_2
|
12
24
|
end
|
13
25
|
|
14
|
-
def self.
|
15
|
-
|
26
|
+
def self.from_eager_loadable?
|
27
|
+
FROM_EAGER_LOADABLE
|
16
28
|
end
|
17
29
|
|
18
|
-
def self.
|
19
|
-
|
30
|
+
def self.group_eager_loadable?
|
31
|
+
GROUP_EAGER_LOADABLE
|
20
32
|
end
|
21
33
|
end
|
22
34
|
end
|
data/lib/goldiloader/version.rb
CHANGED