LoginSignup
0
0

Railsチュートリアル10章まとめ

Posted at

目的

ユーザーが自分のプロフィールを自分で更新できるようにする

10.1 .1 編集フォーム

編集フォームを作成する

・editアクションの実装

users_controller.rb

class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      reset_session
      log_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new', status: :unprocessable_entity
    end
  end

  def edit
    @user = User.find(params[:id])
  end

  private

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

・ユーザーのeditビュー

<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(model: @user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Save changes", class: "btn btn-primary" %>
    <% end %>

    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="https://gravatar.com/emails" target="_blank">change</a>
    </div>
  </div>
</div>


出力されるeditフォームのHTML

<form action="/users/1" accept-charset="UTF-8" method="post">
  <input type="hidden" name="_method" value="patch" />
  .
  .
  .
</form>

・入力フィールドの隠し属性

<input name="_method" type="hidden" value="patch" />

Webブラウザは、RESTの慣習として要求されているPATCHリクエストをそのままでは送信できないので
POSTリクエストと隠しinputフィールドを利用して、PATCHリクエストを偽造している

・新規ユーザー用のPOSTリクエストとユーザー編集用のPATCHリクエスト
form_with(@user)は新規でも編集でも同じものを使用している
新規と編集は
Active Recordのnew_record?論理値メソッドで区別している
→form_with(@user)を使ってフォームを構成すると、
 @user.new_record?がtrueの場合はPOSTを使い、falseの場合はPATCHを使う

$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false

・ナビゲーションバーにあるユーザー設定へのリンクを更新
<%= link_to "Settings", edit_user_path(current_user) %>

_header.html.erb

<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", root_path, id: "logo" %>
    <nav>
      <div class="navbar-header">
        <button id="hamburger" type="button" class="navbar-toggle collapsed">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
      </div>
      <ul id="navbar-menu"
          class="nav navbar-nav navbar-right collapse navbar-collapse">
        <li><%= link_to "Home", root_path %></li>
        <li><%= link_to "Help", help_path %></li>
        <% if logged_in? %>
          <li><%= link_to "Users", '#' %></li>
          <li class="dropdown">
            <a href="#" id="account" class="dropdown-toggle">
              Account <b class="caret"></b>
            </a>
            <ul id="dropdown-menu" class="dropdown-menu">
              <li><%= link_to "Profile", current_user %></li>
              <li><%= link_to "Settings", edit_user_path(current_user) %></li>
              <li class="divider"></li>
              <li>
                <%= link_to "Log out", logout_path,
                                       data: { "turbo-method": :delete } %>
              </li>
            </ul>
          </li>
        <% else %>
          <li><%= link_to "Log in", login_path %></li>
        <% end %>
      </ul>
    </nav>
  </div>
</header>

10.1.2 編集の失敗

編集に失敗した場合について実装する

updateを使って送信されたparamsハッシュに基いてユーザーを更新

無効な情報が送信された場合、更新の結果としてfalseが返され、elseに分岐して編集ページをレンダリング

users_controller.rb

class UsersController < ApplicationController

  def show
    @user = User.find(params[:id])
  end

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      reset_session
      log_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new', status: :unprocessable_entity
    end
  end

  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    if @user.update(user_params)
      # 更新に成功した場合を扱う
    else
      render 'edit', status: :unprocessable_entity
    end
  end

  private

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

10.1.3 編集失敗時のテスト

editビューがレンダリングされるかどうかをチェック

その後、無効な情報を送信してみて、editビューが再描画されるかどうかをチェック
PATCHリクエストを送るためにpatchメソッドを使っている

users_edit_test.rb

require "test_helper"

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "unsuccessful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    patch user_path(@user), params: { user: { name:  "",
                                              email: "foo@invalid",
                                              password:              "foo",
                                              password_confirmation: "bar" } }

    assert_template 'users/edit'
  end
end

10.1.4 TDDで編集を成功させる

編集フォームが動作するようにする

ユーザー情報を更新する正しい振る舞いをテストで定義する

フラッシュメッセージが空でないかどうか確認

プロフィールページにリダイレクトされるかどうかをチェック

データベース内のユーザー情報が正しく変更されたかどうかも検証

users_edit_test.rb

require "test_helper"

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  test "successful edit" do
    get edit_user_path(@user)
    assert_template 'users/edit'
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end
end

updateアクションに更新成功時の処理を追記
users_controller.rb

