activerecord 2.3.5 → 2.3.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +33 -0
- data/Rakefile +1 -1
- data/examples/performance.sql +85 -0
- data/lib/active_record.rb +1 -2
- data/lib/active_record/association_preload.rb +9 -2
- data/lib/active_record/associations.rb +48 -38
- data/lib/active_record/associations/association_collection.rb +15 -11
- data/lib/active_record/associations/association_proxy.rb +16 -6
- data/lib/active_record/associations/belongs_to_association.rb +11 -1
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -10
- data/lib/active_record/associations/has_many_association.rb +5 -0
- data/lib/active_record/associations/has_many_through_association.rb +5 -5
- data/lib/active_record/associations/has_one_association.rb +10 -1
- data/lib/active_record/attribute_methods.rb +5 -1
- data/lib/active_record/autosave_association.rb +66 -35
- data/lib/active_record/base.rb +77 -36
- data/lib/active_record/batches.rb +13 -9
- data/lib/active_record/calculations.rb +6 -3
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +64 -10
- data/lib/active_record/connection_adapters/abstract_adapter.rb +2 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +31 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +31 -66
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +2 -2
- data/lib/active_record/dirty.rb +2 -2
- data/lib/active_record/fixtures.rb +1 -0
- data/lib/active_record/locking/optimistic.rb +34 -1
- data/lib/active_record/migration.rb +5 -0
- data/lib/active_record/nested_attributes.rb +64 -52
- data/lib/active_record/reflection.rb +66 -1
- data/lib/active_record/schema.rb +5 -1
- data/lib/active_record/schema_dumper.rb +3 -0
- data/lib/active_record/serializers/json_serializer.rb +1 -1
- data/lib/active_record/validations.rb +13 -1
- data/lib/active_record/version.rb +1 -1
- data/test/cases/active_schema_test_mysql.rb +22 -0
- data/test/cases/associations/belongs_to_associations_test.rb +13 -0
- data/test/cases/associations/eager_load_nested_include_test.rb +8 -7
- data/test/cases/associations/eager_test.rb +7 -1
- data/test/cases/associations/has_many_associations_test.rb +26 -0
- data/test/cases/associations/inverse_associations_test.rb +566 -0
- data/test/cases/associations_test.rb +10 -0
- data/test/cases/autosave_association_test.rb +86 -10
- data/test/cases/base_test.rb +29 -0
- data/test/cases/batches_test.rb +20 -0
- data/test/cases/calculations_test.rb +2 -3
- data/test/cases/encoding_test.rb +6 -0
- data/test/cases/finder_test.rb +19 -3
- data/test/cases/fixtures_test.rb +5 -0
- data/test/cases/json_serialization_test.rb +14 -0
- data/test/cases/locking_test.rb +48 -3
- data/test/cases/migration_test.rb +115 -0
- data/test/cases/modules_test.rb +28 -0
- data/test/cases/named_scope_test.rb +1 -1
- data/test/cases/nested_attributes_test.rb +239 -7
- data/test/cases/query_cache_test.rb +7 -1
- data/test/cases/reflection_test.rb +47 -7
- data/test/cases/schema_test_postgresql.rb +2 -2
- data/test/cases/validations_i18n_test.rb +6 -36
- data/test/cases/validations_test.rb +33 -1
- data/test/cases/yaml_serialization_test.rb +11 -0
- data/test/fixtures/faces.yml +11 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/fixtures/interests.yml +33 -0
- data/test/fixtures/men.yml +5 -0
- data/test/fixtures/zines.yml +5 -0
- data/test/models/author.rb +3 -0
- data/test/models/bird.rb +6 -0
- data/test/models/company_in_module.rb +17 -0
- data/test/models/event_author.rb +5 -0
- data/test/models/face.rb +7 -0
- data/test/models/interest.rb +5 -0
- data/test/models/invoice.rb +4 -0
- data/test/models/line_item.rb +3 -0
- data/test/models/man.rb +9 -0
- data/test/models/parrot.rb +6 -0
- data/test/models/pirate.rb +10 -0
- data/test/models/ship.rb +10 -1
- data/test/models/ship_part.rb +3 -1
- data/test/models/zine.rb +3 -0
- data/test/schema/schema.rb +41 -0
- metadata +37 -11
- data/lib/active_record/i18n_interpolation_deprecation.rb +0 -26
@@ -244,8 +244,8 @@ module ActiveRecord
|
|
244
244
|
column ? column['name'] : nil
|
245
245
|
end
|
246
246
|
|
247
|
-
def remove_index(table_name,
|
248
|
-
execute "DROP INDEX #{quote_column_name(index_name
|
247
|
+
def remove_index!(table_name, index_name) #:nodoc:
|
248
|
+
execute "DROP INDEX #{quote_column_name(index_name)}"
|
249
249
|
end
|
250
250
|
|
251
251
|
def rename_table(name, new_name)
|
data/lib/active_record/dirty.rb
CHANGED
@@ -167,13 +167,13 @@ module ActiveRecord
|
|
167
167
|
|
168
168
|
module ClassMethods
|
169
169
|
def self.extended(base)
|
170
|
-
base.
|
170
|
+
base.singleton_class.alias_method_chain(:alias_attribute, :dirty)
|
171
171
|
end
|
172
172
|
|
173
173
|
def alias_attribute_with_dirty(new_name, old_name)
|
174
174
|
alias_attribute_without_dirty(new_name, old_name)
|
175
175
|
DIRTY_SUFFIXES.each do |suffix|
|
176
|
-
module_eval <<-STR, __FILE__, __LINE__+1
|
176
|
+
module_eval <<-STR, __FILE__, __LINE__ + 1
|
177
177
|
def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end
|
178
178
|
STR
|
179
179
|
end
|
@@ -23,6 +23,16 @@ module ActiveRecord
|
|
23
23
|
# p2.first_name = "should fail"
|
24
24
|
# p2.save # Raises a ActiveRecord::StaleObjectError
|
25
25
|
#
|
26
|
+
# Optimistic locking will also check for stale data when objects are destroyed. Example:
|
27
|
+
#
|
28
|
+
# p1 = Person.find(1)
|
29
|
+
# p2 = Person.find(1)
|
30
|
+
#
|
31
|
+
# p1.first_name = "Michael"
|
32
|
+
# p1.save
|
33
|
+
#
|
34
|
+
# p2.destroy # Raises a ActiveRecord::StaleObjectError
|
35
|
+
#
|
26
36
|
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
|
27
37
|
# or otherwise apply the business logic needed to resolve the conflict.
|
28
38
|
#
|
@@ -39,6 +49,7 @@ module ActiveRecord
|
|
39
49
|
base.lock_optimistically = true
|
40
50
|
|
41
51
|
base.alias_method_chain :update, :lock
|
52
|
+
base.alias_method_chain :destroy, :lock
|
42
53
|
base.alias_method_chain :attributes_from_column_definition, :lock
|
43
54
|
|
44
55
|
class << base
|
@@ -86,7 +97,7 @@ module ActiveRecord
|
|
86
97
|
end_sql
|
87
98
|
|
88
99
|
unless affected_rows == 1
|
89
|
-
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
|
100
|
+
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"
|
90
101
|
end
|
91
102
|
|
92
103
|
affected_rows
|
@@ -98,6 +109,28 @@ module ActiveRecord
|
|
98
109
|
end
|
99
110
|
end
|
100
111
|
|
112
|
+
def destroy_with_lock #:nodoc:
|
113
|
+
return destroy_without_lock unless locking_enabled?
|
114
|
+
|
115
|
+
unless new_record?
|
116
|
+
lock_col = self.class.locking_column
|
117
|
+
previous_value = send(lock_col).to_i
|
118
|
+
|
119
|
+
affected_rows = connection.delete(
|
120
|
+
"DELETE FROM #{self.class.quoted_table_name} " +
|
121
|
+
"WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} " +
|
122
|
+
"AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}",
|
123
|
+
"#{self.class.name} Destroy"
|
124
|
+
)
|
125
|
+
|
126
|
+
unless affected_rows == 1
|
127
|
+
raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
freeze
|
132
|
+
end
|
133
|
+
|
101
134
|
module ClassMethods
|
102
135
|
DEFAULT_LOCKING_COLUMN = 'lock_version'
|
103
136
|
|
@@ -381,6 +381,7 @@ module ActiveRecord
|
|
381
381
|
def migrate(migrations_path, target_version = nil)
|
382
382
|
case
|
383
383
|
when target_version.nil? then up(migrations_path, target_version)
|
384
|
+
when current_version == 0 && target_version == 0 then # noop
|
384
385
|
when current_version > target_version then down(migrations_path, target_version)
|
385
386
|
else up(migrations_path, target_version)
|
386
387
|
end
|
@@ -408,6 +409,10 @@ module ActiveRecord
|
|
408
409
|
self.new(direction, migrations_path, target_version).run
|
409
410
|
end
|
410
411
|
|
412
|
+
def migrations_path
|
413
|
+
'db/migrate'
|
414
|
+
end
|
415
|
+
|
411
416
|
def schema_migrations_table_name
|
412
417
|
Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
|
413
418
|
end
|
@@ -184,6 +184,8 @@ module ActiveRecord
|
|
184
184
|
# the parent model is saved. This happens inside the transaction initiated
|
185
185
|
# by the parents save method. See ActiveRecord::AutosaveAssociation.
|
186
186
|
module ClassMethods
|
187
|
+
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } }
|
188
|
+
|
187
189
|
# Defines an attributes writer for the specified association(s). If you
|
188
190
|
# are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>, then you
|
189
191
|
# will need to add the attribute writer to the allowed list.
|
@@ -208,39 +210,40 @@ module ActiveRecord
|
|
208
210
|
# nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
|
209
211
|
# exception is raised. If omitted, any number associations can be processed.
|
210
212
|
# Note that the :limit option is only applicable to one-to-many associations.
|
213
|
+
# [:update_only]
|
214
|
+
# Allows you to specify that an existing record may only be updated.
|
215
|
+
# A new record may only be created when there is no existing record.
|
216
|
+
# This option only works for one-to-one associations and is ignored for
|
217
|
+
# collection associations. This option is off by default.
|
211
218
|
#
|
212
219
|
# Examples:
|
213
220
|
# # creates avatar_attributes=
|
214
221
|
# accepts_nested_attributes_for :avatar, :reject_if => proc { |attributes| attributes['name'].blank? }
|
222
|
+
# # creates avatar_attributes=
|
223
|
+
# accepts_nested_attributes_for :avatar, :reject_if => :all_blank
|
215
224
|
# # creates avatar_attributes= and posts_attributes=
|
216
225
|
# accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true
|
217
226
|
def accepts_nested_attributes_for(*attr_names)
|
218
|
-
options = { :allow_destroy => false }
|
227
|
+
options = { :allow_destroy => false, :update_only => false }
|
219
228
|
options.update(attr_names.extract_options!)
|
220
|
-
options.assert_valid_keys(:allow_destroy, :reject_if, :limit)
|
229
|
+
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
|
230
|
+
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
|
221
231
|
|
222
232
|
attr_names.each do |association_name|
|
223
233
|
if reflection = reflect_on_association(association_name)
|
224
|
-
type = case reflection.macro
|
225
|
-
when :has_one, :belongs_to
|
226
|
-
:one_to_one
|
227
|
-
when :has_many, :has_and_belongs_to_many
|
228
|
-
:collection
|
229
|
-
end
|
230
|
-
|
231
234
|
reflection.options[:autosave] = true
|
232
|
-
|
235
|
+
add_autosave_association_callbacks(reflection)
|
236
|
+
nested_attributes_options[association_name.to_sym] = options
|
237
|
+
type = (reflection.collection? ? :collection : :one_to_one)
|
233
238
|
|
234
239
|
# def pirate_attributes=(attributes)
|
235
|
-
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes
|
240
|
+
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes)
|
236
241
|
# end
|
237
|
-
class_eval
|
242
|
+
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
238
243
|
def #{association_name}_attributes=(attributes)
|
239
244
|
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
|
240
245
|
end
|
241
|
-
|
242
|
-
|
243
|
-
add_autosave_association_callbacks(reflection)
|
246
|
+
EOS
|
244
247
|
else
|
245
248
|
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
|
246
249
|
end
|
@@ -257,46 +260,41 @@ module ActiveRecord
|
|
257
260
|
marked_for_destruction?
|
258
261
|
end
|
259
262
|
|
260
|
-
# Deal with deprecated _delete.
|
261
|
-
#
|
262
|
-
def _delete #:nodoc:
|
263
|
-
ActiveSupport::Deprecation.warn "_delete is deprecated in nested attributes. Use _destroy instead."
|
264
|
-
_destroy
|
265
|
-
end
|
266
|
-
|
267
263
|
private
|
268
264
|
|
269
265
|
# Attribute hash keys that should not be assigned as normal attributes.
|
270
266
|
# These hash keys are nested attributes implementation details.
|
271
|
-
|
272
|
-
# TODO Remove _delete from UNASSIGNABLE_KEYS when deprecation warning are
|
273
|
-
# removed.
|
274
|
-
UNASSIGNABLE_KEYS = %w( id _destroy _delete )
|
267
|
+
UNASSIGNABLE_KEYS = %w( id _destroy )
|
275
268
|
|
276
269
|
# Assigns the given attributes to the association.
|
277
270
|
#
|
278
|
-
# If the given attributes include an <tt>:id</tt>
|
279
|
-
# record’s id, then the existing record will be
|
280
|
-
# record
|
271
|
+
# If update_only is false and the given attributes include an <tt>:id</tt>
|
272
|
+
# that matches the existing record’s id, then the existing record will be
|
273
|
+
# modified. If update_only is true, a new record is only created when no
|
274
|
+
# object exists. Otherwise a new record will be built.
|
281
275
|
#
|
282
|
-
# If the given attributes include a matching <tt>:id</tt> attribute
|
283
|
-
# <tt>:_destroy</tt> key set to a truthy value,
|
284
|
-
# will be marked for destruction.
|
276
|
+
# If the given attributes include a matching <tt>:id</tt> attribute, or
|
277
|
+
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
|
278
|
+
# then the existing record will be marked for destruction.
|
285
279
|
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
|
286
|
-
options =
|
280
|
+
options = nested_attributes_options[association_name]
|
287
281
|
attributes = attributes.with_indifferent_access
|
282
|
+
check_existing_record = (options[:update_only] || !attributes['id'].blank?)
|
288
283
|
|
289
|
-
if
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
284
|
+
if check_existing_record && (record = send(association_name)) &&
|
285
|
+
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
|
286
|
+
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
|
287
|
+
|
288
|
+
elsif attributes['id']
|
289
|
+
raise_nested_attributes_record_not_found(association_name, attributes['id'])
|
290
|
+
|
291
|
+
elsif !reject_new_record?(association_name, attributes)
|
292
|
+
method = "build_#{association_name}"
|
293
|
+
if respond_to?(method)
|
294
|
+
send(method, attributes.except(*UNASSIGNABLE_KEYS))
|
295
|
+
else
|
296
|
+
raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
|
297
297
|
end
|
298
|
-
elsif (existing_record = send(association_name)) && existing_record.id.to_s == attributes['id'].to_s
|
299
|
-
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
300
298
|
end
|
301
299
|
end
|
302
300
|
|
@@ -328,7 +326,7 @@ module ActiveRecord
|
|
328
326
|
# { :id => '2', :_destroy => true }
|
329
327
|
# ])
|
330
328
|
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
|
331
|
-
options =
|
329
|
+
options = nested_attributes_options[association_name]
|
332
330
|
|
333
331
|
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
334
332
|
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
@@ -342,15 +340,27 @@ module ActiveRecord
|
|
342
340
|
attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes }
|
343
341
|
end
|
344
342
|
|
343
|
+
association = send(association_name)
|
344
|
+
|
345
|
+
existing_records = if association.loaded?
|
346
|
+
association.to_a
|
347
|
+
else
|
348
|
+
attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact
|
349
|
+
attribute_ids.present? ? association.all(:conditions => {association.primary_key => attribute_ids}) : []
|
350
|
+
end
|
351
|
+
|
345
352
|
attributes_collection.each do |attributes|
|
346
353
|
attributes = attributes.with_indifferent_access
|
347
354
|
|
348
355
|
if attributes['id'].blank?
|
349
356
|
unless reject_new_record?(association_name, attributes)
|
350
|
-
|
357
|
+
association.build(attributes.except(*UNASSIGNABLE_KEYS))
|
351
358
|
end
|
352
|
-
elsif existing_record =
|
359
|
+
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
|
360
|
+
association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
|
353
361
|
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
362
|
+
else
|
363
|
+
raise_nested_attributes_record_not_found(association_name, attributes['id'])
|
354
364
|
end
|
355
365
|
end
|
356
366
|
end
|
@@ -367,8 +377,7 @@ module ActiveRecord
|
|
367
377
|
|
368
378
|
# Determines if a hash contains a truthy _destroy key.
|
369
379
|
def has_destroy_flag?(hash)
|
370
|
-
ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
|
371
|
-
ConnectionAdapters::Column.value_to_boolean(hash['_delete']) # TODO Remove after deprecation.
|
380
|
+
ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
|
372
381
|
end
|
373
382
|
|
374
383
|
# Determines if a new record should be build by checking for
|
@@ -379,14 +388,17 @@ module ActiveRecord
|
|
379
388
|
end
|
380
389
|
|
381
390
|
def call_reject_if(association_name, attributes)
|
382
|
-
callback =
|
383
|
-
|
384
|
-
case callback
|
391
|
+
case callback = nested_attributes_options[association_name][:reject_if]
|
385
392
|
when Symbol
|
386
393
|
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
|
387
394
|
when Proc
|
388
|
-
callback.
|
395
|
+
callback.call(attributes)
|
389
396
|
end
|
390
397
|
end
|
398
|
+
|
399
|
+
def raise_nested_attributes_record_not_found(association_name, record_id)
|
400
|
+
reflection = self.class.reflect_on_association(association_name)
|
401
|
+
raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
|
402
|
+
end
|
391
403
|
end
|
392
404
|
end
|
@@ -212,6 +212,15 @@ module ActiveRecord
|
|
212
212
|
end
|
213
213
|
|
214
214
|
def check_validity!
|
215
|
+
check_validity_of_inverse!
|
216
|
+
end
|
217
|
+
|
218
|
+
def check_validity_of_inverse!
|
219
|
+
unless options[:polymorphic]
|
220
|
+
if has_inverse? && inverse_of.nil?
|
221
|
+
raise InverseOfAssociationNotFoundError.new(self)
|
222
|
+
end
|
223
|
+
end
|
215
224
|
end
|
216
225
|
|
217
226
|
def through_reflection
|
@@ -225,10 +234,64 @@ module ActiveRecord
|
|
225
234
|
nil
|
226
235
|
end
|
227
236
|
|
237
|
+
def has_inverse?
|
238
|
+
!@options[:inverse_of].nil?
|
239
|
+
end
|
240
|
+
|
241
|
+
def inverse_of
|
242
|
+
if has_inverse?
|
243
|
+
@inverse_of ||= klass.reflect_on_association(options[:inverse_of])
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def polymorphic_inverse_of(associated_class)
|
248
|
+
if has_inverse?
|
249
|
+
if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
|
250
|
+
inverse_relationship
|
251
|
+
else
|
252
|
+
raise InverseOfAssociationNotFoundError.new(self, associated_class)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Returns whether or not this association reflection is for a collection
|
258
|
+
# association. Returns +true+ if the +macro+ is one of +has_many+ or
|
259
|
+
# +has_and_belongs_to_many+, +false+ otherwise.
|
260
|
+
def collection?
|
261
|
+
if @collection.nil?
|
262
|
+
@collection = [:has_many, :has_and_belongs_to_many].include?(macro)
|
263
|
+
end
|
264
|
+
@collection
|
265
|
+
end
|
266
|
+
|
267
|
+
# Returns whether or not the association should be validated as part of
|
268
|
+
# the parent's validation.
|
269
|
+
#
|
270
|
+
# Unless you explicitely disable validation with
|
271
|
+
# <tt>:validate => false</tt>, it will take place when:
|
272
|
+
#
|
273
|
+
# * you explicitely enable validation; <tt>:validate => true</tt>
|
274
|
+
# * you use autosave; <tt>:autosave => true</tt>
|
275
|
+
# * the association is a +has_many+ association
|
276
|
+
def validate?
|
277
|
+
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
|
278
|
+
end
|
279
|
+
|
280
|
+
def dependent_conditions(record, base_class, extra_conditions)
|
281
|
+
dependent_conditions = []
|
282
|
+
dependent_conditions << "#{primary_key_name} = #{record.send(name).send(:owner_quoted_id)}"
|
283
|
+
dependent_conditions << "#{options[:as]}_type = '#{base_class.name}'" if options[:as]
|
284
|
+
dependent_conditions << klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
285
|
+
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
|
286
|
+
dependent_conditions << extra_conditions if extra_conditions
|
287
|
+
dependent_conditions = dependent_conditions.gsub('@', '\@')
|
288
|
+
dependent_conditions
|
289
|
+
end
|
290
|
+
|
228
291
|
private
|
229
292
|
def derive_class_name
|
230
293
|
class_name = name.to_s.camelize
|
231
|
-
class_name = class_name.singularize if
|
294
|
+
class_name = class_name.singularize if collection?
|
232
295
|
class_name
|
233
296
|
end
|
234
297
|
|
@@ -300,6 +363,8 @@ module ActiveRecord
|
|
300
363
|
unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
|
301
364
|
raise HasManyThroughSourceAssociationMacroError.new(self)
|
302
365
|
end
|
366
|
+
|
367
|
+
check_validity_of_inverse!
|
303
368
|
end
|
304
369
|
|
305
370
|
def through_reflection_primary_key
|
data/lib/active_record/schema.rb
CHANGED
@@ -28,6 +28,10 @@ module ActiveRecord
|
|
28
28
|
class Schema < Migration
|
29
29
|
private_class_method :new
|
30
30
|
|
31
|
+
def self.migrations_path
|
32
|
+
ActiveRecord::Migrator.migrations_path
|
33
|
+
end
|
34
|
+
|
31
35
|
# Eval the given block. All methods available to the current connection
|
32
36
|
# adapter are available within the block, so you can easily use the
|
33
37
|
# database definition DSL to build up your schema (+create_table+,
|
@@ -44,7 +48,7 @@ module ActiveRecord
|
|
44
48
|
|
45
49
|
unless info[:version].blank?
|
46
50
|
initialize_schema_migrations_table
|
47
|
-
assume_migrated_upto_version
|
51
|
+
assume_migrated_upto_version(info[:version], migrations_path)
|
48
52
|
end
|
49
53
|
end
|
50
54
|
end
|
@@ -171,6 +171,9 @@ HEADER
|
|
171
171
|
statment_parts << (':name => ' + index.name.inspect)
|
172
172
|
statment_parts << ':unique => true' if index.unique
|
173
173
|
|
174
|
+
index_lengths = index.lengths.compact if index.lengths.is_a?(Array)
|
175
|
+
statment_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present?
|
176
|
+
|
174
177
|
' ' + statment_parts.join(', ')
|
175
178
|
end
|
176
179
|
|
@@ -79,7 +79,7 @@ module ActiveRecord #:nodoc:
|
|
79
79
|
|
80
80
|
def as_json(options = nil) #:nodoc:
|
81
81
|
hash = Serializer.new(self, options).serializable_record
|
82
|
-
hash = { self.class.model_name.element => hash } if include_root_in_json
|
82
|
+
hash = { options[:root] || self.class.model_name.element => hash } if include_root_in_json
|
83
83
|
hash
|
84
84
|
end
|
85
85
|
|