activerecord 2.1.2 → 2.2.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 (110) hide show
  1. data/CHANGELOG +32 -6
  2. data/README +0 -0
  3. data/Rakefile +4 -5
  4. data/lib/active_record.rb +11 -10
  5. data/lib/active_record/aggregations.rb +110 -38
  6. data/lib/active_record/association_preload.rb +104 -15
  7. data/lib/active_record/associations.rb +427 -212
  8. data/lib/active_record/associations/association_collection.rb +101 -16
  9. data/lib/active_record/associations/association_proxy.rb +65 -13
  10. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -0
  12. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +13 -3
  13. data/lib/active_record/associations/has_many_association.rb +28 -28
  14. data/lib/active_record/associations/has_many_through_association.rb +21 -19
  15. data/lib/active_record/associations/has_one_association.rb +24 -7
  16. data/lib/active_record/associations/has_one_through_association.rb +3 -4
  17. data/lib/active_record/attribute_methods.rb +13 -5
  18. data/lib/active_record/base.rb +435 -212
  19. data/lib/active_record/calculations.rb +12 -5
  20. data/lib/active_record/callbacks.rb +28 -9
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +355 -0
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +42 -215
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -5
  24. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +48 -7
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -4
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -26
  28. data/lib/active_record/connection_adapters/mysql_adapter.rb +71 -45
  29. data/lib/active_record/connection_adapters/postgresql_adapter.rb +155 -84
  30. data/lib/active_record/dirty.rb +25 -7
  31. data/lib/active_record/dynamic_finder_match.rb +41 -0
  32. data/lib/active_record/fixtures.rb +10 -9
  33. data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
  34. data/lib/active_record/locale/en.yml +54 -0
  35. data/lib/active_record/migration.rb +47 -10
  36. data/lib/active_record/named_scope.rb +29 -16
  37. data/lib/active_record/reflection.rb +118 -54
  38. data/lib/active_record/schema_dumper.rb +13 -7
  39. data/lib/active_record/test_case.rb +18 -5
  40. data/lib/active_record/transactions.rb +89 -34
  41. data/lib/active_record/validations.rb +270 -180
  42. data/lib/active_record/version.rb +1 -1
  43. data/test/cases/active_schema_test_mysql.rb +5 -0
  44. data/test/cases/adapter_test.rb +6 -0
  45. data/test/cases/aggregations_test.rb +39 -0
  46. data/test/cases/associations/belongs_to_associations_test.rb +10 -0
  47. data/test/cases/associations/eager_load_nested_include_test.rb +30 -12
  48. data/test/cases/associations/eager_test.rb +54 -5
  49. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +77 -10
  50. data/test/cases/associations/has_many_associations_test.rb +74 -7
  51. data/test/cases/associations/has_many_through_associations_test.rb +50 -3
  52. data/test/cases/associations/has_one_associations_test.rb +17 -0
  53. data/test/cases/associations/has_one_through_associations_test.rb +49 -1
  54. data/test/cases/associations_test.rb +0 -0
  55. data/test/cases/attribute_methods_test.rb +59 -4
  56. data/test/cases/base_test.rb +93 -21
  57. data/test/cases/binary_test.rb +1 -5
  58. data/test/cases/calculations_test.rb +5 -0
  59. data/test/cases/callbacks_observers_test.rb +38 -0
  60. data/test/cases/connection_test_mysql.rb +1 -1
  61. data/test/cases/defaults_test.rb +32 -1
  62. data/test/cases/deprecated_finder_test.rb +0 -0
  63. data/test/cases/dirty_test.rb +13 -0
  64. data/test/cases/finder_test.rb +162 -12
  65. data/test/cases/fixtures_test.rb +32 -3
  66. data/test/cases/helper.rb +15 -0
  67. data/test/cases/i18n_test.rb +41 -0
  68. data/test/cases/inheritance_test.rb +2 -2
  69. data/test/cases/lifecycle_test.rb +0 -0
  70. data/test/cases/locking_test.rb +4 -9
  71. data/test/cases/method_scoping_test.rb +109 -2
  72. data/test/cases/migration_test.rb +43 -8
  73. data/test/cases/multiple_db_test.rb +25 -0
  74. data/test/cases/named_scope_test.rb +74 -0
  75. data/test/cases/pooled_connections_test.rb +103 -0
  76. data/test/cases/readonly_test.rb +0 -0
  77. data/test/cases/reflection_test.rb +11 -3
  78. data/test/cases/reload_models_test.rb +20 -0
  79. data/test/cases/sanitize_test.rb +25 -0
  80. data/test/cases/schema_authorization_test_postgresql.rb +2 -2
  81. data/test/cases/transactions_test.rb +62 -12
  82. data/test/cases/unconnected_test.rb +0 -0
  83. data/test/cases/validations_i18n_test.rb +921 -0
  84. data/test/cases/validations_test.rb +44 -33
  85. data/test/connections/native_mysql/connection.rb +1 -3
  86. data/test/fixtures/companies.yml +1 -0
  87. data/test/fixtures/customers.yml +10 -1
  88. data/test/fixtures/fixture_database.sqlite3 +0 -0
  89. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  90. data/test/fixtures/organizations.yml +5 -0
  91. data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
  92. data/test/models/author.rb +3 -0
  93. data/test/models/category.rb +3 -0
  94. data/test/models/club.rb +6 -0
  95. data/test/models/company.rb +25 -1
  96. data/test/models/customer.rb +19 -1
  97. data/test/models/member.rb +2 -0
  98. data/test/models/member_detail.rb +4 -0
  99. data/test/models/organization.rb +4 -0
  100. data/test/models/parrot.rb +1 -0
  101. data/test/models/post.rb +3 -0
  102. data/test/models/reply.rb +0 -0
  103. data/test/models/topic.rb +3 -0
  104. data/test/schema/schema.rb +12 -1
  105. metadata +22 -10
  106. data/lib/active_record/vendor/mysql.rb +0 -1214
  107. data/test/cases/adapter_test_sqlserver.rb +0 -95
  108. data/test/cases/table_name_test_sqlserver.rb +0 -23
  109. data/test/cases/threaded_connections_test.rb +0 -48
  110. data/test/schema/sqlserver_specific_schema.rb +0 -5
