This page looks best with JavaScript enabled

【Rails】ルーティングのカスタムparamとform_withの自動生成URLを両立する

 ·  ☕ 2 分で読めます
✏️

環境

  • Ruby 3.0.2
  • Rails 6.1.4

前提

ルーティングでURLの動的な値に好きなカラム(今回はuuid)を指定する。
これによって、/articles/1などの単純な連番のURLではなく、articles/slug-nameのようにわかりやすいURLやarticles/5Kx8uaDy_Vynのような推測されにくいURLを指定することができる。

1
2
3
Rails.application.routes.draw do
  resources :articles, param: :uuid
end
1
2
3
4
5
6
7
8
9
class ArticlesController < ApplicationController
  def new
    @article = Article.new
  end

  def edit
    @article = Article.find_by!(uuid: params[:uuid])
  end
end

問題点

その1

article_path(article)のように引数にオブジェクトを渡したときの値が/articles/1のようになる。
そのため、期待するURLを取得するためにはarticle_path(article.uuid)のように、引数に値を直接入れる必要がある。

その2

newとeditでフォームを共有しているとき、form_withでよしなにURLを生成したいときがある。
しかし、単に以下のようにすることはできない。

1
2
3
4
<%= form_with model: @article do |f| %>
  <%= f.label :title %>
  <%= f.text_field :title %>
  <%= f.submit %>

これでは、POST /articlesPATCH /articles/1のように、:idのURLになってしまう。

解決法

1
2
3
4
5
class Article < ApplicationRecord
  def to_param
    uuid
  end
end

または

1
2
3
class Article < ApplicationRecord
  to_param :uuid
end

で、パスの引数にオブジェクトをまるごと渡したときのカラムを指定できる。

ちなみに、前者は/articles/{uuidの値}になり、後者は/articles/{idの値}-{uuidの値}になる。
どちらも、コントローラで取得するparams[:uuid]の値は同じになる。

余談

ちなみに、これはカラムである必要はないので、

1
2
3
4
5
class Article < ApplicationRecord
  def to_param
    created_at.strftime("%Y%m%d%H%M%S%N")
  end
end

のようなことも可能ではある。
(ナノ秒単位で同じレコードが存在しないことが前提ではあるが)(ついでに、単純にはfind_byできないですね)(使えるようにカスタマイズする必要はありそう)

参考

Share on

END
END
@aiandrox

目次