Home > Mobile >  No route matches [GET] when trying to [PATCH] with link_to
No route matches [GET] when trying to [PATCH] with link_to

Time:11-15

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.

  • Related