activerecord 2.2.3 → 2.3.2

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.

Files changed (120) hide show
  1. data/CHANGELOG +438 -396
  2. data/Rakefile +4 -2
  3. data/lib/active_record.rb +46 -43
  4. data/lib/active_record/association_preload.rb +34 -19
  5. data/lib/active_record/associations.rb +193 -251
  6. data/lib/active_record/associations/association_collection.rb +38 -21
  7. data/lib/active_record/associations/association_proxy.rb +11 -4
  8. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +2 -2
  9. data/lib/active_record/associations/has_many_association.rb +2 -2
  10. data/lib/active_record/associations/has_many_through_association.rb +8 -8
  11. data/lib/active_record/associations/has_one_association.rb +11 -2
  12. data/lib/active_record/attribute_methods.rb +1 -0
  13. data/lib/active_record/autosave_association.rb +349 -0
  14. data/lib/active_record/base.rb +292 -106
  15. data/lib/active_record/batches.rb +73 -0
  16. data/lib/active_record/calculations.rb +34 -16
  17. data/lib/active_record/callbacks.rb +37 -8
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +16 -0
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -0
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +103 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -6
  22. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +28 -25
  23. data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -5
  24. data/lib/active_record/connection_adapters/mysql_adapter.rb +50 -21
  25. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -41
  26. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  27. data/lib/active_record/connection_adapters/sqlite_adapter.rb +41 -21
  28. data/lib/active_record/dirty.rb +1 -1
  29. data/lib/active_record/dynamic_scope_match.rb +25 -0
  30. data/lib/active_record/fixtures.rb +193 -198
  31. data/lib/active_record/locale/en.yml +1 -1
  32. data/lib/active_record/locking/optimistic.rb +33 -0
  33. data/lib/active_record/migration.rb +8 -2
  34. data/lib/active_record/named_scope.rb +13 -6
  35. data/lib/active_record/nested_attributes.rb +329 -0
  36. data/lib/active_record/query_cache.rb +25 -13
  37. data/lib/active_record/reflection.rb +6 -1
  38. data/lib/active_record/schema_dumper.rb +2 -0
  39. data/lib/active_record/serialization.rb +3 -1
  40. data/lib/active_record/serializers/json_serializer.rb +19 -0
  41. data/lib/active_record/serializers/xml_serializer.rb +28 -13
  42. data/lib/active_record/session_store.rb +318 -0
  43. data/lib/active_record/test_case.rb +15 -9
  44. data/lib/active_record/timestamp.rb +2 -2
  45. data/lib/active_record/transactions.rb +58 -8
  46. data/lib/active_record/validations.rb +29 -24
  47. data/lib/active_record/version.rb +2 -2
  48. data/test/cases/ar_schema_test.rb +0 -1
  49. data/test/cases/associations/belongs_to_associations_test.rb +35 -131
  50. data/test/cases/associations/cascaded_eager_loading_test.rb +8 -0
  51. data/test/cases/associations/eager_load_nested_include_test.rb +29 -0
  52. data/test/cases/associations/eager_test.rb +137 -7
  53. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +45 -7
  54. data/test/cases/associations/has_many_associations_test.rb +110 -149
  55. data/test/cases/associations/has_many_through_associations_test.rb +39 -7
  56. data/test/cases/associations/has_one_associations_test.rb +39 -92
  57. data/test/cases/associations/has_one_through_associations_test.rb +34 -3
  58. data/test/cases/associations/inner_join_association_test.rb +0 -5
  59. data/test/cases/associations/join_model_test.rb +5 -7
  60. data/test/cases/attribute_methods_test.rb +13 -1
  61. data/test/cases/autosave_association_test.rb +901 -0
  62. data/test/cases/base_test.rb +41 -21
  63. data/test/cases/batches_test.rb +61 -0
  64. data/test/cases/calculations_test.rb +37 -17
  65. data/test/cases/callbacks_test.rb +43 -5
  66. data/test/cases/connection_pool_test.rb +25 -0
  67. data/test/cases/copy_table_test_sqlite.rb +11 -0
  68. data/test/cases/datatype_test_postgresql.rb +1 -0
  69. data/test/cases/defaults_test.rb +37 -26
  70. data/test/cases/dirty_test.rb +26 -2
  71. data/test/cases/finder_test.rb +79 -44
  72. data/test/cases/fixtures_test.rb +15 -19
  73. data/test/cases/helper.rb +26 -19
  74. data/test/cases/inheritance_test.rb +2 -2
  75. data/test/cases/json_serialization_test.rb +1 -1
  76. data/test/cases/locking_test.rb +23 -5
  77. data/test/cases/method_scoping_test.rb +126 -3
  78. data/test/cases/migration_test.rb +253 -237
  79. data/test/cases/named_scope_test.rb +73 -3
  80. data/test/cases/nested_attributes_test.rb +509 -0
  81. data/test/cases/query_cache_test.rb +0 -4
  82. data/test/cases/reflection_test.rb +13 -3
  83. data/test/cases/reload_models_test.rb +3 -1
  84. data/test/cases/repair_helper.rb +50 -0
  85. data/test/cases/schema_dumper_test.rb +0 -1
  86. data/test/cases/transactions_test.rb +177 -12
  87. data/test/cases/validations_i18n_test.rb +288 -294
  88. data/test/cases/validations_test.rb +230 -180
  89. data/test/cases/xml_serialization_test.rb +19 -1
  90. data/test/fixtures/fixture_database.sqlite3 +0 -0
  91. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  92. data/test/fixtures/member_types.yml +6 -0
  93. data/test/fixtures/members.yml +3 -1
  94. data/test/fixtures/people.yml +10 -1
  95. data/test/fixtures/toys.yml +4 -0
  96. data/test/models/author.rb +1 -2
  97. data/test/models/bird.rb +3 -0
  98. data/test/models/category.rb +1 -0
  99. data/test/models/company.rb +3 -0
  100. data/test/models/developer.rb +12 -0
  101. data/test/models/event.rb +3 -0
  102. data/test/models/member.rb +1 -0
  103. data/test/models/member_detail.rb +1 -0
  104. data/test/models/member_type.rb +3 -0
  105. data/test/models/owner.rb +2 -1
  106. data/test/models/parrot.rb +2 -0
  107. data/test/models/person.rb +6 -0
  108. data/test/models/pet.rb +2 -1
  109. data/test/models/pirate.rb +55 -1
  110. data/test/models/post.rb +6 -0
  111. data/test/models/project.rb +1 -0
  112. data/test/models/reply.rb +6 -0
  113. data/test/models/ship.rb +8 -1
  114. data/test/models/ship_part.rb +5 -0
  115. data/test/models/topic.rb +13 -1
  116. data/test/models/toy.rb +4 -0
  117. data/test/schema/schema.rb +35 -2
  118. metadata +70 -9
  119. data/test/fixtures/fixture_database.sqlite +0 -0
  120. data/test/fixtures/fixture_database_2.sqlite +0 -0
