我可以在after_commit(on :: create)回调中更新Active Record吗?

时间:2014-02-23 23:11:45

标签: ruby-on-rails activerecord callback

创建记录后,我发送了一封电子邮件,我在after_commit回调中发送了这封电子邮件。我想将电子邮件的Message-Id标题保存为记录中的属性,以便稍后使用。我将其实现为:

after_commit on: :create do
  if email = Mailer.email(self).deliver
    # `self.message_id = email.message_id` has no effect, so I'm calling update()
    self.update message_id: email.message_id
  end
end

令人惊讶(对我而言),这会导致无限的电子邮件发送循环(抱歉,Mailgun);看来即使指定了on: :create ,也会在更新时调用回调。

我没有看到这种方法有什么问题吗?我怎么能把这个值附加到这个记录上?

我唯一的想法是尝试gating the callback on previous_changes,但无论如何我想了解为什么这不能正常工作。

3 个答案:

答案 0 :(得分:3)

“我没有看到这种方法有什么问题吗?”

我还希望update回调中的after_commit仅触发on: update回调。但是我在Rails源代码中找到了this

def committed!(should_run_callbacks = true) #:nodoc:
  _run_commit_callbacks if should_run_callbacks && destroyed? || persisted?
ensure
  force_clear_transaction_record_state
end

只有在运行所有after_commit回调之后,Rails才会清除事务的new_record状态。

“我还能如何将此值附加到此记录中?”

想法1:

self.update_columns message_id: email.message_id

这不会创建事务,因此不会触发另一个after_commit回调。我认为在交易记录外发送电子邮件和商店ID是合适的。毕竟,如果出现故障,您无法使用消息ID 回滚发送电子邮件来回滚更新记录。它本质上不是原子的。

想法2:

after_commit回调队列中,发送带有消息ID的电子邮件和更新记录的ActiveJob作业。使用像SuckerPunch或DelayedJob这样的真实后端,作业将以记录“Global ID”排队。 Job的perform方法获取一个新加载的记录,其实例变量中没有存储事务状态。

答案 1 :(得分:0)

这可能有效

after_commit :email_send, :if => lambda{ new_record? }

after_commit :email_send, :on => :create

after_create :email_send


def email_send
    if email = Mailer.email(self).deliver
        # `self.message_id = email.message_id` has no effect, so I'm calling update()
        self.update message_id: email.message_id
    end
end

答案 2 :(得分:0)

检查message_id的先前存在似乎已在我的特定情况下解决了问题:

after_commit on: :create do
  unless self.message_id
    if email = Mailer.email(self).deliver
      self.update message_id: email.message_id
    end
  end
end

但是我想可能有一种情况我无法逃避,所以理解为什么在更新时调用on: :create回调仍然很好。