has_one maintains the association with separate after_create/after_update

This way parent models can get their own after_create and
after_update callbacks fired after has_one has done its job.
This commit is contained in:
Xavier Noria 2010-10-22 10:28:53 +02:00
parent fc8c072988
commit 5b86c3e5bb
5 changed files with 78 additions and 1 deletions

View File

@ -1,5 +1,8 @@
*Rails 3.1.0 (unreleased)*
* has_one maintains the association with separate after_create/after_update instead
of a single after_save. [fxn]
* The following code:
Model.limit(10).scoping { Model.count }

View File

@ -167,7 +167,16 @@ module ActiveRecord
else
if reflection.macro == :has_one
define_method(save_method) { save_has_one_association(reflection) }
after_save save_method
# Configures two callbacks instead of a single after_save so that
# the model may rely on their execution order relative to its
# own callbacks.
#
# For example, given that after_creates run before after_saves, if
# we configured instead an after_save there would be no way to fire
# a custom after_create callback after the child association gets
# created.
after_create save_method
after_update save_method
else
define_method(save_method) { save_belongs_to_association(reflection) }
before_save save_method

View File

@ -17,6 +17,7 @@ require 'models/tag'
require 'models/tagging'
require 'models/treasure'
require 'models/company'
require 'models/eye'
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
def test_autosave_should_be_a_valid_option_for_has_one
@ -170,6 +171,25 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
firm.account = Account.find(:first).clone
assert_queries(2) { firm.save! }
end
def test_callbacks_firing_order_on_create
eye = Eye.create(:iris_attributes => {:color => 'honey'})
assert_equal [true, false], eye.after_create_callbacks_stack
end
def test_callbacks_firing_order_on_update
eye = Eye.create(:iris_attributes => {:color => 'honey'})
eye.update_attributes(:iris_attributes => {:color => 'green'})
assert_equal [true, false], eye.after_update_callbacks_stack
end
def test_callbacks_firing_order_on_save
eye = Eye.create(:iris_attributes => {:color => 'honey'})
assert_equal [false, false], eye.after_save_callbacks_stack
eye.update_attributes(:iris_attributes => {:color => 'blue'})
assert_equal [false, false, false, false], eye.after_save_callbacks_stack
end
end
class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase

View File

@ -0,0 +1,37 @@
class Eye < ActiveRecord::Base
attr_reader :after_create_callbacks_stack
attr_reader :after_update_callbacks_stack
attr_reader :after_save_callbacks_stack
# Callbacks configured before the ones has_one sets up.
after_create :trace_after_create
after_update :trace_after_update
after_save :trace_after_save
has_one :iris
accepts_nested_attributes_for :iris
# Callbacks configured after the ones has_one sets up.
after_create :trace_after_create2
after_update :trace_after_update2
after_save :trace_after_save2
def trace_after_create
(@after_create_callbacks_stack ||= []) << iris.new_record?
end
alias trace_after_create2 trace_after_create
def trace_after_update
(@after_update_callbacks_stack ||= []) << iris.changed?
end
alias trace_after_update2 trace_after_update
def trace_after_save
(@after_save_callbacks_stack ||= []) << iris.changed?
end
alias trace_after_save2 trace_after_save
end
class Iris < ActiveRecord::Base
belongs_to :eye
end

View File

@ -156,6 +156,11 @@ ActiveRecord::Schema.define do
t.integer :company_id
end
create_table :iris, :force => true do |t|
t.integer :eye
t.string :color
end
create_table :customers, :force => true do |t|
t.string :name
t.integer :balance, :default => 0
@ -194,6 +199,9 @@ ActiveRecord::Schema.define do
t.integer :car_id
end
create_table :eyes, :force => true do |t|
end
create_table :tyres, :force => true do |t|
t.integer :car_id
end