has_one supports the :dependent => :delete option which skips the typical callback chain and deletes the associated object directly from the database. Closes #5927.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4848 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Jeremy Kemper 2006-08-29 17:06:27 +00:00
parent 92f1e26a1c
commit 3704088ebd
9 changed files with 64 additions and 16 deletions

View File

@ -1,5 +1,7 @@
*SVN* *SVN*
* has_one supports the :dependent => :delete option which skips the typical callback chain and deletes the associated object directly from the database. #5927 [Chris Mear, Jonathan Viney]
* Nested subclasses are not prefixed with the parent class' table_name since they should always use the base class' table_name. #5911 [Jonathan Viney] * Nested subclasses are not prefixed with the parent class' table_name since they should always use the base class' table_name. #5911 [Jonathan Viney]
* SQLServer: work around bug where some unambiguous date formats are not correctly identified if the session language is set to german. #5894 [Tom Ward, kruth@bfpi] * SQLServer: work around bug where some unambiguous date formats are not correctly identified if the session language is set to german. #5894 [Tom Ward, kruth@bfpi]

View File

@ -570,8 +570,9 @@ module ActiveRecord
# sql fragment, such as "rank = 5". # sql fragment, such as "rank = 5".
# * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as # * <tt>:order</tt> - specify the order from which the associated object will be picked at the top. Specified as
# an "ORDER BY" sql fragment, such as "last_name, first_name DESC" # an "ORDER BY" sql fragment, such as "last_name, first_name DESC"
# * <tt>:dependent</tt> - if set to :destroy (or true) all the associated objects are destroyed when this object is. Also, # * <tt>:dependent</tt> - if set to :destroy (or true) the associated object is destroyed when this object is. If set to
# association is assigned. # :delete the associated object is deleted *without* calling its destroy method. If set to :nullify the associated
# object's foreign key is set to NULL. Also, association is assigned.
# * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id" # of this class in lower-case and "_id" suffixed. So a +Person+ class that makes a has_one association will use "person_id"
# as the default foreign_key. # as the default foreign_key.
@ -1020,12 +1021,14 @@ module ActiveRecord
case reflection.options[:dependent] case reflection.options[:dependent]
when :destroy, true when :destroy, true
module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'" module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
when :delete
module_eval "before_destroy '#{reflection.class_name}.delete(#{reflection.name}.id) unless #{reflection.name}.nil?'"
when :nullify when :nullify
module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'" module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil)'"
when nil, false when nil, false
# pass # pass
else else
raise ArgumentError, "The :dependent option expects either :destroy or :nullify." raise ArgumentError, "The :dependent option expects either :destroy, :delete or :nullify."
end end
end end

View File

@ -91,6 +91,10 @@ end
class HasOneAssociationsTest < Test::Unit::TestCase class HasOneAssociationsTest < Test::Unit::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects fixtures :accounts, :companies, :developers, :projects, :developers_projects
def setup
Account.destroyed_account_ids.clear
end
def test_has_one def test_has_one
assert_equal companies(:first_firm).account, Account.find(1) assert_equal companies(:first_firm).account, Account.find(1)
assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit
@ -168,8 +172,22 @@ class HasOneAssociationsTest < Test::Unit::TestCase
num_accounts = Account.count num_accounts = Account.count
firm = Firm.find(1) firm = Firm.find(1)
assert !firm.account.nil? assert !firm.account.nil?
account_id = firm.account.id
assert_equal [], Account.destroyed_account_ids[firm.id]
firm.destroy firm.destroy
assert_equal num_accounts - 1, Account.count assert_equal num_accounts - 1, Account.count
assert_equal [account_id], Account.destroyed_account_ids[firm.id]
end
def test_exclusive_dependence
num_accounts = Account.count
firm = ExclusivelyDependentFirm.find(9)
assert !firm.account.nil?
account_id = firm.account.id
assert_equal [], Account.destroyed_account_ids[firm.id]
firm.destroy
assert_equal num_accounts - 1, Account.count
assert_equal [], Account.destroyed_account_ids[firm.id]
end end
def test_succesful_build_association def test_succesful_build_association

View File

