2011-11-02 67 views
2

考虑以下规格:ActiveRecord的回调不执行

describe Participation do 
    describe "invitation by email", :focus do 
    let(:participation) { build :participation } # Factory 
    let(:email)   { participation.email } 

    it "should send an invitation" do 
     # This one is failing 
     binding.pry # The code below is executed here 
     participation.should_receive(:invite_user!) 
     participation.save! 
    end 

    context "when user already exists" do 
     let!(:existing) { create :user, :email => email } 
     it "should not send an invitation" do 
     participation.should_not_receive(:invite_user!) 
     participation.save! 
     end 
    end 
    end 
end 

我似乎无法与以下实现传递:

class Participation < ActiveRecord::Base 
    attr_accessor :email 

    belongs_to :user 
    validates :email, :email => true, :on => :create, :if => :using_email? 

    before_validation :set_user_by_email, :if  => :using_email?, :on => :create 
    before_create  :mark_for_invitation, :unless => :user_exists? 
    after_create  :invite_user!,  :if  => :marked_for_invitation? 


    def using_email? 
    email.present? 
    end 

    def user_exists? 
    user.present? and user.persisted? 
    end 

    def set_user_by_email 
    self.user = User.find_by_email(email) 
    self.user ||= User.new(email: email).tap do |u| 
     u.status = :invited 
    end 
    end 

    def mark_for_invitation 
    @invite_user = true 
    true # make sure not cancelling the callback chain 
    end 

    def marked_for_invitation? 
    [email protected]_user 
    end 

    def invite_user! 
    # TODO: Send the invitation email or something 
    end 
end 

我真的不能看到我在做什么错误。下面是从失败规范“控制台”输出:

# Check the before_validation callback options: 
participation.user # nil 
participation.valid? # true 
participation.user # User{id: nil} 

# Check the before_create callback options: 
participation.user_exists? # false 
participation.mark_for_invitation # true 

# Check the after_create callback options: 
participation.marked_for_invitation? # true 

# After all this I expect the "invite_user!" to be called: 
participation.stub(:invite_user!) { puts "Doesn't get called :(" } 
participation.save! # => true, Nothing is printed, which is consistent with the spec 
participation.user_id # => 11, so the user has been saved 

你能找出问题,为什么User#invite_user!不叫?

+1

您能否提供rspec和rails版本?该代码没有明显的问题。 – Sigurd

+1

如果使用after_save而不是after_create回调,它会工作吗? – John

+0

确实如此。 –

回答

6

belongs_to关联默认为:autosave => true,因此用户记录会在保存Partecipation时保留下来,活动记录实现它的方式是通过简单定义保存用户的before_save回调。

由于before_screate回调函数在before_save回调函数[1]后被调用,所以mark_for_invitation回调函数在用户关联被保存后被“调用”,因此它永远不会被实际执行,因为:user_exists?方法在那一点上总是为真。

解决的办法是改变

before_create :mark_for_invitation, :unless => :user_exists?

成:

before_save :mark_for_invitation, :on=>:create, :unless => :user_exists? 

和belongs_to的

这里之前就把它说明这个问题的文章:http://pivotallabs.com/users/danny/blog/articles/1767-activerecord-callbacks-autosave-before-this-and-that-etc-

[1]参见: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

+0

非常感谢您对问题的解释和关注。 –