data/Rakefile CHANGED
@@ -30,7 +30,9 @@ desc 'Run mysql, sqlite, and postgresql tests by default'
30
30
  task :default => :test
31
31
 
32
32
  desc 'Run mysql, sqlite, and postgresql tests'
33
- task :test => %w(test_mysql test_sqlite3 test_postgresql)
33
+ task :test => defined?(JRUBY_VERSION) ?
34
+ %w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
35
+ %w(test_mysql test_sqlite3 test_postgresql)
34
36
 
35
37
  for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb )
36
38
  Rake::TestTask.new("test_#{adapter}") { |t|
@@ -175,7 +177,7 @@ spec = Gem::Specification.new do |s|
175
177
  s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
176
178
  end
177
179
 
178
- s.add_dependency('activesupport', '= 2.2.3' + PKG_BUILD)
180
+ s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD)
179
181
 
180
182
  s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
181
183
  s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2004-2008 David Heinemeier Hansson
2
+ # Copyright (c) 2004-2009 David Heinemeier Hansson
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -31,51 +31,54 @@ rescue LoadError
31
31
  end
32
32
  end
33
33
 
34
- require 'active_record/base'
35
- require 'active_record/named_scope'
36
- require 'active_record/observer'
37
- require 'active_record/query_cache'
38
- require 'active_record/validations'
39
- require 'active_record/callbacks'
40
- require 'active_record/reflection'
41
- require 'active_record/associations'
42
- require 'active_record/association_preload'
43
- require 'active_record/aggregations'
44
- require 'active_record/transactions'
45
- require 'active_record/timestamp'
46
- require 'active_record/locking/optimistic'
47
- require 'active_record/locking/pessimistic'
48
- require 'active_record/migration'
49
- require 'active_record/schema'
50
- require 'active_record/calculations'
51
- require 'active_record/serialization'
52
- require 'active_record/attribute_methods'
53
- require 'active_record/dirty'
54
- require 'active_record/dynamic_finder_match'
34
+ module ActiveRecord
35
+ # TODO: Review explicit loads to see if they will automatically be handled by the initilizer.
36
+ def self.load_all!
37
+ [Base, DynamicFinderMatch, ConnectionAdapters::AbstractAdapter]
38
+ end
55
39
 
56
- ActiveRecord::Base.class_eval do
57
- extend ActiveRecord::QueryCache
58
- include ActiveRecord::Validations
59
- include ActiveRecord::Locking::Optimistic
60
- include ActiveRecord::Locking::Pessimistic
61
- include ActiveRecord::AttributeMethods
62
- include ActiveRecord::Dirty
63
- include ActiveRecord::Callbacks
64
- include ActiveRecord::Observing
65
- include ActiveRecord::Timestamp
66
- include ActiveRecord::Associations
67
- include ActiveRecord::NamedScope
68
- include ActiveRecord::AssociationPreload
69
- include ActiveRecord::Aggregations
70
- include ActiveRecord::Transactions
71
- include ActiveRecord::Reflection
72
- include ActiveRecord::Calculations
73
- include ActiveRecord::Serialization
74
- end
40
+ autoload :VERSION, 'active_record/version'
75
41
 
76
- require 'active_record/connection_adapters/abstract_adapter'
42
+ autoload :ActiveRecordError, 'active_record/base'
43
+ autoload :ConnectionNotEstablished, 'active_record/base'
77
44
 
78
- require 'active_record/schema_dumper'
45
+ autoload :Aggregations, 'active_record/aggregations'
46
+ autoload :AssociationPreload, 'active_record/association_preload'
47
+ autoload :Associations, 'active_record/associations'
48
+ autoload :AttributeMethods, 'active_record/attribute_methods'
49
+ autoload :AutosaveAssociation, 'active_record/autosave_association'
50
+ autoload :Base, 'active_record/base'
51
+ autoload :Batches, 'active_record/batches'
52
+ autoload :Calculations, 'active_record/calculations'
53
+ autoload :Callbacks, 'active_record/callbacks'
54
+ autoload :Dirty, 'active_record/dirty'
55
+ autoload :DynamicFinderMatch, 'active_record/dynamic_finder_match'
56
+ autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match'
57
+ autoload :Migration, 'active_record/migration'
58
+ autoload :Migrator, 'active_record/migration'
59
+ autoload :NamedScope, 'active_record/named_scope'
60
+ autoload :NestedAttributes, 'active_record/nested_attributes'
61
+ autoload :Observing, 'active_record/observer'
62
+ autoload :QueryCache, 'active_record/query_cache'
63
+ autoload :Reflection, 'active_record/reflection'
64
+ autoload :Schema, 'active_record/schema'
65
+ autoload :SchemaDumper, 'active_record/schema_dumper'
66
+ autoload :Serialization, 'active_record/serialization'
67
+ autoload :SessionStore, 'active_record/session_store'
68
+ autoload :TestCase, 'active_record/test_case'
69
+ autoload :Timestamp, 'active_record/timestamp'
70
+ autoload :Transactions, 'active_record/transactions'
71
+ autoload :Validations, 'active_record/validations'
72
+
73
+ module Locking
74
+ autoload :Optimistic, 'active_record/locking/optimistic'
75
+ autoload :Pessimistic, 'active_record/locking/pessimistic'
76
+ end
77
+
78
+ module ConnectionAdapters
79
+ autoload :AbstractAdapter, 'active_record/connection_adapters/abstract_adapter'
80
+ end
81
+ end
79
82
 
80
83
  require 'active_record/i18n_interpolation_deprecation'
81
84
  I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
@@ -43,7 +43,7 @@ module ActiveRecord
43
43
  # loading in a more high-level (application developer-friendly) manner.
44
44
  module ClassMethods
45
45
  protected
46
-
46
+
47
47
  # Eager loads the named associations for the given ActiveRecord record(s).
48
48
  #
49
49
  # In this description, 'association name' shall refer to the name passed
@@ -94,8 +94,8 @@ module ActiveRecord
94
94
  raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol)
95
95
  preload_associations(records, parent, preload_options)
96
96
  reflection = reflections[parent]
97
- parents = records.map {|record| record.send(reflection.name)}.flatten
98
- unless parents.empty? || parents.first.nil?
97
+ parents = records.map {|record| record.send(reflection.name)}.flatten.compact
98
+ unless parents.empty?
99
99
  parents.first.class.preload_associations(parents, child)
100
100
  end
101
101
  end
@@ -113,7 +113,7 @@ module ActiveRecord
113
113
  # unnecessarily
114
114
  records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
115
115
  raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
116
-
116
+
117
117
  # 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus,
118
118
  # the following could call 'preload_belongs_to_association',
119
119
  # 'preload_has_many_association', etc.
@@ -128,7 +128,7 @@ module ActiveRecord
128
128
  association_proxy.target.push(*[associated_record].flatten)
129
129
  end
130
130
  end