class UsersController < ApplicationController
  .
  .
  .
  def update
    @user = User.find(params[:id])
    if @user.update(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit', status: :unprocessable_entity
    end
  end
  .
  .
  .
end

・パスワードが空のままでも更新できるようにする
has_secure_passwordでは追加したバリデーションとは別に、
オブジェクト生成時に存在性を検証するようになっているため、
空のパスワード(nil)が誤って新規ユーザー登録時に有効になることはない

allow_nil: true

user.rb

class User < ApplicationRecord
  attr_accessor :remember_token
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: true
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
  .
  .
  .
end

10.2 認可

認可のためのシステムを実装する

・ログインしていないユーザーが保護されたページにアクセスしようとする場合について対処する
ユーザーをログインページに転送し、わかりやすいメッセージも表示する

10.2.1 ユーザーにログインを要求する

・beforeフィルター
before_actionメソッドを使って何らかの処理が実行される直前に特定のメソッドを実行する仕組み
※デフォルトでは、beforeフィルターはコントローラ内のすべてのアクションに適用されるため
 :onlyオプション(ハッシュ)を渡すことで、このフィルタが:editと:updateアクションだけに
 適用されるように制限をかけている
 今後どこかの時点で :destroyアクションを保護するのに使われる可能性があるので、
 ステータスコード:see_otherを含めることで将来のコードを保証している

・セキュリティモデルに関する実装を取り外してもテストが green になる
テストスイートでそれを検出できるようにする

正しい種類のHTTPリクエストを使ってeditアクションとupdateアクションをそれぞれ実行し、flashにメッセージが代入されたかどうか、ログイン画面にリダイレクトされたかどうかを確認する

users_controller_test.rb

require "test_helper"

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  test "should redirect edit when not logged in" do
    get edit_user_path(@user)
    assert_not flash.empty?
    assert_redirected_to login_url
  end

  test "should redirect update when not logged in" do
    patch user_path(@user), params: { user: { name: @user.name,
                                              email: @user.email } }
    assert_not flash.empty?
    assert_redirected_to login_url
  end
end

10.2.2 正しいユーザーを要求する

ユーザーが自分の情報だけを編集できるようにする

・log_in_asメソッドを使って、editアクションとupdateアクションをテストする
間違ったユーザーが編集しようとしたときのテスト

テストコード
users_controller_test.rb

require "test_helper"

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user       = users(:michael)
    @other_user = users(:archer)
  end
  .
  .
  .
  test "should redirect edit when logged in as wrong user" do
    log_in_as(@other_user)
    get edit_user_path(@user)
    assert flash.empty?
    assert_redirected_to root_url
  end

  test "should redirect update when logged in as wrong user" do
    log_in_as(@other_user)
    patch user_path(@user), params: { user: { name: @user.name,
                                              email: @user.email } }
    assert flash.empty?
    assert_redirected_to root_url
  end
end

プロダクトコード
リダイレクトの後にstatus :see_otherを追加することで、フィルタがdestroyアクションの保護に使われても正常に動くようにし、将来のコードを保証している

beforeフィルターを使って編集/更新ページを保護する
users_controller.rb

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:edit, :update]
  before_action :correct_user,   only: [:edit, :update]
  .
  .
  .
  def edit
  end

  def update
    if @user.update(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit', status: :unprocessable_entity
    end
  end
  .
  .
  .
  private

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

    # beforeフィルタ

    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url, status: :see_other
      end
    end

    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url, status: :see_other) unless @user == current_user
    end
end

・リファクタリング
current_user?という論理値を返すメソッドを実装

sessions_helper.rb