@@ -2,6 +2,19 @@ require 'set'
2
2
 
3
3
  module ActiveRecord
4
4
  module Associations
5
+ # AssociationCollection is an abstract class that provides common stuff to
6
+ # ease the implementation of association proxies that represent
7
+ # collections. See the class hierarchy in AssociationProxy.
8
+ #
9
+ # You need to be careful with assumptions regarding the target: The proxy
10
+ # does not fetch records from the database until it needs them, but new
11
+ # ones created with +build+ are added to the target. So, the target may be
12
+ # non-empty and still lack children waiting to be read from the database.
13
+ # If you look directly to the database you cannot assume that's the entire
14
+ # collection because new records may have beed added to the target, etc.
15
+ #
16
+ # If you need to work on all current children, new and existing records,
17
+ # +load_target+ and the +loaded+ flag are your friends.
5
18
  class AssociationCollection < AssociationProxy #:nodoc:
6
19
  def initialize(owner, reflection)
7
20
  super
@@ -14,7 +27,7 @@ module ActiveRecord
14
27
  # If using a custom finder_sql, scan the entire collection.
15
28
  if @reflection.options[:finder_sql]
16
29
  expects_array = args.first.kind_of?(Array)
17
- ids = args.flatten.compact.uniq.map(&:to_i)
30
+ ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
18
31
 
19
32
  if ids.size == 1
20
33
  id = ids.first
@@ -50,7 +63,7 @@ module ActiveRecord
50
63
 
51
64
  # Fetches the first one using SQL if possible.
52
65
  def first(*args)
53
- if fetch_first_or_last_using_find? args
66
+ if fetch_first_or_last_using_find?(args)
54
67
  find(:first, *args)
55
68
  else
56
69
  load_target unless loaded?
