Search code examples
actionmailerdelayed-jobruby-on-rails-4.2rails-activejob

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


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!


Solution

  • 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 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

    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.