131
-
131
+
132
132
  def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
133
133
  parent_records.each do |parent_record|
134
134
  parent_record.send("set_#{reflection_name}_target", associated_record)
@@ -183,18 +183,19 @@ module ActiveRecord
183
183
  conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}"
184
184
  conditions << append_conditions(reflection, preload_options)
185
185
 
186
- associated_records = reflection.klass.find(:all, :conditions => [conditions, ids],
187
- :include => options[:include],
188
- :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} as t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
189
- :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
190
- :order => options[:order])
191
-
186
+ associated_records = reflection.klass.with_exclusive_scope do
187
+ reflection.klass.find(:all, :conditions => [conditions, ids],
188
+ :include => options[:include],
189
+ :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
190
+ :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
191
+ :order => options[:order])
192
+ end
192
193
  set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
193
194
  end
194
195
 
195
196
  def preload_has_one_association(records, reflection, preload_options={})
196
197
  return if records.first.send("loaded_#{reflection.name}?")
197
- id_to_record_map, ids = construct_id_map(records)
198
+ id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key])
198
199
  options = reflection.options
199
200
  records.each {|record| record.send("set_#{reflection.name}_target", nil)}
200
201
  if options[:through]
@@ -204,9 +205,18 @@ module ActiveRecord
204
205
  unless through_records.empty?
205
206
  source = reflection.source_reflection.name
206
207
  through_records.first.class.preload_associations(through_records, source)
207
- through_records.each do |through_record|
208
- add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
209
- reflection.name, through_record.send(source))
208
+ if through_reflection.macro == :belongs_to
209
+ rev_id_to_record_map, rev_ids = construct_id_map(records, through_primary_key)
210
+ rev_primary_key = through_reflection.klass.primary_key
211
+ through_records.each do |through_record|
212
+ add_preloaded_record_to_collection(rev_id_to_record_map[through_record[rev_primary_key].to_s],
213
+ reflection.name, through_record.send(source))
214
+ end
215
+ else
216
+ through_records.each do |through_record|
217
+ add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
218
+ reflection.name, through_record.send(source))
219
+ end
210
220
  end
211
221
  end
212
222
  else
@@ -219,7 +229,7 @@ module ActiveRecord
219
229
  options = reflection.options
220
230
 
221
231
  primary_key_name = reflection.through_reflection_primary_key_name
222
- id_to_record_map, ids = construct_id_map(records, primary_key_name)
232
+ id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key])
223
233
  records.each {|record| record.send(reflection.name).loaded}
224
234
 
225
235
  if options[:through]
@@ -239,7 +249,7 @@ module ActiveRecord
239
249
  reflection.primary_key_name)
240
250
  end
241
251
  end
242
-
252
+
243
253
  def preload_through_records(records, reflection, through_association)
244
254
  through_reflection = reflections[through_association]
245
255
  through_primary_key = through_reflection.primary_key_name
@@ -307,6 +317,7 @@ module ActiveRecord
307
317
 
308
318
  klasses_and_ids.each do |klass_and_id|
309
319
  klass_name, id_map = *klass_and_id
320
+ next if id_map.empty?
310
321
  klass = klass_name.constantize
311
322
 
312
323
  table_name = klass.quoted_table_name
@@ -323,11 +334,13 @@ module ActiveRecord
323
334
  end
324
335
  conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
325
336
  conditions << append_conditions(reflection, preload_options)
326
- associated_records = klass.find(:all, :conditions => [conditions, ids],
337
+ associated_records = klass.with_exclusive_scope do
338
+ klass.find(:all, :conditions => [conditions, ids],
327
339
  :include => options[:include],
328
340
  :select => options[:select],
329
341
  :joins => options[:joins],
330
342
  :order => options[:order])
343
+ end
331
344
  set_association_single_records(id_map, reflection.name, associated_records, primary_key)
332
345
  end
333
346
  end
@@ -345,13 +358,15 @@ module ActiveRecord
345
358
 
346
359
  conditions << append_conditions(reflection, preload_options)
347
360
 
348
- reflection.klass.find(:all,
361
+ reflection.klass.with_exclusive_scope do
362
+ reflection.klass.find(:all,
349
363
  :select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
350
364
  :include => preload_options[:include] || options[:include],
351
365
  :conditions => [conditions, ids],
352
366
  :joins => options[:joins],
353
367
  :group => preload_options[:group] || options[:group],
354
368
  :order => preload_options[:order] || options[:order])
369
+ end
355
370
  end
356
371
 
357
372
 
@@ -1,13 +1,3 @@
1
- require 'active_record/associations/association_proxy'
2
- require 'active_record/associations/association_collection'
3
- require 'active_record/associations/belongs_to_association'
4
- require 'active_record/associations/belongs_to_polymorphic_association'
5
- require 'active_record/associations/has_one_association'
6
- require 'active_record/associations/has_many_association'
7
- require 'active_record/associations/has_many_through_association'
8
- require 'active_record/associations/has_and_belongs_to_many_association'
9
- require 'active_record/associations/has_one_through_association'
10
-
11
1
  module ActiveRecord
12
2
  class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
13
3
  def initialize(owner_class_name, reflection)
@@ -32,7 +22,7 @@ module ActiveRecord
32
22
  through_reflection = reflection.through_reflection
33
23
  source_reflection_names = reflection.source_reflection_names
34
24
  source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
35
- super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :connector => 'or'}?")
25
+ super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence(:two_words_connector => ' or ', :last_word_connector => ', or ', :locale => :en)}?")
36
26
  end
37
27
  end
38
28
 
@@ -61,6 +51,12 @@ module ActiveRecord
61
51
  end
62
52
  end
63
53
 
54
+ class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc:
55
+ def initialize(reflection)
56
+ super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.")
57
+ end
58
+ end
59
+
64
60
  class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
65
61
  def initialize(reflection)
66
62
  super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
@@ -75,6 +71,18 @@ module ActiveRecord
75
71
 
76
72
  # See ActiveRecord::Associations::ClassMethods for documentation.
77
73
  module Associations # :nodoc:
74
+ # These classes will be loaded when associations are created.
75
+ # So there is no need to eager load them.
76
+ autoload :AssociationCollection, 'active_record/associations/association_collection'
77
+ autoload :AssociationProxy, 'active_record/associations/association_proxy'
78
+ autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
79
+ autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
80
+ autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association'
81
+ autoload :HasManyAssociation, 'active_record/associations/has_many_association'
82
+ autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
83
+ autoload :HasOneAssociation, 'active_record/associations/has_one_association'
84
+ autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
85
+
78
86
  def self.included(base)
79
87
  base.extend(ClassMethods)
80
88
  end
@@ -86,6 +94,18 @@ module ActiveRecord
86
94
  end unless self.new_record?
87
95
  end
88
96
 
