【Rails7】 名前空間付きコントローラにおけるlink_to 削除でRoutingErrorになった

管理者向けの商品削除機能を実施するために、名前空間付きコントローラー(今回のケースではadmin/products)でlink_toメソッドを使用しました。

この際、名前空間の存在を意識せずに実装するとRoutingErrorになったので、原因と解消方法を投稿します。


目次


環境

背景

ディレクトリ構造:

app/
|-- controllers/
|   |-- admin/
|   |   `-- products_controller.rb
|   `-- products_controller.rb
|-- views/
|   |-- admin/
|   |   `-- products/
|   |       `-- index.html.erb
|   `-- products/
|       `-- index.html.erb
...

ルーティング:

namespace :admin do
    resources :products
  end
  resources :products, only: %i[index show]

コントローラ:

# app/controllers/products_controller.rb
class ProductsController < ApplicationController
  def index
    @products = Product.all
  end

  def show
    @product = Product.find(params[:id])
  end
end

# app/controllers/admin/products_controller.rb

class Admin::ProductsController < ApplicationController
  before_action :set_product, only: %i[ edit update destroy ]

# 中略 #

  def destroy
    @product.destroy
    redirect_to admin_products_path, flash: { notice: "#{@product.name}を削除しました"}
  end

  private

  def set_product
    @product = Product.find_by(id: params[:id])
  end

end

一般ユーザー向けにはdomain/productsで商品一覧ページを表示させ、管理者向けにdomain/admin/productsで管理画面内の商品一覧ページを表示させ、そこで削除機能を実装します。

エラーの内容

今回、管理画面内の商品一覧ページに下記のような削除リンクを設けました。

app/views/admin/products/index.html.erb

<% @products.each do |product| %>
~~
 <%= link_to product, data: { turbo_method: :delete, turbo_confirm: '削除してもよろしいですか?'} %>
~~
<% end %>

link_to の引数に、インスタンスを渡し、turbo_method: :deleteを指定することで、destroyアクションの実行を期待しました。

しかし、以下のようなエラーが出てしまいました。

ActionController::RoutingError (No route matches [DELETE] "/products/[:id]"):

ルーティングも設定しているのに、ルーティングエラーが発生とは・・・。

エラーの原因

エラーの内容をよく見ると[DELETE] "/products/[:id]")ということで、admin/products/[:id]ではなく、一般ユーザー向けに作成したproductsのパスになっていました。

そもそも、link_toメソッドの引数にモデルオブジェクト(インスタンス)を渡すと、そのオブジェクトのshowアクションにマッピングされるURL(例えば /products/1)が生成されます。

そのため、link_toメソッドでは引数にデルオブジェクト(インスタンス)を渡し、turbo_method: :deleteを指定するだけで、特定の商品を削除できるはずでした。

しかし、上記のコードでは、名前空間を考慮せずに標準の商品コントローラーへのパスを構築してしまうため、admin配下の商品コントローラにルーティングされるよう明示的に記述する必要があったようです。

link_toがあまりにもスマートな機能だからといって、考えなしにインスタンスを指定するのはよくありませんでした。。。

解決策

この問題を回避するためには、link_toメソッドに明示的にパスを指定する必要があります。

具体的には、以下の2通りがあります

# 名前空間を明示的に指定したパスヘルパー
<%= link_to admin_product_path(product), data: { turbo_method: :delete, turbo_confirm: '削除してもよろしいですか?'} %>
# ポリモーフィックURLヘルパー
<%= link_to [ :admin, product ], data: { turbo_method: :delete, turbo_confirm: '削除してもよろしいですか?'} %>

参考:https://runebook.dev/ja/docs/rails/actiondispatch/routing/polymorphicroutes

いずれにしても、admin配下のproductsコントローラを使うことを明示的に指定しないといけません。