eager_record 0.0.3 → 0.1.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.
- data/README.rdoc +1 -1
- data/lib/eager_record.rb +4 -173
- data/lib/eager_record/eager_preloading.rb +80 -0
- data/lib/eager_record/scoped_preloading.rb +99 -0
- data/lib/eager_record/version.rb +1 -1
- data/rails/init.rb +1 -1
- metadata +5 -3
data/README.rdoc
CHANGED
@@ -47,7 +47,7 @@ generate two SQL queries. A couple of caveats:
|
|
47
47
|
tell eager record you want to use this feature (put this line in a file in
|
48
48
|
`config/initializers`:
|
49
49
|
|
50
|
-
EagerRecord.
|
50
|
+
EagerRecord::ScopedPreloading.install
|
51
51
|
|
52
52
|
=== Does it work with all association types?
|
53
53
|
|
data/lib/eager_record.rb
CHANGED
@@ -4,182 +4,13 @@ require 'digest'
|
|
4
4
|
|
5
5
|
module EagerRecord
|
6
6
|
autoload :VERSION, File.join(File.dirname(__FILE__), 'eager_record', 'version')
|
7
|
-
|
8
|
-
|
7
|
+
autoload :EagerPreloading, File.join(File.dirname(__FILE__), 'eager_record', 'eager_preloading')
|
8
|
+
autoload :ScopedPreloading, File.join(File.dirname(__FILE__), 'eager_record', 'scoped_preloading')
|
9
9
|
|
10
10
|
class <<self
|
11
11
|
def install
|
12
|
-
|
13
|
-
|
14
|
-
include(EagerRecord::BaseExtensions::InstanceMethods)
|
15
|
-
end
|
16
|
-
ActiveRecord::Associations::AssociationProxy.module_eval { include(EagerRecord::AssociationProxyExtensions) }
|
17
|
-
ActiveRecord::Associations::AssociationCollection.module_eval { include(EagerRecord::AssociationCollectionExtensions) }
|
18
|
-
ActiveRecord::Associations::HasManyAssociation.module_eval { include(EagerRecord::HasManyAssociationExtensions) }
|
19
|
-
end
|
20
|
-
|
21
|
-
def use_scoped_preload=(flag)
|
22
|
-
@use_scoped_preload = flag
|
23
|
-
end
|
24
|
-
|
25
|
-
def use_scoped_preload?
|
26
|
-
!!@use_scoped_preload
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
module BaseExtensions
|
31
|
-
module ClassMethods
|
32
|
-
def self.extended(base)
|
33
|
-
(class <<base; self; end).module_eval do
|
34
|
-
alias_method_chain :find_by_sql, :eager_preloading
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def find_by_sql_with_eager_preloading(*args)
|
39
|
-
collection = find_by_sql_without_eager_preloading(*args)
|
40
|
-
grouped_collections = collection.group_by { |record| record.class }
|
41
|
-
grouped_collections.values.each do |grouped_collection|
|
42
|
-
if grouped_collection.length > 1
|
43
|
-
grouped_collection.each do |record|
|
44
|
-
record.instance_variable_set(:@originating_collection, grouped_collection)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
collection
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
module InstanceMethods
|
53
|
-
def self.included(base)
|
54
|
-
base.has_many TEMPORARY_SCOPED_PRELOAD_ASSOCIATION, :readonly => true
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
def scoped_preloaded_associations
|
60
|
-
@scoped_preloaded_associations ||= Hash.new { |h, k| h[k] = {}}
|
61
|
-
end
|
62
|
-
|
63
|
-
def scoped_preloaded_associations_for(association_name)
|
64
|
-
scoped_preloaded_associations[association_name.to_sym]
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
module AssociationProxyExtensions
|
70
|
-
|
71
|
-
def self.included(base)
|
72
|
-
base.module_eval do
|
73
|
-
alias_method_chain :load_target, :eager_preloading
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def load_target_with_eager_preloading
|
78
|
-
return nil unless defined?(@loaded)
|
79
|
-
|
80
|
-
if !loaded? and (!@owner.new_record? || foreign_key_present)
|
81
|
-
if originating_collection = @owner.instance_variable_get(:@originating_collection)
|
82
|
-
association_name = @reflection.name
|
83
|
-
@owner.class.__send__(:preload_associations, originating_collection, association_name)
|
84
|
-
new_association = @owner.__send__(:association_instance_get, association_name)
|
85
|
-
if new_association && __id__ != new_association.__id__ && new_association.loaded?
|
86
|
-
@target = new_association.target
|
87
|
-
@loaded = true
|
88
|
-
return
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
load_target_without_eager_preloading
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
module AssociationCollectionExtensions
|
97
|
-
def self.included(base)
|
98
|
-
base.module_eval do
|
99
|
-
alias_method_chain :load_target, :eager_preloading
|
100
|
-
alias_method_chain :find, :eager_preloading
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def load_target_with_eager_preloading
|
105
|
-
if !@owner.new_record? || foreign_key_present
|
106
|
-
if !loaded?
|
107
|
-
if originating_collection = @owner.instance_variable_get(:@originating_collection)
|
108
|
-
@owner.class.__send__(:preload_associations, originating_collection, @reflection.name)
|
109
|
-
return target if loaded?
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
load_target_without_eager_preloading
|
114
|
-
end
|
115
|
-
|
116
|
-
#
|
117
|
-
# Because of some likely unintentional plumbing in the scoping/association
|
118
|
-
# delegation chain, current_scoped_methods returns an association proxy's
|
119
|
-
# scope when called on the association collection. This means that, among
|
120
|
-
# other things, a named scope called on an association collection will
|
121
|
-
# duplicate the association collection's SQL restriction.
|
122
|
-
#
|
123
|
-
def current_scoped_methods
|
124
|
-
@reflection.klass.__send__(:current_scoped_methods)
|
125
|
-
end
|
126
|
-
|
127
|
-
def find_with_eager_preloading(*args)
|
128
|
-
if EagerRecord.use_scoped_preload? && originating_collection = @owner.instance_variable_get(:@originating_collection)
|
129
|
-
find_using_scoped_preload(originating_collection, *args)
|
130
|
-
else
|
131
|
-
find_without_eager_preloading(*args)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
private
|
136
|
-
|
137
|
-
#
|
138
|
-
# Subclasses can override this
|
139
|
-
#
|
140
|
-
def find_using_scoped_preload(originating_collection, *args)
|
141
|
-
find_without_eager_preloading(*args)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
module HasManyAssociationExtensions
|
146
|
-
def find_using_scoped_preload(originating_collection, *args)
|
147
|
-
options = args.extract_options!
|
148
|
-
reflection_name = @reflection.name
|
149
|
-
current_scope =
|
150
|
-
if current_scoped_methods && current_scoped_methods[:find] #XXX regression test
|
151
|
-
@reflection.options.merge(current_scoped_methods[:find])
|
152
|
-
else
|
153
|
-
@reflection.options
|
154
|
-
end
|
155
|
-
owner_class = @owner.class
|
156
|
-
reflection_class = @reflection.klass
|
157
|
-
scope_key = current_scope.inspect
|
158
|
-
if preloaded_association = @owner.__send__(:scoped_preloaded_associations_for, reflection_name)[scope_key]
|
159
|
-
return preloaded_association
|
160
|
-
end
|
161
|
-
reflection = owner_class.__send__(
|
162
|
-
:create_has_many_reflection,
|
163
|
-
TEMPORARY_SCOPED_PRELOAD_ASSOCIATION,
|
164
|
-
current_scope.merge(
|
165
|
-
:class_name => reflection_class.name,
|
166
|
-
:readonly => true
|
167
|
-
)
|
168
|
-
)
|
169
|
-
originating_collection.each do |record|
|
170
|
-
association = ActiveRecord::Associations::HasManyAssociation.new(record, reflection)
|
171
|
-
record.__send__(:association_instance_set, TEMPORARY_SCOPED_PRELOAD_ASSOCIATION, association)
|
172
|
-
end
|
173
|
-
owner_class.__send__(:preload_has_many_association, originating_collection, reflection)
|
174
|
-
originating_collection.each do |record|
|
175
|
-
record.instance_eval do
|
176
|
-
@scoped_preloaded_associations ||= Hash.new { |h, k| h[k] = {} }
|
177
|
-
@scoped_preloaded_associations[reflection_name][scope_key] =
|
178
|
-
association_instance_get(TEMPORARY_SCOPED_PRELOAD_ASSOCIATION)
|
179
|
-
association_instance_set(TEMPORARY_SCOPED_PRELOAD_ASSOCIATION, nil)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
@owner.__send__(:scoped_preloaded_associations_for, reflection_name)[scope_key]
|
12
|
+
EagerPreloading.install
|
13
|
+
ScopedPreloading.install
|
183
14
|
end
|
184
15
|
end
|
185
16
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module EagerRecord
|
2
|
+
module EagerPreloading
|
3
|
+
class <<self
|
4
|
+
def install
|
5
|
+
ActiveRecord::Base.module_eval do
|
6
|
+
extend(EagerRecord::EagerPreloading::BaseExtensions)
|
7
|
+
end
|
8
|
+
ActiveRecord::Associations::AssociationProxy.module_eval { include(EagerRecord::EagerPreloading::AssociationProxyExtensions) }
|
9
|
+
ActiveRecord::Associations::AssociationCollection.module_eval { include(EagerRecord::EagerPreloading::AssociationCollectionExtensions) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module BaseExtensions
|
14
|
+
def self.extended(base)
|
15
|
+
(class <<base; self; end).module_eval do
|
16
|
+
alias_method_chain :find_by_sql, :eager_preloading
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_by_sql_with_eager_preloading(*args)
|
21
|
+
collection = find_by_sql_without_eager_preloading(*args)
|
22
|
+
grouped_collections = collection.group_by { |record| record.class }
|
23
|
+
grouped_collections.values.each do |grouped_collection|
|
24
|
+
if grouped_collection.length > 1
|
25
|
+
grouped_collection.each do |record|
|
26
|
+
record.instance_variable_set(:@originating_collection, grouped_collection)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
collection
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module AssociationProxyExtensions
|
35
|
+
def self.included(base)
|
36
|
+
base.module_eval do
|
37
|
+
alias_method_chain :load_target, :eager_preloading
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_target_with_eager_preloading
|
42
|
+
return nil unless defined?(@loaded)
|
43
|
+
|
44
|
+
if !loaded? and (!@owner.new_record? || foreign_key_present)
|
45
|
+
if originating_collection = @owner.instance_variable_get(:@originating_collection)
|
46
|
+
association_name = @reflection.name
|
47
|
+
@owner.class.__send__(:preload_associations, originating_collection, association_name)
|
48
|
+
new_association = @owner.__send__(:association_instance_get, association_name)
|
49
|
+
if new_association && __id__ != new_association.__id__ && new_association.loaded?
|
50
|
+
@target = new_association.target
|
51
|
+
@loaded = true
|
52
|
+
return
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
load_target_without_eager_preloading
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module AssociationCollectionExtensions
|
61
|
+
def self.included(base)
|
62
|
+
base.module_eval do
|
63
|
+
alias_method_chain :load_target, :eager_preloading
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def load_target_with_eager_preloading
|
68
|
+
if !@owner.new_record? || foreign_key_present
|
69
|
+
if !loaded?
|
70
|
+
if originating_collection = @owner.instance_variable_get(:@originating_collection)
|
71
|
+
@owner.class.__send__(:preload_associations, originating_collection, @reflection.name)
|
72
|
+
return target if loaded?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
load_target_without_eager_preloading
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module EagerRecord
|
2
|
+
|
3
|
+
class ScopedPreloading
|
4
|
+
TEMPORARY_SCOPED_PRELOAD_ASSOCIATION = :"_temporary_association_for_scoped_preloading"
|
5
|
+
|
6
|
+
class <<self
|
7
|
+
def install
|
8
|
+
ActiveRecord::Base.module_eval { include(EagerRecord::ScopedPreloading::BaseExtensions) }
|
9
|
+
ActiveRecord::Associations::AssociationCollection.module_eval { include(EagerRecord::ScopedPreloading::AssociationCollectionExtensions) }
|
10
|
+
ActiveRecord::Associations::HasManyAssociation.module_eval { include(EagerRecord::ScopedPreloading::HasManyAssociationExtensions) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module BaseExtensions
|
15
|
+
def self.included(base)
|
16
|
+
base.has_many TEMPORARY_SCOPED_PRELOAD_ASSOCIATION, :readonly => true
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def scoped_preloaded_associations
|
22
|
+
@scoped_preloaded_associations ||= Hash.new { |h, k| h[k] = {}}
|
23
|
+
end
|
24
|
+
|
25
|
+
def scoped_preloaded_associations_for(association_name)
|
26
|
+
scoped_preloaded_associations[association_name.to_sym]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module AssociationCollectionExtensions
|
31
|
+
def self.included(base)
|
32
|
+
base.module_eval { alias_method_chain :find, :scoped_preloading }
|
33
|
+
end
|
34
|
+
#
|
35
|
+
# Because of some likely unintentional plumbing in the scoping/association
|
36
|
+
# delegation chain, current_scoped_methods returns an association proxy's
|
37
|
+
# scope when called on the association collection. This means that, among
|
38
|
+
# other things, a named scope called on an association collection will
|
39
|
+
# duplicate the association collection's SQL restriction.
|
40
|
+
#
|
41
|
+
def current_scoped_methods
|
42
|
+
@reflection.klass.__send__(:current_scoped_methods)
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_with_scoped_preloading(*args)
|
46
|
+
if originating_collection = @owner.instance_variable_get(:@originating_collection)
|
47
|
+
find_using_scoped_preload(originating_collection, *args)
|
48
|
+
else
|
49
|
+
find_without_scoped_preloading(*args)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_using_scoped_preload(originating_collection, *args)
|
54
|
+
find_without_scoped_preloading(*args)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module HasManyAssociationExtensions
|
59
|
+
def find_using_scoped_preload(originating_collection, *args)
|
60
|
+
options = args.extract_options!
|
61
|
+
reflection_name = @reflection.name
|
62
|
+
current_scope =
|
63
|
+
if current_scoped_methods && current_scoped_methods[:find] #XXX regression test
|
64
|
+
@reflection.options.merge(current_scoped_methods[:find])
|
65
|
+
else
|
66
|
+
@reflection.options
|
67
|
+
end
|
68
|
+
owner_class = @owner.class
|
69
|
+
reflection_class = @reflection.klass
|
70
|
+
scope_key = current_scope.inspect
|
71
|
+
if preloaded_association = @owner.__send__(:scoped_preloaded_associations_for, reflection_name)[scope_key]
|
72
|
+
return preloaded_association
|
73
|
+
end
|
74
|
+
reflection = owner_class.__send__(
|
75
|
+
:create_has_many_reflection,
|
76
|
+
TEMPORARY_SCOPED_PRELOAD_ASSOCIATION,
|
77
|
+
current_scope.merge(
|
78
|
+
:class_name => reflection_class.name,
|
79
|
+
:readonly => true
|
80
|
+
)
|
81
|
+
)
|
82
|
+
originating_collection.each do |record|
|
83
|
+
association = ActiveRecord::Associations::HasManyAssociation.new(record, reflection)
|
84
|
+
record.__send__(:association_instance_set, TEMPORARY_SCOPED_PRELOAD_ASSOCIATION, association)
|
85
|
+
end
|
86
|
+
owner_class.__send__(:preload_has_many_association, originating_collection, reflection)
|
87
|
+
originating_collection.each do |record|
|
88
|
+
record.instance_eval do
|
89
|
+
@scoped_preloaded_associations ||= Hash.new { |h, k| h[k] = {} }
|
90
|
+
@scoped_preloaded_associations[reflection_name][scope_key] =
|
91
|
+
association_instance_get(TEMPORARY_SCOPED_PRELOAD_ASSOCIATION)
|
92
|
+
association_instance_set(TEMPORARY_SCOPED_PRELOAD_ASSOCIATION, nil)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
@owner.__send__(:scoped_preloaded_associations_for, reflection_name)[scope_key]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/eager_record/version.rb
CHANGED
data/rails/init.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
EagerRecord.install
|
1
|
+
EagerRecord::EagerPreloading.install
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 0.0.3
|
9
|
+
version: 0.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Mat Brown
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-05-
|
17
|
+
date: 2010-05-28 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -40,7 +40,9 @@ extra_rdoc_files: []
|
|
40
40
|
files:
|
41
41
|
- rails/init.rb
|
42
42
|
- lib/eager_record.rb
|
43
|
+
- lib/eager_record/scoped_preloading.rb
|
43
44
|
- lib/eager_record/version.rb
|
45
|
+
- lib/eager_record/eager_preloading.rb
|
44
46
|
- README.rdoc
|
45
47
|
- History.txt
|
46
48
|
has_rdoc: true
|