97
+ private
98
+ # Gets the specified association instance if it responds to :loaded?, nil otherwise.
99
+ def association_instance_get(name)
100
+ association = instance_variable_get("@#{name}")
101
+ association if association.respond_to?(:loaded?)
102
+ end
103
+
104
+ # Set the specified association instance.
105
+ def association_instance_set(name, association)
106
+ instance_variable_set("@#{name}", association)
107
+ end
108
+
89
109
  # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
90
110
  # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
91
111
  # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own <tt>attr*</tt>
@@ -119,41 +139,40 @@ module ActiveRecord
119
139
  # | | belongs_to |
120
140
  # generated methods | belongs_to | :polymorphic | has_one
121
141
  # ----------------------------------+------------+--------------+---------
122
- # #other | X | X | X
123
- # #other=(other) | X | X | X
124
- # #build_other(attributes={}) | X | | X
125
- # #create_other(attributes={}) | X | | X
126
- # #other.create!(attributes={}) | | | X
127
- # #other.nil? | X | X |
142
+ # other | X | X | X
143
+ # other=(other) | X | X | X
144
+ # build_other(attributes={}) | X | | X
145
+ # create_other(attributes={}) | X | | X
146
+ # other.create!(attributes={}) | | | X
128
147
  #
129
148
  # ===Collection associations (one-to-many / many-to-many)
130
149
  # | | | has_many
131
150
  # generated methods | habtm | has_many | :through
132
151
  # ----------------------------------+-------+----------+----------
133
- # #others | X | X | X
134
- # #others=(other,other,...) | X | X | X
135
- # #other_ids | X | X | X
136
- # #other_ids=(id,id,...) | X | X | X
137
- # #others<< | X | X | X
138
- # #others.push | X | X | X
139
- # #others.concat | X | X | X
140
- # #others.build(attributes={}) | X | X | X
141
- # #others.create(attributes={}) | X | X | X
142
- # #others.create!(attributes={}) | X | X | X
143
- # #others.size | X | X | X
144
- # #others.length | X | X | X
145
- # #others.count | X | X | X
146
- # #others.sum(args*,&block) | X | X | X
147
- # #others.empty? | X | X | X
148
- # #others.clear | X | X | X
149
- # #others.delete(other,other,...) | X | X | X
150
- # #others.delete_all | X | X |
151
- # #others.destroy_all | X | X | X
152
- # #others.find(*args) | X | X | X
153
- # #others.find_first | X | |
154
- # #others.exist? | X | X | X
155
- # #others.uniq | X | X | X
156
- # #others.reset | X | X | X
152
+ # others | X | X | X
153
+ # others=(other,other,...) | X | X | X
154
+ # other_ids | X | X | X
155
+ # other_ids=(id,id,...) | X | X | X
156
+ # others<< | X | X | X
157
+ # others.push | X | X | X
158
+ # others.concat | X | X | X
159
+ # others.build(attributes={}) | X | X | X
160
+ # others.create(attributes={}) | X | X | X
161
+ # others.create!(attributes={}) | X | X | X
162
+ # others.size | X | X | X
163
+ # others.length | X | X | X
164
+ # others.count | X | X | X
165
+ # others.sum(args*,&block) | X | X | X
166
+ # others.empty? | X | X | X
167
+ # others.clear | X | X | X
168
+ # others.delete(other,other,...) | X | X | X
169
+ # others.delete_all | X | X |
170
+ # others.destroy_all | X | X | X
171
+ # others.find(*args) | X | X | X
172
+ # others.find_first | X | |
173
+ # others.exists? | X | X | X
174
+ # others.uniq | X | X | X
175
+ # others.reset | X | X | X
157
176
  #
158
177
  # == Cardinality and associations
159
178
  #
@@ -254,6 +273,10 @@ module ActiveRecord
254
273
  # You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
255
274
  # aware of, mostly involving the saving of associated objects.
256
275
  #
276
+ # Unless you enable the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
277
+ # <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association,
278
+ # in which case the members are always saved.
279
+ #
257
280
  # === One-to-one associations
258
281
  #
259
282
  # * Assigning an object to a +has_one+ association automatically saves that object and the object being replaced (if there is one), in
@@ -650,7 +673,7 @@ module ActiveRecord
650
673
  # Returns the number of associated objects.
651
674
  # [collection.find(...)]
652
675
  # Finds an associated object according to the same rules as ActiveRecord::Base.find.
653
- # [collection.exist?(...)]
676
+ # [collection.exists?(...)]
654
677
  # Checks whether an associated object with the given conditions exists.
655
678
  # Uses the same rules as ActiveRecord::Base.exists?.
656
679
  # [collection.build(attributes = {}, ...)]
@@ -680,7 +703,7 @@ module ActiveRecord
680
703
  # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
681
704
  # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
682
705
  # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
683
- # * <tt>Firm#clients.exist?(:name => 'ACME')</tt> (similar to <tt>Client.exist?(:name => 'ACME', :firm_id => firm.id)</tt>)
706
+ # * <tt>Firm#clients.exists?(:name => 'ACME')</tt> (similar to <tt>Client.exists?(:name => 'ACME', :firm_id => firm.id)</tt>)
684
707
  # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
685
708
  # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
686
709
  # The declaration can also include an options hash to specialize the behavior of the association.
@@ -722,6 +745,8 @@ module ActiveRecord
722
745
  # Specify second-order associations that should be eager loaded when the collection is loaded.
723
746
  # [:group]
724
747
  # An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
748
+ # [:having]
749
+ # Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
725
750
  # [:limit]
726
751
  # An integer determining the limit on the number of rows that should be returned.
727
752
  # [:offset]
@@ -748,6 +773,9 @@ module ActiveRecord
748
773
  # If true, all the associated objects are readonly through the association.
749
774
  # [:validate]
750
775
  # If false, don't validate the associated objects when saving the parent object. true by default.
776
+ # [:autosave]
777
+ # If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
778
+ #
751
779
  # Option examples:
752
780
  # has_many :comments, :order => "posted_on"
753
781
  # has_many :comments, :include => :author
@@ -764,11 +792,7 @@ module ActiveRecord
764
792
  # 'ORDER BY p.first_name'
765
793
  def has_many(association_id, options = {}, &extension)
766
794
  reflection = create_has_many_reflection(association_id, options, &extension)
767
-
768
795
  configure_dependency_for_has_many(reflection)
769
-
770
- add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
771
- add_multiple_associated_save_callbacks(reflection.name)
772
796
  add_association_callbacks(reflection.name, reflection.options)
773
797
 
774
798
  if options[:through]
@@ -790,8 +814,6 @@ module ActiveRecord
790
814
  # [association=(associate)]
791
815
  # Assigns the associate object, extracts the primary key, sets it as the foreign key,
792
816
  # and saves the associate object.
793
- # [association.nil?]
794
- # Returns +true+ if there is no associated object.
795
817
  # [build_association(attributes = {})]
796
818
  # Returns a new object of the associated type that has been instantiated
797
819
  # with +attributes+ and linked to this object through a foreign key, but has not
