1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails初学者向け】このコード何が間違っている? 〜 理解度を試す10個の問題 〜

1
Posted at

目次

【はじめに】 本記事を書こうと思った理由

現在、RUNTEQでRails基礎を学習中です。そんな中、

  • Rails基礎で学習した内容が理解できているか
  • 間違ったコードを見たときに「ん?」と違和感に気づけるか

今日ではAIを使っての開発が当たり前となるなかで、こういった静的なコード確認が基礎理解を深めるうえでも有効ではないかと考え、この度、本記事を書くに至りました。

【前提】 対象読者

本記事はRails初学者向けに書いた記事となります。作成者も現在、Rails基礎を学習中の身ですので、記載内容に足りない部分もあるかと思います。

今回は記事に記載したコードの違和感に気づき、修正できるかなど初学者が理解度を測るうえでご利用ください。

また気づいた点や解答例を学習のタネに自ら調査し、理解を深めていただくとより良い学習になるかと思います。

これらの点を前提に、本記事をお読みいただけますと幸いです。


※以下のタスクアプリは実在しません。よって、こちらに記載しているコード以外をお見せすることもできません。
※以下のコードにはCSSの記述はありませんが、今回の問題ではHTML・CSSにおける誤りは問いません。
※全てのファイルを以下に記載しているわけではございません。
 (例) app/views/tasks/new.html.erb や edit.html.erb や show.html.erb などは未記載

簡単なタスクアプリの仕様

  • ログイン済みユーザー全員が全タスクを閲覧・編集・削除できる
  • タスクの閲覧・作成・編集・削除にはログインが必要
  • タスク一覧画面
    • 画面タイトルと各タスクの概要を表示する
    • 新規作成画面へのリンクを表示する
    • タスク一覧には「未着手」「完了」の両方のタスクを、作成日時が古い順に表示する
    • 各タスクの概要
      • 「タスクタイトル」「担当者:⚪︎⚪︎」「タスクステータス(未着手・完了)」「期限」を表示する
      • タスク詳細画面へのリンクを表示する
      • タスク削除用のリンクを表示する
  • タスク作成・編集画面
    • タイトル(title), ステータス(status), 期限(due_on)を入力してタスク作成・編集できる
  • タスク
    • タイトル・ステータスは必須項目、期限は任意項目とし、due_onにはNULLを保存できる
  • その他
    • 省略している認証メソッド、Userモデルの関連付け、フォーム、フラッシュ表示、Turboの読み込みは実装済みとする
  • 画面イメージ(問題を解くうえでのイメージとしてお使いください)

タスク一覧のモダンUI (1).png

間違ったコード10個の問題

これから、以下に 『実行時エラー・仕様との不一致・性能上の問題』 を記載します。
さて、あなたは、以下に書かれたコードの違和感に気づけるでしょうか。

◆ config/routes.rb

Rails.application.routes.draw do
  root "tasks#index"

  resources :tasks, only: [
    :index, :show, :new, :create,
    :edit, :update
  ]
end

◆ app/controllers/tasks_controller.rb

class TasksController < ApplicationController
  before_action :require_login, except: [:create]
  before_action :set_task, only: [:show, :edit, :update, :destroy]

  def index
    @tasks = Task.order(created_at: :asc).first
  end

  def show
  end

  def new
    @task = Task.new
  end

  def create
    @task = current_user.tasks.build(task_params)
    
    if @task.save
      redirect_to tasks_path, notice: "タスクを作成しました"
    else
      render :index, status: :unprocessable_entity
    end
  end

  def edit
  end

  def update
    @task.update(task_params)
    redirect_to tasks_path, notice: "タスクを更新しました"
  end

  def destroy
    @task.destroy!
    redirect_to tasks_path, notice: "タスクを削除しました", status: :see_other
  end

  private

  def set_task
    @task = Task.find(params[:id])
  end

  def task_params
    params.require(:task).permit(:name, :status, :due_on)
  end
end

◆ app/models/task.rb

class Task < ApplicationRecord
  belongs_to :user

  validates :title, presence: true

  validates :status, inclusion: { in: %w[未着手 進行中] }
end

◆ app/views/tasks/index.html.erb

<h1>タスク一覧</h1>

<%= link_to "新規作成", new_task_path %>

