ActionMailer 找不到 reset_token,报告缺少 :id 键

ActionMailer can't find reset_token, reports missing :id key

我正在使用 Delayed Job 作为我的 ActiveJob 排队后端,我正在尝试使用 ActionMailerdeliver_later 方法发送电子邮件。我相信我已经正确设置了所有 Delayed Job 的设置,并且我是 运行 我的开发机器上的后台工作者。

当我发送密码重置电子邮件时,我收到以下错误:

[Worker(host:Computer pid:7240)] Job ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper (id=1) FAILED (5 prior attempts) with ActionView::Template::Error: No route matches {:action=>"edit", :controller=>"password_resets", :email=>"user@example.com", :id=> nil} missing required keys: [:id]

这是我发送密码重置电子邮件的方式。这位于我的 User 模型中:

def send_password_reset_email
  UserMailer.password_reset(self).deliver_later
end

我的密码重置设置与 上的设置非常相似,因为我没有将 reset_token 存储在数据库中,而是将其作为虚拟属性,我认为可能是我的问题,但我想尽可能避免存储该值。有没有办法可以将生成的 reset_token 传递给延迟作业工作人员?也有可能我的问题与其他问题有关。

如有任何帮助,我们将不胜感激!

我想通了!我一直都有答案;我必须将 reset_token 存储在数据库中。我将从下面的 中复制答案。感谢 sevenseacat 的回答。

When you don't use workers, you're storing the reset_token in the User instance, then passing that same User instance to your mailer - hence the reset_token is still available.

When you use workers, your worker only has the User's ID, so it's reloading the User instance from the database. Because the reset_token isn't being stored in the database, it's coming back nil.

Either you should be saving the reset_token in the database, or your password email should be using reset_digest in the URL

我将 reset_token 从虚拟属性更改为数据库列后,问题就解决了。我的密码重置邮件正在发送中。

编辑(2016 年 1 月 18 日)

我想添加一些额外的信息来解释为什么 reset_token 解决了问题,即使错误消息声称缺少 id。在我的密码重置电子邮件中,我生成 edit 密码重置操作 URL,如下所示:

<%= edit_password_resets_path(@user.reset_token) %>

我的修改密码重置操作路径如下:

edit_password_resets   GET   /password_resets/:id/edit

创建 URL 时,您指定的第一个参数将填充 URL 的 :id 段。在我的例子中,@user.reset_token 被填充为 id,导致生成的 URL 成为 /password_resets/{reset token here}/edit。当异步作业尝试生成 URL 时,它期望为 URL 的 id 段指定一个值。我把我的 reset_token 放在 id 中,因为 reset_token 是一个虚拟属性,当 ActiveJob 运行 时等于 nil,它由于缺少值而引发错误。

我只是想自己解决一个类似的问题,但得出了一个略有不同的解决方案。为避免将重置令牌存储在数据库中,您可以重构 UserMailer.password_reset(self) 以采用两个参数 UserMailer.password_reset(self, self.reset_token)。然后创建两个实例变量传递给邮件程序模板:

@user = user
@reset_token = reset_token

最后在邮件程序模板中,您可以这样做:

<%= edit_password_resets_path(@reset_token) %>`