I'm using Delayed Job as my ActiveJob
queuing backend, and I'm trying to send out emails using ActionMailer
's deliver_later
method. I believe I've got all of Delayed Job's setup correct, and I'm running a background worker on my development machine.
When I send out a password reset email, I receive the following error:
[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]
Here is how I'm sending out the password reset email. This is located in my User
model:
def send_password_reset_email
UserMailer.password_reset(self).deliver_later
end
My password reset setup is very similar to the one on this SO post in that I don't store my reset_token
in the database and instead have it as a virtual attribute, and I think that might be my issue, but I want to avoid storing that value if possible. Is there a way I can pass the generated reset_token
to the Delayed Job worker? It is also possible that my problem is related to something else.
Any help would be greatly appreciated!
I figured it out! I had the answer all along; I had to store my reset_token
in the database. I'll copy the answer from this Stack Overflow post below. Credit goes to sevenseacat for the answer.
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 thereset_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 usingreset_digest
in the URL
After I changed my reset_token
from a virtual attribute to a database column, the issue was resolved. My password reset emails are now being sent out.
EDIT (January 18, 2016)
I wanted to add a little extra information explaining why the reset_token
solved the issue even though the error message claimed that the id
was missing. In my password reset email, I generate the edit
password reset action URL as follows:
<%= edit_password_resets_path(@user.reset_token) %>
The route for my edit password reset action is as follows:
edit_password_resets GET /password_resets/:id/edit
When creating a URL, the first parameter that you specify fills in the :id
segment of the URL. In my case, @user.reset_token
was being filled in for the id
, causing the generated URL to be /password_resets/{reset token here}/edit
. When the async job was trying to generate the URL, it was expecting a value to be specified for the id
segment of the URL. I put my reset_token
in for the id
, and since reset_token
was a virtual attribute and was equal to nil
when ActiveJob
ran, it threw an error since it was missing a value.