<% @tasks.each do |task| %>
  <div class="task">
    <h2><% task.title %></h2>

    <div><%= task.status %></div>

    <div>担当者:<%= task.user.name %></div>

    <div>期限:<%= task.due_on.strftime("%m/%d") || "未設定" %></div>

    <%= link_to "詳細", task_path(task) %> | 

    <%= link_to "削除",
                task_path(task),
                data: {
                  turbo_method: :delete,
                  turbo_confirm: "削除しますか?"
                } %>
  </div>
<% end %>

---( 🔎 Thinking Time 🤔 )---

...さて、10個の問題にあなたは気づくことができたでしょうか?

考えた内容を、以下の解答例と比べてみてください。

間違ったコード10個の解答・原因・修正例

  • 問題①:タスク削除のアクションが実行されない

    • 原因と修正例
      • 原因:routesにdestroyアクションが定義されていないため、DELETEリクエストに対応するルートがなく正常にルーティングできない
      • 修正例:only:オプションを外して、:destroyを含むルートを定義する
        config/routes.rb
        # 修正前
        Rails.application.routes.draw do
          root "tasks#index"
        
          resources :tasks, only: [
            :index, :show, :new, :create,
            :edit, :update
          ]
        end
        
        # 修正後
        Rails.application.routes.draw do
          root "tasks#index"
        
          resources :tasks
        end
        

  • 問題②:タスク作成処理だけログイン認証の対象外となっている

    • 原因と修正例
      • 原因:TasksControllerの before_action :require_login, except: [:create] の except: [:create] により、createアクションだけ除外されてしまっているため
      • 修正例:except: [:create]を外して、全アクションにログイン確認を適用する
        app/controllers/tasks_controller.rb
        # 修正前
        before_action :require_login, except: [:create]
        
        # 修正後
        before_action :require_login
        

  • 問題③:タスク一覧画面の描画時に <% @tasks.each do |task| %> で NoMethodError が発生する

    • 原因と修正例
      • 原因:TasksControllerのindexアクションで .first が実行され、「Taskインスタンスまたはnil」が返されているにもかかわらず、@tasks.each メソッドを使用しているため(Taskインスタンス・nilは、eachメソッドを持たないため)
      • 修正例:@tasks は、tasksテーブルで保持するレコードを全て取得できるようにActiveRecord::Relationを設定する(仕様に合わせ、orderの条件は昇順とする)
        app/controllers/tasks_controller.rb
        # 修正前
        def index
          @tasks = Task.order(created_at: :asc).first
        end
        
        # 修正後
        def index
          @tasks = Task.order(created_at: :asc)
        end
        
        (※最終的には問題9の解答の通りとするとよい)

  • 問題④:タスク作成時に正常なタイトルを入力してもバリデーションエラーが発生する

    • 原因と修正例
      • 原因:Taskクラスで :title は必須とするバリデーションを設定しているのに対し、createアクションの @task.save で保存しようとする @task において :title が設定されていないため

      • 修正例:@task = current_user.tasks.build(task_params) の task_params メソッド内のストロングパラメータの :name を :title へ修正する

        app/controllers/tasks_controller.rb
        # 修正前
        def task_params
          params.require(:task).permit(:name, :status, :due_on)
        end
        
        # 修正後
        def task_params
          params.require(:task).permit(:title, :status, :due_on)
        end
        

  • 問題⑤:タスク保存に失敗した場合、タスク一覧画面を描画させていることで @tasks.each で NoMethodError が発生している

    • 原因と修正例
      • 原因:render :indexは、indexアクションを実行せずにindex.html.erbを描画するが、createアクションでは@tasksを設定しておらず、Viewでnilに対してeachを呼び出しているため

      • 修正例:render :new でタスク作成画面を描画させる

        app/controllers/tasks_controller.rb
        # 修正前
        if @task.save
          redirect_to tasks_path, notice: "タスクを作成しました"
        else
          render :index, status: :unprocessable_entity
        end
        
        # 修正後
        if @task.save
          redirect_to tasks_path, notice: "タスクを作成しました"
        else
          render :new, status: :unprocessable_entity
        end
        

  • 問題⑥:更新失敗でも成功メッセージが表示されてしまう

    • 原因と修正例
      • 原因:TasksControllerのupdateアクションで成功・失敗の判定処理が書かれていないため

      • 修正例:成功・失敗の判定処理を書く

        app/controllers/tasks_controller.rb
        # 修正前
        def update
          @task.update(task_params)
          redirect_to tasks_path, notice: "タスクを更新しました"
        end
        
        # 修正後
        def update
          if @task.update(task_params)
            redirect_to tasks_path, notice: "タスクを更新しました"
          else
            render :edit, status: :unprocessable_entity
          end
        end
        

  • 問題⑦:タスクのステータスを完了とすることができない

    • 原因と修正例
      • 原因:Taskクラスで :status のバリデーションとして、未着手 進行中 だけが書き込めるようになっているため

      • 修正例:進行中 を 完了 に修正する

        app/models/task.rb
        # 修正前
        validates :status, inclusion: { in: %w[未着手 進行中] }
        
        # 修正後
        validates :status, inclusion: { in: %w[未着手 完了] }
        

  • 問題⑧:タスク一覧画面でタスクのタイトルが表示されない

    • 原因と修正例
      • 原因:index.html.erb のタイトルを表示する部分で、コードを実行するためだけのタグで囲まれ、HTMLにレンダリング(出力)されていないため

      • 修正例:結果をHTMLにレンダリング(出力)するためのタグで囲む

        app/views/tasks/index.html.erb
        # 修正前
        <h2><% task.title %></h2>
        
        # 修正後
        <h2><%= task.title %></h2>
        

  • 問題⑨:担当者を表示する処理でN+1問題が発生する可能性がある

    • 原因と修正例
      • 原因:担当者欄を出力する task.user.name までにタスクに紐づくユーザー情報を取得できていないため

      • 修正例:@tasksのインスタンス変数を設定する場面で、合わせてユーザーの情報も取得できる条件を加える

        app/controllers/tasks_controller.rb
        # 修正前
        def index
          @tasks = Task.order(created_at: :asc)
        end
        
        # 修正後
        def index
          @tasks = Task.includes(:user).order(created_at: :asc)
        end
        

  • 問題⑩:タスク一覧画面の描画時に、期限が未設定のタスクを表示すると <div>期限:<%= task.due_on.strftime("%m/%d") %></div> で NoMethodError が発生する

    • 原因と修正例
      • 原因:app/models/task.rb で due_on は必須バリデーションが組まれておらず、設定値なしで登録された場合、task.due_on はnilとなり、nilはstrftimeメソッドを持たないため

      • 修正例:task.due_onがnilの場合に、安全ナビゲーション演算子の&.を使い、strftimeを実行しないようにする

        app/views/tasks/index.html.erb
        # 修正前
        <div>期限:<%= task.due_on.strftime("%m/%d") || "未設定" %></div>
        
        # 修正後
        <div>期限:<%= task.due_on&.strftime("%m/%d") || "未設定" %></div>
        

