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,8 +1,17 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
require 'active_support/core_ext/module/deprecation'
|
3
|
+
require 'active_support/core_ext/object/inclusion'
|
4
|
+
|
1
5
|
module ActiveRecord
|
2
6
|
# = Active Record Reflection
|
3
7
|
module Reflection # :nodoc:
|
4
8
|
extend ActiveSupport::Concern
|
5
9
|
|
10
|
+
included do
|
11
|
+
class_attribute :reflections
|
12
|
+
self.reflections = {}
|
13
|
+
end
|
14
|
+
|
6
15
|
# Reflection enables to interrogate Active Record classes and objects
|
7
16
|
# about their associations and aggregations. This information can,
|
8
17
|
# for example, be used in a form builder that takes an Active Record object
|
@@ -20,23 +29,14 @@ module ActiveRecord
|
|
20
29
|
when :composed_of
|
21
30
|
reflection = AggregateReflection.new(macro, name, options, active_record)
|
22
31
|
end
|
23
|
-
write_inheritable_hash :reflections, name => reflection
|
24
|
-
reflection
|
25
|
-
end
|
26
32
|
|
27
|
-
|
28
|
-
|
29
|
-
#
|
30
|
-
# Invoice.reflections
|
31
|
-
# Account.reflections
|
32
|
-
#
|
33
|
-
def reflections
|
34
|
-
read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
|
33
|
+
self.reflections = self.reflections.merge(name => reflection)
|
34
|
+
reflection
|
35
35
|
end
|
36
36
|
|
37
37
|
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
38
38
|
def reflect_on_all_aggregations
|
39
|
-
reflections.values.
|
39
|
+
reflections.values.grep(AggregateReflection)
|
40
40
|
end
|
41
41
|
|
42
42
|
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
|
@@ -58,7 +58,7 @@ module ActiveRecord
|
|
58
58
|
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
59
59
|
#
|
60
60
|
def reflect_on_all_associations(macro = nil)
|
61
|
-
association_reflections = reflections.values.
|
61
|
+
association_reflections = reflections.values.grep(AssociationReflection)
|
62
62
|
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
63
63
|
end
|
64
64
|
|
@@ -164,7 +164,7 @@ module ActiveRecord
|
|
164
164
|
|
165
165
|
def initialize(macro, name, options, active_record)
|
166
166
|
super
|
167
|
-
@collection = [:has_many, :has_and_belongs_to_many]
|
167
|
+
@collection = macro.in?([:has_many, :has_and_belongs_to_many])
|
168
168
|
end
|
169
169
|
|
170
170
|
# Returns a new, unsaved instance of the associated class. +options+ will
|
@@ -198,8 +198,21 @@ module ActiveRecord
|
|
198
198
|
@quoted_table_name ||= klass.quoted_table_name
|
199
199
|
end
|
200
200
|
|
201
|
+
def foreign_key
|
202
|
+
@foreign_key ||= options[:foreign_key] || derive_foreign_key
|
203
|
+
end
|
204
|
+
|
201
205
|
def primary_key_name
|
202
|
-
|
206
|
+
foreign_key
|
207
|
+
end
|
208
|
+
deprecate :primary_key_name => :foreign_key
|
209
|
+
|
210
|
+
def foreign_type
|
211
|
+
@foreign_type ||= options[:foreign_type] || "#{name}_type"
|
212
|
+
end
|
213
|
+
|
214
|
+
def type
|
215
|
+
@type ||= "#{options[:as]}_type"
|
203
216
|
end
|
204
217
|
|
205
218
|
def primary_key_column
|
@@ -211,7 +224,10 @@ module ActiveRecord
|
|
211
224
|
end
|
212
225
|
|
213
226
|
def association_primary_key
|
214
|
-
@association_primary_key ||=
|
227
|
+
@association_primary_key ||=
|
228
|
+
options[:primary_key] ||
|
229
|
+
!options[:polymorphic] && klass.primary_key ||
|
230
|
+
'id'
|
215
231
|
end
|
216
232
|
|
217
233
|
def active_record_primary_key
|
@@ -247,18 +263,32 @@ module ActiveRecord
|
|
247
263
|
end
|
248
264
|
|
249
265
|
def through_reflection
|
250
|
-
|
251
|
-
end
|
252
|
-
|
253
|
-
def through_reflection_primary_key_name
|
266
|
+
nil
|
254
267
|
end
|
255
268
|
|
256
269
|
def source_reflection
|
257
270
|
nil
|
258
271
|
end
|
259
272
|
|
273
|
+
# A chain of reflections from this one back to the owner. For more see the explanation in
|
274
|
+
# ThroughReflection.
|
275
|
+
def chain
|
276
|
+
[self]
|
277
|
+
end
|
278
|
+
|
279
|
+
# An array of arrays of conditions. Each item in the outside array corresponds to a reflection
|
280
|
+
# in the #chain. The inside arrays are simply conditions (and each condition may itself be
|
281
|
+
# a hash, array, arel predicate, etc...)
|
282
|
+
def conditions
|
283
|
+
conditions = [options[:conditions]].compact
|
284
|
+
conditions << { type => active_record.base_class.name } if options[:as]
|
285
|
+
[conditions]
|
286
|
+
end
|
287
|
+
|
288
|
+
alias :source_macro :macro
|
289
|
+
|
260
290
|
def has_inverse?
|
261
|
-
|
291
|
+
@options[:inverse_of]
|
262
292
|
end
|
263
293
|
|
264
294
|
def inverse_of
|
@@ -297,22 +327,36 @@ module ActiveRecord
|
|
297
327
|
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
|
298
328
|
end
|
299
329
|
|
300
|
-
def dependent_conditions(record, base_class, extra_conditions)
|
301
|
-
dependent_conditions = []
|
302
|
-
dependent_conditions << "#{primary_key_name} = #{record.send(name).send(:owner_quoted_id)}"
|
303
|
-
dependent_conditions << "#{options[:as]}_type = '#{base_class.name}'" if options[:as]
|
304
|
-
dependent_conditions << klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
305
|
-
dependent_conditions << extra_conditions if extra_conditions
|
306
|
-
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
|
307
|
-
dependent_conditions = dependent_conditions.gsub('@', '\@')
|
308
|
-
dependent_conditions
|
309
|
-
end
|
310
|
-
|
311
330
|
# Returns +true+ if +self+ is a +belongs_to+ reflection.
|
312
331
|
def belongs_to?
|
313
332
|
macro == :belongs_to
|
314
333
|
end
|
315
334
|
|
335
|
+
def association_class
|
336
|
+
case macro
|
337
|
+
when :belongs_to
|
338
|
+
if options[:polymorphic]
|
339
|
+
Associations::BelongsToPolymorphicAssociation
|
340
|
+
else
|
341
|
+
Associations::BelongsToAssociation
|
342
|
+
end
|
343
|
+
when :has_and_belongs_to_many
|
344
|
+
Associations::HasAndBelongsToManyAssociation
|
345
|
+
when :has_many
|
346
|
+
if options[:through]
|
347
|
+
Associations::HasManyThroughAssociation
|
348
|
+
else
|
349
|
+
Associations::HasManyAssociation
|
350
|
+
end
|
351
|
+
when :has_one
|
352
|
+
if options[:through]
|
353
|
+
Associations::HasOneThroughAssociation
|
354
|
+
else
|
355
|
+
Associations::HasOneAssociation
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
316
360
|
private
|
317
361
|
def derive_class_name
|
318
362
|
class_name = name.to_s.camelize
|
@@ -320,7 +364,7 @@ module ActiveRecord
|
|
320
364
|
class_name
|
321
365
|
end
|
322
366
|
|
323
|
-
def
|
367
|
+
def derive_foreign_key
|
324
368
|
if belongs_to?
|
325
369
|
"#{name}_id"
|
326
370
|
elsif options[:as]
|
@@ -334,6 +378,8 @@ module ActiveRecord
|
|
334
378
|
# Holds all the meta-data about a :through association as it was specified
|
335
379
|
# in the Active Record class.
|
336
380
|
class ThroughReflection < AssociationReflection #:nodoc:
|
381
|
+
delegate :foreign_key, :foreign_type, :association_foreign_key, :active_record_primary_key, :to => :source_reflection
|
382
|
+
|
337
383
|
# Gets the source of the through reflection. It checks both a singularized
|
338
384
|
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
|
339
385
|
#
|
@@ -361,6 +407,88 @@ module ActiveRecord
|
|
361
407
|
@through_reflection ||= active_record.reflect_on_association(options[:through])
|
362
408
|
end
|
363
409
|
|
410
|
+
# Returns an array of reflections which are involved in this association. Each item in the
|
411
|
+
# array corresponds to a table which will be part of the query for this association.
|
412
|
+
#
|
413
|
+
# The chain is built by recursively calling #chain on the source reflection and the through
|
414
|
+
# reflection. The base case for the recursion is a normal association, which just returns
|
415
|
+
# [self] as its #chain.
|
416
|
+
def chain
|
417
|
+
@chain ||= begin
|
418
|
+
chain = source_reflection.chain + through_reflection.chain
|
419
|
+
chain[0] = self # Use self so we don't lose the information from :source_type
|
420
|
+
chain
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
# Consider the following example:
|
425
|
+
#
|
426
|
+
# class Person
|
427
|
+
# has_many :articles
|
428
|
+
# has_many :comment_tags, :through => :articles
|
429
|
+
# end
|
430
|
+
#
|
431
|
+
# class Article
|
432
|
+
# has_many :comments
|
433
|
+
# has_many :comment_tags, :through => :comments, :source => :tags
|
434
|
+
# end
|
435
|
+
#
|
436
|
+
# class Comment
|
437
|
+
# has_many :tags
|
438
|
+
# end
|
439
|
+
#
|
440
|
+
# There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags,
|
441
|
+
# but only Comment.tags will be represented in the #chain. So this method creates an array
|
442
|
+
# of conditions corresponding to the chain. Each item in the #conditions array corresponds
|
443
|
+
# to an item in the #chain, and is itself an array of conditions from an arbitrary number
|
444
|
+
# of relevant reflections, plus any :source_type or polymorphic :as constraints.
|
445
|
+
def conditions
|
446
|
+
@conditions ||= begin
|
447
|
+
conditions = source_reflection.conditions
|
448
|
+
|
449
|
+
# Add to it the conditions from this reflection if necessary.
|
450
|
+
conditions.first << options[:conditions] if options[:conditions]
|
451
|
+
|
452
|
+
through_conditions = through_reflection.conditions
|
453
|
+
|
454
|
+
if options[:source_type]
|
455
|
+
through_conditions.first << { foreign_type => options[:source_type] }
|
456
|
+
end
|
457
|
+
|
458
|
+
# Recursively fill out the rest of the array from the through reflection
|
459
|
+
conditions += through_conditions
|
460
|
+
|
461
|
+
# And return
|
462
|
+
conditions
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
# The macro used by the source association
|
467
|
+
def source_macro
|
468
|
+
source_reflection.source_macro
|
469
|
+
end
|
470
|
+
|
471
|
+
# A through association is nested iff there would be more than one join table
|
472
|
+
def nested?
|
473
|
+
chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
|
474
|
+
end
|
475
|
+
|
476
|
+
# We want to use the klass from this reflection, rather than just delegate straight to
|
477
|
+
# the source_reflection, because the source_reflection may be polymorphic. We still
|
478
|
+
# need to respect the source_reflection's :primary_key option, though.
|
479
|
+
def association_primary_key
|
480
|
+
@association_primary_key ||= begin
|
481
|
+
# Get the "actual" source reflection if the immediate source reflection has a
|
482
|
+
# source reflection itself
|
483
|
+
source_reflection = self.source_reflection
|
484
|
+
while source_reflection.source_reflection
|
485
|
+
source_reflection = source_reflection.source_reflection
|
486
|
+
end
|
487
|
+
|
488
|
+
source_reflection.options[:primary_key] || klass.primary_key
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
364
492
|
# Gets an array of possible <tt>:through</tt> source reflection names:
|
365
493
|
#
|
366
494
|
# [:singularized, :pluralized]
|
@@ -369,11 +497,23 @@ module ActiveRecord
|
|
369
497
|
@source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
|
370
498
|
end
|
371
499
|
|
500
|
+
def source_options
|
501
|
+
source_reflection.options
|
502
|
+
end
|
503
|
+
|
504
|
+
def through_options
|
505
|
+
through_reflection.options
|
506
|
+
end
|
507
|
+
|
372
508
|
def check_validity!
|
373
509
|
if through_reflection.nil?
|
374
510
|
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
|
375
511
|
end
|
376
512
|
|
513
|
+
if through_reflection.options[:polymorphic]
|
514
|
+
raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
|
515
|
+
end
|
516
|
+
|
377
517
|
if source_reflection.nil?
|
378
518
|
raise HasManyThroughSourceAssociationNotFoundError.new(self)
|
379
519
|
end
|
@@ -383,24 +523,16 @@ module ActiveRecord
|
|
383
523
|
end
|
384
524
|
|
385
525
|
if source_reflection.options[:polymorphic] && options[:source_type].nil?
|
386
|
-
raise
|
526
|
+
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
|
387
527
|
end
|
388
528
|
|
389
|
-
|
390
|
-
raise
|
529
|
+
if macro == :has_one && through_reflection.collection?
|
530
|
+
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
|
391
531
|
end
|
392
532
|
|
393
533
|
check_validity_of_inverse!
|
394
534
|
end
|
395
535
|
|
396
|
-
def through_reflection_primary_key
|
397
|
-
through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.primary_key_name
|
398
|
-
end
|
399
|
-
|
400
|
-
def through_reflection_primary_key_name
|
401
|
-
through_reflection.primary_key_name if through_reflection.belongs_to?
|
402
|
-
end
|
403
|
-
|
404
536
|
private
|
405
537
|
def derive_class_name
|
406
538
|
# get the class_name of the belongs_to association of the through reflection
|
@@ -5,29 +5,75 @@ module ActiveRecord
|
|
5
5
|
class Relation
|
6
6
|
JoinOperation = Struct.new(:relation, :join_class, :on)
|
7
7
|
ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
|
8
|
-
MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having]
|
9
|
-
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from]
|
8
|
+
MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind]
|
9
|
+
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from, :reorder]
|
10
10
|
|
11
11
|
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
|
12
12
|
|
13
|
+
# These are explicitly delegated to improve performance (avoids method_missing)
|
13
14
|
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
|
14
|
-
delegate :
|
15
|
+
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :to => :klass
|
15
16
|
|
16
17
|
attr_reader :table, :klass, :loaded
|
17
|
-
attr_accessor :extensions
|
18
|
+
attr_accessor :extensions, :default_scoped
|
18
19
|
alias :loaded? :loaded
|
20
|
+
alias :default_scoped? :default_scoped
|
19
21
|
|
20
22
|
def initialize(klass, table)
|
21
23
|
@klass, @table = klass, table
|
22
24
|
|
23
25
|
@implicit_readonly = nil
|
24
26
|
@loaded = false
|
27
|
+
@default_scoped = false
|
25
28
|
|
26
29
|
SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
|
27
30
|
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
|
28
31
|
@extensions = []
|
29
32
|
end
|
30
33
|
|
34
|
+
def insert(values)
|
35
|
+
primary_key_value = nil
|
36
|
+
|
37
|
+
if primary_key && Hash === values
|
38
|
+
primary_key_value = values[values.keys.find { |k|
|
39
|
+
k.name == primary_key
|
40
|
+
}]
|
41
|
+
|
42
|
+
if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
|
43
|
+
primary_key_value = connection.next_sequence_value(klass.sequence_name)
|
44
|
+
values[klass.arel_table[klass.primary_key]] = primary_key_value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
im = arel.create_insert
|
49
|
+
im.into @table
|
50
|
+
|
51
|
+
conn = @klass.connection
|
52
|
+
|
53
|
+
substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
|
54
|
+
binds = substitutes.map do |arel_attr, value|
|
55
|
+
[@klass.columns_hash[arel_attr.name], value]
|
56
|
+
end
|
57
|
+
|
58
|
+
substitutes.each_with_index do |tuple, i|
|
59
|
+
tuple[1] = conn.substitute_at(binds[i][0], i)
|
60
|
+
end
|
61
|
+
|
62
|
+
if values.empty? # empty insert
|
63
|
+
im.values = Arel.sql(connection.empty_insert_statement_value)
|
64
|
+
else
|
65
|
+
im.insert substitutes
|
66
|
+
end
|
67
|
+
|
68
|
+
conn.insert(
|
69
|
+
im.to_sql,
|
70
|
+
'SQL',
|
71
|
+
primary_key,
|
72
|
+
primary_key_value,
|
73
|
+
nil,
|
74
|
+
binds)
|
75
|
+
end
|
76
|
+
|
31
77
|
def new(*args, &block)
|
32
78
|
scoping { @klass.new(*args, &block) }
|
33
79
|
end
|
@@ -47,25 +93,28 @@ module ActiveRecord
|
|
47
93
|
end
|
48
94
|
|
49
95
|
def respond_to?(method, include_private = false)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
return true if @klass.send(:all_attributes_exists?, match.attribute_names)
|
54
|
-
elsif match = DynamicScopeMatch.match(method)
|
55
|
-
return true if @klass.send(:all_attributes_exists?, match.attribute_names)
|
56
|
-
else
|
96
|
+
arel.respond_to?(method, include_private) ||
|
97
|
+
Array.method_defined?(method) ||
|
98
|
+
@klass.respond_to?(method, include_private) ||
|
57
99
|
super
|
58
|
-
end
|
59
100
|
end
|
60
101
|
|
61
102
|
def to_a
|
62
103
|
return @records if loaded?
|
63
104
|
|
64
|
-
@records =
|
105
|
+
@records = if @readonly_value.nil? && !@klass.locking_enabled?
|
106
|
+
eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values)
|
107
|
+
else
|
108
|
+
IdentityMap.without do
|
109
|
+
eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values)
|
110
|
+
end
|
111
|
+
end
|
65
112
|
|
66
113
|
preload = @preload_values
|
67
114
|
preload += @includes_values unless eager_loading?
|
68
|
-
preload.each
|
115
|
+
preload.each do |associations|
|
116
|
+
ActiveRecord::Associations::Preloader.new(@records, associations).run
|
117
|
+
end
|
69
118
|
|
70
119
|
# @readonly_value is true only if set explicitly. @implicit_readonly is true if there
|
71
120
|
# are JOINS and no explicit SELECT.
|
@@ -120,12 +169,7 @@ module ActiveRecord
|
|
120
169
|
# Please check unscoped if you want to remove all previous scopes (including
|
121
170
|
# the default_scope) during the execution of a block.
|
122
171
|
def scoping
|
123
|
-
@klass.
|
124
|
-
begin
|
125
|
-
yield
|
126
|
-
ensure
|
127
|
-
@klass.scoped_methods.pop
|
128
|
-
end
|
172
|
+
@klass.send(:with_scope, self, :overwrite) { yield }
|
129
173
|
end
|
130
174
|
|
131
175
|
# Updates all records with details given if they match a set of conditions supplied, limits and order can
|
@@ -153,16 +197,30 @@ module ActiveRecord
|
|
153
197
|
#
|
154
198
|
# # Update all books that match conditions, but limit it to 5 ordered by date
|
155
199
|
# Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
|
200
|
+
#
|
201
|
+
# # Conditions from the current relation also works
|
202
|
+
# Book.where('title LIKE ?', '%Rails%').update_all(:author => 'David')
|
203
|
+
#
|
204
|
+
# # The same idea applies to limit and order
|
205
|
+
# Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(:author => 'David')
|
156
206
|
def update_all(updates, conditions = nil, options = {})
|
207
|
+
IdentityMap.repository[symbolized_base_class].clear if IdentityMap.enabled?
|
157
208
|
if conditions || options.present?
|
158
209
|
where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates)
|
159
210
|
else
|
211
|
+
limit = nil
|
212
|
+
order = []
|
160
213
|
# Apply limit and order only if they're both present
|
161
214
|
if @limit_value.present? == @order_values.present?
|
162
|
-
arel.
|
163
|
-
|
164
|
-
except(:limit, :order).update_all(updates)
|
215
|
+
limit = arel.limit
|
216
|
+
order = arel.orders
|
165
217
|
end
|
218
|
+
|
219
|
+
stmt = arel.compile_update(Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates)))
|
220
|
+
stmt.take limit if limit
|
221
|
+
stmt.order(*order)
|
222
|
+
stmt.key = table[primary_key]
|
223
|
+
@klass.connection.update stmt.to_sql, 'SQL', bind_values
|
166
224
|
end
|
167
225
|
end
|
168
226
|
|
@@ -219,6 +277,7 @@ module ActiveRecord
|
|
219
277
|
#
|
220
278
|
# Person.destroy_all("last_login < '2004-04-04'")
|
221
279
|
# Person.destroy_all(:status => "inactive")
|
280
|
+
# Person.where(:age => 0..18).destroy_all
|
222
281
|
def destroy_all(conditions = nil)
|
223
282
|
if conditions
|
224
283
|
where(conditions).destroy_all
|
@@ -268,12 +327,23 @@ module ActiveRecord
|
|
268
327
|
#
|
269
328
|
# Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
|
270
329
|
# Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
|
330
|
+
# Post.where(:person_id => 5).where(:category => ['Something', 'Else']).delete_all
|
271
331
|
#
|
272
332
|
# Both calls delete the affected posts all at once with a single DELETE statement.
|
273
333
|
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
|
274
334
|
# +after_destroy+ callbacks, use the +destroy_all+ method instead.
|
275
335
|
def delete_all(conditions = nil)
|
276
|
-
|
336
|
+
IdentityMap.repository[symbolized_base_class] = {} if IdentityMap.enabled?
|
337
|
+
if conditions
|
338
|
+
where(conditions).delete_all
|
339
|
+
else
|
340
|
+
statement = arel.compile_delete
|
341
|
+
affected = @klass.connection.delete(
|
342
|
+
statement.to_sql, 'SQL', bind_values)
|
343
|
+
|
344
|
+
reset
|
345
|
+
affected
|
346
|
+
end
|
277
347
|
end
|
278
348
|
|
279
349
|
# Deletes the row with a primary key matching the +id+ argument, using a
|
@@ -297,7 +367,8 @@ module ActiveRecord
|
|
297
367
|
# # Delete multiple rows
|
298
368
|
# Todo.delete([2,3,4])
|
299
369
|
def delete(id_or_array)
|
300
|
-
|
370
|
+
IdentityMap.remove_by_id(self.symbolized_base_class, id_or_array) if IdentityMap.enabled?
|
371
|
+
where(primary_key => id_or_array).delete_all
|
301
372
|
end
|
302
373
|
|
303
374
|
def reload
|
@@ -313,33 +384,20 @@ module ActiveRecord
|
|
313
384
|
self
|
314
385
|
end
|
315
386
|
|
316
|
-
def primary_key
|
317
|
-
@primary_key ||= table[@klass.primary_key]
|
318
|
-
end
|
319
|
-
|
320
387
|
def to_sql
|
321
388
|
@to_sql ||= arel.to_sql
|
322
389
|
end
|
323
390
|
|
324
391
|
def where_values_hash
|
325
|
-
|
326
|
-
|
327
|
-
}
|
328
|
-
|
329
|
-
|
330
|
-
where.right.respond_to?(:value) ? where.right.value : where.right
|
331
|
-
]
|
332
|
-
}]
|
392
|
+
equalities = with_default_scope.where_values.grep(Arel::Nodes::Equality).find_all { |node|
|
393
|
+
node.left.relation.name == table_name
|
394
|
+
}
|
395
|
+
|
396
|
+
Hash[equalities.map { |where| [where.left.name, where.right] }]
|
333
397
|
end
|
334
398
|
|
335
399
|
def scope_for_create
|
336
|
-
@scope_for_create ||=
|
337
|
-
if @create_with_value
|
338
|
-
@create_with_value.reverse_merge(where_values_hash)
|
339
|
-
else
|
340
|
-
where_values_hash
|
341
|
-
end
|
342
|
-
end
|
400
|
+
@scope_for_create ||= where_values_hash.merge(@create_with_value || {})
|
343
401
|
end
|
344
402
|
|
345
403
|
def eager_loading?
|
@@ -351,7 +409,7 @@ module ActiveRecord
|
|
351
409
|
when Relation
|
352
410
|
other.to_sql == to_sql
|
353
411
|
when Array
|
354
|
-
to_a == other
|
412
|
+
to_a == other
|
355
413
|
end
|
356
414
|
end
|
357
415
|
|
@@ -359,13 +417,20 @@ module ActiveRecord
|
|
359
417
|
to_a.inspect
|
360
418
|
end
|
361
419
|
|
420
|
+
def with_default_scope #:nodoc:
|
421
|
+
if default_scoped?
|
422
|
+
default_scope = @klass.send(:build_default_scope)
|
423
|
+
default_scope ? default_scope.merge(self) : self
|
424
|
+
else
|
425
|
+
self
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
362
429
|
protected
|
363
430
|
|
364
431
|
def method_missing(method, *args, &block)
|
365
432
|
if Array.method_defined?(method)
|
366
433
|
to_a.send(method, *args, &block)
|
367
|
-
elsif @klass.scopes[method]
|
368
|
-
merge(@klass.send(method, *args, &block))
|
369
434
|
elsif @klass.respond_to?(method)
|
370
435
|
scoping { @klass.send(method, *args, &block) }
|
371
436
|
elsif arel.respond_to?(method)
|
@@ -378,8 +443,19 @@ module ActiveRecord
|
|
378
443
|
private
|
379
444
|
|
380
445
|
def references_eager_loaded_tables?
|
446
|
+
joined_tables = arel.join_sources.map do |join|
|
447
|
+
if join.is_a?(Arel::Nodes::StringJoin)
|
448
|
+
tables_in_string(join.left)
|
449
|
+
else
|
450
|
+
[join.left.table_name, join.left.table_alias]
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
joined_tables += [table.name, table.table_alias]
|
455
|
+
|
381
456
|
# always convert table names to downcase as in Oracle quoted table names are in uppercase
|
382
|
-
joined_tables =
|
457
|
+
joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq
|
458
|
+
|
383
459
|
(tables_in_string(to_sql) - joined_tables).any?
|
384
460
|
end
|
385
461
|
|
@@ -387,7 +463,7 @@ module ActiveRecord
|
|
387
463
|
return [] if string.blank?
|
388
464
|
# always convert table names to downcase as in Oracle quoted table names are in uppercase
|
389
465
|
# ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
|
390
|
-
string.scan(/([a-zA-Z_][
|
466
|
+
string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
|
391
467
|
end
|
392
468
|
|
393
469
|
end
|