@@ -810,7 +832,6 @@ module ActiveRecord
810
832
  # An Account class declares <tt>has_one :beneficiary</tt>, which will add:
811
833
  # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
812
834
  # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
813
- # * <tt>Account#beneficiary.nil?</tt>
814
835
  # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
815
836
  # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
816
837
  #
@@ -853,14 +874,16 @@ module ActiveRecord
853
874
  # [:source]
854
875
  # Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
855
876
  # inferred from the association. <tt>has_one :favorite, :through => :favorites</tt> will look for a
856
- # <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
877
+ # <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
857
878
  # [:source_type]
858
879
  # Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
859
- # association is a polymorphic +belongs_to+.
880
+ # association is a polymorphic +belongs_to+.
860
881
  # [:readonly]
861
882
  # If true, the associated object is readonly through the association.
862
883
  # [:validate]
863
884
  # If false, don't validate the associated object when saving the parent object. +false+ by default.
885
+ # [:autosave]
886
+ # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
864
887
  #
865
888
  # Option examples:
866
889
  # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
@@ -877,25 +900,9 @@ module ActiveRecord
877
900
  association_accessor_methods(reflection, ActiveRecord::Associations::HasOneThroughAssociation)
878
901
  else
879
902
  reflection = create_has_one_reflection(association_id, options)
880
-
881
- ivar = "@#{reflection.name}"
882
-
883
- method_name = "has_one_after_save_for_#{reflection.name}".to_sym
884
- define_method(method_name) do
885
- association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
886
-
887
- if !association.nil? && (new_record? || association.new_record? || association[reflection.primary_key_name] != id)
888
- association[reflection.primary_key_name] = id
889
- association.save(true)
890
- end
891
- end
892
- after_save method_name
893
-
894
- add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
895
903
  association_accessor_methods(reflection, HasOneAssociation)
896
904
  association_constructor_method(:build, reflection, HasOneAssociation)
897
905
  association_constructor_method(:create, reflection, HasOneAssociation)
898
-
899
906
  configure_dependency_for_has_one(reflection)
900
907
  end
901
908
  end
@@ -912,8 +919,6 @@ module ActiveRecord
912
919
  # Returns the associated object. +nil+ is returned if none is found.
913
920
  # [association=(associate)]
914
921
  # Assigns the associate object, extracts the primary key, and sets it as the foreign key.
915
- # [association.nil?]
916
- # Returns +true+ if there is no associated object.
917
922
  # [build_association(attributes = {})]
918
923
  # Returns a new object of the associated type that has been instantiated
919
924
  # with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
@@ -931,7 +936,6 @@ module ActiveRecord
931
936
  # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
932
937
  # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
933
938
  # * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
934
- # * <tt>Post#author.nil?</tt>
935
939
  # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
936
940
  # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
937
941
  # The declaration can also include an options hash to specialize the behavior of the association.
@@ -975,6 +979,8 @@ module ActiveRecord
975
979
  # If true, the associated object is readonly through the association.
976
980
  # [:validate]
977
981
  # If false, don't validate the associated objects when saving the parent object. +false+ by default.
982
+ # [:autosave]
983
+ # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
978
984
  #
979
985
  # Option examples:
980
986
  # belongs_to :firm, :foreign_key => "client_of"
@@ -987,54 +993,17 @@ module ActiveRecord
987
993
  def belongs_to(association_id, options = {})
988
994
  reflection = create_belongs_to_reflection(association_id, options)
989
995
 
990
- ivar = "@#{reflection.name}"
991
-
992
996
  if reflection.options[:polymorphic]
993
997
  association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
994
-
995
- method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym
996
- define_method(method_name) do
997
- association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
998
-
999
- if association && association.target
1000
- if association.new_record?
1001
- association.save(true)
1002
- end
1003
-
1004
- if association.updated?
1005
- self[reflection.primary_key_name] = association.id
1006
- self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
1007
- end
1008
- end
1009
- end
1010
- before_save method_name
1011
998
  else
1012
999
  association_accessor_methods(reflection, BelongsToAssociation)
1013
1000
  association_constructor_method(:build, reflection, BelongsToAssociation)
1014
1001
  association_constructor_method(:create, reflection, BelongsToAssociation)
1015
-
1016
- method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym
1017
- define_method(method_name) do
1018
- association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
1019
-
1020
- if !association.nil?
1021
- if association.new_record?
1022
- association.save(true)
1023
- end
1024
-
1025
- if association.updated?
1026
- self[reflection.primary_key_name] = association.id
1027
- end
1028
- end
1029
- end
1030
- before_save method_name
1031
1002
  end
1032
1003
 
1033
1004
  # Create the callbacks to update counter cache
1034
1005
  if options[:counter_cache]
1035
- cache_column = options[:counter_cache] == true ?
1036
- "#{self.to_s.demodulize.underscore.pluralize}_count" :
1037
- options[:counter_cache]
1006
+ cache_column = reflection.counter_cache_column
1038
1007
 
1039
1008
  method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
1040
1009
  define_method(method_name) do
@@ -1055,8 +1024,6 @@ module ActiveRecord
1055
1024
  )
1056
1025
  end
1057
1026
 
1058
- add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
1059
-
1060
1027
  configure_dependency_for_belongs_to(reflection)
1061
1028
  end
1062
1029
 
@@ -1071,6 +1038,22 @@ module ActiveRecord
1071
1038
  # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
1072
1039
  # custom <tt>:join_table</tt> option if you need to.
1073
1040
  #
1041
+ # The join table should not have a primary key or a model associated with it. You must manually generate the
1042
+ # join table with a migration such as this:
1043
+ #
1044
+ # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
1045
+ # def self.up
1046
+ # create_table :developers_projects, :id => false do |t|
1047
+ # t.integer :developer_id
1048
+ # t.integer :project_id
1049
+ # end
1050
+ # end
1051
+ #
1052
+ # def self.down
1053
+ # drop_table :developers_projects
1054
+ # end
1055
+ # end
1056
+ #
1074
1057
  # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
1075
1058
  # +has_and_belongs_to_many+ associations. Records returned from join tables with additional attributes will be marked as
1076
1059
  # readonly (because we can't save changes to the additional attributes). It's strongly recommended that you upgrade any
@@ -1103,7 +1086,7 @@ module ActiveRecord
1103
1086
  # Finds an associated object responding to the +id+ and that
1104
1087
  # meets the condition that it has to be associated with this object.
1105
1088
  # Uses the same rules as ActiveRecord::Base.find.
1106
- # [collection.exist?(...)]
1089
+ # [collection.exists?(...)]
1107
1090
  # Checks whether an associated object with the given conditions exists.
1108
1091
  # Uses the same rules as ActiveRecord::Base.exists?.
1109
1092
  # [collection.build(attributes = {})]
@@ -1129,7 +1112,7 @@ module ActiveRecord
1129
1112
  # * <tt>Developer#projects.empty?</tt>