@ -8,7 +8,7 @@ class CalculationsTest < Test::Unit::TestCase
fixtures :companies, :accounts, :topics fixtures :companies, :accounts, :topics
def test_should_sum_field def test_should_sum_field
assert_equal 265, Account.sum(:credit_limit) assert_equal 318, Account.sum(:credit_limit)
end end
def test_should_average_field def test_should_average_field
@ -49,13 +49,13 @@ class CalculationsTest < Test::Unit::TestCase
def test_should_order_by_grouped_field def test_should_order_by_grouped_field
c = Account.sum(:credit_limit, :group => :firm_id, :order => "firm_id") c = Account.sum(:credit_limit, :group => :firm_id, :order => "firm_id")
assert_equal [1, 2, 6], c.keys.compact assert_equal [1, 2, 6, 9], c.keys.compact
end end
def test_should_order_by_calculation def test_should_order_by_calculation
c = Account.sum(:credit_limit, :group => :firm_id, :order => "sum_credit_limit desc, firm_id") c = Account.sum(:credit_limit, :group => :firm_id, :order => "sum_credit_limit desc, firm_id")
assert_equal [105, 60, 50, 50], c.keys.collect { |k| c[k] } assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] }
assert_equal [6, 2, 1], c.keys.compact assert_equal [6, 2, 9, 1], c.keys.compact
end end
def test_should_limit_calculation def test_should_limit_calculation
@ -114,8 +114,8 @@ class CalculationsTest < Test::Unit::TestCase
end end
def test_should_calculate_with_invalid_field def test_should_calculate_with_invalid_field
assert_equal 5, Account.calculate(:count, '*') assert_equal 6, Account.calculate(:count, '*')
assert_equal 5, Account.calculate(:count, :all) assert_equal 6, Account.calculate(:count, :all)
end end
def test_should_calculate_grouped_with_invalid_field def test_should_calculate_grouped_with_invalid_field
@ -193,7 +193,7 @@ class CalculationsTest < Test::Unit::TestCase
end end
def test_should_count_selected_field_with_include def test_should_count_selected_field_with_include
assert_equal 5, Account.count(:distinct => true, :include => :firm) assert_equal 6, Account.count(:distinct => true, :include => :firm)
assert_equal 3, Account.count(:distinct => true, :include => :firm, :select => :credit_limit) assert_equal 4, Account.count(:distinct => true, :include => :firm, :select => :credit_limit)
end end
end end

View File

@ -454,8 +454,8 @@ class FinderTest < Test::Unit::TestCase
end end
def test_select_values def test_select_values
assert_equal ["1","2","3","4","5","6","7","8"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s } assert_equal ["1","2","3","4","5","6","7","8","9"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s }
assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel"], Company.connection.select_values("SELECT name FROM companies ORDER BY id") assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
end end
protected protected

View File

@ -20,4 +20,9 @@ last_account:
rails_core_account_2: rails_core_account_2:
id: 5 id: 5
firm_id: 6 firm_id: 6
credit_limit: 55 credit_limit: 55
odegy_account:
id: 6
firm_id: 9
credit_limit: 53

View File

@ -47,4 +47,9 @@ leetsoft:
jadedpixel: jadedpixel:
id: 8 id: 8
name: Jadedpixel name: Jadedpixel
client_of: 6 client_of: 6
odegy:
id: 9
name: Odegy
type: ExclusivelyDependentFirm

View File

@ -42,6 +42,9 @@ class DependentFirm < Company
has_many :companies, :foreign_key => 'client_of', :order => "id", :dependent => :nullify has_many :companies, :foreign_key => 'client_of', :order => "id", :dependent => :nullify
end end
class ExclusivelyDependentFirm < Company
has_one :account, :foreign_key => "firm_id", :dependent => :delete
end
class Client < Company class Client < Company
belongs_to :firm, :foreign_key => "client_of" belongs_to :firm, :foreign_key => "client_of"
@ -83,6 +86,18 @@ end
class Account < ActiveRecord::Base class Account < ActiveRecord::Base
belongs_to :firm belongs_to :firm
def self.destroyed_account_ids
@destroyed_account_ids ||= Hash.new { |h,k| h[k] = [] }
end
before_destroy do |account|
if account.firm
Account.destroyed_account_ids[account.firm.id] << account.id
end
true
end
protected protected
def validate def validate
errors.add_on_empty "credit_limit" errors.add_on_empty "credit_limit"

View File

@ -58,7 +58,7 @@ class InheritanceTest < Test::Unit::TestCase
end end
def test_inheritance_condition def test_inheritance_condition
assert_equal 8, Company.count assert_equal 9, Company.count
assert_equal 2, Firm.count assert_equal 2, Firm.count
assert_equal 3, Client.count assert_equal 3, Client.count
end end