Обратный вызов ActiveRecord не выполняется - PullRequest
2 голосов
/ 03 ноября 2011

С учетом следующей спецификации:

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?
    !!@invite_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 Ответ

7 голосов
/ 11 ноября 2011

По умолчанию для ассоциации own_to установлено значение :autosave => true, поэтому запись пользователя сохраняется при сохранении Partecipation, а активная запись реализует его, просто определяя обратный вызов before_save, который сохраняет пользователя.

Посколькуобратные вызовы before_create вызываются после обратных вызовов 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?

и поставить его передостоит__ *

. Вот статья, котораяобъясняет это: 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

...