1130
1113
  # * <tt>Developer#projects.size</tt>
1131
1114
  # * <tt>Developer#projects.find(id)</tt>
1132
- # * <tt>Developer#clients.exist?(...)</tt>
1115
+ # * <tt>Developer#clients.exists?(...)</tt>
1133
1116
  # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("project_id" => id)</tt>)
1134
1117
  # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>)
1135
1118
  # The declaration may include an options hash to specialize the behavior of the association.
@@ -1147,11 +1130,12 @@ module ActiveRecord
1147
1130
  # [:foreign_key]
1148
1131
  # Specify the foreign key used for the association. By default this is guessed to be the name
1149
1132
  # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_and_belongs_to_many+ association
1150
- # will use "person_id" as the default <tt>:foreign_key</tt>.
1133
+ # to Project will use "person_id" as the default <tt>:foreign_key</tt>.
1151
1134
  # [:association_foreign_key]
1152
- # Specify the association foreign key used for the association. By default this is
1153
- # guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is Project,
1154
- # the +has_and_belongs_to_many+ association will use "project_id" as the default <tt>:association_foreign_key</tt>.
1135
+ # Specify the foreign key used for the association on the receiving side of the association.
1136
+ # By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
1137
+ # So if a Person class makes a +has_and_belongs_to_many+ association to Project,
1138
+ # the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
1155
1139
  # [:conditions]
1156
1140
  # Specify the conditions that the associated object must meet in order to be included as a +WHERE+
1157
1141
  # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
@@ -1179,6 +1163,8 @@ module ActiveRecord
1179
1163
  # Specify second-order associations that should be eager loaded when the collection is loaded.
1180
1164
  # [:group]
1181
1165
  # An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
1166
+ # [:having]
1167
+ # Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
1182
1168
  # [:limit]
1183
1169
  # An integer determining the limit on the number of rows that should be returned.
1184
1170
  # [:offset]
@@ -1190,6 +1176,8 @@ module ActiveRecord
1190
1176
  # If true, all the associated objects are readonly through the association.
1191
1177
  # [:validate]
1192
1178
  # If false, don't validate the associated objects when saving the parent object. +true+ by default.
1179
+ # [:autosave]
1180
+ # If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
1193
1181
  #
1194
1182
  # Option examples:
1195
1183
  # has_and_belongs_to_many :projects
@@ -1201,20 +1189,17 @@ module ActiveRecord
1201
1189
  # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
1202
1190
  def has_and_belongs_to_many(association_id, options = {}, &extension)
1203
1191
  reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
1204
-
1205
- add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
1206
- add_multiple_associated_save_callbacks(reflection.name)
1207
1192
  collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
1208
1193
 
1209
1194
  # Don't use a before_destroy callback since users' before_destroy
1210
1195
  # callbacks will be executed after the association is wiped out.
1211
1196
  old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
1212
1197
  class_eval <<-end_eval unless method_defined?(old_method)
1213
- alias_method :#{old_method}, :destroy_without_callbacks
1214
- def destroy_without_callbacks
1215
- #{reflection.name}.clear
1216
- #{old_method}
1217
- end
1198
+ alias_method :#{old_method}, :destroy_without_callbacks # alias_method :destroy_without_habtm_shim_for_posts, :destroy_without_callbacks
1199
+ def destroy_without_callbacks # def destroy_without_callbacks
1200
+ #{reflection.name}.clear # posts.clear
1201
+ #{old_method} # destroy_without_habtm_shim_for_posts
1202
+ end # end
1218
1203
  end_eval
1219
1204
 
1220
1205
  add_association_callbacks(reflection.name, options)
@@ -1237,33 +1222,30 @@ module ActiveRecord
1237
1222
  end
1238
1223
 
1239
1224
  def association_accessor_methods(reflection, association_proxy_class)
1240
- ivar = "@#{reflection.name}"
1241
-
1242
1225
  define_method(reflection.name) do |*params|
1243
1226
  force_reload = params.first unless params.empty?
1244
-
1245
- association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
1227
+ association = association_instance_get(reflection.name)
1246
1228
 
1247
1229
  if association.nil? || force_reload
1248
1230
  association = association_proxy_class.new(self, reflection)
1249
1231
  retval = association.reload
1250
1232
  if retval.nil? and association_proxy_class == BelongsToAssociation
1251
- instance_variable_set(ivar, nil)
1233
+ association_instance_set(reflection.name, nil)
1252
1234
  return nil
1253
1235
  end
1254
- instance_variable_set(ivar, association)
1236
+ association_instance_set(reflection.name, association)
1255
1237
  end
1256
1238
 
1257
1239
  association.target.nil? ? nil : association
1258
1240
  end
1259
1241
 
1260
1242
  define_method("loaded_#{reflection.name}?") do
1261
- association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
1243
+ association = association_instance_get(reflection.name)
1262
1244
  association && association.loaded?
1263
1245
  end
1264
1246
 
1265
1247
  define_method("#{reflection.name}=") do |new_value|
1266
- association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
1248
+ association = association_instance_get(reflection.name)
1267
1249
 
1268
1250
  if association.nil? || association.target != new_value
1269
1251
  association = association_proxy_class.new(self, reflection)
@@ -1274,7 +1256,7 @@ module ActiveRecord
1274
1256
  self.send(reflection.name, new_value)
1275
1257
  else
1276
1258
  association.replace(new_value)
1277
- instance_variable_set(ivar, new_value.nil? ? nil : association)
1259
+ association_instance_set(reflection.name, new_value.nil? ? nil : association)
1278
1260
  end
1279
1261
  end
1280
1262
 
@@ -1282,20 +1264,18 @@ module ActiveRecord
1282
1264
  return if target.nil? and association_proxy_class == BelongsToAssociation
1283
1265
  association = association_proxy_class.new(self, reflection)
1284
1266
  association.target = target
1285
- instance_variable_set(ivar, association)
1267
+ association_instance_set(reflection.name, association)
1286
1268
  end
1287
1269
  end
1288
1270
 
1289
1271
  def collection_reader_method(reflection, association_proxy_class)
1290
1272
  define_method(reflection.name) do |*params|
1291
- ivar = "@#{reflection.name}"
1292
-
1293
1273
  force_reload = params.first unless params.empty?
1294
- association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
1274
+ association = association_instance_get(reflection.name)
1295
1275
 
1296
- unless association.respond_to?(:loaded?)
1276
+ unless association
1297
1277
  association = association_proxy_class.new(self, reflection)
1298
- instance_variable_set(ivar, association)
1278
+ association_instance_set(reflection.name, association)
1299
1279
  end
1300
1280
 
1301
1281
  association.reload if force_reload
@@ -1330,86 +1310,15 @@ module ActiveRecord
1330
1310
  end
1331
1311
  end
1332
1312
 
