ユーザーの作成・編集機能

はじめに

画面を操作してユーザーの情報を作成・編集ができるようにします。
railsの基本的な機能をなぞる箇所が多いのでrailsの説明は少なめになります。
今回の実装の中で理解が不十分な箇所が見つかった場合はrails tutorialやprogateを見返してみてください。

ゴールの確認

ちょっと横着して作成も編集の同じ/userのページに実装します。
初めてuserを作成するときは作成を。それ以降は編集機能にします。

ここでの差分はgithubにあげています。困った際に参照ください

とにかく画面を映す

まずは/userを指定したときにどんな形でもいいので画面が表示されるようにしましょう。

今の時点で/userにアクセスすると当然エラーになります。

No route matches [GET] "/user"

routeの設定

エラーを見るにrouteが設定されていないとのことなのでrouteを設定します

# frozen_string_literal: true

Rails.application.routes.draw do
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  resource :user, only: [:show]
end

早速ですが、補足しておきたいことがあります。

今の段階で解放したいrouteは/userのみですが、resourceを使っています。
routeの書き方としてはget '/patients/:id', to: 'patients#show', as: 'patient' のようにリクエスト名にパスを指定しながら書く方法がありますが、特段の理由がない限りresourcesを使った書き方にこだわります。生成されるパスに規則性が生まれますし、新しくパスを増やすたいときに少しの変更で追加できるので基本的にはresourcesを使ってパスを増やしていくのがよいでしょう。

また、resources(複数形)ではなくresource(単数系)のメソッドを使っている点にも注意が必要です。
resourcesとresourceでは生成されるパスが異なります。
今回はuserはポートフォリオ管理者の情報のみを作成・編集するページでuserは必ず一人です。そのためindexページは不要ですし、編集ページにおいても必ず一人のみを編集するため、パラメータにidがなくてもレコードを特定することができます。
このようなユースケースのときにresourceメソッドを採用すると最小限のパスを提供してくれます。

それでは改めて/userにアクセスしてみましょう。
まだエラーになります。
uninitialized constant UsersController

Controllerの作成

エラーの内容からコントローラがないとのことなのでコントローラを作成しましょう

class UsersController < ApplicationController

  def show
  end
end

それではまだ/userにアクセスしてみます。
まだエラーになりますね。
UsersController#show is missing a template for request formats: text/html

テンプレートの作成

エラーの内容からアクションに該当するテンプレートファイルがないとのことなので作成しましょう

<div>
  this is user edit page
</div>

/userにアクセスしてみましょう。
今度は表示されました。

bootstrapのインストール

ここから、formをこの画面に作成していくのですが、一からそれなりに使いやすいフォームをhtmlで実装していくのは大変です。この先もhtmlやcssで時間を使われるのは辛いですし、ないよりこの画面はポートフォリオ管理者(自分自身)しか見ることはないので見た目にそこまでこだわりたくはないです。
そこで、cssのフレームワークであるbootstrapをインストールしたいです。

インストール方法

インストールの方法はrubygem.orgのbootstrapのドキュメントを参照してもらうのが最新のより正しい情報を参照できてよいです。

インストール方法はバージョンによって大きく変わってしまいます。
また、つまづきポイントもそれなりにあると思われます。
全ての手順を上から正確にできているか。そもそもbundle installが成功しているか
それでも動作しない場合はdocker compose up –buildをしてイメージを作成し直してみてください

私がbootstrapをインストールする過程で生じた差分を載せておきます

作成機能

作成機能を作るにあたってフォームをまず作ります。bootstrapを使って見た目のフォームを先に作ってからそのフォームにrubyを埋め込んでいきます。
最終的なhtml.erbを載せておきます。この時点だと必要なメソッドがコントローラで実装されていないのでエラーになると思います。

<div class="container">
  <main>
    <div class="row g-5 py-5">
      <div class="col-md-7 col-lg-8">
        <h4 class="mb-3">Intoduction</h4>
        <%= form_with model: user, url: user_path do |f| %>
          <div class="row g-3">
            <div class="col-12">
              <%= f.label :name, "Name", class: "form-label" %>
              <%= f.text_field :name, class: "form-control" %>
              <div class="invalid-feedback">
                Valid first name is required.
              </div>
            </div>

            <div class="col-12">
              <%= f.label :email, "Email", class: "form-label" %>
              <%= f.email_field :email, class: "form-control", placeholder: "you@example.com" %>
              <div class="invalid-feedback">
                Please enter a valid email address for shipping updates.
              </div>
            </div>
            <div class="col-sm-6">
              <%= f.label :postal_code, "Postal Code", class: "form-label" %>
              <%= f.text_field :postal_code, class: "form-control" %>
            </div>

            <div class="col-12">
              <%= f.label :address, "Address", class: "form-label" %>
              <%= f.text_field :address, class: "form-control", placeholder: "1234 Main St" %>
              <div class="invalid-feedback">
                Please enter your shipping address.
              </div>
            </div>

          <hr class="my-4">

          <%= f.submit "Submit", class: "w-100 btn btn-primary btn-lg" %>
        <% end %>
      </div>
    </div>
  </main>
