activerecord 2.1.0 → 2.1.1

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 (86) hide show
  1. data/CHANGELOG +34 -0
  2. data/README +0 -0
  3. data/Rakefile +6 -5
  4. data/lib/active_record.rb +8 -10
  5. data/lib/active_record/association_preload.rb +17 -12
  6. data/lib/active_record/associations.rb +45 -27
  7. data/lib/active_record/associations/association_collection.rb +8 -5
  8. data/lib/active_record/associations/association_proxy.rb +2 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +0 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +2 -3
  12. data/lib/active_record/associations/has_many_association.rb +16 -4
  13. data/lib/active_record/associations/has_many_through_association.rb +1 -1
  14. data/lib/active_record/associations/has_one_association.rb +2 -2
  15. data/lib/active_record/associations/has_one_through_association.rb +4 -0
  16. data/lib/active_record/base.rb +33 -15
  17. data/lib/active_record/calculations.rb +20 -7
  18. data/lib/active_record/callbacks.rb +0 -0
  19. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -6
  20. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +17 -10
  21. data/lib/active_record/connection_adapters/abstract_adapter.rb +0 -0
  22. data/lib/active_record/connection_adapters/mysql_adapter.rb +53 -24
  23. data/lib/active_record/connection_adapters/postgresql_adapter.rb +66 -20
  24. data/lib/active_record/connection_adapters/sqlite_adapter.rb +12 -0
  25. data/lib/active_record/dirty.rb +10 -3
  26. data/lib/active_record/fixtures.rb +0 -0
  27. data/lib/active_record/locking/optimistic.rb +1 -0
  28. data/lib/active_record/migration.rb +35 -8
  29. data/lib/active_record/named_scope.rb +6 -1
  30. data/lib/active_record/observer.rb +7 -5
  31. data/lib/active_record/test_case.rb +13 -2
  32. data/lib/active_record/validations.rb +19 -9
  33. data/lib/active_record/version.rb +1 -1
  34. data/test/cases/active_schema_test_postgresql.rb +2 -2
  35. data/test/cases/adapter_test.rb +1 -1
  36. data/test/cases/associations/belongs_to_associations_test.rb +19 -0
  37. data/test/cases/associations/cascaded_eager_loading_test.rb +13 -1
  38. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -0
  39. data/test/cases/associations/eager_test.rb +25 -1
  40. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +27 -5
  41. data/test/cases/associations/has_many_associations_test.rb +106 -4
  42. data/test/cases/associations/has_many_through_associations_test.rb +10 -0
  43. data/test/cases/associations/has_one_associations_test.rb +22 -0
  44. data/test/cases/associations/has_one_through_associations_test.rb +44 -5
  45. data/test/cases/associations/join_model_test.rb +7 -0
  46. data/test/cases/associations_test.rb +2 -2
  47. data/test/cases/attribute_methods_test.rb +10 -10
  48. data/test/cases/base_test.rb +39 -16
  49. data/test/cases/calculations_test.rb +53 -1
  50. data/test/cases/column_definition_test.rb +36 -0
  51. data/test/cases/database_statements_test.rb +12 -0
  52. data/test/cases/defaults_test.rb +1 -1
  53. data/test/cases/deprecated_finder_test.rb +0 -0
  54. data/test/cases/dirty_test.rb +94 -0
  55. data/test/cases/finder_test.rb +7 -0
  56. data/test/cases/fixtures_test.rb +0 -0
  57. data/test/cases/helper.rb +5 -5
  58. data/test/cases/inheritance_test.rb +9 -2
  59. data/test/cases/lifecycle_test.rb +54 -1
  60. data/test/cases/locking_test.rb +20 -0
  61. data/test/cases/method_scoping_test.rb +11 -1
  62. data/test/cases/migration_test.rb +147 -22
  63. data/test/cases/multiple_db_test.rb +1 -1
  64. data/test/cases/named_scope_test.rb +50 -1
  65. data/test/cases/query_cache_test.rb +4 -3
  66. data/test/cases/readonly_test.rb +0 -0
  67. data/test/cases/reflection_test.rb +3 -3
  68. data/test/cases/schema_dumper_test.rb +46 -0
  69. data/test/cases/unconnected_test.rb +0 -0
  70. data/test/cases/validations_test.rb +30 -5
  71. data/test/debug.log +358 -0
  72. data/test/fixtures/fixture_database.sqlite3 +0 -0
  73. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  74. data/test/models/author.rb +4 -0
  75. data/test/models/category.rb +1 -0
  76. data/test/models/company.rb +10 -1
  77. data/test/models/developer.rb +4 -1
  78. data/test/models/person.rb +1 -1
  79. data/test/models/post.rb +6 -1
  80. data/test/models/project.rb +1 -1
  81. data/test/models/reply.rb +0 -0
  82. data/test/models/topic.rb +1 -0
  83. data/test/schema/mysql_specific_schema.rb +2 -2
  84. data/test/schema/schema.rb +8 -0
  85. metadata +11 -5
  86. data/lib/active_record/vendor/db2.rb +0 -362