1333
- def add_single_associated_validation_callbacks(association_name)
1334
- method_name = "validate_associated_records_for_#{association_name}".to_sym
1335
- define_method(method_name) do
1336
- association = instance_variable_get("@#{association_name}")
1337
- if !association.nil?
1338
- errors.add association_name unless association.target.nil? || association.valid?
1339
- end
1340
- end
1341
-
1342
- validate method_name
1343
- end
1344
-
1345
- def add_multiple_associated_validation_callbacks(association_name)
1346
- method_name = "validate_associated_records_for_#{association_name}".to_sym
1347
- ivar = "@#{association_name}"
1348
-
1349
- define_method(method_name) do
1350
- association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
1351
-
1352
- if association.respond_to?(:loaded?)
1353
- if new_record?
1354
- association
1355
- elsif association.loaded?
1356
- association.select { |record| record.new_record? }
1357
- else
1358
- association.target.select { |record| record.new_record? }
1359
- end.each do |record|
1360
- errors.add association_name unless record.valid?
1361
- end
1362
- end
1363
- end
1364
-
1365
- validate method_name
1366
- end
1367
-
1368
- def add_multiple_associated_save_callbacks(association_name)
1369
- ivar = "@#{association_name}"
1370
-
1371
- method_name = "before_save_associated_records_for_#{association_name}".to_sym
1372
- define_method(method_name) do
1373
- @new_record_before_save = new_record?
1374
- true
1375
- end
1376
- before_save method_name
1377
-
1378
- method_name = "after_create_or_update_associated_records_for_#{association_name}".to_sym
1379
- define_method(method_name) do
1380
- association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
1381
-
1382
- records_to_save = if @new_record_before_save
1383
- association
1384
- elsif association.respond_to?(:loaded?) && association.loaded?
1385
- association.select { |record| record.new_record? }
1386
- elsif association.respond_to?(:loaded?) && !association.loaded?
1387
- association.target.select { |record| record.new_record? }
1388
- else
1389
- []
1390
- end
1391
- records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank?
1392
-
1393
- # reconstruct the SQL queries now that we know the owner's id
1394
- association.send(:construct_sql) if association.respond_to?(:construct_sql)
1395
- end
1396
-
1397
- # Doesn't use after_save as that would save associations added in after_create/after_update twice
1398
- after_create method_name
1399
- after_update method_name
1400
- end
1401
-
1402
1313
  def association_constructor_method(constructor, reflection, association_proxy_class)
1403
1314
  define_method("#{constructor}_#{reflection.name}") do |*params|
1404
- ivar = "@#{reflection.name}"
1405
-
1406
1315
  attributees = params.first unless params.empty?
1407
1316
  replace_existing = params[1].nil? ? true : params[1]
1408
- association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
1317
+ association = association_instance_get(reflection.name)
1409
1318
 
1410
- if association.nil?
1319
+ unless association
1411
1320
  association = association_proxy_class.new(self, reflection)
1412
- instance_variable_set(ivar, association)
1321
+ association_instance_set(reflection.name, association)
1413
1322
  end
1414
1323
 
1415
1324
  if association_proxy_class == HasOneAssociation
@@ -1447,7 +1356,7 @@ module ActiveRecord
1447
1356
  dependent_conditions << sanitize_sql(reflection.options[:conditions]) if reflection.options[:conditions]
1448
1357
  dependent_conditions << extra_conditions if extra_conditions
1449
1358
  dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
1450
-
1359
+ dependent_conditions = dependent_conditions.gsub('@', '\@')
1451
1360
  case reflection.options[:dependent]
1452
1361
  when :destroy
1453
1362
  method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
@@ -1457,22 +1366,22 @@ module ActiveRecord
1457
1366
  before_destroy method_name
1458
1367
  when :delete_all
1459
1368
  module_eval %Q{
1460
- before_destroy do |record|
1461
- delete_all_has_many_dependencies(record,
1462
- "#{reflection.name}",
1463
- #{reflection.class_name},
1464
- "#{dependent_conditions}")
1465
- end
1369
+ before_destroy do |record| # before_destroy do |record|
1370
+ delete_all_has_many_dependencies(record, # delete_all_has_many_dependencies(record,
1371
+ "#{reflection.name}", # "posts",
1372
+ #{reflection.class_name}, # Post,
1373
+ %@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...)
1374
+ end # end
1466
1375
  }
1467
1376
  when :nullify
1468
1377
  module_eval %Q{
1469
- before_destroy do |record|
1470
- nullify_has_many_dependencies(record,
1471
- "#{reflection.name}",
1472
- #{reflection.class_name},
1473
- "#{reflection.primary_key_name}",
1474
- "#{dependent_conditions}")
1475
- end
1378
+ before_destroy do |record| # before_destroy do |record|
1379
+ nullify_has_many_dependencies(record, # nullify_has_many_dependencies(record,
1380
+ "#{reflection.name}", # "posts",
1381
+ #{reflection.class_name}, # Post,
1382
+ "#{reflection.primary_key_name}", # "user_id",
1383
+ %@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...)
1384
+ end # end
1476
1385
  }
1477
1386
  else
1478
1387
  raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})"
@@ -1525,14 +1434,14 @@ module ActiveRecord
1525
1434
  association = send(reflection.name)
1526
1435
  association.destroy unless association.nil?
1527
1436
  end
1528
- before_destroy method_name
1437
+ after_destroy method_name
1529
1438
  when :delete
1530
1439
  method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym
1531
1440
  define_method(method_name) do
1532
1441
  association = send(reflection.name)
1533
1442
  association.delete unless association.nil?
1534
1443
  end
1535
- before_destroy method_name
1444
+ after_destroy method_name
1536
1445
  else
1537
1446
  raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})"
1538
1447
  end