module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
    # セッションリプレイ攻撃から保護する
    # 詳しくは https://bit.ly/33UvK0w を参照
    session[:session_token] = user.session_token
  end

  # ユーザーを永続セッションに保存する
  def remember(user)
    user.remember
    cookies.permanent.encrypted[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

  # 記憶トークンのcookieに対応するユーザーを返す
  def current_user
    if (user_id = session[:user_id])
      user = User.find_by(id: user_id)
      if user && session[:session_token] == user.session_token
        @current_user = user
      end
    elsif (user_id = cookies.encrypted[:user_id])
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end

  # 渡されたユーザーがカレントユーザーであればtrueを返す
  def current_user?(user)
    user && user == current_user
  end
  .
  .
  .
end

・ユーザーにセッショントークンのメソッドを追加
user.rb

class User < ApplicationRecord
  .
  .
  .
  # 永続化セッションのためにユーザーをデータベースに記憶する
  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
    remember_digest
  end

  # セッションハイジャック防止のためにセッショントークンを返す
  # この記憶ダイジェストを再利用しているのは単に利便性のため
  def session_token
    remember_digest || remember
  end
  .
  .
  .
end

・プロダクトコード書き換え
users_controller.rb

# 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url, status: :see_other) unless current_user?(@user)
    end

10.2.3 フレンドリーフォワーディング

保護されたページにユーザーがアクセスしようとすると
問答無用で自分のプロフィールページに移動させられるため、
直前にユーザーが開こうとしていたページにリダイレクトするよう実装する

・フレンドリーフォワーディングのテスト
編集ページにアクセスした後、
ログイン後に、デフォルトのプロフィールページではなく
編集ページにリダイレクトされているかどうかをチェックする

test/integration/
require "test_helper"

class UsersEditTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end
  .
  .
  .
  test "successful edit with friendly forwarding" do
    get edit_user_path(@user)
    log_in_as(@user)
    assert_redirected_to edit_user_url(@user)
    name  = "Foo Bar"
    email = "foo@bar.com"
    patch user_path(@user), params: { user: { name:  name,
                                              email: email,
                                              password:              "",
                                              password_confirmation: "" } }
    assert_not flash.empty?
    assert_redirected_to @user
    @user.reload
    assert_equal name,  @user.name
    assert_equal email, @user.email
  end
end

・リクエストされたページを保管する
 リクエストが送られたURLをsession変数の:forwarding_urlキーに保存
(GETリクエストが送られたときだけ)
if request.get?という条件文で考慮している

ヘルパー
sessions_helper.rb

module SessionsHelper
  .
  .
  .
  # アクセスしようとしたURLを保存する
  def store_location
    session[:forwarding_url] = request.original_url if request.get?
  end
end

コントローラー
users_controller.rb

# ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url, status: :see_other
      end
    end

フォワーディング自体を実装するには、リクエストされたURLが存在する場合はそこにリダイレクトし、存在しない場合は何らかのデフォルトURLにリダイレクトするようにする

・転送先URLが存在する場合は次のように最初にセッションを取得しておく
セッション固定攻撃の対処のために

forwarding_url = session[:forwarding_url]
reset_session
log_in user

・転送先URLが存在する場合はそこにリダイレクトし、転送先URLがnilの場合はユーザーのプロフィールにリダイレクトできるようになる
redirect_to forwarding_url || user

sessions_controller.rb

class SessionsController < ApplicationController
  .
  .
  .
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      forwarding_url = session[:forwarding_url]
      reset_session
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      log_in user
      redirect_to forwarding_url || user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new', status: :unprocessable_entity
    end
  end
  .
  .
  .
end

10.3 全てのユーザーを表示する

すべてのユーザーを一覧表示するindexアクションを追加する

10.3.1 ユーザーの一覧ページ

ユーザーのshowページについては、ユーザーがログインしているかどうかに関わらず、サイトにアクセスするすべてのユーザーから見えるようにして、
ユーザーのindexページはログインしたユーザーにしか見せないようにし、未登録のユーザーがデフォルトで表示できるページを制限する

・indexアクションのリダイレクトをテスト
users_controller_test.rb

require "test_helper"

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user       = users(:michael)
    @other_user = users(:archer)
  end

  test "should get new" do
    get signup_path
    assert_response :success
  end

  test "should redirect index when not logged in" do
    get users_path
    assert_redirected_to login_url
  end
  .
  .
  .
end

・beforeフィルターのlogged_in_userにindexアクションを追加して、アクションを保護する

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update]
  before_action :correct_user,   only: [:edit, :update]

  def index
  end

  def show
    @user = User.find(params[:id])
  end
  .
  .
  .
end

・すべてのユーザーを表示するために、
 全ユーザーを保存する変数を作成して、ユーザー一覧を表示するindexビューを実装する

users_controller.rb

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update]
  .
  .
  .
  def index
    @users = User.all
  end
  .
  .
  .
end

ユーザーのindexビュー
eachメソッドを使い実装
各ユーザーの行をリストタグulで囲み、その中にユーザーのGravatar画像と名前を表示する

index.html.erb

<% provide(:title, 'All users') %>
<h1>All users</h1>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

・gravatar_forヘルパーにオプション引数を追加
デフォルト以外のサイズを指定するオプションを渡す

users_helper.rb

module UsersHelper

  # 引数で与えられたユーザーのGravatar画像を返す
  def gravatar_for(user, options = { size: 80 })
    size         = options[:size]
    gravatar_id  = Digest::MD5::hexdigest(user.email.downcase)
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
end

10.3.2 サンプルのユーザー

indexページにrubyを使用して複数のユーザーを表示する

・サンプルユーザーを生成するRubyスクリプト(Railsタスク)を追加
Example Userという名前とメールアドレスを持つユーザー1人の他に、
それらしい名前とメールアドレスを持つユーザーを99人作成している

seeds.rb

# メインのサンプルユーザーを1人作成する
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar")

# 追加のユーザーをまとめて生成する
99.times do |n|
  name  = Faker::Name.name
  email = "example-#{n+1}@railstutorial.org"
  password = "password"
  User.create!(name:  name,
               email: email,
               password:              password,
               password_confirmation: password)
