activerecord 3.1.11 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. data/CHANGELOG.md +6294 -97
  2. data/README.rdoc +2 -2
  3. data/examples/performance.rb +55 -31
  4. data/lib/active_record/aggregations.rb +2 -2
  5. data/lib/active_record/associations/association.rb +2 -42
  6. data/lib/active_record/associations/association_scope.rb +3 -30
  7. data/lib/active_record/associations/builder/association.rb +6 -4
  8. data/lib/active_record/associations/builder/belongs_to.rb +3 -3
  9. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  10. data/lib/active_record/associations/builder/has_many.rb +4 -4
  11. data/lib/active_record/associations/builder/has_one.rb +5 -6
  12. data/lib/active_record/associations/builder/singular_association.rb +3 -16
  13. data/lib/active_record/associations/collection_association.rb +55 -28
  14. data/lib/active_record/associations/collection_proxy.rb +1 -35
  15. data/lib/active_record/associations/has_many_association.rb +5 -1
  16. data/lib/active_record/associations/has_many_through_association.rb +11 -8
  17. data/lib/active_record/associations/join_dependency.rb +1 -1
  18. data/lib/active_record/associations/preloader/association.rb +3 -1
  19. data/lib/active_record/associations.rb +82 -69
  20. data/lib/active_record/attribute_assignment.rb +221 -0
  21. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +3 -3
  23. data/lib/active_record/attribute_methods/primary_key.rb +62 -25
  24. data/lib/active_record/attribute_methods/read.rb +72 -83
  25. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
  27. data/lib/active_record/attribute_methods/write.rb +27 -5
  28. data/lib/active_record/attribute_methods.rb +209 -30
  29. data/lib/active_record/autosave_association.rb +23 -8
  30. data/lib/active_record/base.rb +217 -1709
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
  33. data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
  34. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  35. data/lib/active_record/connection_adapters/abstract/quoting.rb +9 -12
  36. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
  37. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +43 -22
  38. data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
  39. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  40. data/lib/active_record/connection_adapters/column.rb +2 -2
  41. data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
  42. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
  43. data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
  44. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  45. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
  46. data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
  47. data/lib/active_record/counter_cache.rb +4 -3
  48. data/lib/active_record/dynamic_matchers.rb +79 -0
  49. data/lib/active_record/errors.rb +11 -1
  50. data/lib/active_record/explain.rb +83 -0
  51. data/lib/active_record/explain_subscriber.rb +21 -0
  52. data/lib/active_record/fixtures/file.rb +65 -0
  53. data/lib/active_record/fixtures.rb +31 -76
  54. data/lib/active_record/identity_map.rb +4 -11
  55. data/lib/active_record/inheritance.rb +167 -0
  56. data/lib/active_record/integration.rb +49 -0
  57. data/lib/active_record/locking/optimistic.rb +30 -25
  58. data/lib/active_record/locking/pessimistic.rb +23 -1
  59. data/lib/active_record/log_subscriber.rb +3 -3
  60. data/lib/active_record/migration/command_recorder.rb +8 -8
  61. data/lib/active_record/migration.rb +47 -30
  62. data/lib/active_record/model_schema.rb +366 -0
  63. data/lib/active_record/nested_attributes.rb +3 -2
  64. data/lib/active_record/persistence.rb +51 -9
  65. data/lib/active_record/querying.rb +58 -0
  66. data/lib/active_record/railtie.rb +24 -28
  67. data/lib/active_record/railties/controller_runtime.rb +3 -1
  68. data/lib/active_record/railties/databases.rake +134 -77
  69. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  70. data/lib/active_record/readonly_attributes.rb +26 -0
  71. data/lib/active_record/reflection.rb +7 -15
  72. data/lib/active_record/relation/batches.rb +5 -2
  73. data/lib/active_record/relation/calculations.rb +27 -6
  74. data/lib/active_record/relation/delegation.rb +49 -0
  75. data/lib/active_record/relation/finder_methods.rb +6 -5
  76. data/lib/active_record/relation/predicate_builder.rb +12 -19
  77. data/lib/active_record/relation/query_methods.rb +76 -10
  78. data/lib/active_record/relation/spawn_methods.rb +11 -2
  79. data/lib/active_record/relation.rb +77 -34
  80. data/lib/active_record/result.rb +1 -1
  81. data/lib/active_record/sanitization.rb +194 -0
  82. data/lib/active_record/schema_dumper.rb +5 -2
  83. data/lib/active_record/scoping/default.rb +142 -0
  84. data/lib/active_record/scoping/named.rb +202 -0
  85. data/lib/active_record/scoping.rb +152 -0
  86. data/lib/active_record/serialization.rb +1 -43
  87. data/lib/active_record/serializers/xml_serializer.rb +2 -44
  88. data/lib/active_record/session_store.rb +15 -15
  89. data/lib/active_record/store.rb +50 -0
  90. data/lib/active_record/test_case.rb +11 -7
  91. data/lib/active_record/timestamp.rb +16 -3
  92. data/lib/active_record/transactions.rb +5 -5
  93. data/lib/active_record/translation.rb +22 -0
  94. data/lib/active_record/validations/associated.rb +5 -4
  95. data/lib/active_record/validations/uniqueness.rb +4 -4
  96. data/lib/active_record/validations.rb +1 -1
  97. data/lib/active_record/version.rb +2 -2
  98. data/lib/active_record.rb +28 -2
  99. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  100. data/lib/rails/generators/active_record/migration/templates/migration.rb +9 -3
  101. data/lib/rails/generators/active_record/model/model_generator.rb +5 -1
  102. data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
  103. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
  104. metadata +50 -40
  105. checksums.yaml +0 -7
  106. data/lib/active_record/named_scope.rb +0 -200