@@ -1551,7 +1460,7 @@ module ActiveRecord
1551
1460
  @@valid_keys_for_has_many_association = [
1552
1461
  :class_name, :table_name, :foreign_key, :primary_key,
1553
1462
  :dependent,
1554
- :select, :conditions, :include, :order, :group, :limit, :offset,
1463
+ :select, :conditions, :include, :order, :group, :having, :limit, :offset,
1555
1464
  :as, :through, :source, :source_type,
1556
1465
  :uniq,
1557
1466
  :finder_sql, :counter_sql,
@@ -1607,7 +1516,7 @@ module ActiveRecord
1607
1516
  mattr_accessor :valid_keys_for_has_and_belongs_to_many_association
1608
1517
  @@valid_keys_for_has_and_belongs_to_many_association = [
1609
1518
  :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
1610
- :select, :conditions, :include, :order, :group, :limit, :offset,
1519
+ :select, :conditions, :include, :order, :group, :having, :limit, :offset,
1611
1520
  :uniq,
1612
1521
  :finder_sql, :counter_sql, :delete_sql, :insert_sql,
1613
1522
  :before_add, :after_add, :before_remove, :after_remove,
@@ -1621,6 +1530,10 @@ module ActiveRecord
1621
1530
  options[:extend] = create_extension_modules(association_id, extension, options[:extend])
1622
1531
 
1623
1532
  reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
1533
+
1534
+ if reflection.association_foreign_key == reflection.primary_key_name
1535
+ raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
1536
+ end
1624
1537
 
1625
1538
  reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
1626
1539
 
@@ -1656,7 +1569,7 @@ module ActiveRecord
1656
1569
  add_conditions!(sql, options[:conditions], scope)
1657
1570
  add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
1658
1571
 
1659
- add_group!(sql, options[:group], scope)
1572
+ add_group!(sql, options[:group], options[:having], scope)
1660
1573
  add_order!(sql, options[:order], scope)
1661
1574
  add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
1662
1575
  add_lock!(sql, options, scope)
@@ -1712,7 +1625,7 @@ module ActiveRecord
1712
1625
  end
1713
1626
 
1714
1627
  add_conditions!(sql, options[:conditions], scope)
1715
- add_group!(sql, options[:group], scope)
1628
+ add_group!(sql, options[:group], options[:having], scope)
1716
1629
 
1717
1630
  if order && is_distinct
1718
1631
  connection.add_order_by_for_association_limiting!(sql, :order => order)
@@ -1725,46 +1638,70 @@ module ActiveRecord
1725
1638
  return sanitize_sql(sql)
1726
1639
  end
1727
1640
 
1641
+ def tables_in_string(string)
1642
+ return [] if string.blank?
1643
+ string.scan(/([\.a-zA-Z_]+).?\./).flatten
1644
+ end
1645
+
1728
1646
  def conditions_tables(options)
1729
1647
  # look in both sets of conditions
1730
1648
  conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
1731
1649
  case cond
1732
1650
  when nil then all
1733
1651
  when Array then all << cond.first
1652
+ when Hash then all << cond.keys
1734
1653
  else all << cond
1735
1654
  end
1736
1655
  end
1737
- conditions.join(' ').scan(/([\.a-zA-Z_]+).?\./).flatten
1656
+ tables_in_string(conditions.join(' '))
1738
1657
  end
1739
1658
 
1740
1659
  def order_tables(options)
1741
1660
  order = [options[:order], scope(:find, :order) ].join(", ")
1742
1661
  return [] unless order && order.is_a?(String)
1743
- order.scan(/([\.a-zA-Z_]+).?\./).flatten
1662
+ tables_in_string(order)
1744
1663
  end
1745
1664
 
1746
1665
  def selects_tables(options)
1747
1666
  select = options[:select]
1748
1667
  return [] unless select && select.is_a?(String)
1749
- select.scan(/"?([\.a-zA-Z_]+)"?.?\./).flatten
1668
+ tables_in_string(select)
1669
+ end
1670
+
1671
+ def joined_tables(options)
1672
+ scope = scope(:find)
1673
+ joins = options[:joins]
1674
+ merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
1675
+ [table_name] + case merged_joins
1676
+ when Symbol, Hash, Array
1677
+ if array_of_strings?(merged_joins)
1678
+ tables_in_string(merged_joins.join(' '))
1679
+ else
1680
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
1681
+ join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact
1682
+ end
1683
+ else
1684
+ tables_in_string(merged_joins)
1685
+ end
1750
1686
  end
1751
1687
 
1752
1688
  # Checks if the conditions reference a table other than the current model table
1753
- def include_eager_conditions?(options, tables = nil)
1754
- ((tables || conditions_tables(options)) - [table_name]).any?
1689
+ def include_eager_conditions?(options, tables = nil, joined_tables = nil)
1690
+ ((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any?
1755
1691
  end
1756
1692
 
1757
1693
  # Checks if the query order references a table other than the current model's table.
1758
- def include_eager_order?(options, tables = nil)
1759
- ((tables || order_tables(options)) - [table_name]).any?
1694
+ def include_eager_order?(options, tables = nil, joined_tables = nil)
1695
+ ((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any?
1760
1696
  end
1761
1697
 
1762
- def include_eager_select?(options)
1763
- (selects_tables(options) - [table_name]).any?
1698
+ def include_eager_select?(options, joined_tables = nil)
1699
+ (selects_tables(options) - (joined_tables || joined_tables(options))).any?
1764
1700
  end
1765
1701
 
1766
1702
  def references_eager_loaded_tables?(options)
1767
- include_eager_order?(options) || include_eager_conditions?(options) || include_eager_select?(options)
1703
+ joined_tables = joined_tables(options)
1704
+ include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables)
1768
1705
  end
1769
1706
 
1770
1707
  def using_limitable_reflections?(reflections)
@@ -1919,9 +1856,10 @@ module ActiveRecord
1919
1856
  def construct(parent, associations, joins, row)
1920
1857
  case associations
1921
1858
  when Symbol, String
1922
- while (join = joins.shift).reflection.name.to_s != associations.to_s
1923
- raise ConfigurationError, "Not Enough Associations" if joins.empty?
1924
- end
1859
+ join = joins.detect{|j| j.reflection.name.to_s == associations.to_s && j.parent_table_name == parent.class.table_name }
1860
+ raise(ConfigurationError, "No such association") if join.nil?
1861
+
1862
+ joins.delete(join)
1925
1863
  construct_association(parent, join, row)
1926
1864
  when Array
1927
1865
  associations.each do |association|
@@ -1929,7 +1867,11 @@ module ActiveRecord
1929
1867
  end
1930
1868
  when Hash
1931
1869
  associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
1932
- association = construct_association(parent, joins.shift, row)
1870
+ join = joins.detect{|j| j.reflection.name.to_s == name.to_s && j.parent_table_name == parent.class.table_name }
1871
+ raise(ConfigurationError, "No such association") if join.nil?
1872
+
1873
+ association = construct_association(parent, join, row)
1874
+ joins.delete(join)
1933
1875
  construct(association, associations[name], joins, row) if association
1934
1876
  end
1935
1877
  else
@@ -2141,7 +2083,7 @@ module ActiveRecord
2141
2083
  aliased_table_name,
2142
2084
  foreign_key,
2143
2085
  parent.aliased_table_name,
2144
- parent.primary_key
2086
+ reflection.options[:primary_key] || parent.primary_key
2145
2087
  ]
2146
2088
  end
2147
2089
  when :belongs_to
@@ -2159,7 +2101,7 @@ module ActiveRecord
2159
2101
  klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record?
2160
2102
 
2161
2103
  [through_reflection, reflection].each do |ref|
2162
- join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))} " if ref && ref.options[:conditions]
2104
+ join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions]
2163
2105
  end
2164
2106
 
2165
2107
  join
@@ -2168,7 +2110,7 @@ module ActiveRecord
2168
2110
  protected
2169
2111
 
2170
2112
  def aliased_table_name_for(name, suffix = nil)
2171
- if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{name.downcase}\son}
2113
+ if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{active_record.connection.quote_table_name name.downcase}\son}
2172
2114
  @join_dependency.table_aliases[name] += 1
2173
2115
  end
2174
2116