end

・データベースをリセットして、Railsタスクを実行
$ rails db:migrate:reset
$ rails db:seed

10.3.3 ページネーション

1つのページで一度に表示できるユーザーを設定する

・will_paginateメソッド

index.html.erb

<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

<%= will_paginate %>

・paginateメソッド
will_paginateではpaginateメソッドで得た結果が必要のため
indexアクションでUsersをページネートする

users_controller.rb

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update]
  .
  .
  .
  def index
    @users = User.paginate(page: params[:page])
  end
  .
  .
  .
end

10.3.4 ユーザー一覧のテスト

ユーザーの一覧ページのテストを作成
1ログイン
2indexページにアクセス
3最初のページにユーザーがいることを確認
4ページネーションのリンクがあることを確認

users_index_test.rb

require "test_helper"

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "index including pagination" do
    log_in_as(@user)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination'
    User.paginate(page: 1).each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
    end
  end
end

10.3.5 パーシャルのリファクタリング

ユーザー一覧ページのリファクタリングを行う

・ユーザーのliをrender呼び出しに置き換える
index.html.erb

<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <%= render user %>
  <% end %>
</ul>

<%= will_paginate %>

・renderを@users変数に対して直接実行する

<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <%= render @users %>
</ul>

<%= will_paginate %>

10.4 ユーザーを削除する

ユーザーを削除するためのリンクを追加する

10.4.1 管理ユーザー

特権を持つ管理ユーザーを識別するために、論理値型のadmin属性をUserモデルに追加
rails generate migration add_admin_to_users admin:boolean

seeds.rb

# メインのサンプルユーザーを1人作成する
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar",
             admin: true)

・Strong Parameters
paramsハッシュに対してrequireとpermitを呼び出す

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

10.4.2 destroyアクション

destroyアクションへのリンクを追加

ユーザー削除用リンクの実装(管理者にのみ表示される)
_user.html.erb

<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
  <% if current_user.admin? && !current_user?(user) %>
    | <%= link_to "delete", user, data: { "turbo-method": :delete,
                                          turbo_confirm: "You sure?" } %>
  <% end %>
</li>

"turbo-method": :delete
リンクに必要なDELETEリクエストを発行する準備をする

turbo_confirm: "You sure?"
JavaScriptのconfirmボックスを表示する

10.4.3 ユーザー削除のテスト

ユーザー削除のテストを作成する

2ケース確認する
ユーザーがログインしていない場合は、ログイン画面にリダイレクトされること
ログイン済みであっても管理者でない場合は、ホーム画面にリダイレクトされること

users_controller_test.rb

require "test_helper"

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user       = users(:michael)
    @other_user = users(:archer)
  end
  .
  .
  .
  test "should redirect destroy when not logged in" do
    assert_no_difference 'User.count' do
      delete user_path(@user)
    end
    assert_response :see_other
    assert_redirected_to login_url
  end

  test "should redirect destroy when logged in as a non-admin" do
    log_in_as(@other_user)
    assert_no_difference 'User.count' do
      delete user_path(@user)
    end
    assert_response :see_other
    assert_redirected_to root_url
  end
end

assert_no_differenceメソッドを使って、
ユーザー数が変化しないことを確認している

・管理者が削除リンクをクリックしたときに、ユーザーが削除されたことを確認する
assert_differenceメソッドでユーザーが削除されたことを確認
→DELETEリクエストを適切なURLに向けて発行し、ユーザー数が1減ったかどうかをUser.countで確認している

リダイレクトURLとHTTPステータスコードが正しいことも検証
→ーユーザーを1人削除したら、redirect users_urlで同じページにリダイレクトする
 ここではdeleteリクエストを使っているので、ステータスコードは:see_otherになる

users_index_test.rb

require "test_helper"

class UsersIndexTest < ActionDispatch::IntegrationTest

  def setup
    @admin     = users(:michael)
    @non_admin = users(:archer)
  end

  test "index as admin including pagination and delete links" do
    log_in_as(@admin)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination'
    first_page_of_users = User.paginate(page: 1)
    first_page_of_users.each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
      unless user == @admin
        assert_select 'a[href=?]', user_path(user), text: 'delete'
      end
    end
    assert_difference 'User.count', -1 do
      delete user_path(@non_admin)
      assert_response :see_other
      assert_redirected_to users_url
    end
  end

  test "index as non-admin" do
    log_in_as(@non_admin)
    get users_path
    assert_select 'a', text: 'delete', count: 0
  end
end

感想

ユーザー情報の更新について学びました!
普段使っているユーザー情報ページでのバリデートやリダイレクトなどの仕組みを知ることができてとても勉強になりました!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0