@@ -16,21 +16,16 @@ module ActiveRecord
16
16
 
17
17
  module ClassMethods
18
18
  protected
19
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
20
- # This enhanced read method automatically converts the UTC time stored in the database to the time
19
+ # The enhanced read method automatically converts the UTC time stored in the database to the time
21
20
  # zone stored in Time.zone.
22
- def define_method_attribute(attr_name)
23
- if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
24
- method_body, line = <<-EOV, __LINE__ + 1
25
- def _#{attr_name}
26
- cached = @attributes_cache['#{attr_name}']
27
- return cached if cached
28
- time = _read_attribute('#{attr_name}')
29
- @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
30
- end
31
- alias #{attr_name} _#{attr_name}
32
- EOV
33
- generated_attribute_methods.module_eval(method_body, __FILE__, line)
21
+ def attribute_cast_code(attr_name)
22
+ column = columns_hash[attr_name]
23
+
24
+ if create_time_zone_conversion_attribute?(attr_name, column)
25
+ typecast = "v = #{super}"
26
+ time_zone_conversion = "v.acts_like?(:time) ? v.in_time_zone : v"
27
+
28
+ "((#{typecast}) && (#{time_zone_conversion}))"
34
29
  else
35
30
  super
36
31
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  module ClassMethods
11
11
  protected
12
12
  def define_method_attribute=(attr_name)
13
- if attr_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
13
+ if attr_name =~ ActiveModel::AttributeMethods::NAME_COMPILABLE_REGEXP
14
14
  generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
15
15
  else
16
16
  generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
@@ -24,12 +24,14 @@ module ActiveRecord
24
24
  # for fixnum and float columns are turned into +nil+.
25
25
  def write_attribute(attr_name, value)
26
26
  attr_name = attr_name.to_s
27
- attr_name = self.class.primary_key if attr_name == 'id'
27
+ attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key
28
28
  @attributes_cache.delete(attr_name)
29
- if (column = column_for_attribute(attr_name)) && column.number?
30
- @attributes[attr_name] = convert_number_column_value(value)
29
+ column = column_for_attribute(attr_name)
30
+
31
+ if column || @attributes.has_key?(attr_name)
32
+ @attributes[attr_name] = type_cast_attribute_for_write(column, value)
31
33
  else
32
- @attributes[attr_name] = value
34
+ raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
33
35
  end
34
36
  end
35
37
  alias_method :raw_write_attribute, :write_attribute
@@ -39,6 +41,26 @@ module ActiveRecord
39
41
  def attribute=(attribute_name, value)
40
42
  write_attribute(attribute_name, value)
41
43
  end
44
+
45
+ def type_cast_attribute_for_write(column, value)
46
+ if column && column.number?
47
+ convert_number_column_value(value)
48
+ else
49
+ value
50
+ end
51
+ end
52
+
53
+ def convert_number_column_value(value)
54
+ if value == false
55
+ 0
56
+ elsif value == true
57
+ 1
58
+ elsif value.is_a?(String) && value.blank?
59
+ nil
60
+ else
61
+ value
62
+ end
63
+ end
42
64
  end