10問を通して見えてきたRailsの重要な仕組み

今回の10問を通して、Railsではそれぞれのコードの役割だけでなく、処理の流れや仕組みを理解することが重要だと感じました。

例えば、Active Recordではメソッドによって戻り値やSQLの実行タイミングが異なり、それらを理解していないと、思わぬエラーやN+1問題につながることがあります。

また、ControllerとViewの関係では、renderによってViewを描画しても、別のControllerアクションが実行されるわけではないため、必要なインスタンス変数を適切に準備する必要があります。

さらに、ValidationやStrong Parameters、認証処理など、コードが動くだけでなく、意図したとおりに安全かつ正しく動作することも大切です。

Railsは便利な機能が数多く用意されていますが、その裏側で何が起きているのかを意識しながら学習することが重要ということですね。

【まとめ】 問題を通して

いかがでしたでしょうか。
10個の問題に気づき、修正することはできましたでしょうか。

全てに気づき、修正できた方は、今回扱った10のポイントについて、理解を確認できたと思います。

正解できなかった方も『しっかり理解できていないんだ。ダメだ。。。』と思うことなく、参考ドキュメントのRailsガイドやRubyリファレンスマニュアルを読んで、少しずつ理解していきましょう。
私もまだまだ勉強不足な点は多々ありますので、一緒に頑張りましょう!

以上

1
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?