diff options
author | José Valim <jose.valim@gmail.com> | 2009-11-23 22:04:18 -0200 |
---|---|---|
committer | José Valim <jose.valim@gmail.com> | 2009-11-23 22:04:18 -0200 |
commit | 4ff66b6b85d1351e447f18c027f1dba6bcf23a7b (patch) | |
tree | a46d6c65c5c67964bc8658bf991157f0f8056f55 /activerecord | |
parent | 01ae99c681d31803f3a29f8305c9a041aa456660 (diff) | |
parent | 934bb012ba3f1da5cd181ae5c2d84f697a3c58a1 (diff) | |
download | rails-4ff66b6b85d1351e447f18c027f1dba6bcf23a7b.tar.gz rails-4ff66b6b85d1351e447f18c027f1dba6bcf23a7b.tar.bz2 rails-4ff66b6b85d1351e447f18c027f1dba6bcf23a7b.zip |
Merge branch 'master' of git://github.com/rails/rails
Diffstat (limited to 'activerecord')
12 files changed, 84 insertions, 114 deletions
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index e41fda7a4b..9f7b2a60b2 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -1,3 +1,6 @@ +require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/enumerable' + module ActiveRecord # See ActiveRecord::AssociationPreload::ClassMethods for documentation. module AssociationPreload #:nodoc: @@ -82,7 +85,7 @@ module ActiveRecord # only one level deep in the +associations+ argument, i.e. it's not passed # to the child associations when +associations+ is a Hash. def preload_associations(records, associations, preload_options={}) - records = [records].flatten.compact.uniq + records = Array.wrap(records).compact.uniq return if records.empty? case associations when Array then associations.each {|association| preload_associations(records, association, preload_options)} @@ -92,7 +95,7 @@ module ActiveRecord raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol) preload_associations(records, parent, preload_options) reflection = reflections[parent] - parents = records.map {|record| record.send(reflection.name)}.flatten.compact + parents = records.sum { |record| Array.wrap(record.send(reflection.name)) } unless parents.empty? parents.first.class.preload_associations(parents, child) end @@ -123,7 +126,8 @@ module ActiveRecord parent_records.each do |parent_record| association_proxy = parent_record.send(reflection_name) association_proxy.loaded - association_proxy.target.push(*[associated_record].flatten) + association_proxy.target.push *Array.wrap(associated_record) + association_proxy.__send__(:set_inverse_instance, associated_record, parent_record) end end @@ -254,6 +258,7 @@ module ActiveRecord through_reflection = reflections[through_association] through_primary_key = through_reflection.primary_key_name + through_records = [] if reflection.options[:source_type] interface = reflection.source_reflection.options[:foreign_type] preload_options = {:conditions => ["#{connection.quote_column_name interface} = ?", reflection.options[:source_type]]} @@ -262,23 +267,22 @@ module ActiveRecord records.first.class.preload_associations(records, through_association, preload_options) # Dont cache the association - we would only be caching a subset - through_records = [] records.each do |record| proxy = record.send(through_association) if proxy.respond_to?(:target) - through_records << proxy.target + through_records.concat Array.wrap(proxy.target) proxy.reset else # this is a has_one :through reflection through_records << proxy if proxy end end - through_records.flatten! else records.first.class.preload_associations(records, through_association) - through_records = records.map {|record| record.send(through_association)}.flatten + records.each do |record| + through_records.concat Array.wrap(record.send(through_association)) + end end - through_records.compact! through_records end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 3a5f3ed030..0fcd288fc5 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/enumerable' module ActiveRecord class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc: @@ -60,6 +61,12 @@ module ActiveRecord end end + class HasAndBelongsToManyAssociationWithPrimaryKeyError < ActiveRecordError #:nodoc: + def initialize(reflection) + super("Primary key is not allowed in a has_and_belongs_to_many join table (#{reflection.options[:join_table]}).") + end + end + class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc: def initialize(reflection) super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.") @@ -1396,8 +1403,8 @@ module ActiveRecord end define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| - ids = (new_value || []).reject { |nid| nid.blank? } - send("#{reflection.name}=", reflection.klass.find(ids)) + ids = (new_value || []).reject { |nid| nid.blank? }.map(&:to_i) + send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids)) end end end @@ -1674,7 +1681,6 @@ module ActiveRecord def create_has_and_belongs_to_many_reflection(association_id, options, &extension) options.assert_valid_keys(valid_keys_for_has_and_belongs_to_many_association) - options[:extend] = create_extension_modules(association_id, extension, options[:extend]) reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self) @@ -1684,6 +1690,9 @@ module ActiveRecord end reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name)) + if connection.supports_primary_key? && (connection.primary_key(reflection.options[:join_table]) rescue false) + raise HasAndBelongsToManyAssociationWithPrimaryKeyError.new(reflection) + end reflection end @@ -1922,12 +1931,16 @@ module ActiveRecord reflection = base.reflections[name] is_collection = [:has_many, :has_and_belongs_to_many].include?(reflection.macro) - parent_records = records.map do |record| - descendant = record.send(reflection.name) - next unless descendant - descendant.target.uniq! if is_collection - descendant - end.flatten.compact + parent_records = [] + records.each do |record| + if descendant = record.send(reflection.name) + if is_collection + parent_records.concat descendant.target.uniq + else + parent_records << descendant + end + end + end remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty? end diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 75218c01d2..7d8f4670fa 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -256,10 +256,16 @@ module ActiveRecord end end - # Array#flatten has problems with recursive arrays. Going one level - # deeper solves the majority of the problems. - def flatten_deeper(array) - array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten + if RUBY_VERSION < '1.9.2' + # Array#flatten has problems with recursive arrays before Ruby 1.9.2. + # Going one level deeper solves the majority of the problems. + def flatten_deeper(array) + array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten + end + else + def flatten_deeper(array) + array.flatten + end end # Returns the ID of the owner, quoted if needed. diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index c646fe488b..b01faa5212 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -1,11 +1,6 @@ module ActiveRecord module Associations class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc: - def initialize(owner, reflection) - super - @primary_key_list = {} - end - def create(attributes = {}) create_record(attributes) { |record| insert_record(record) } end @@ -23,9 +18,7 @@ module ActiveRecord end def has_primary_key? - return @has_primary_key unless @has_primary_key.nil? - @has_primary_key = (@owner.connection.supports_primary_key? && - @owner.connection.primary_key(@reflection.options[:join_table])) + @has_primary_key ||= @owner.connection.supports_primary_key? && @owner.connection.primary_key(@reflection.options[:join_table]) end protected @@ -40,11 +33,6 @@ module ActiveRecord end def insert_record(record, force = true, validate = true) - if has_primary_key? - raise ActiveRecord::ConfigurationError, - "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})." - end - if record.new_record? if force record.save! diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index ab7ad34b9e..3a9a67e3a2 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -31,11 +31,10 @@ module ActiveRecord self.class.define_attribute_methods method_name = method_id.to_s guard_private_attribute_method!(method_name, args) - if self.class.generated_attribute_methods.instance_methods.include?(method_name) - return self.send(method_id, *args, &block) - end + send(method_id, *args, &block) + else + super end - super end def respond_to?(*args) diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index c8cd79a2b0..986bc7009b 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -23,16 +23,6 @@ module ActiveRecord # p2.first_name = "should fail" # p2.save # Raises a ActiveRecord::StaleObjectError # - # Optimistic locking will also check for stale data when objects are destroyed. Example: - # - # p1 = Person.find(1) - # p2 = Person.find(1) - # - # p1.first_name = "Michael" - # p1.save - # - # p2.destroy # Raises a ActiveRecord::StaleObjectError - # # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, # or otherwise apply the business logic needed to resolve the conflict. # @@ -49,7 +39,6 @@ module ActiveRecord self.lock_optimistically = true alias_method_chain :update, :lock - alias_method_chain :destroy, :lock alias_method_chain :attributes_from_column_definition, :lock class << self @@ -111,29 +100,6 @@ module ActiveRecord end end - def destroy_with_lock #:nodoc: - return destroy_without_lock unless locking_enabled? - - unless new_record? - lock_col = self.class.locking_column - previous_value = send(lock_col).to_i - - arel_table = self.class.arel_table(self.class.table_name) - - affected_rows = arel_table.where( - arel_table[self.class.primary_key].eq(quoted_id).and( - arel_table[self.class.locking_column].eq(quote_value(previous_value)) - ) - ).delete - - unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object" - end - end - - freeze - end - module ClassMethods DEFAULT_LOCKING_COLUMN = 'lock_version' diff --git a/activerecord/test/cases/associations/habtm_join_table_test.rb b/activerecord/test/cases/associations/habtm_join_table_test.rb index bf3e04c3eb..745f169ad7 100644 --- a/activerecord/test/cases/associations/habtm_join_table_test.rb +++ b/activerecord/test/cases/associations/habtm_join_table_test.rb @@ -36,21 +36,9 @@ class HabtmJoinTableTest < ActiveRecord::TestCase uses_transaction :test_should_raise_exception_when_join_table_has_a_primary_key def test_should_raise_exception_when_join_table_has_a_primary_key if ActiveRecord::Base.connection.supports_primary_key? - assert_raise ActiveRecord::ConfigurationError do - jaime = MyReader.create(:name=>"Jaime") - jaime.my_books << MyBook.create(:name=>'Great Expectations') + assert_raise ActiveRecord::HasAndBelongsToManyAssociationWithPrimaryKeyError do + MyReader.has_and_belongs_to_many :my_books end end end - - uses_transaction :test_should_cache_result_of_primary_key_check - def test_should_cache_result_of_primary_key_check - if ActiveRecord::Base.connection.supports_primary_key? - ActiveRecord::Base.connection.stubs(:primary_key).with('my_books_my_readers').returns(false).once - weaz = MyReader.create(:name=>'Weaz') - - weaz.my_books << MyBook.create(:name=>'Great Expectations') - weaz.my_books << MyBook.create(:name=>'Greater Expectations') - end - end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 5f13b66d11..fe68d03de2 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -137,6 +137,28 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert !posts(:welcome).reload.people(true).include?(people(:michael)) end + def test_replace_order_is_preserved + posts(:welcome).people.clear + posts(:welcome).people = [people(:david), people(:michael)] + assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.all(:order => 'id').map(&:person_id) + + # Test the inverse order in case the first success was a coincidence + posts(:welcome).people.clear + posts(:welcome).people = [people(:michael), people(:david)] + assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.all(:order => 'id').map(&:person_id) + end + + def test_replace_by_id_order_is_preserved + posts(:welcome).people.clear + posts(:welcome).person_ids = [people(:david).id, people(:michael).id] + assert_equal [people(:david).id, people(:michael).id], posts(:welcome).readers.all(:order => 'id').map(&:person_id) + + # Test the inverse order in case the first success was a coincidence + posts(:welcome).people.clear + posts(:welcome).person_ids = [people(:michael).id, people(:david).id] + assert_equal [people(:michael).id, people(:david).id], posts(:welcome).readers.all(:order => 'id').map(&:person_id) + end + def test_associate_with_create assert_queries(1) { posts(:thinking) } diff --git a/activerecord/test/cases/copy_table_test_sqlite.rb b/activerecord/test/cases/copy_table_test_sqlite.rb index de8af30997..575b4806c1 100644 --- a/activerecord/test/cases/copy_table_test_sqlite.rb +++ b/activerecord/test/cases/copy_table_test_sqlite.rb @@ -1,7 +1,7 @@ require "cases/helper" class CopyTableTest < ActiveRecord::TestCase - fixtures :companies, :comments + fixtures :customers, :companies, :comments def setup @connection = ActiveRecord::Base.connection @@ -27,8 +27,8 @@ class CopyTableTest < ActiveRecord::TestCase test_copy_table('customers', 'customers2', :rename => {'name' => 'person_name'}) do |from, to, options| expected = column_values(from, 'name') - assert expected.any?, 'only nils in resultset; real values are needed' assert_equal expected, column_values(to, 'person_name') + assert expected.any?, "No values in table: #{expected.inspect}" end end diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index e177235591..a64c01292f 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -38,24 +38,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_raise(ActiveRecord::StaleObjectError) { p2.save! } end - def test_lock_destroy - p1 = Person.find(1) - p2 = Person.find(1) - assert_equal 0, p1.lock_version - assert_equal 0, p2.lock_version - - p1.first_name = 'stu' - p1.save! - assert_equal 1, p1.lock_version - assert_equal 0, p2.lock_version - - assert_raises(ActiveRecord::StaleObjectError) { p2.destroy } - - assert p1.destroy - assert_equal true, p1.frozen? - assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) } - end - def test_lock_repeating p1 = Person.find(1) p2 = Person.find(1) @@ -282,11 +264,14 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) assert first.end > second.end end - def test_second_lock_waits - assert [0.2, 1, 5].any? { |zzz| - first, second = duel(zzz) { Person.find 1, :lock => true } - second.end > first.end - } + # Hit by ruby deadlock detection since connection checkout is mutexed. + if RUBY_VERSION < '1.9.0' + def test_second_lock_waits + assert [0.2, 1, 5].any? { |zzz| + first, second = duel(zzz) { Person.find 1, :lock => true } + second.end > first.end + } + end end protected diff --git a/activerecord/test/fixtures/edges.yml b/activerecord/test/fixtures/edges.yml index c16c70dd2f..b804f7b6a6 100644 --- a/activerecord/test/fixtures/edges.yml +++ b/activerecord/test/fixtures/edges.yml @@ -1,6 +1,5 @@ <% (1..4).each do |id| %> edge_<%= id %>: - id: <%= id %> source_id: <%= id %> sink_id: <%= id + 1 %> -<% end %>
\ No newline at end of file +<% end %> diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 15e5e12d03..0dd9da4c11 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -160,7 +160,7 @@ ActiveRecord::Schema.define do t.integer :access_level, :default => 1 end - create_table :edges, :force => true do |t| + create_table :edges, :force => true, :id => false do |t| t.column :source_id, :integer, :null => false t.column :sink_id, :integer, :null => false end |