Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 251 lines (200 sloc) 7.096 kB
0e0866e @miloops Introduced ActiveRecord::Relation, a layer between an ARel relation a…
miloops authored
1 module ActiveRecord
2 class Relation
7aabaac @lifo Organize Relation methods into separate modules
lifo authored
3 include QueryMethods, FinderMethods, CalculationMethods
1785e1b @lifo Rename Relation#create_new_relation to spawn and refactor preload/eag…
lifo authored
4
02207dc @lifo Add Model.readonly and association_collection#readonly finder method
lifo authored
5 delegate :length, :collect, :map, :each, :all?, :to => :to_a
61fa111 @lifo Refactor Relation#readonly using attr_writer
lifo authored
6
6f5f23a @lifo Add Relation#includes to be an equivalent of current finder option :i…
lifo authored
7 attr_reader :relation, :klass
8 attr_writer :readonly, :table
9 attr_accessor :preload_associations, :eager_load_associations, :include_associations
61fa111 @lifo Refactor Relation#readonly using attr_writer
lifo authored
10
1785e1b @lifo Rename Relation#create_new_relation to spawn and refactor preload/eag…
lifo authored
11 def initialize(klass, relation)
60926db @miloops Inline initializer setup.
miloops authored
12 @klass, @relation = klass, relation
1785e1b @lifo Rename Relation#create_new_relation to spawn and refactor preload/eag…
lifo authored
13 @preload_associations = []
14 @eager_load_associations = []
6f5f23a @lifo Add Relation#includes to be an equivalent of current finder option :i…
lifo authored
15 @include_associations = []
1785e1b @lifo Rename Relation#create_new_relation to spawn and refactor preload/eag…
lifo authored
16 @loaded, @readonly = false
c01c21b @miloops Added association preload to relation.
miloops authored
17 end
18
65200d2 @lifo Implement Relation#new
lifo authored
19 def new(*args, &block)
ac1df91 @lifo Implement Relation#create and Relation#create!
lifo authored
20 with_create_scope { @klass.new(*args, &block) }
21 end
22
23 def create(*args, &block)
24 with_create_scope { @klass.create(*args, &block) }
25 end
26
27 def create!(*args, &block)
28 with_create_scope { @klass.create!(*args, &block) }
65200d2 @lifo Implement Relation#new
lifo authored
29 end
30
a8b10a2 @lifo Add relation#merge to merge two relations
lifo authored
31 def merge(r)
b95cc72 @lifo Raise ArgumentError when trying to merge relations of different classes
lifo authored
32 raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass
33
6f5f23a @lifo Add Relation#includes to be an equivalent of current finder option :i…
lifo authored
34 merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.include_associations)
32b48bf @lifo Use arel predicates instead of strings wherever possible when merging…
lifo authored
35 merged_relation.readonly = r.readonly
f1acf1c @lifo Give higher preference to second relation's equality predicates when …
lifo authored
36
32b48bf @lifo Use arel predicates instead of strings wherever possible when merging…
lifo authored
37 [self.relation, r.relation].each do |arel|
f1acf1c @lifo Give higher preference to second relation's equality predicates when …
lifo authored
38 merged_relation = merged_relation.
32b48bf @lifo Use arel predicates instead of strings wherever possible when merging…
lifo authored
39 joins(arel.joins(arel)).
40 group(arel.groupings).
41 order(arel.send(:order_clauses).join(', ')).
42 limit(arel.taken).
43 offset(arel.skipped).
44 select(arel.send(:select_clauses)).
45 from(arel.sources)
f1acf1c @lifo Give higher preference to second relation's equality predicates when …
lifo authored
46 end
47
48 merged_wheres = @relation.wheres
49
50 r.wheres.each do |w|
51 if w.is_a?(Arel::Predicates::Equality)
52 merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name }
53 end
54
55 merged_wheres << w
56 end
57
58 merged_relation.where(*merged_wheres)
a8b10a2 @lifo Add relation#merge to merge two relations
lifo authored
59 end
60
61 alias :& :merge
62
981f696 @lifo Relation#respond_to? should take second argument for responding to pr…
lifo authored
63 def respond_to?(method, include_private = false)
64 return true if @relation.respond_to?(method, include_private) || Array.method_defined?(method)
bc933d0 @lifo Make sure Relation responds to dynamic finder methods
lifo authored
65
66 if match = DynamicFinderMatch.match(method)
67 return true if @klass.send(:all_attributes_exists?, match.attribute_names)
68 elsif match = DynamicScopeMatch.match(method)
69 return true if @klass.send(:all_attributes_exists?, match.attribute_names)
70 else
71 super
72 end
74ed123 @miloops Override respond_to? in ActiveRecord::Relation to go with
miloops authored
73 end
74
3c5a7dc @lifo Cache the loaded relations
lifo authored
75 def to_a
76 return @records if loaded?
77
eb7fdb9 @lifo Make Relation#includes behave exactly like the existing :include option
lifo authored
78 find_with_associations = @eager_load_associations.any? || references_eager_loaded_tables?
6f5f23a @lifo Add Relation#includes to be an equivalent of current finder option :i…
lifo authored
79
80 @records = if find_with_associations
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
81 begin
82 @klass.send(:find_with_associations, {
3c5a7dc @lifo Cache the loaded relations
lifo authored
83 :select => @relation.send(:select_clauses).join(', '),
84 :joins => @relation.joins(relation),
85 :group => @relation.send(:group_clauses).join(', '),
86 :order => @relation.send(:order_clauses).join(', '),
d92c4a8 @lifo Add find(ids) to relations
lifo authored
87 :conditions => where_clause,
3c5a7dc @lifo Cache the loaded relations
lifo authored
88 :limit => @relation.taken,
6f5e3a0 @lifo Relation should supply :from to find_with_associations
lifo authored
89 :offset => @relation.skipped,
ea7b5ff @jeremy Use present rather than any
jeremy authored
90 :from => (@relation.send(:from_clauses) if @relation.send(:sources).present?)
3c5a7dc @lifo Cache the loaded relations
lifo authored
91 },
eb7fdb9 @lifo Make Relation#includes behave exactly like the existing :include option
lifo authored
92 ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @include_associations, nil))
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
93 rescue ThrowResult
94 []
3c5a7dc @lifo Cache the loaded relations
lifo authored
95 end
96 else
97 @klass.find_by_sql(@relation.to_sql)
98 end
99
6f5f23a @lifo Add Relation#includes to be an equivalent of current finder option :i…
lifo authored
100 preload = @preload_associations
101 preload += @include_associations unless find_with_associations
102 preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
103
3c5a7dc @lifo Cache the loaded relations
lifo authored
104 @records.each { |record| record.readonly! } if @readonly
105
106 @loaded = true
107 @records
108 end
109
110 alias all to_a
111
f290e68 @lifo Add Relation#size and Relation#empty?
lifo authored
112 def size
113 loaded? ? @records.length : count
114 end
115
116 def empty?
117 loaded? ? @records.empty? : count.zero?
118 end
119
bdf59a5 @lifo Add Relation#any? and Relation#many?
lifo authored
120 def any?
121 if block_given?
122 to_a.any? { |*block_args| yield(*block_args) }
123 else
124 !empty?
125 end
126 end
127
128 def many?
129 if block_given?
130 to_a.many? { |*block_args| yield(*block_args) }
131 else
8734f9a @lifo Relation#many? shoud load the records if there's a LIMIT
lifo authored
132 @relation.send(:taken).present? ? to_a.many? : size > 1
bdf59a5 @lifo Add Relation#any? and Relation#many?
lifo authored
133 end
134 end
135
bbdeaae @lifo Add relation.destroy_all
lifo authored
136 def destroy_all
137 to_a.each {|object| object.destroy}
138 reset
139 end
140
54b80c7 @lifo Add Relation#delete_all
lifo authored
141 def delete_all
13989ff @lifo Use relation#delete_all for Model.delete_all
lifo authored
142 @relation.delete.tap { reset }
54b80c7 @lifo Add Relation#delete_all
lifo authored
143 end
144
d5f9173 @lifo Add Relation#delete [Pratik Naik, Emilio Tagua]
lifo authored
145 def delete(id_or_array)
146 where(@klass.primary_key => id_or_array).delete_all
147 end
148
3c5a7dc @lifo Cache the loaded relations
lifo authored
149 def loaded?
150 @loaded
151 end
152
9a9f97a @lifo Add relation.reload to force reloading the records
lifo authored
153 def reload
154 @loaded = false
bbdeaae @lifo Add relation.destroy_all
lifo authored
155 reset
156 end
157
158 def reset
e9ebf8b @lifo Cache Relation#to_sql
lifo authored
159 @first = @last = @create_scope = @joined_tables = @to_sql = nil
bbdeaae @lifo Add relation.destroy_all
lifo authored
160 @records = []
9a9f97a @lifo Add relation.reload to force reloading the records
lifo authored
161 self
162 end
163
1785e1b @lifo Rename Relation#create_new_relation to spawn and refactor preload/eag…
lifo authored
164 def spawn(relation = @relation)
f1acf1c @lifo Give higher preference to second relation's equality predicates when …
lifo authored
165 relation = Relation.new(@klass, relation)
1785e1b @lifo Rename Relation#create_new_relation to spawn and refactor preload/eag…
lifo authored
166 relation.readonly = @readonly
167 relation.preload_associations = @preload_associations
168 relation.eager_load_associations = @eager_load_associations
6f5f23a @lifo Add Relation#includes to be an equivalent of current finder option :i…
lifo authored
169 relation.include_associations = @include_associations
93555c6 @lifo Add Relation#table to get the relevant Arel::Table
lifo authored
170 relation.table = table
1785e1b @lifo Rename Relation#create_new_relation to spawn and refactor preload/eag…
lifo authored
171 relation
172 end
173
93555c6 @lifo Add Relation#table to get the relevant Arel::Table
lifo authored
174 def table
175 @table ||= Arel::Table.new(@klass.table_name, Arel::Sql::Engine.new(@klass))
176 end
177
178 def primary_key
179 @primary_key ||= table[@klass.primary_key]
180 end
181
e9ebf8b @lifo Cache Relation#to_sql
lifo authored
182 def to_sql
183 @to_sql ||= @relation.to_sql
184 end
185
8829d6e @lifo Make Model.find_by_* and Model.find_all_by_* use relations and remove…
lifo authored
186 protected
a7fd564 @lifo Add Model.select/group/order/limit/joins/conditions/preload/eager_loa…
lifo authored
187
188 def method_missing(method, *args, &block)
189 if @relation.respond_to?(method)
190 @relation.send(method, *args, &block)
191 elsif Array.method_defined?(method)
192 to_a.send(method, *args, &block)
f6f416c @lifo Add find_by_* and find_all_by_* finders to ActiveRecord::Relation
lifo authored
193 elsif match = DynamicFinderMatch.match(method)
194 attributes = match.attribute_names
195 super unless @klass.send(:all_attributes_exists?, attributes)
196
197 if match.finder?
8829d6e @lifo Make Model.find_by_* and Model.find_all_by_* use relations and remove…
lifo authored
198 find_by_attributes(match, attributes, *args)
d511de0 @lifo Add find_or_create_by_* and find_or_initialize_by_* to relations
lifo authored
199 elsif match.instantiator?
200 find_or_instantiator_by_attributes(match, attributes, *args, &block)
f6f416c @lifo Add find_by_* and find_all_by_* finders to ActiveRecord::Relation
lifo authored
201 end
a7fd564 @lifo Add Model.select/group/order/limit/joins/conditions/preload/eager_loa…
lifo authored
202 else
203 super
0e0866e @miloops Introduced ActiveRecord::Relation, a layer between an ARel relation a…
miloops authored
204 end
a7fd564 @lifo Add Model.select/group/order/limit/joins/conditions/preload/eager_loa…
lifo authored
205 end
206
ac1df91 @lifo Implement Relation#create and Relation#create!
lifo authored
207 def with_create_scope
208 @klass.send(:with_scope, :create => create_scope) { yield }
209 end
210
65200d2 @lifo Implement Relation#new
lifo authored
211 def create_scope
212 @create_scope ||= wheres.inject({}) do |hash, where|
213 hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality)
214 hash
215 end
216 end
217
498fddc @lifo Fix join string for the WHERE clause
lifo authored
218 def where_clause(join_string = " AND ")
d92c4a8 @lifo Add find(ids) to relations
lifo authored
219 @relation.send(:where_clauses).join(join_string)
220 end
d5e98dc @lifo Add relation.last and relation.reverse_order
lifo authored
221
eb7fdb9 @lifo Make Relation#includes behave exactly like the existing :include option
lifo authored
222 def references_eager_loaded_tables?
223 include_eager_order? || include_eager_conditions? || include_eager_select?
224 end
225
226 def include_eager_order?
227 order_clause = @relation.send(:order_clauses).join(', ')
228 (tables_in_string(order_clause) - joined_tables).any?
229 end
230
231 def include_eager_conditions?
232 (tables_in_string(where_clause) - joined_tables).any?
233 end
234
235 def include_eager_select?
236 select_clause = @relation.send(:select_clauses).join(', ')
237 (tables_in_string(select_clause) - joined_tables).any?
238 end
239
240 def joined_tables
241 @joined_tables ||= (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq
242 end
243
244 def tables_in_string(string)
245 return [] if string.blank?
246 string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.uniq
247 end
248
0e0866e @miloops Introduced ActiveRecord::Relation, a layer between an ARel relation a…
miloops authored
249 end
250 end
Something went wrong with that request. Please try again.