Techioz Blog

Rails 7 - 新しいレコードの作成中にルーティングエラーが発生する

概要

新しいレコードの作成中にルーティング エラーが発生します。 Rails が POST リクエストを GET とみなす理由が理解できません。詳細は以下のとおりです。

以下のようなフォームがあります。

<%= form_with model: @user, url: home_new_user_path, method: :post do |f|%>

<%= hidden_field_tag :authenticity_token, form_authenticity_token %>

<%= f.text_field :name, placeholder: "Enter name", class: 'form-content form-content w-100 p-1', autocomplete: "off" %>

<%= f.text_field :email, placeholder: "Enter email", class: 'form-content form-content w-100 p-1', autocomplete: "off" %> 


<%= f.text_field :password, placeholder: "Enter password", class: 'form-content form-content w-100 p-1', autocomplete: "off" %>


<%= f.text_field :password_confirmation, placeholder: "Enter password to confirm", class: 'form-content form-content w-100 p-1', autocomplete: "off" %>

<%= f.submit %>

<%end%>

以下は Routes.rb です

  post 'home/new_user', to: 'home#new_user'

以下はコントローラーのメソッドです。

  def new_user
   user_to_be_created = User.create!(name: params[:name], email: params[:email], password: params[:password], password_confirmation: params[:password_confirmation])
  end 

このコントローラー メソッドにアクセスするコードを表示します。

<%= link_to "New user", home_new_user_path, method: :post %>

ただし、次のようなルーティング エラーが発生します。

No route matches [GET] "/home/new_user"

解決策

Rails を使用しているかもしれませんが、作成しているコードは Rails コードではありません。

Rails では、レコードの作成は、new と create という 2 つの別々のアクションによって処理されます。

HTTP Method   Path         Description
----------------------------------------------------------------
GET           /users/new   Renders a form which is used to create a user.
POST          /users       Responds to form submissions and creates the user

フォームを表示する新しいアクションは、べき等アクションであるため、GET を使用します。何も作成または変更せず、すべての訪問者には同じように見えます。

実際のレコードの作成は、コレクション パス (/users) にフォームをポストすることで行われます。レコードが有効でない場合、新しいビューは応答としてレンダリングされますが、今回は検証によるエラー メッセージが含まれている必要があります。

これらのルートは、リソース マクロを使用して生成できます。

# routes.rb
resources :users, only: [:new, :create]

新しいアクションへのリンクは次のようにして行われます。

<%= link_to "New user", new_user_path %>

慣例により、これらのリクエストには UsersController で応答します。

アプリケーション内のすべてをゴッドクラス HomeController にまとめてしまうのは、良い習慣とは言えません。

class UsersController < ApplicationController
  before_action :authenticate_user!
  # @todo authorize that the current user should be allowed to create users

  # GET /users/new
  def new
    @user = User.new
  end

  # POST /users/new
  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to '/somewhere' # @todo replace with actual path
    else
      render :new
    end
  end

  private

  def user_params
    params.require(:user)
          .permit(
            :name, :email, :password, :password_confirmation
          )
  end
end

フォームでも次の規則を使用する必要があります。

# app/views/users/_form.html.erb
<%= form_with model: @user do |f|%>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>
 
      <ul>
      <% @user.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%# do not use placeholders as labels. It's a huge accessibilty anti-pattern %>
    <%= f.label :name %> 
    <%= f.text_field :name, class: 'form-content form-content w-100 p-1', autocomplete: "off" %>
  </div>
  <div class="field">
    <%= f.label :email %>
    <%= f.text_field :email, class: 'form-content form-content w-100 p-1', autocomplete: "off" %> 
  </div>
  <div class="field">
    <%= f.label :password %>
    <%= f.text_field :password, class: 'form-content form-content w-100 p-1', autocomplete: "off" %>
  </div>
  <div class="field">
    <%= f.label :password_confirmation %>
    <%= f.text_field :password_confirmation, class: 'form-content form-content w-100 p-1', autocomplete: "off" %>
  </div>
  <div class="action"> 
    <%= f.submit %>
  </div>
<% end %>
# app/views/users/new.html.erb
<%= render partial: "form" %>

User のインスタンスを渡すと、Rails はモデルが永続化されているかどうかを確認し、自動的にリクエスト メソッドを POST に設定し、クラス名から正しいルート users_path を導出します。どちらも明示的に設定しないことで、同じフォームを編集に再利用できます。

実際に運用アプリでこれを使用することを計画している場合は、代わりに Devise::Invitable を使用することを強くお勧めします。これにより、管理者がユーザーのパスワードを設定し、そのパスワードをユーザーに伝えるという煩わしさと潜在的なセキュリティ上の危険がなくなります。

車輪の再発明はしないでください。