【Rails7】初めてログインするユーザーにチュートリアル画面を表示させたい

自作アプリを作っています。プログラミング学習5ヶ月目のサラリーマンです。

今回は、自作アプリの使い方をわかりやすくするためにチュートリアル画面を作りたくなったので、まずはその動線を作っていきます。

作っているアプリ

ほしいものリストのアプリを作っています。
今回は、「ほしいものリストにアイテムを追加する」ステップを初めて使う人向けの、チュートリアル画面を作っていきます。

方針

  • 初ログインかどうかの判別は、できるだけシンプルな処理で実装したい

  • 使い方を画像や文章で出すのではなく、1ステップ目を体験してもらうスタイルにしたい(1件目のitemをcreateしたらゴール)

  • ユーザー情報に「最初のログインである」というフラグを持たせる

  • items_controllerにて、create時にそのフラグをupdateするコードを書く

実装方法

ユーザーモデルにカラムを追加

rails g migration AddFirstSignInToUsers first_sign_in:boolean

ターミナルで上記を実行後、マイグレーションファイルにデフォルト値を追記

class AddFirstSignInToUsers < ActiveRecord::Migration[7.0]
  def change
    add_column :users, :first_sign_in, :boolean, default: true, null: false
  end
end

ここでdefault: trueを指定しておくと、すでにDB上に作られていたuserたちにも、とりあえずtrueが付与されるので空白になりません。
→空白があることによる変なエラーが起きにくい気がします。

で、migrateします。

rails db:migrate

チュートリアル画面の生成

ターミナルで以下を入力します。

rails g controller Tutorial index

これで、チュートリアル画面自体のビューとコントローラーを生成します。

ルーティング

config/routes.rb

  get 'tutorial', to: 'tutorial#index'

チュートリアル画面に行くためのルーティングを記述します。

コントローラー

class TutorialController < ApplicationController
  def index
    @item = Item.new
  end
end

チュートリアルコントローラーには、ややこしいことはさせない方向でいきましょう。
「新規アイテム作成」のチュートリアル画面に行くだけ。
インスタンス変数@itemを持って、新規アイテムを作るフォームへ移るだけです。

ビュー

フォームはnew_item_pathのものを流用したテンプレートがあったのでそのまま呼び出し。

<div class="container mt-4">
  <p>ほしいものリストへようこそ。さっそく最初のアイテムを登録しましょう。</p>
  <%= turbo_frame_tag "item_form" do %>
    <%= render partial: 'items/form', locals: { item: @item } %>
  <% end %>
</div>

ここで気を付けるのは、このビューはviews/tutorial/index というパスなので render partial: 'items/form'と1個上からのパス指定で書いてあげることです。

同じitems配下のビューからは単に'form'で呼び出せますが、今回はitemsの外から呼ぶので丁寧に呼ぶ必要があります。

従来のコントローラの書き換え

items_controller.rb

  def create
    @user = current_user
    @item = Item.new(item_params)    
    if @item.save
      @user.update_attribute(:first_sign_in, false)
      redirect_to root_path
    else
      respond_to do |format|
        format.html { render :new, status: :unprocessable_entity }
        format.turbo_stream { render turbo_stream: turbo_stream.replace("item_form", partial: "items/form", locals: { item: @item }) }
      end
    end
  end

ポイントは5行目の@user.update_attribute(:first_sign_in, false)
ここで、ユーザーのfirst_sign_inフラグをfalseにします。この設計が良いのかどうかは、まだわかりません。
(2回目以降も、ずっとこのupdateが走るのは、たぶんよくない気もする。一応if文入れた方がいいのかな?でも、ifをチェックしてるコストをかけるくらいなら、毎回updateしちゃってもよいのか?…まだわかりません)

ダメだったコントローラの書き方

失敗メモ。
ユーザーの「first_sign_in」フラグの更新に失敗したコードも書いておきま
す。最初、5行目をこう書きました。

 @user.update(first_sign_in: false)

この書き方だと、このコードに書いてないユーザーモデルのパスワードなどのバリデーションに引っかかってしまってエラーが出ていました。(ログを辿って発見)

Failed to update user's first_sign_in flag: Password can't be blank, Password is too short (minimum is 6 characters), Password confirmation can't be blank

エラーログ

仕方ないので、指定したattributeだけ書き換えるコードに変更。
地味に()の中の記述方法が変わっているので注意。引数が2個になります。

@user.update_attribute(:first_sign_in, false)

これできちんと動きました。
そのユーザーが1件目のアイテムを登録した時、first_sign_inフラグがfalseになり、その後はチュートリアル画面ではなく普通のroot_pathに飛ぶようになります。

まとめ

はじめてログインしたユーザーをチュートリアル画面に飛ばすには、

  1. ユーザーテーブルにログインが初めてかどうか管理するカラムをつくる

  2. チュートリアルの画面をつくる

  3. ルーティングを書く

  4. コントローラーを書く
    このとき、ユーザー全体をupdateするとバリデーションに引っかかったので、attributeだけ更新するように記述した。

画面はこれから作るけど、とにかく「1件目のアイテムを登録してない人にはチュートリアル画面に行ってもらう」という道筋だけできました。

AIに聞いたらめちゃめちゃ長いJSのスクリプトでupdate をかけるやり方を教えてくれたけど、今回は自分の脳内に収まるコードベースにしたいので却下しました。
今の自分でも使えるレベルのコードから逸脱せずに、わかる範囲のコードで対応したかったので、このようにシンプルな方法をとりました。

できれば、チュートリアル画面ではオーバーレイとかを上手く使ってわかりやすい画面を作りたいなと妄想しつつ、今日はここまでです。

最後までお読みいただきありがとうございました。

この記事が気に入ったらサポートをしてみませんか?