data/CHANGELOG CHANGED
@@ -1,3 +1,37 @@
1
+ *2.1.1 (September 4th, 2008)*
2
+
3
+ * Set config.active_record.timestamped_migrations = false to have migrations with numeric prefix instead of UTC timestamp. #446. [Andrew Stone, Nik Wakelin]
4
+
5
+ * Fixed that create database statements would always include "DEFAULT NULL" (Nick Sieger) [#334]
6
+
7
+ * change_column_default preserves the not-null constraint. #617 [Tarmo Tänav]
8
+
9
+ * Add :tokenizer option to validates_length_of to specify how to split up the attribute string. #507. [David Lowenfels] Example :
10
+
11
+ # Ensure essay contains at least 100 words.
12
+ validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
13
+
14
+ * Always treat integer :limit as byte length. #420 [Tarmo Tänav]
15
+
16
+ * Partial updates don't update lock_version if nothing changed. #426 [Daniel Morrison]
17
+
18
+ * Fix column collision with named_scope and :joins. #46 [Duncan Beevers, Mark Catley]
19
+
20
+ * db:migrate:down and :up update schema_migrations. #369 [Michael Raidel, RaceCondition]
21
+
22
+ * PostgreSQL: support :conditions => [':foo::integer', { :foo => 1 }] without treating the ::integer typecast as a bind variable. [Tarmo Tänav]
23
+
24
+ * MySQL: rename_column preserves column defaults. #466 [Diego Algorta]
25
+
26
+ * Add :from option to calculations. #397 [Ben Munat]
27
+
28
+ * Add :validate option to associations to enable/disable the automatic validation of associated models. Resolves #301. [Jan De Poorter]
29
+
30
+ * PostgreSQL: use 'INSERT ... RETURNING id' for 8.2 and later. [Jeremy Kemper]
31
+
32
+ * Added SQL escaping for :limit and :offset in MySQL [Jonathan Wiess]
33
+
34
+
1
35
  *2.1.0 (May 31st, 2008)*
2
36
 
3
37
  * Add ActiveRecord::Base.sti_name that checks ActiveRecord::Base#store_full_sti_class? and returns either the full or demodulized name. [rick]
data/README CHANGED
File without changes
data/Rakefile CHANGED
@@ -5,6 +5,7 @@ require 'rake/rdoctask'
5
5
  require 'rake/packagetask'
6
6
  require 'rake/gempackagetask'
7
7
  require 'rake/contrib/sshpublisher'
8
+ require 'rake/contrib/rubyforgepublisher'
8
9
 
9
10
  require File.join(File.dirname(__FILE__), 'lib', 'active_record', 'version')
10
11
  require File.expand_path(File.dirname(__FILE__)) + "/test/config"
@@ -141,7 +142,7 @@ Rake::RDocTask.new { |rdoc|
141
142
  rdoc.title = "Active Record -- Object-relation mapping put on rails"
142
143
  rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
143
144
  rdoc.options << '--charset' << 'utf-8'
144
- rdoc.template = "#{ENV['template']}.rb" if ENV['template']
145
+ rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
145
146
  rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
146
147
  rdoc.rdoc_files.include('lib/**/*.rb')
147
148
  rdoc.rdoc_files.exclude('lib/active_record/vendor/*')
@@ -171,7 +172,7 @@ spec = Gem::Specification.new do |s|
171
172
  s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
172
173
  end
173
174
 
174
- s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD)
175
+ s.add_dependency('activesupport', '= 2.1.1' + PKG_BUILD)
175
176
 
176
177
  s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
177
178
  s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
@@ -225,13 +226,13 @@ end
225
226
 
226
227
  desc "Publish the beta gem"
227
228
  task :pgem => [:package] do
228
- Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
229
- `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
229
+ Rake::SshFilePublisher.new("david@greed.loudthinking.com", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
230
+ `ssh david@greed.loudthinking.com '/u/sites/gems/gemupdate.sh'`
230
231
  end
231
232
 
232
233
  desc "Publish the API documentation"
233
234
  task :pdoc => [:rdoc] do
234
- Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ar", "doc").upload
235
+ Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload
235
236
  end
236
237
 
237
238
  desc "Publish the release files to RubyForge."
@@ -24,16 +24,14 @@
24
24
  $:.unshift(File.dirname(__FILE__)) unless
25
25
  $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
26
26
 
27
- unless defined? ActiveSupport
28
- active_support_path = File.dirname(__FILE__) + "/../../activesupport/lib"
29
- if File.exist?(active_support_path)
30
- $:.unshift active_support_path
31
- require 'active_support'
32
- else
33
- require 'rubygems'
34
- gem 'activesupport'
35
- require 'active_support'
36
- end
27
+ active_support_path = File.dirname(__FILE__) + "/../../activesupport/lib"
28
+ if File.exist?(active_support_path)
29
+ $:.unshift active_support_path
30
+ require 'active_support'
31
+ else
32
+ require 'rubygems'
33
+ gem 'activesupport'
34
+ require 'active_support'
37
35
  end
38
36
 
39
37
  require 'active_record/base'
@@ -51,9 +51,7 @@ module ActiveRecord
51
51
 
52
52
  def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
53
53
  parent_records.each do |parent_record|
54
- association_proxy = parent_record.send(reflection_name)
55
- association_proxy.loaded
56
- association_proxy.target = associated_record
54
+ parent_record.send("set_#{reflection_name}_target", associated_record)
57
55
  end
58
56
  end
59
57
 
@@ -103,17 +101,17 @@ module ActiveRecord
103
101
  associated_records = reflection.klass.find(:all, :conditions => [conditions, ids],
104
102
  :include => options[:include],
105
103
  :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}",
106
- :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as _parent_record_id",
104
+ :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
107
105
  :order => options[:order])
108
106
 
109
- set_association_collection_records(id_to_record_map, reflection.name, associated_records, '_parent_record_id')
107
+ set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
110
108
  end
111
109
 
112
110
  def preload_has_one_association(records, reflection, preload_options={})
113
111
  id_to_record_map, ids = construct_id_map(records)
114
112
  options = reflection.options
113
+ records.each {|record| record.send("set_#{reflection.name}_target", nil)}
115
114
  if options[:through]
116
- records.each {|record| record.send(reflection.name) && record.send(reflection.name).loaded}
117
115
  through_records = preload_through_records(records, reflection, options[:through])
118
116
  through_reflection = reflections[options[:through]]
119
117
  through_primary_key = through_reflection.primary_key_name
@@ -126,8 +124,6 @@ module ActiveRecord
126
124
  end
127
125
  end
128
126
  else
129
- records.each {|record| record.send("set_#{reflection.name}_target", nil)}
130
-
131
127
  set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name)
132
128
  end
133
129
  end
@@ -188,7 +184,6 @@ module ActiveRecord
188
184
  through_records
189
185
  end
190
186
 
191
- # FIXME: quoting
192
187
  def preload_belongs_to_association(records, reflection, preload_options={})
193
188
  options = reflection.options
194
189
  primary_key_name = reflection.primary_key_name
@@ -227,9 +222,19 @@ module ActiveRecord
227
222
 
228
223
  table_name = klass.quoted_table_name
229
224
  primary_key = klass.primary_key
230
- conditions = "#{table_name}.#{primary_key} IN (?)"
225
+ conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} IN (?)"
231
226
  conditions << append_conditions(options, preload_options)
232
- associated_records = klass.find(:all, :conditions => [conditions, id_map.keys.uniq],
227
+ column_type = klass.columns.detect{|c| c.name == primary_key}.type
228
+ ids = id_map.keys.uniq.map do |id|
229
+ if column_type == :integer
230
+ id.to_i
231
+ elsif column_type == :float
232
+ id.to_f
233
+ else
234
+ id
235
+ end
236
+ end
237
+ associated_records = klass.find(:all, :conditions => [conditions, ids],
233
238
  :include => options[:include],
234
239
  :select => options[:select],
235
240
  :joins => options[:joins],
@@ -243,7 +248,7 @@ module ActiveRecord
243
248
  table_name = reflection.klass.quoted_table_name
244
249
 
245
250
  if interface = reflection.options[:as]
246
- conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} IN (?) and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.name.demodulize}'"
251
+ conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} IN (?) and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'"
247
252
  else
248
253
  foreign_key = reflection.primary_key_name
249
254
  conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} IN (?)"
@@ -690,6 +690,7 @@ module ActiveRecord
690
690
  # association is a polymorphic +belongs_to+.
691
691
  # * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
692
692
  # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
693
+ # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. true by default.
693
694
  #
694
695
  # Option examples:
695
696
  # has_many :comments, :order => "posted_on"
@@ -710,6 +711,7 @@ module ActiveRecord
710
711
 
711
712
  configure_dependency_for_has_many(reflection)
712
713
 
714
+ add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
713
715
  add_multiple_associated_save_callbacks(reflection.name)
714
716
  add_association_callbacks(reflection.name, reflection.options)
715
717
 
@@ -769,6 +771,7 @@ module ActiveRecord
769
771
  # * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
770
772
  # association is a polymorphic +belongs_to+.
771
773
  # * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
774
+ # * <tt>:validate</tt> - If false, don't validate the associated object when saving the parent object. +false+ by default.
772
775
  #
773
776
  # Option examples:
774
777
  # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
@@ -799,7 +802,7 @@ module ActiveRecord
799
802
  end
800
803
  after_save method_name
801
804
 
802
- add_single_associated_save_callbacks(reflection.name)
805
+ add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
803
806
  association_accessor_methods(reflection, HasOneAssociation)
804
807
  association_constructor_method(:build, reflection, HasOneAssociation)
805
808
  association_constructor_method(:create, reflection, HasOneAssociation)
@@ -857,6 +860,7 @@ module ActiveRecord
857
860
  # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
858
861
  # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
859
862
  # * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
863
+ # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +false+ by default.
860
864
  #
861
865
  # Option examples:
862
866
  # belongs_to :firm, :foreign_key => "client_of"
@@ -937,6 +941,8 @@ module ActiveRecord
937
941
  )
938
942
  end
939
943
 
944
+ add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
945
+
940
946
  configure_dependency_for_belongs_to(reflection)
941
947
  end
942
948
 
@@ -1025,6 +1031,7 @@ module ActiveRecord
1025
1031
  # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
1026
1032
  # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
1027
1033
  # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
1034
+ # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +true+ by default.
1028
1035
  #
1029
1036
  # Option examples:
1030
1037
  # has_and_belongs_to_many :projects
@@ -1037,6 +1044,7 @@ module ActiveRecord
1037
1044
  def has_and_belongs_to_many(association_id, options = {}, &extension)
1038
1045
  reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
1039
1046
 
1047
+ add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
1040
1048
  add_multiple_associated_save_callbacks(reflection.name)
1041
1049
  collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
1042
1050
 
@@ -1103,10 +1111,9 @@ module ActiveRecord
1103
1111
  association.create_through_record(new_value)
1104
1112
  self.send(reflection.name, new_value)
1105
1113
  else
1106
- association.replace(new_value)
1114
+ association.replace(new_value)
1115
+ instance_variable_set(ivar, new_value.nil? ? nil : association)
1107
1116
  end
1108
-
1109
- instance_variable_set(ivar, new_value.nil? ? nil : association)
1110
1117
  end
1111
1118
 
1112
1119
  define_method("set_#{reflection.name}_target") do |target|
@@ -1157,7 +1164,7 @@ module ActiveRecord
1157
1164
  end
1158
1165
  end
1159
1166
 
1160
- def add_single_associated_save_callbacks(association_name)
1167
+ def add_single_associated_validation_callbacks(association_name)
1161
1168
  method_name = "validate_associated_records_for_#{association_name}".to_sym
1162
1169
  define_method(method_name) do
1163
1170
  association = instance_variable_get("@#{association_name}")
@@ -1169,7 +1176,7 @@ module ActiveRecord
1169
1176
  validate method_name
1170
1177
  end
1171
1178
 
1172
- def add_multiple_associated_save_callbacks(association_name)
1179
+ def add_multiple_associated_validation_callbacks(association_name)
1173
1180
  method_name = "validate_associated_records_for_#{association_name}".to_sym
1174
1181
  ivar = "@#{association_name}"
1175
1182
 
@@ -1190,6 +1197,10 @@ module ActiveRecord
1190
1197
  end
1191
1198
 
1192
1199
  validate method_name
1200
+ end
1201
+
1202
+ def add_multiple_associated_save_callbacks(association_name)
1203
+ ivar = "@#{association_name}"
1193
1204
 
1194
1205
  method_name = "before_save_associated_records_for_#{association_name}".to_sym
1195
1206
  define_method(method_name) do
@@ -1211,7 +1222,6 @@ module ActiveRecord
1211
1222
  else
1212
1223
  []
1213
1224
  end
1214
-
1215
1225
  records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank?
1216
1226
 
1217
1227
  # reconstruct the SQL queries now that we know the owner's id
@@ -1343,7 +1353,8 @@ module ActiveRecord
1343
1353
  :uniq,
1344
1354
  :finder_sql, :counter_sql,
1345
1355
  :before_add, :after_add, :before_remove, :after_remove,
1346
- :extend, :readonly
1356
+ :extend, :readonly,
1357
+ :validate
1347
1358
  )
1348
1359
 
1349
1360
  options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1353,7 +1364,7 @@ module ActiveRecord
1353
1364
 
1354
1365
  def create_has_one_reflection(association_id, options)
1355
1366
  options.assert_valid_keys(
1356
- :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly
1367
+ :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate
1357
1368
  )
1358
1369
 
1359
1370
  create_reflection(:has_one, association_id, options, self)
@@ -1361,7 +1372,7 @@ module ActiveRecord
1361
1372
 
1362
1373
  def create_has_one_through_reflection(association_id, options)
1363
1374
  options.assert_valid_keys(
1364
- :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type
1375
+ :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate
1365
1376
  )
1366
1377
  create_reflection(:has_one, association_id, options, self)
1367
1378
  end
@@ -1369,7 +1380,7 @@ module ActiveRecord
1369
1380
  def create_belongs_to_reflection(association_id, options)
1370
1381
  options.assert_valid_keys(
1371
1382
  :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
1372
- :counter_cache, :extend, :polymorphic, :readonly
1383
+ :counter_cache, :extend, :polymorphic, :readonly, :validate
1373
1384
  )
1374
1385
 
1375
1386
  reflection = create_reflection(:belongs_to, association_id, options, self)
@@ -1388,7 +1399,8 @@ module ActiveRecord
1388
1399
  :uniq,
1389
1400
  :finder_sql, :delete_sql, :insert_sql,
1390
1401
  :before_add, :after_add, :before_remove, :after_remove,
1391
- :extend, :readonly
1402
+ :extend, :readonly,
1403
+ :validate
1392
1404
  )
1393
1405
 
1394
1406
  options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1465,10 +1477,15 @@ module ActiveRecord
1465
1477
  join_dependency.joins_for_table_name(table)
1466
1478
  }.flatten.compact.uniq
1467
1479
 
1480
+ order = options[:order]
1481
+ if scoped_order = (scope && scope[:order])
1482
+ order = order ? "#{order}, #{scoped_order}" : scoped_order
1483
+ end
1484
+
1468
1485
  is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
1469
1486
  sql = "SELECT "
1470
1487
  if is_distinct
1471
- sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order])
1488
+ sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", order)
1472
1489
  else
1473
1490
  sql << primary_key
1474
1491
  end
@@ -1482,8 +1499,8 @@ module ActiveRecord
1482
1499
  add_conditions!(sql, options[:conditions], scope)
1483
1500
  add_group!(sql, options[:group], scope)
1484
1501
 
1485
- if options[:order] && is_distinct
1486
- connection.add_order_by_for_association_limiting!(sql, options)
1502
+ if order && is_distinct
1503
+ connection.add_order_by_for_association_limiting!(sql, :order => order)
1487
1504
  else
1488
1505
  add_order!(sql, options[:order], scope)
1489
1506
  end
@@ -1502,19 +1519,19 @@ module ActiveRecord
1502
1519
  else all << cond
1503
1520
  end
1504
1521
  end
1505
- conditions.join(' ').scan(/([\.\w]+).?\./).flatten
1522
+ conditions.join(' ').scan(/([\.a-zA-Z_]+).?\./).flatten
1506
1523
  end
1507
1524
 
1508
1525
  def order_tables(options)
1509
- order = options[:order]
1526
+ order = [options[:order], scope(:find, :order) ].join(", ")
1510
1527
  return [] unless order && order.is_a?(String)
1511
- order.scan(/([\.\w]+).?\./).flatten
1528
+ order.scan(/([\.a-zA-Z_]+).?\./).flatten
1512
1529
  end
1513
1530
 
1514
1531
  def selects_tables(options)
1515
1532
  select = options[:select]
1516
1533
  return [] unless select && select.is_a?(String)
1517
- select.scan(/"?([\.\w]+)"?.?\./).flatten
1534
+ select.scan(/"?([\.a-zA-Z_]+)"?.?\./).flatten
1518
1535
  end
1519
1536
 
1520
1537
  # Checks if the conditions reference a table other than the current model table
@@ -1638,7 +1655,9 @@ module ActiveRecord
1638
1655
  end
1639
1656
 
1640
1657
  def join_for_table_name(table_name)
1641
- @joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil
1658
+ join = (@joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first) rescue nil
1659
+ return join unless join.nil?
1660
+ @joins.select{|j|j.is_a?(JoinAssociation) && j.aliased_join_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil
1642
1661
  end
1643
1662
 
1644
1663
  def joins_for_table_name(table_name)
@@ -1714,6 +1733,7 @@ module ActiveRecord
1714
1733
  collection.target.push(association)
1715
1734
  when :has_one
1716
1735
  return if record.id.to_s != join.parent.record_id(row).to_s
1736
+ return if record.instance_variable_defined?("@#{join.reflection.name}")
1717
1737
  association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
1718
1738
  record.send("set_#{join.reflection.name}_target", association)
1719
1739
  when :belongs_to
@@ -1795,7 +1815,7 @@ module ActiveRecord
1795
1815
  @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
1796
1816
  end
1797
1817
 
1798
- if reflection.macro == :has_many && reflection.options[:through]
1818
+ if [:has_many, :has_one].include?(reflection.macro) && reflection.options[:through]
1799
1819
  @aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
1800
1820
  end
1801
1821
  end
@@ -1819,7 +1839,7 @@ module ActiveRecord
1819
1839
  ]
1820
1840
  when :has_many, :has_one
1821
1841
  case
1822
- when reflection.macro == :has_many && reflection.options[:through]
1842
+ when reflection.options[:through]
1823
1843
  through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
1824
1844
 
1825
1845
  jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
@@ -1855,7 +1875,7 @@ module ActiveRecord
1855
1875
  jt_sti_extra = " AND %s.%s = %s" % [
1856
1876
  connection.quote_table_name(aliased_join_table_name),
1857
1877
  connection.quote_column_name(through_reflection.active_record.inheritance_column),
1858
- through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
1878
+ through_reflection.klass.quote_value(through_reflection.klass.sti_name)]
1859
1879
  end
1860
1880
  when :belongs_to
1861
1881
  first_key = primary_key
@@ -1920,10 +1940,8 @@ module ActiveRecord
1920
1940
  else
1921
1941
  ""
1922
1942
  end || ''
1923
- join << %(AND %s.%s = %s ) % [
1924
- connection.quote_table_name(aliased_table_name),
1925
- connection.quote_column_name(klass.inheritance_column),
1926
- klass.quote_value(klass.name.demodulize)] unless klass.descends_from_active_record?
1943
+ join << %(AND %s) % [
1944
+ klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record?
1927
1945
 
1928
1946
  [through_reflection, reflection].each do |ref|
1929
1947
  join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions]
@@ -78,11 +78,14 @@ module ActiveRecord
78
78
  @loaded = false
79
79
  end
80
80
 
81
- def build(attributes = {})
81
+ def build(attributes = {}, &block)
82
82
  if attributes.is_a?(Array)
83
- attributes.collect { |attr| build(attr) }
83
+ attributes.collect { |attr| build(attr, &block) }
84
84
  else
85
- build_record(attributes) { |record| set_belongs_to_association_for(record) }
85
+ build_record(attributes) do |record|
86
+ block.call(record) if block_given?
87
+ set_belongs_to_association_for(record)
88
+ end
86
89
  end
87
90
  end
88
91
 
@@ -187,7 +190,7 @@ module ActiveRecord
187
190
  if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
188
191
  @target.size
189
192
  elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
190
- unsaved_records = Array(@target.detect { |r| r.new_record? })
193
+ unsaved_records = @target.select { |r| r.new_record? }
191
194
  unsaved_records.size + count_records
192
195
  else
193
196
  count_records
@@ -335,7 +338,7 @@ module ActiveRecord
335
338
  callback(:before_add, record)
336
339
  yield(record) if block_given?
337
340
  @target ||= [] unless loaded?
338
- @target << record
341
+ @target << record unless @reflection.options[:uniq] && @target.include?(record)
339
342
  callback(:after_add, record)
340
343
  record
341
344
  end