@@ -60,7 +73,7 @@ module ActiveRecord
60
73
 
61
74
  # Fetches the last one using SQL if possible.
62
75
  def last(*args)
63
- if fetch_first_or_last_using_find? args
76
+ if fetch_first_or_last_using_find?(args)
64
77
  find(:last, *args)
65
78
  else
66
79
  load_target unless loaded?
@@ -95,7 +108,7 @@ module ActiveRecord
95
108
  result = true
96
109
  load_target if @owner.new_record?
97
110
 
98
- @owner.transaction do
111
+ transaction do
99
112
  flatten_deeper(records).each do |record|
100
113
  raise_on_type_mismatch(record)
101
114
  add_record_to_target_with_callbacks(record) do |r|
@@ -110,6 +123,21 @@ module ActiveRecord
110
123
  alias_method :push, :<<
111
124
  alias_method :concat, :<<
112
125
 
126
+ # Starts a transaction in the association class's database connection.
127
+ #
128
+ # class Author < ActiveRecord::Base
129
+ # has_many :books
130
+ # end
131
+ #
132
+ # Author.find(:first).books.transaction do
133
+ # # same effect as calling Book.transaction
134
+ # end
135
+ def transaction(*args)
136
+ @reflection.klass.transaction(*args) do
137
+ yield
138
+ end
139
+ end
140
+
113
141
  # Remove all records from this association
114
142
  def delete_all
115
143
  load_target
@@ -126,12 +154,47 @@ module ActiveRecord
126
154
  end
127
155
  end
128
156
 
129
- # Remove +records+ from this association. Does not destroy +records+.
157
+ # Count all records using SQL. If the +:counter_sql+ option is set for the association, it will
158
+ # be used for the query. If no +:counter_sql+ was supplied, but +:finder_sql+ was set, the
159
+ # descendant's +construct_sql+ method will have set :counter_sql automatically.
160
+ # Otherwise, construct options and pass them with scope to the target class's +count+.
161
+ def count(*args)
162
+ if @reflection.options[:counter_sql]
163
+ @reflection.klass.count_by_sql(@counter_sql)
164
+ else
165
+ column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
166
+ if @reflection.options[:uniq]
167
+ # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
168
+ column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
169
+ options.merge!(:distinct => true)
170
+ end
171
+
172
+ value = @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
173
+
174
+ limit = @reflection.options[:limit]
175
+ offset = @reflection.options[:offset]
176
+
177
+ if limit || offset
178
+ [ [value - offset.to_i, 0].max, limit.to_i ].min
179
+ else
180
+ value
181
+ end
182
+ end
183
+ end
184
+
185
+
186
+ # Removes +records+ from this association calling +before_remove+ and
187
+ # +after_remove+ callbacks.
188
+ #
189
+ # This method is abstract in the sense that +delete_records+ has to be
190
+ # provided by descendants. Note this method does not imply the records
191
+ # are actually removed from the database, that depends precisely on
192
+ # +delete_records+. They are in any case removed from the collection.
130
193
  def delete(*records)
131
194
  records = flatten_deeper(records)
132
195
  records.each { |record| raise_on_type_mismatch(record) }
133
196
 
134
- @owner.transaction do
197
+ transaction do
135
198
  records.each { |record| callback(:before_remove, record) }
136
199
 
137
200
  old_records = records.reject {|r| r.new_record? }
@@ -158,7 +221,7 @@ module ActiveRecord
158
221
  end
159
222
 
160
223
  def destroy_all
161
- @owner.transaction do
224
+ transaction do
162
225
  each { |record| record.destroy }
163
226
  end
164
227
 
@@ -183,12 +246,21 @@ module ActiveRecord
183
246
  end
184
247
  end
185
248
 
186
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
187
- # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
188
- # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
249
+ # Returns the size of the collection by executing a SELECT COUNT(*)
250
+ # query if the collection hasn't been loaded, and calling
251
+ # <tt>collection.size</tt> if it has.
252
+ #
253
+ # If the collection has been already loaded +size+ and +length+ are
254
+ # equivalent. If not and you are going to need the records anyway
255
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
256
+ #
257
+ # This method is abstract in the sense that it relies on
258
+ # +count_records+, which is a method descendants have to provide.
189
259
  def size