43
65
  end
44
66
  end
@@ -1,4 +1,6 @@
1
1
  require 'active_support/core_ext/enumerable'
2
+ require 'active_support/deprecation'
3
+ require 'thread'
2
4
 
3
5
  module ActiveRecord
4
6
  # = Active Record Attribute Methods
@@ -6,69 +8,246 @@ module ActiveRecord
6
8
  extend ActiveSupport::Concern
7
9
  include ActiveModel::AttributeMethods
8
10
 
11
+ included do
12
+ include Read
13
+ include Write
14
+ include BeforeTypeCast
15
+ include Query
16
+ include PrimaryKey
17
+ include TimeZoneConversion
18
+ include Dirty
19
+ include Serialization
20
+ include DeprecatedUnderscoreRead
21
+
22
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
23
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
24
+ # (Alias for the protected read_attribute method).
25
+ def [](attr_name)
26
+ read_attribute(attr_name)
27
+ end
28
+
29
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
30
+ # (Alias for the protected write_attribute method).
31
+ def []=(attr_name, value)
32
+ write_attribute(attr_name, value)
33
+ end
34
+ end
35
+
9
36
  module ClassMethods
10
37
  # Generates all the attribute related methods for columns in the database
11
38
  # accessors, mutators and query methods.
12
39
  def define_attribute_methods
13
- return if attribute_methods_generated?
14
- super(column_names)
15
- @attribute_methods_generated = true
40
+ # Use a mutex; we don't want two thread simaltaneously trying to define
41
+ # attribute methods.
42
+ @attribute_methods_mutex ||= Mutex.new
43
+
44
+ @attribute_methods_mutex.synchronize do
45
+ return if attribute_methods_generated?
46
+ superclass.define_attribute_methods unless self == base_class
47
+ super(column_names)
48
+ @attribute_methods_generated = true
49
+ end
16
50
  end
17
51
 
18
52
  def attribute_methods_generated?
19
53
  @attribute_methods_generated ||= false
20
54
  end
21
55
 
22
- def undefine_attribute_methods(*args)
56
+ # We will define the methods as instance methods, but will call them as singleton
57
+ # methods. This allows us to use method_defined? to check if the method exists,
58
+ # which is fast and won't give any false positives from the ancestors (because
59
+ # there are no ancestors).
60
+ def generated_external_attribute_methods
61
+ @generated_external_attribute_methods ||= Module.new { extend self }
62
+ end
63
+
64
+ def undefine_attribute_methods
23
65
  super
24
66
  @attribute_methods_generated = false
25
67
  end
26
68
 
27
- # Checks whether the method is defined in the model or any of its subclasses
28
- # that also derive from Active Record. Raises DangerousAttributeError if the
29
- # method is defined by Active Record though.
30
69
  def instance_method_already_implemented?(method_name)
31
- method_name = method_name.to_s
32
- index = ancestors.index(ActiveRecord::Base) || ancestors.length
33
- @_defined_class_methods ||= ancestors.first(index).map { |m|
34
- m.instance_methods(false) | m.private_instance_methods(false)
35
- }.flatten.map {|m| m.to_s }.to_set
70
+ if dangerous_attribute_method?(method_name)
71
+ raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
72
+ end
73
+
74
+ if superclass == Base
75
+ super
76
+ else
77
+ # If B < A and A defines its own attribute method, then we don't want to overwrite that.
78
+ defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
79
+ defined && !ActiveRecord::Base.method_defined?(method_name) || super
80
+ end
81
+ end
82
+
83
+ # A method name is 'dangerous' if it is already defined by Active Record, but
84
+ # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
85
+ def dangerous_attribute_method?(name)
86
+ method_defined_within?(name, Base)
87
+ end
36
88
 
37
- @@_defined_activerecord_methods ||= defined_activerecord_methods
38
- raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
39
- @_defined_class_methods.include?(method_name)
89
+ def method_defined_within?(name, klass, sup = klass.superclass)
90
+ if klass.method_defined?(name) || klass.private_method_defined?(name)
91
+ if sup.method_defined?(name) || sup.private_method_defined?(name)
92
+ klass.instance_method(name).owner != sup.instance_method(name).owner
93
+ else
94
+ true
95
+ end
96
+ else
97
+ false
98
+ end
40
99
  end