</div>

これを開くためにusersコントローラへ下記を実装してみてください

  def user
    @user ||= User.first || User.new
  end
  helper_method :user

では、フォームが送信されたときのrouteとアクションを実装していきます

# frozen_string_literal: true

Rails.application.routes.draw do
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  resource :user, only: %i[show create]
end
class UsersController < ApplicationController

  def show
  end

  def create
	  user.assign_attributes(user_params)
    if user.save
      redirect_to user_path
    else
      render :show
    end
  end

  def user
    @user ||= User.first || User.new
  end
  helper_method :user


  def user_params
    params.require(:user).permit(:name, :email, :postal_code, :address)
  end
end

フォームに値を入力して送信をしてみてください。レコードが作成されるでしょうか?
もし、すでにレコードを作成済みの場合はDBクライアントツールから直接削除して試してみてください

編集機能

それでは編集機能を作成します

routeに編集用のパスを追加して、アクションをコントローラに実装します

# frozen_string_literal: true

Rails.application.routes.draw do
  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  resource :user, only: %i[show update create]
end
class UsersController < ApplicationController

  def show
  end

  def create
    user.assign_attributes(user_params)
    if user.save
      redirect_to user_path
    else
      render :show
    end
  end

  def update
    user.assign_attributes(user_params)
    if user.save
      redirect_to user_path
    else
      render :show
    end
  end

  def user
    @user ||= User.first || User.new
  end
  helper_method :user


  def user_params
    params.require(:user).permit(:name, :email, :postal_code, :address)
  end
end

作成した後、さらに値を書き換えてフォームを送信してみてください。その値がレコードに反映されることを確認しましょう。

同じフォームを使ってupdateとcreateのパスに都合よくアクションが分かれるのはrailsのform_withのおかげです。form_withの引数に入れているインスタンスがすでにレコードが作成されている場合はputリクエストを。まだレコードが作成されていないインスタンスの場合はpostリクエストを送信する仕様になっています。

フラッシュ

今のままだと、作成、編集に成功・失敗したかどうかが分かりにくいのでフラッシュメッセージを入れます。

方針としては、flashがある場合にメッセージを表示させるためのパーシャルを用意して、それを一番application.html.erbに配置します。
コントローラでflashメッセージを仕込むためのコードを入れておきます

<% flash_mapper = {success: "alert-success", error: "alert-danger" }%>

<% flash.each do |type, message| %>
  <div class="alert <%= flash_mapper.fetch(type.to_sym, "alert-primary") %>" role="alert">
    <%= message %>
  </div>
<% end %>
<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
  </head>

  <body>
    <%= render partial: "shared/flash_message" %>
    <%= yield %>
  </body>
</html>
class UsersController < ApplicationController

  def show
  end

  def create
    user.assign_attributes(user_params)
    if user.save
      flash[:success] = "Record successfully created"
      redirect_to user_path
    else
      flash.now[:error] = "Failed to create Record"
      render :show
    end
  end

  def update
    user.assign_attributes(user_params)
    if user.save
      flash[:success] = "Record successfully updated"
      redirect_to user_path
    else
      flash.now[:error] = "Failed to update Record"
      render :show
    end
  end

  def user
    @user ||= User.first || User.new
  end
  helper_method :user


  def user_params
    params.require(:user).permit(:name, :email, :postal_code, :address)
  end
end

まとめ

なるべく自分が実装する手順に近い順番で紹介をしてきましたが、もっと細かく動作確認を繰り返しながら実装しています。特にcontrollerについてはbinding.pryを入れてコンソールで試してうまくいったコードをソースコードに順番に書いていっています。

いろんなところでブレークポイントを駆使してメソッドの返り値や動作を確かめつつ実装していくとrubyやrailsへの解像度も高まると思います。

コメント

タイトルとURLをコピーしました