190
260
  if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
191
261
  @target.size
262
+ elsif !loaded? && @reflection.options[:group]
263
+ load_target.size
192
264
  elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
193
265
  unsaved_records = @target.select { |r| r.new_record? }
194
266
  unsaved_records.size + count_records
@@ -197,12 +269,18 @@ module ActiveRecord
197
269
  end
198
270
  end
199
271
 
200
- # Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check
201
- # whether the collection is empty, use collection.length.zero? instead of collection.empty?
272
+ # Returns the size of the collection calling +size+ on the target.
273
+ #
274
+ # If the collection has been already loaded +length+ and +size+ are
275
+ # equivalent. If not and you are going to need the records anyway this
276
+ # method will take one less query. Otherwise +size+ is more efficient.
202
277
  def length
203
278
  load_target.size
204
279
  end
205
280
 
281
+ # Equivalent to <tt>collection.size.zero?</tt>. If the collection has
282
+ # not been already loaded and you are going to fetch the records anyway
283
+ # it is better to check <tt>collection.length.zero?</tt>.
206
284
  def empty?
207
285
  size.zero?
208
286
  end
@@ -235,7 +313,7 @@ module ActiveRecord
235
313
  other = other_array.size < 100 ? other_array : other_array.to_set
236
314
  current = @target.size < 100 ? @target : @target.to_set
237
315
 
238
- @owner.transaction do
316
+ transaction do
239
317
  delete(@target.select { |v| !other.include?(v) })
240
318
  concat(other_array.select { |v| !current.include?(v) })
241
319
  end
@@ -248,6 +326,10 @@ module ActiveRecord
248
326
  exists?(record)
249
327
  end
250
328
 
329
+ def proxy_respond_to?(method, include_private = false)
330
+ super || @reflection.klass.respond_to?(method, include_private)
331
+ end
332
+
251
333
  protected
252
334
  def construct_find_options!(options)
253
335
  end
@@ -316,7 +398,9 @@ module ActiveRecord
316
398
  def create_record(attrs)
317
399
  attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
318
400
  ensure_owner_is_not_new
319
- record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { @reflection.klass.new(attrs) }
401
+ record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
402
+ @reflection.build_association(attrs)
403
+ end
320
404
  if block_given?
321
405
  add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
322
406
  else
@@ -326,7 +410,7 @@ module ActiveRecord
326
410
 
327
411
  def build_record(attrs)
328
412
  attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
329
- record = @reflection.klass.new(attrs)
413
+ record = @reflection.build_association(attrs)
330
414
  if block_given?
331
415
  add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
332
416
  else
@@ -361,7 +445,8 @@ module ActiveRecord
361
445
  end
362
446
 
363
447
  def fetch_first_or_last_using_find?(args)
364
- args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || !@target.blank? || args.first.kind_of?(Integer))
448
+ args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] ||
449
+ @target.any? { |record| record.new_record? } || args.first.kind_of?(Integer))
365
450
  end
366
451
  end
367
452
  end
@@ -39,7 +39,7 @@ module ActiveRecord
39
39
  # though the object behind <tt>blog.posts</tt> is not an Array, but an
40
40
  # ActiveRecord::Associations::HasManyAssociation.
41
41
  #
42
- # The <tt>@target</tt> object is not loaded until needed. For example,
42
+ # The <tt>@target</tt> object is not \loaded until needed. For example,
43
43
  #
44
44
  # blog.posts.count
45
45
  #
@@ -57,76 +57,109 @@ module ActiveRecord
57
57
  reset
58
58
  end
59
59
 
60
+ # Returns the owner of the proxy.
60
61
  def proxy_owner
61
62
  @owner
62
63
  end
63
64
 
65
+ # Returns the reflection object that represents the association handled
66
+ # by the proxy.
64
67
  def proxy_reflection
65
68
  @reflection