41
100
 
42
- def defined_activerecord_methods
43
- active_record = ActiveRecord::Base
44
- super_klass = ActiveRecord::Base.superclass
45
- methods = (active_record.instance_methods - super_klass.instance_methods) +
46
- (active_record.private_instance_methods - super_klass.private_instance_methods)
47
- methods.map {|m| m.to_s }.to_set
101
+ def attribute_method?(attribute)
102
+ super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
103
+ end
104
+
105
+ # Returns an array of column names as strings if it's not
106
+ # an abstract class and table exists.
107
+ # Otherwise it returns an empty array.
108
+ def attribute_names
109
+ @attribute_names ||= if !abstract_class? && table_exists?
110
+ column_names
111
+ else
112
+ []
113
+ end
48
114
  end
49
115
  end
50
116
 
51
- def method_missing(method_id, *args, &block)
52
- # If we haven't generated any methods yet, generate them, then
53
- # see if we've created the method we're looking for.
54
- if !self.class.attribute_methods_generated?
117
+ # If we haven't generated any methods yet, generate them, then
118
+ # see if we've created the method we're looking for.
119
+ def method_missing(method, *args, &block)
120
+ unless self.class.attribute_methods_generated?
55
121
  self.class.define_attribute_methods
56
- method_name = method_id.to_s
57
- guard_private_attribute_method!(method_name, args)
58
- send(method_id, *args, &block)
122
+
123
+ if respond_to_without_attributes?(method)
124
+ send(method, *args, &block)
125
+ else
126
+ super
127
+ end
59
128
  else
60
129
  super
61
130
  end
62
131
  end
63
132
 
133
+ def attribute_missing(match, *args, &block)
134
+ if self.class.columns_hash[match.attr_name]
135
+ ActiveSupport::Deprecation.warn(
136
+ "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \
137
+ "dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \
138
+ "is a column of the table. If this error has happened through normal usage of Active " \
139
+ "Record (rather than through your own code or external libraries), please report it as " \
140
+ "a bug."
141
+ )
142
+ end
143
+
144
+ super
145
+ end
146
+
64
147
  def respond_to?(name, include_private = false)
65
148
  self.class.define_attribute_methods unless self.class.attribute_methods_generated?
66
149
  super
67
150
  end
68
151
 
152
+ # Returns true if the given attribute is in the attributes hash
153
+ def has_attribute?(attr_name)
154
+ @attributes.has_key?(attr_name.to_s)
155
+ end
156
+
157
+ # Returns an array of names for the attributes available on this object.
158
+ def attribute_names
159
+ @attributes.keys
160
+ end
161
+
162
+ # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
163
+ def attributes
164
+ Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
165
+ end
166
+
167
+ # Returns an <tt>#inspect</tt>-like string for the value of the
168
+ # attribute +attr_name+. String attributes are truncated upto 50
169
+ # characters, and Date and Time attributes are returned in the
170
+ # <tt>:db</tt> format. Other attributes return the value of
171
+ # <tt>#inspect</tt> without modification.
172
+ #
173
+ # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
174
+ #
175
+ # person.attribute_for_inspect(:name)
176
+ # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
177
+ #
178
+ # person.attribute_for_inspect(:created_at)
179
+ # # => '"2009-01-12 04:48:57"'
180
+ def attribute_for_inspect(attr_name)
181
+ value = read_attribute(attr_name)
182
+
183
+ if value.is_a?(String) && value.length > 50
184
+ "#{value[0..50]}...".inspect
185
+ elsif value.is_a?(Date) || value.is_a?(Time)
186
+ %("#{value.to_s(:db)}")
187
+ else
188
+ value.inspect
189
+ end
190
+ end
191
+
192
+ # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
193
+ # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
194
+ def attribute_present?(attribute)
195
+ value = read_attribute(attribute)
196
+ !value.nil? || (value.respond_to?(:empty?) && !value.empty?)
197
+ end
198
+
199
+ # Returns the column object for the named attribute.
200
+ def column_for_attribute(name)
201
+ self.class.columns_hash[name.to_s]
202
+ end
203
+
69
204
  protected
70
- def attribute_method?(attr_name)
71
- attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
205
+
206
+ def clone_attributes(reader_method = :read_attribute, attributes = {})
207
+ attribute_names.each do |name|
208
+ attributes[name] = clone_attribute_value(reader_method, name)
72
209
  end
