activerecord 3.0.20 → 3.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +220 -91
- data/README.rdoc +3 -3
- data/examples/performance.rb +88 -109
- data/lib/active_record.rb +6 -2
- data/lib/active_record/aggregations.rb +22 -45
- data/lib/active_record/associations.rb +264 -991
- data/lib/active_record/associations/alias_tracker.rb +85 -0
- data/lib/active_record/associations/association.rb +231 -0
- data/lib/active_record/associations/association_scope.rb +120 -0
- data/lib/active_record/associations/belongs_to_association.rb +40 -60
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
- data/lib/active_record/associations/builder/association.rb +53 -0
- data/lib/active_record/associations/builder/belongs_to.rb +85 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
- data/lib/active_record/associations/builder/has_many.rb +65 -0
- data/lib/active_record/associations/builder/has_one.rb +63 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +524 -0
- data/lib/active_record/associations/collection_proxy.rb +125 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
- data/lib/active_record/associations/has_many_association.rb +50 -79
- data/lib/active_record/associations/has_many_through_association.rb +98 -67
- data/lib/active_record/associations/has_one_association.rb +45 -115
- data/lib/active_record/associations/has_one_through_association.rb +21 -25
- data/lib/active_record/associations/join_dependency.rb +215 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_helper.rb +56 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +126 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +67 -0
- data/lib/active_record/associations/singular_association.rb +55 -0
- data/lib/active_record/associations/through_association.rb +80 -0
- data/lib/active_record/attribute_methods.rb +19 -5
- data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
- data/lib/active_record/attribute_methods/dirty.rb +8 -2
- data/lib/active_record/attribute_methods/primary_key.rb +33 -13
- data/lib/active_record/attribute_methods/read.rb +17 -17
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
- data/lib/active_record/attribute_methods/write.rb +2 -1
- data/lib/active_record/autosave_association.rb +66 -45
- data/lib/active_record/base.rb +445 -273
- data/lib/active_record/callbacks.rb +24 -33
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
- data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
- data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
- data/lib/active_record/connection_adapters/column.rb +268 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
- data/lib/active_record/counter_cache.rb +7 -4
- data/lib/active_record/fixtures.rb +174 -192
- data/lib/active_record/identity_map.rb +131 -0
- data/lib/active_record/locking/optimistic.rb +20 -14
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +24 -4
- data/lib/active_record/migration.rb +265 -144
- data/lib/active_record/migration/command_recorder.rb +103 -0
- data/lib/active_record/named_scope.rb +68 -25
- data/lib/active_record/nested_attributes.rb +58 -15
- data/lib/active_record/observer.rb +3 -7
- data/lib/active_record/persistence.rb +58 -38
- data/lib/active_record/query_cache.rb +25 -3
- data/lib/active_record/railtie.rb +21 -12
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/databases.rake +147 -116
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/reflection.rb +176 -44
- data/lib/active_record/relation.rb +125 -49
- data/lib/active_record/relation/batches.rb +7 -5
- data/lib/active_record/relation/calculations.rb +50 -18
- data/lib/active_record/relation/finder_methods.rb +47 -26
- data/lib/active_record/relation/predicate_builder.rb +24 -21
- data/lib/active_record/relation/query_methods.rb +117 -101
- data/lib/active_record/relation/spawn_methods.rb +27 -20
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/schema.rb +5 -6
- data/lib/active_record/schema_dumper.rb +11 -13
- data/lib/active_record/serialization.rb +2 -2
- data/lib/active_record/serializers/xml_serializer.rb +10 -10
- data/lib/active_record/session_store.rb +8 -2
- data/lib/active_record/test_case.rb +9 -20
- data/lib/active_record/timestamp.rb +21 -9
- data/lib/active_record/transactions.rb +16 -15
- data/lib/active_record/validations.rb +21 -22
- data/lib/active_record/validations/associated.rb +3 -1
- data/lib/active_record/validations/uniqueness.rb +48 -58
- data/lib/active_record/version.rb +3 -3
- data/lib/rails/generators/active_record.rb +6 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
- metadata +106 -77
- checksums.yaml +0 -7
- data/lib/active_record/association_preload.rb +0 -431
- data/lib/active_record/associations/association_collection.rb +0 -572
- data/lib/active_record/associations/association_proxy.rb +0 -304
- data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'active_support/core_ext/object/blank'
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
|
-
module Batches
|
4
|
+
module Batches
|
5
5
|
# Yields each record that was found by the find +options+. The find is
|
6
6
|
# performed by find_in_batches with a batch size of 1000 (or as
|
7
7
|
# specified by the <tt>:batch_size</tt> option).
|
@@ -20,6 +20,8 @@ module ActiveRecord
|
|
20
20
|
find_in_batches(options) do |records|
|
21
21
|
records.each { |record| yield record }
|
22
22
|
end
|
23
|
+
|
24
|
+
self
|
23
25
|
end
|
24
26
|
|
25
27
|
# Yields each batch of records that was found by the find +options+ as
|
@@ -37,7 +39,7 @@ module ActiveRecord
|
|
37
39
|
# ascending on the primary key ("id ASC") to make the batch ordering
|
38
40
|
# work. This also mean that this method only works with integer-based
|
39
41
|
# primary keys. You can't set the limit either, that's used to control
|
40
|
-
# the
|
42
|
+
# the batch sizes.
|
41
43
|
#
|
42
44
|
# Example:
|
43
45
|
#
|
@@ -63,7 +65,7 @@ module ActiveRecord
|
|
63
65
|
batch_size = options.delete(:batch_size) || 1000
|
64
66
|
|
65
67
|
relation = relation.except(:order).order(batch_order).limit(batch_size)
|
66
|
-
records = relation.where(primary_key.gteq(start)).all
|
68
|
+
records = relation.where(table[primary_key].gteq(start)).all
|
67
69
|
|
68
70
|
while records.any?
|
69
71
|
yield records
|
@@ -71,7 +73,7 @@ module ActiveRecord
|
|
71
73
|
break if records.size < batch_size
|
72
74
|
|
73
75
|
if primary_key_offset = records.last.id
|
74
|
-
records = relation.where(primary_key.gt(primary_key_offset)).to_a
|
76
|
+
records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
|
75
77
|
else
|
76
78
|
raise "Primary key not included in the custom select clause"
|
77
79
|
end
|
@@ -81,7 +83,7 @@ module ActiveRecord
|
|
81
83
|
private
|
82
84
|
|
83
85
|
def batch_order
|
84
|
-
"#{
|
86
|
+
"#{quoted_table_name}.#{quoted_primary_key} ASC"
|
85
87
|
end
|
86
88
|
end
|
87
89
|
end
|
@@ -166,9 +166,9 @@ module ActiveRecord
|
|
166
166
|
if operation == "count"
|
167
167
|
column_name ||= (select_for_count || :all)
|
168
168
|
|
169
|
-
|
169
|
+
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
|
170
170
|
distinct = true
|
171
|
-
column_name =
|
171
|
+
column_name = primary_key if column_name == :all
|
172
172
|
end
|
173
173
|
|
174
174
|
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
|
@@ -196,29 +196,36 @@ module ActiveRecord
|
|
196
196
|
end
|
197
197
|
|
198
198
|
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
|
199
|
-
column = aggregate_column(column_name)
|
200
|
-
|
201
199
|
# Postgresql doesn't like ORDER BY when there are no GROUP BY
|
202
200
|
relation = except(:order)
|
203
|
-
select_value = operation_over_aggregate_column(column, operation, distinct)
|
204
201
|
|
205
|
-
relation.
|
202
|
+
if operation == "count" && (relation.limit_value || relation.offset_value)
|
203
|
+
# Shortcut when limit is zero.
|
204
|
+
return 0 if relation.limit_value == 0
|
205
|
+
|
206
|
+
query_builder = build_count_subquery(relation, column_name, distinct)
|
207
|
+
else
|
208
|
+
column = aggregate_column(column_name)
|
209
|
+
|
210
|
+
select_value = operation_over_aggregate_column(column, operation, distinct)
|
211
|
+
|
212
|
+
relation.select_values = [select_value]
|
213
|
+
|
214
|
+
query_builder = relation.arel
|
215
|
+
end
|
206
216
|
|
207
|
-
type_cast_calculated_value(@klass.connection.select_value(
|
217
|
+
type_cast_calculated_value(@klass.connection.select_value(query_builder.to_sql), column_for(column_name), operation)
|
208
218
|
end
|
209
219
|
|
210
220
|
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
|
211
221
|
group_attr = @group_values
|
212
222
|
association = @klass.reflect_on_association(group_attr.first.to_sym)
|
213
223
|
associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
|
214
|
-
group_fields = Array(associated ? association.
|
215
|
-
group_aliases =
|
216
|
-
group_columns = {
|
217
|
-
|
218
|
-
|
219
|
-
group_aliases << column_alias_for(field)
|
220
|
-
group_columns[column_alias_for(field)] = column_for(field)
|
221
|
-
end
|
224
|
+
group_fields = Array(associated ? association.foreign_key : group_attr)
|
225
|
+
group_aliases = group_fields.map { |field| column_alias_for(field) }
|
226
|
+
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
|
227
|
+
[aliaz, column_for(field)]
|
228
|
+
}
|
222
229
|
|
223
230
|
group = @klass.connection.adapter_name == 'FrontBase' ? group_aliases : group_fields
|
224
231
|
|
@@ -228,9 +235,19 @@ module ActiveRecord
|
|
228
235
|
aggregate_alias = column_alias_for(operation, column_name)
|
229
236
|
end
|
230
237
|
|
238
|
+
select_values = [
|
239
|
+
operation_over_aggregate_column(
|
240
|
+
aggregate_column(column_name),
|
241
|
+
operation,
|
242
|
+
distinct).as(aggregate_alias)
|
243
|
+
]
|
244
|
+
|
245
|
+
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
|
246
|
+
"#{field} AS #{aliaz}"
|
247
|
+
}
|
248
|
+
|
231
249
|
relation = except(:group).group(group.join(','))
|
232
|
-
relation.select_values =
|
233
|
-
group_fields.each_index{ |i| relation.select_values << "#{group_fields[i]} AS #{group_aliases[i]}" }
|
250
|
+
relation.select_values = select_values
|
234
251
|
|
235
252
|
calculated_data = @klass.connection.select_all(relation.to_sql)
|
236
253
|
|
@@ -241,7 +258,9 @@ module ActiveRecord
|
|
241
258
|
end
|
242
259
|
|
243
260
|
ActiveSupport::OrderedHash[calculated_data.map do |row|
|
244
|
-
key =
|
261
|
+
key = group_columns.map { |aliaz, column|
|
262
|
+
type_cast_calculated_value(row[aliaz], column)
|
263
|
+
}
|
245
264
|
key = key.first if key.size == 1
|
246
265
|
key = key_records[key] if associated
|
247
266
|
[key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
|
@@ -291,5 +310,18 @@ module ActiveRecord
|
|
291
310
|
select if select !~ /(,|\*)/
|
292
311
|
end
|
293
312
|
end
|
313
|
+
|
314
|
+
def build_count_subquery(relation, column_name, distinct)
|
315
|
+
column_alias = Arel.sql('count_column')
|
316
|
+
subquery_alias = Arel.sql('subquery_for_count')
|
317
|
+
|
318
|
+
aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
|
319
|
+
relation.select_values = [aliased_column]
|
320
|
+
subquery = relation.arel.as(subquery_alias)
|
321
|
+
|
322
|
+
sm = Arel::SelectManager.new relation.engine
|
323
|
+
select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
|
324
|
+
sm.project(select_value).from(subquery)
|
325
|
+
end
|
294
326
|
end
|
295
327
|
end
|
@@ -19,7 +19,7 @@ module ActiveRecord
|
|
19
19
|
#
|
20
20
|
# All approaches accept an options hash as their last parameter.
|
21
21
|
#
|
22
|
-
# ====
|
22
|
+
# ==== Options
|
23
23
|
#
|
24
24
|
# * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>["user_name = ?", username]</tt>,
|
25
25
|
# or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
|
@@ -123,6 +123,12 @@ module ActiveRecord
|
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
126
|
+
# Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
|
127
|
+
# is found. Note that <tt>first!</tt> accepts no arguments.
|
128
|
+
def first!
|
129
|
+
first or raise RecordNotFound
|
130
|
+
end
|
131
|
+
|
126
132
|
# A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
|
127
133
|
# same arguments to this method as you can to <tt>find(:last)</tt>.
|
128
134
|
def last(*args)
|
@@ -137,6 +143,12 @@ module ActiveRecord
|
|
137
143
|
end
|
138
144
|
end
|
139
145
|
|
146
|
+
# Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
|
147
|
+
# is found. Note that <tt>last!</tt> accepts no arguments.
|
148
|
+
def last!
|
149
|
+
last or raise RecordNotFound
|
150
|
+
end
|
151
|
+
|
140
152
|
# A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
|
141
153
|
# same arguments to this method as you can to <tt>find(:all)</tt>.
|
142
154
|
def all(*args)
|
@@ -168,9 +180,7 @@ module ActiveRecord
|
|
168
180
|
# Person.exists?(:name => "David")
|
169
181
|
# Person.exists?(['name LIKE ?', "%#{query}%"])
|
170
182
|
# Person.exists?
|
171
|
-
def exists?(id =
|
172
|
-
return false if id.nil?
|
173
|
-
|
183
|
+
def exists?(id = nil)
|
174
184
|
id = id.id if ActiveRecord::Base === id
|
175
185
|
|
176
186
|
join_dependency = construct_join_dependency_for_association_find
|
@@ -181,7 +191,7 @@ module ActiveRecord
|
|
181
191
|
when Array, Hash
|
182
192
|
relation = relation.where(id)
|
183
193
|
else
|
184
|
-
relation = relation.where(table[primary_key
|
194
|
+
relation = relation.where(table[primary_key].eq(id)) if id
|
185
195
|
end
|
186
196
|
|
187
197
|
connection.select_value(relation.to_sql) ? true : false
|
@@ -191,7 +201,8 @@ module ActiveRecord
|
|
191
201
|
|
192
202
|
def find_with_associations
|
193
203
|
join_dependency = construct_join_dependency_for_association_find
|
194
|
-
|
204
|
+
relation = construct_relation_for_association_find(join_dependency)
|
205
|
+
rows = connection.select_all(relation.to_sql, 'SQL', relation.bind_values)
|
195
206
|
join_dependency.instantiate(rows)
|
196
207
|
rescue ThrowResult
|
197
208
|
[]
|
@@ -199,18 +210,18 @@ module ActiveRecord
|
|
199
210
|
|
200
211
|
def construct_join_dependency_for_association_find
|
201
212
|
including = (@eager_load_values + @includes_values).uniq
|
202
|
-
|
213
|
+
ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
|
203
214
|
end
|
204
215
|
|
205
216
|
def construct_relation_for_association_calculations
|
206
217
|
including = (@eager_load_values + @includes_values).uniq
|
207
|
-
join_dependency = ActiveRecord::Associations::
|
218
|
+
join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
|
208
219
|
relation = except(:includes, :eager_load, :preload)
|
209
220
|
apply_join_dependency(relation, join_dependency)
|
210
221
|
end
|
211
222
|
|
212
223
|
def construct_relation_for_association_find(join_dependency)
|
213
|
-
relation = except(:includes, :eager_load, :preload, :select).select(
|
224
|
+
relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
|
214
225
|
apply_join_dependency(relation, join_dependency)
|
215
226
|
end
|
216
227
|
|
@@ -232,11 +243,13 @@ module ActiveRecord
|
|
232
243
|
end
|
233
244
|
|
234
245
|
def construct_limited_ids_condition(relation)
|
235
|
-
orders = relation.order_values
|
236
|
-
values = @klass.connection.distinct("#{@klass.connection.quote_table_name
|
246
|
+
orders = relation.order_values
|
247
|
+
values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
|
237
248
|
|
238
|
-
|
239
|
-
|
249
|
+
relation = relation.dup
|
250
|
+
|
251
|
+
ids_array = relation.select(values).collect {|row| row[primary_key]}
|
252
|
+
ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
|
240
253
|
end
|
241
254
|
|
242
255
|
def find_by_attributes(match, attributes, *args)
|
@@ -266,8 +279,8 @@ module ActiveRecord
|
|
266
279
|
|
267
280
|
unless record
|
268
281
|
record = @klass.new do |r|
|
269
|
-
r.
|
270
|
-
r.
|
282
|
+
r.assign_attributes(protected_attributes_for_create)
|
283
|
+
r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
|
271
284
|
end
|
272
285
|
yield(record) if block_given?
|
273
286
|
record.save if match.instantiator == :create
|
@@ -298,19 +311,33 @@ module ActiveRecord
|
|
298
311
|
def find_one(id)
|
299
312
|
id = id.id if ActiveRecord::Base === id
|
300
313
|
|
301
|
-
|
314
|
+
if IdentityMap.enabled? && where_values.blank? &&
|
315
|
+
limit_value.blank? && order_values.blank? &&
|
316
|
+
includes_values.blank? && preload_values.blank? &&
|
317
|
+
readonly_value.nil? && joins_values.blank? &&
|
318
|
+
!@klass.locking_enabled? &&
|
319
|
+
record = IdentityMap.get(@klass, id)
|
320
|
+
return record
|
321
|
+
end
|
322
|
+
|
323
|
+
column = columns_hash[primary_key]
|
324
|
+
|
325
|
+
substitute = connection.substitute_at(column, @bind_values.length)
|
326
|
+
relation = where(table[primary_key].eq(substitute))
|
327
|
+
relation.bind_values = [[column, id]]
|
328
|
+
record = relation.first
|
302
329
|
|
303
330
|
unless record
|
304
331
|
conditions = arel.where_sql
|
305
332
|
conditions = " [#{conditions}]" if conditions
|
306
|
-
raise RecordNotFound, "Couldn't find #{@klass.name} with
|
333
|
+
raise RecordNotFound, "Couldn't find #{@klass.name} with #{primary_key}=#{id}#{conditions}"
|
307
334
|
end
|
308
335
|
|
309
336
|
record
|
310
337
|
end
|
311
338
|
|
312
339
|
def find_some(ids)
|
313
|
-
result = where(primary_key.in(ids)).all
|
340
|
+
result = where(table[primary_key].in(ids)).all
|
314
341
|
|
315
342
|
expected_size =
|
316
343
|
if @limit_value && ids.size > @limit_value
|
@@ -327,8 +354,8 @@ module ActiveRecord
|
|
327
354
|
if result.size == expected_size
|
328
355
|
result
|
329
356
|
else
|
330
|
-
conditions = arel.
|
331
|
-
conditions = " [
|
357
|
+
conditions = arel.where_sql
|
358
|
+
conditions = " [#{conditions}]" if conditions
|
332
359
|
|
333
360
|
error = "Couldn't find all #{@klass.name.pluralize} with IDs "
|
334
361
|
error << "(#{ids.join(", ")})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
|
@@ -352,14 +379,8 @@ module ActiveRecord
|
|
352
379
|
end
|
353
380
|
end
|
354
381
|
|
355
|
-
def column_aliases(join_dependency)
|
356
|
-
join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name|
|
357
|
-
"#{connection.quote_table_name join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
|
358
|
-
end
|
359
|
-
|
360
382
|
def using_limitable_reflections?(reflections)
|
361
383
|
reflections.none? { |r| r.collection? }
|
362
384
|
end
|
363
|
-
|
364
385
|
end
|
365
386
|
end
|
@@ -1,38 +1,42 @@
|
|
1
1
|
module ActiveRecord
|
2
|
-
class PredicateBuilder
|
3
|
-
|
4
|
-
def initialize(engine)
|
5
|
-
@engine = engine
|
6
|
-
end
|
7
|
-
|
8
|
-
def build_from_hash(attributes, default_table, allow_table_name = true)
|
2
|
+
class PredicateBuilder # :nodoc:
|
3
|
+
def self.build_from_hash(engine, attributes, default_table)
|
9
4
|
predicates = attributes.map do |column, value|
|
10
5
|
table = default_table
|
11
6
|
|
12
|
-
if
|
13
|
-
table = Arel::Table.new(column,
|
14
|
-
|
15
|
-
if value.empty?
|
16
|
-
'1 = 2'
|
17
|
-
else
|
18
|
-
build_from_hash(value, table, false)
|
19
|
-
end
|
7
|
+
if value.is_a?(Hash)
|
8
|
+
table = Arel::Table.new(column, engine)
|
9
|
+
build_from_hash(engine, value, table)
|
20
10
|
else
|
21
11
|
column = column.to_s
|
22
12
|
|
23
|
-
if
|
13
|
+
if column.include?('.')
|
24
14
|
table_name, column = column.split('.', 2)
|
25
|
-
table = Arel::Table.new(table_name,
|
15
|
+
table = Arel::Table.new(table_name, engine)
|
26
16
|
end
|
27
17
|
|
28
|
-
attribute = table[column]
|
18
|
+
attribute = table[column.to_sym]
|
29
19
|
|
30
20
|
case value
|
31
|
-
when
|
21
|
+
when ActiveRecord::Relation
|
22
|
+
value.select_values = [value.klass.arel_table['id']] if value.select_values.empty?
|
23
|
+
attribute.in(value.arel.ast)
|
24
|
+
when Array, ActiveRecord::Associations::CollectionProxy
|
32
25
|
values = value.to_a.map { |x|
|
33
26
|
x.is_a?(ActiveRecord::Base) ? x.id : x
|
34
27
|
}
|
35
|
-
|
28
|
+
|
29
|
+
if values.include?(nil)
|
30
|
+
values = values.compact
|
31
|
+
if values.empty?
|
32
|
+
attribute.eq nil
|
33
|
+
else
|
34
|
+
attribute.in(values.compact).or attribute.eq(nil)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
attribute.in(values)
|
38
|
+
end
|
39
|
+
|
36
40
|
when Range, Arel::Relation
|
37
41
|
attribute.in(value)
|
38
42
|
when ActiveRecord::Base
|
@@ -48,6 +52,5 @@ module ActiveRecord
|
|
48
52
|
|
49
53
|
predicates.flatten
|
50
54
|
end
|
51
|
-
|
52
55
|
end
|
53
56
|
end
|
@@ -6,13 +6,15 @@ module ActiveRecord
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
attr_accessor :includes_values, :eager_load_values, :preload_values,
|
9
|
-
:select_values, :group_values, :order_values, :
|
10
|
-
:
|
9
|
+
:select_values, :group_values, :order_values, :joins_values,
|
10
|
+
:where_values, :having_values, :bind_values,
|
11
|
+
:limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value,
|
12
|
+
:from_value, :reorder_value
|
11
13
|
|
12
14
|
def includes(*args)
|
13
15
|
args.reject! {|a| a.blank? }
|
14
16
|
|
15
|
-
return
|
17
|
+
return self if args.empty?
|
16
18
|
|
17
19
|
relation = clone
|
18
20
|
relation.includes_values = (relation.includes_values + args).flatten.uniq
|
@@ -20,14 +22,18 @@ module ActiveRecord
|
|
20
22
|
end
|
21
23
|
|
22
24
|
def eager_load(*args)
|
25
|
+
return self if args.blank?
|
26
|
+
|
23
27
|
relation = clone
|
24
|
-
relation.eager_load_values += args
|
28
|
+
relation.eager_load_values += args
|
25
29
|
relation
|
26
30
|
end
|
27
31
|
|
28
32
|
def preload(*args)
|
33
|
+
return self if args.blank?
|
34
|
+
|
29
35
|
relation = clone
|
30
|
-
relation.preload_values += args
|
36
|
+
relation.preload_values += args
|
31
37
|
relation
|
32
38
|
end
|
33
39
|
|
@@ -42,44 +48,59 @@ module ActiveRecord
|
|
42
48
|
end
|
43
49
|
|
44
50
|
def group(*args)
|
51
|
+
return self if args.blank?
|
52
|
+
|
45
53
|
relation = clone
|
46
|
-
relation.group_values += args.flatten
|
54
|
+
relation.group_values += args.flatten
|
47
55
|
relation
|
48
56
|
end
|
49
57
|
|
50
58
|
def order(*args)
|
59
|
+
return self if args.blank?
|
60
|
+
|
51
61
|
relation = clone
|
52
|
-
relation.order_values += args.flatten
|
62
|
+
relation.order_values += args.flatten
|
53
63
|
relation
|
54
64
|
end
|
55
65
|
|
56
66
|
def reorder(*args)
|
67
|
+
return self if args.blank?
|
68
|
+
|
57
69
|
relation = clone
|
58
|
-
|
59
|
-
relation.order_values = args
|
60
|
-
relation.reorder_flag = true
|
61
|
-
end
|
70
|
+
relation.reorder_value = args.flatten
|
62
71
|
relation
|
63
72
|
end
|
64
73
|
|
65
74
|
def joins(*args)
|
75
|
+
return self if args.compact.blank?
|
76
|
+
|
66
77
|
relation = clone
|
67
78
|
|
68
79
|
args.flatten!
|
69
|
-
relation.joins_values += args
|
80
|
+
relation.joins_values += args
|
70
81
|
|
71
82
|
relation
|
72
83
|
end
|
73
84
|
|
85
|
+
def bind(value)
|
86
|
+
relation = clone
|
87
|
+
relation.bind_values += [value]
|
88
|
+
relation
|
89
|
+
end
|
90
|
+
|
74
91
|
def where(opts, *rest)
|
92
|
+
return self if opts.blank?
|
93
|
+
|
75
94
|
relation = clone
|
76
|
-
relation.where_values += build_where(opts, rest)
|
95
|
+
relation.where_values += build_where(opts, rest)
|
77
96
|
relation
|
78
97
|
end
|
79
98
|
|
80
99
|
def having(*args)
|
100
|
+
return self if args.blank?
|
101
|
+
|
81
102
|
relation = clone
|
82
|
-
relation.having_values += build_where(*args)
|
103
|
+
relation.having_values += build_where(*args)
|
83
104
|
relation
|
84
105
|
end
|
85
106
|
|
@@ -116,7 +137,7 @@ module ActiveRecord
|
|
116
137
|
|
117
138
|
def create_with(value)
|
118
139
|
relation = clone
|
119
|
-
relation.create_with_value = value
|
140
|
+
relation.create_with_value = value && (@create_with_value || {}).merge(value)
|
120
141
|
relation
|
121
142
|
end
|
122
143
|
|
@@ -126,8 +147,10 @@ module ActiveRecord
|
|
126
147
|
relation
|
127
148
|
end
|
128
149
|
|
129
|
-
def extending(*modules
|
130
|
-
modules << Module.new(&
|
150
|
+
def extending(*modules)
|
151
|
+
modules << Module.new(&Proc.new) if block_given?
|
152
|
+
|
153
|
+
return self if modules.empty?
|
131
154
|
|
132
155
|
relation = clone
|
133
156
|
relation.send(:apply_modules, modules.flatten)
|
@@ -135,86 +158,73 @@ module ActiveRecord
|
|
135
158
|
end
|
136
159
|
|
137
160
|
def reverse_order
|
138
|
-
order_clause = arel.order_clauses
|
139
|
-
relation = except(:order)
|
161
|
+
order_clause = arel.order_clauses
|
140
162
|
|
141
|
-
order = order_clause.
|
142
|
-
"#{
|
143
|
-
reverse_sql_order(order_clause)
|
163
|
+
order = order_clause.empty? ?
|
164
|
+
"#{table_name}.#{primary_key} DESC" :
|
165
|
+
reverse_sql_order(order_clause).join(', ')
|
144
166
|
|
145
|
-
|
167
|
+
except(:order).order(Arel.sql(order))
|
146
168
|
end
|
147
169
|
|
148
170
|
def arel
|
149
|
-
@arel ||= build_arel
|
150
|
-
end
|
151
|
-
|
152
|
-
def custom_join_sql(*joins)
|
153
|
-
arel = table.select_manager
|
154
|
-
|
155
|
-
joins.each do |join|
|
156
|
-
next if join.blank?
|
157
|
-
|
158
|
-
@implicit_readonly = true
|
159
|
-
|
160
|
-
case join
|
161
|
-
when Array
|
162
|
-
join = Arel.sql(join.join(' ')) if array_of_strings?(join)
|
163
|
-
when String
|
164
|
-
join = Arel.sql(join)
|
165
|
-
end
|
166
|
-
|
167
|
-
arel.join(join)
|
168
|
-
end
|
169
|
-
|
170
|
-
arel.join_sql
|
171
|
+
@arel ||= with_default_scope.build_arel
|
171
172
|
end
|
172
173
|
|
173
174
|
def build_arel
|
174
|
-
arel = table
|
175
|
+
arel = table.from table
|
175
176
|
|
176
|
-
|
177
|
+
build_joins(arel, @joins_values) unless @joins_values.empty?
|
177
178
|
|
178
|
-
|
179
|
+
collapse_wheres(arel, (@where_values - ['']).uniq)
|
179
180
|
|
180
|
-
arel
|
181
|
+
arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
|
181
182
|
|
182
|
-
arel
|
183
|
-
arel
|
183
|
+
arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
|
184
|
+
arel.skip(@offset_value) if @offset_value
|
184
185
|
|
185
|
-
arel
|
186
|
+
arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
|
186
187
|
|
187
|
-
|
188
|
+
order = @reorder_value ? @reorder_value : @order_values
|
189
|
+
arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
|
188
190
|
|
189
|
-
|
191
|
+
build_select(arel, @select_values.uniq)
|
190
192
|
|
191
|
-
arel
|
192
|
-
arel
|
193
|
+
arel.from(@from_value) if @from_value
|
194
|
+
arel.lock(@lock_value) if @lock_value
|
193
195
|
|
194
196
|
arel
|
195
197
|
end
|
196
198
|
|
197
199
|
private
|
198
200
|
|
199
|
-
def
|
200
|
-
|
201
|
+
def custom_join_ast(table, joins)
|
202
|
+
joins = joins.reject { |join| join.blank? }
|
201
203
|
|
202
|
-
|
203
|
-
equality.left
|
204
|
-
end
|
204
|
+
return [] if joins.empty?
|
205
205
|
|
206
|
-
|
207
|
-
|
208
|
-
|
206
|
+
@implicit_readonly = true
|
207
|
+
|
208
|
+
joins.map do |join|
|
209
|
+
case join
|
210
|
+
when Array
|
211
|
+
join = Arel.sql(join.join(' ')) if array_of_strings?(join)
|
212
|
+
when String
|
213
|
+
join = Arel.sql(join)
|
209
214
|
end
|
210
|
-
|
215
|
+
table.create_string_join(join)
|
211
216
|
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def collapse_wheres(arel, wheres)
|
220
|
+
equalities = wheres.grep(Arel::Nodes::Equality)
|
221
|
+
|
222
|
+
arel.where(Arel::Nodes::And.new(equalities)) unless equalities.empty?
|
212
223
|
|
213
224
|
(wheres - equalities).each do |where|
|
214
225
|
where = Arel.sql(where) if String === where
|
215
|
-
arel
|
226
|
+
arel.where(Arel::Nodes::Grouping.new(where))
|
216
227
|
end
|
217
|
-
arel
|
218
228
|
end
|
219
229
|
|
220
230
|
def build_where(opts, other = [])
|
@@ -223,48 +233,60 @@ module ActiveRecord
|
|
223
233
|
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
|
224
234
|
when Hash
|
225
235
|
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
|
226
|
-
PredicateBuilder.
|
236
|
+
PredicateBuilder.build_from_hash(table.engine, attributes, table)
|
227
237
|
else
|
228
238
|
[opts]
|
229
239
|
end
|
230
240
|
end
|
231
241
|
|
232
|
-
def build_joins(
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
242
|
+
def build_joins(manager, joins)
|
243
|
+
buckets = joins.group_by do |join|
|
244
|
+
case join
|
245
|
+
when String
|
246
|
+
'string_join'
|
247
|
+
when Hash, Symbol, Array
|
248
|
+
'association_join'
|
249
|
+
when ActiveRecord::Associations::JoinDependency::JoinAssociation
|
250
|
+
'stashed_join'
|
251
|
+
when Arel::Nodes::Join
|
252
|
+
'join_node'
|
253
|
+
else
|
254
|
+
raise 'unknown class: %s' % join.class.name
|
255
|
+
end
|
239
256
|
end
|
240
257
|
|
241
|
-
|
258
|
+
association_joins = buckets['association_join'] || []
|
259
|
+
stashed_association_joins = buckets['stashed_join'] || []
|
260
|
+
join_nodes = buckets['join_node'] || []
|
261
|
+
string_joins = (buckets['string_join'] || []).map { |x|
|
262
|
+
x.strip
|
263
|
+
}.uniq
|
264
|
+
|
265
|
+
join_list = custom_join_ast(manager, string_joins)
|
242
266
|
|
243
|
-
|
244
|
-
|
267
|
+
join_dependency = ActiveRecord::Associations::JoinDependency.new(
|
268
|
+
@klass,
|
269
|
+
association_joins,
|
270
|
+
join_list
|
271
|
+
)
|
245
272
|
|
246
|
-
|
273
|
+
join_nodes.each do |join|
|
274
|
+
join_dependency.alias_tracker.aliased_name_for(join.left.name.downcase)
|
275
|
+
end
|
247
276
|
|
248
277
|
join_dependency.graft(*stashed_association_joins)
|
249
278
|
|
250
279
|
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
|
251
280
|
|
252
|
-
|
253
|
-
|
281
|
+
# FIXME: refactor this to build an AST
|
254
282
|
join_dependency.join_associations.each do |association|
|
255
|
-
|
256
|
-
to_join << [association_relation.first, association.join_type, association.association_join.first]
|
257
|
-
to_join << [association_relation.last, association.join_type, association.association_join.last]
|
258
|
-
else
|
259
|
-
to_join << [association_relation, association.join_type, association.association_join]
|
260
|
-
end
|
283
|
+
association.join_to(manager)
|
261
284
|
end
|
262
285
|
|
263
|
-
|
264
|
-
|
265
|
-
end
|
286
|
+
manager.join_sources.concat join_nodes.uniq
|
287
|
+
manager.join_sources.concat join_list
|
266
288
|
|
267
|
-
|
289
|
+
manager
|
268
290
|
end
|
269
291
|
|
270
292
|
def build_select(arel, selects)
|
@@ -272,7 +294,7 @@ module ActiveRecord
|
|
272
294
|
@implicit_readonly = false
|
273
295
|
arel.project(*selects)
|
274
296
|
else
|
275
|
-
arel.project(
|
297
|
+
arel.project(@klass.arel_table[Arel.star])
|
276
298
|
end
|
277
299
|
end
|
278
300
|
|
@@ -284,15 +306,9 @@ module ActiveRecord
|
|
284
306
|
end
|
285
307
|
|
286
308
|
def reverse_sql_order(order_query)
|
287
|
-
order_query.
|
288
|
-
|
289
|
-
|
290
|
-
elsif s.match(/\s(desc|DESC)$/)
|
291
|
-
s.gsub!(/\s(desc|DESC)$/, ' ASC')
|
292
|
-
else
|
293
|
-
s.concat(' DESC')
|
294
|
-
end
|
295
|
-
}.join(',')
|
309
|
+
order_query.join(', ').split(',').collect do |s|
|
310
|
+
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
|
311
|
+
end
|
296
312
|
end
|
297
313
|
|
298
314
|
def array_of_strings?(o)
|