66
69
  end
67
70
 
71
+ # Returns the \target of the proxy, same as +target+.
68
72
  def proxy_target
69
73
  @target
70
74
  end
71
75
 
76
+ # Does the proxy or its \target respond to +symbol+?
72
77
  def respond_to?(*args)
73
78
  proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
74
79
  end
75
80
 
76
- # Explicitly proxy === because the instance method removal above
77
- # doesn't catch it.
81
+ # Forwards <tt>===</tt> explicitly to the \target because the instance method
82
+ # removal above doesn't catch it. Loads the \target if needed.
78
83
  def ===(other)
79
84
  load_target
80
85
  other === @target
81
86
  end
82
87
 
88
+ # Returns the name of the table of the related class:
89
+ #
90
+ # post.comments.aliased_table_name # => "comments"
91
+ #
83
92
  def aliased_table_name
84
93
  @reflection.klass.table_name
85
94
  end
86
95
 
96
+ # Returns the SQL string that corresponds to the <tt>:conditions</tt>
97
+ # option of the macro, if given, or +nil+ otherwise.
87
98
  def conditions
88
- @conditions ||= interpolate_sql(sanitize_sql(@reflection.options[:conditions])) if @reflection.options[:conditions]
99
+ @conditions ||= interpolate_sql(@reflection.sanitized_conditions) if @reflection.sanitized_conditions
89
100
  end
90
101
  alias :sql_conditions :conditions
91
102
 
103
+ # Resets the \loaded flag to +false+ and sets the \target to +nil+.
92
104
  def reset
93
105
  @loaded = false
94
106
  @target = nil
95
107
  end
96
108
 
109
+ # Reloads the \target and returns +self+ on success.
97
110
  def reload
98
111
  reset
99
112
  load_target
100
113
  self unless @target.nil?
101
114
  end
102
115
 
116
+ # Has the \target been already \loaded?
103
117
  def loaded?
104
118
  @loaded
105
119
  end
106
120
 
121
+ # Asserts the \target has been loaded setting the \loaded flag to +true+.
107
122
  def loaded
108
123
  @loaded = true
109
124
  end
110
125
 
126
+ # Returns the target of this proxy, same as +proxy_target+.
111
127
  def target
112
128
  @target
113
129
  end
114
130
 
131
+ # Sets the target of this proxy to <tt>\target</tt>, and the \loaded flag to +true+.
115
132
  def target=(target)
116
133
  @target = target
117
134
  loaded
118
135
  end
119
136
 
137
+ # Forwards the call to the target. Loads the \target if needed.
120
138
  def inspect
121
139
  load_target
122
140
  @target.inspect
123
141
  end
124
142
 
143
+ def send(method, *args)
144
+ if proxy_respond_to?(method)
145
+ super
146
+ else
147
+ load_target
148
+ @target.send(method, *args)
149
+ end
150
+ end
151
+
125
152
  protected
153
+ # Does the association have a <tt>:dependent</tt> option?
126
154
  def dependent?
127
155
  @reflection.options[:dependent]
128
156
  end
129
157
 
158
+ # Returns a string with the IDs of +records+ joined with a comma, quoted
159
+ # if needed. The result is ready to be inserted into a SQL IN clause.
160
+ #
161
+ # quoted_record_ids(records) # => "23,56,58,67"
162
+ #
130
163
  def quoted_record_ids(records)
131
164
  records.map { |record| record.quoted_id }.join(',')
132
165
  end
@@ -135,10 +168,13 @@ module ActiveRecord
135
168
  @owner.send(:interpolate_sql, sql, record)
136
169
  end
137
170
 
171
+ # Forwards the call to the reflection class.
138
172
  def sanitize_sql(sql)
139
173
  @reflection.klass.send(:sanitize_sql, sql)
140
174
  end
141
175
 
176
+ # Assigns the ID of the owner to the corresponding foreign key in +record+.
177
+ # If the association is polymorphic the type of the owner is also set.
142
178
  def set_belongs_to_association_for(record)
143
179
  if @reflection.options[:as]