210
+ attributes
211
+ end
212
+
213
+ def clone_attribute_value(reader_method, attribute_name)
214
+ value = send(reader_method, attribute_name)
215
+ value.duplicable? ? value.clone : value
216
+ rescue TypeError, NoMethodError
217
+ value
218
+ end
219
+
220
+ # Returns a copy of the attributes hash where all the values have been safely quoted for use in
221
+ # an Arel insert/update method.
222
+ def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
223
+ attrs = {}
224
+ klass = self.class
225
+ arel_table = klass.arel_table
226
+
227
+ attribute_names.each do |name|
228
+ if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
229
+
230
+ if include_readonly_attributes || !self.class.readonly_attributes.include?(name)
231
+
232
+ value = if klass.serialized_attributes.include?(name)
233
+ @attributes[name].serialized_value
234
+ else
235
+ # FIXME: we need @attributes to be used consistently.
236
+ # If the values stored in @attributes were already type
237
+ # casted, this code could be simplified
238
+ read_attribute(name)
239
+ end
240
+
241
+ attrs[arel_table[name]] = value
242
+ end
243
+ end
244
+ end
245
+
246
+ attrs
247
+ end
248
+
249
+ def attribute_method?(attr_name)
250
+ attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
251
+ end
73
252
  end
74
253
  end
@@ -21,6 +21,21 @@ module ActiveRecord
21
21
  # Note that <tt>:autosave => false</tt> is not same as not declaring <tt>:autosave</tt>.
22
22
  # When the <tt>:autosave</tt> option is not present new associations are saved.
23
23
  #
24
+ # == Validation
25
+ #
26
+ # Children records are validated unless <tt>:validate</tt> is +false+.
27
+ #
28
+ # == Callbacks
29
+ #
30
+ # Association with autosave option defines several callbacks on your
31
+ # model (before_save, after_create, after_update). Please note that
32
+ # callbacks are executed in the order they were defined in
33
+ # model. You should avoid modyfing the association content, before
34
+ # autosave callbacks are executed. Placing your callbacks after
35
+ # associations is usually a good practice.
36
+ #
37
+ # == Examples
38
+ #
24
39
  # === One-to-one Example
25
40
  #
26
41
  # class Post
@@ -109,10 +124,7 @@ module ActiveRecord
109
124
  # Now it _is_ removed from the database:
110
125
  #
111
126
  # Comment.find_by_id(id).nil? # => true
112
- #
113
- # === Validation
114
- #
115
- # Children records are validated unless <tt>:validate</tt> is +false+.
127
+
116
128
  module AutosaveAssociation
117
129
  extend ActiveSupport::Concern
118
130
 
@@ -161,7 +173,7 @@ module ActiveRecord
161
173
  #
162
174
  # For performance reasons, we don't check whether to validate at runtime.
163
175
  # However the validation and callback methods are lazy and those methods
164
- # get created when they are invoked for the very first time. However,
176
+ # get created when they are invoked for the very first time. However,
165
177
  # this can change, for instance, when using nested attributes, which is
166
178
  # called _after_ the association has been defined. Since we don't want
167
179
  # the callbacks to get defined multiple times, there are guards that
@@ -264,7 +276,7 @@ module ActiveRecord
264
276
  # turned on for the association.
265
277
  def validate_single_association(reflection)
266
278
  association = association_instance_get(reflection.name)
267
- record = association && association.target
279
+ record = association && association.reader
268
280
  association_valid?(reflection, record) if record
269
281
  end
270
282
 
@@ -331,7 +343,7 @@ module ActiveRecord
331
343
  if autosave
332
344
  saved = association.insert_record(record, false)
333
345
  else
334
- association.insert_record(record)
346
+ association.insert_record(record) unless reflection.nested?
335
347
  end
336
348
  elsif autosave
337
349
  saved = record.save(:validate => false)
@@ -370,7 +382,10 @@ module ActiveRecord
370
382
  else
371
383
  key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
372
384
  if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave)
373
- record[reflection.foreign_key] = key
385
+ unless reflection.through_reflection
386
+ record[reflection.foreign_key] = key
387
+ end
388
+
374
389
  saved = record.save(:validate => !autosave)
375
390
  raise ActiveRecord::Rollback if !saved && autosave
376
391
  saved