I want to send clients that did not complete a checkout an email with a magic link that will log them in before hitting an update
action in a controller.
I'm sending the following link in the email body:
<%= link_to(
"Continue to checkout",
"#{checkout_url(host: @account.complete_url, id: @user.current_subscription_cart)}?msgver=#{@user.create_message_verifier}",
method: :patch,
subscription_cart: { item_id: @item_id },
) %>
My checkouts_controller
has an update
action:
def update
# update cart with item_id param and continue
end
And my routes
look like this:
resources :checkouts, only: [:create, :update]
which gives the following update
route:
checkout_path PATCH /checkouts/:id(.:format) checkouts#update
The link_to
in the email body produces a link with a data-method="patch"
property
<a data-method="patch" href="https://demo.test.io/checkouts/67?msgver=TOKEN">Continue to checkout</a>
=> https://demo.test.io/checkouts/67?msgver=TOKEN
but when I click on it I get the following error:
No route matches [GET] "/checkouts/67"
Why is it attempting a GET
request when I'm specifying method: :patch
?
CodePudding user response:
As pointed out by @AbM you need to send out a link to a route that responds to GET requests. Emails clients are unlikely to let you run JS or include forms in the email body - so you shouldn't assume that you'll be able to send anything but GET.
If you want an example of how this can be done you don't have to look further then the Devise::Confirmable
module that solves pretty much the exact same problem:
Prefix Verb Method URI Pattern Description
new_user_confirmation GET /users/confirmation/new Form for resending the confirmation email
user_confirmation GET /users/confirmation The link thats mailed out with a token added - actually confirms the user
POST /users/confirmation Resends the confirmation email
The beauty of this design is that users confirmation are modeled as a RESTful resource even if no separate model exists.
In your case the implementation could look something like:
resources :checkouts do
resource :confirmation, only: [:new, :create, :show]
end
# Handles email confirmations of checkouts
class ConfirmationsController < ApplicationController
before_action :set_checkout
# GET /checkouts/1/confirmation/new
# Form for resending the confirmation email
def new
end
# POST /checkouts/1/confirmation
# Resends the confirmation email - because shit happens.
def create
@todo generate new token and resend the confirmation email
end
# GET /checkouts/1/confirmation&token=jdW0rSaYXWI7Ck_rOeSL-A
# Confirms the checkout by verifying that a valid token is passed
def show
if @checkout.valid_token?(params[:token])
@checkout.confirm!
redirect_to '/whatever_the_next_step_is'
else
flash.now('Invalid token')
render :new, status: :unauthorized
end
end
private
def set_checkout
@checkout = Checkout.find(params[:checkout_id])
end
end
Here we are taking a slight liberty with the rule that that GET requests should always be idempotent as clicking the link actually updates the checkout. Its a fair tradeoff though as it requires one less click from the user.
If you want to maintain that idempotency you could send a link to the edit
action which contains a form to update
the confirmation. Devise::Invitable does this.