144
180
  record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
@@ -148,6 +184,7 @@ module ActiveRecord
148
184
  end
149
185
  end
150
186
 
187
+ # Merges into +options+ the ones coming from the reflection.
151
188
  def merge_options_from_reflection!(options)
152
189
  options.reverse_merge!(
153
190
  :group => @reflection.options[:group],
@@ -160,13 +197,17 @@ module ActiveRecord
160
197
  )
161
198
  end
162
199
 
200
+ # Forwards +with_scope+ to the reflection.
163
201
  def with_scope(*args, &block)
164
202
  @reflection.klass.send :with_scope, *args, &block
165
203
  end
166
204
 
167
205
  private
206
+ # Forwards any missing method call to the \target.
168
207
  def method_missing(method, *args)
169
208
  if load_target
209
+ raise NoMethodError unless @target.respond_to?(method)
210
+
170
211
  if block_given?
171
212
  @target.send(method, *args) { |*block_args| yield(*block_args) }
172
213
  else
@@ -175,16 +216,16 @@ module ActiveRecord
175
216
  end
176
217
  end
177
218
 
178
- # Loads the target if needed and returns it.
219
+ # Loads the \target if needed and returns it.
179
220
  #
180
221
  # This method is abstract in the sense that it relies on +find_target+,
181
222
  # which is expected to be provided by descendants.
182
223
  #
183
- # If the target is already loaded it is just returned. Thus, you can call
184
- # +load_target+ unconditionally to get the target.
224
+ # If the \target is already \loaded it is just returned. Thus, you can call
225
+ # +load_target+ unconditionally to get the \target.
185
226
  #
186
227
  # ActiveRecord::RecordNotFound is rescued within the method, and it is
187
- # not reraised. The proxy is reset and +nil+ is the return value.
228
+ # not reraised. The proxy is \reset and +nil+ is the return value.
188
229
  def load_target
189
230
  return nil unless defined?(@loaded)
190
231
 
@@ -198,22 +239,33 @@ module ActiveRecord
198
239
  reset
199
240
  end
200
241
 
201
- # Can be overwritten by associations that might have the foreign key available for an association without
202
- # having the object itself (and still being a new record). Currently, only belongs_to presents this scenario.
242
+ # Can be overwritten by associations that might have the foreign key
243
+ # available for an association without having the object itself (and
244
+ # still being a new record). Currently, only +belongs_to+ presents
245
+ # this scenario (both vanilla and polymorphic).
203
246
  def foreign_key_present
204
247
  false
205
248
  end
206
249
 
250
+ # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
251
+ # the kind of the class of the associated objects. Meant to be used as
252
+ # a sanity check when you are about to assign an associated record.
207
253
  def raise_on_type_mismatch(record)
208
- unless record.is_a?(@reflection.klass)
254
+ unless record.is_a?(@reflection.klass) || record.is_a?(@reflection.class_name.constantize)
209
255
  message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
210
256
  raise ActiveRecord::AssociationTypeMismatch, message
211
257
  end
212
258
  end
213
259
 
214
- # Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems.
260
+ # Array#flatten has problems with recursive arrays. Going one level
261
+ # deeper solves the majority of the problems.
215
262
  def flatten_deeper(array)
216
- array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
263
+ array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten
264
+ end
265
+
266
+ # Returns the ID of the owner, quoted if needed.
267
+ def owner_quoted_id
268
+ @owner.quoted_id
217
269
  end
218
270
  end
219
271
  end
@@ -2,11 +2,11 @@ module ActiveRecord
2
2
  module Associations
3
3
  class BelongsToAssociation < AssociationProxy #:nodoc:
4
4
  def create(attributes = {})
5
- replace(@reflection.klass.create(attributes))
5
+ replace(@reflection.create_association(attributes))
6
6
  end
7
7
 
8
8
  def build(attributes = {})
9
- replace(@reflection.klass.new(attributes))
9
+ replace(@reflection.build_association(attributes))
10
10
  end
11
11
 
12
12
  def replace(record)