【Rails】データの一括更新

# 記事概要
1画面でデータを一括更新するアプリを作成する。

# 言語やフレームワーク
|| 使用技術 |
| ---- | ---- |
| フロントエンド | HTML <br> CSS |
| バックエンド | Ruby 3.2.0 <br> Ruby on Rails 7.0.8.6 |
| データベース | MySQL |
| インフラ | - |
| API | - |
| その他 | - |

# 前提
- データ作成機能は作成しない
- 更新対象のデータは1テーブルである
- 1画面かつ1回の処理でデータ更新を行う

# サンプルアプリ(GitHub)
https://github.com/likapantrip/update-all.git

# 処理画面
### データ更新成功
#### 更新前
[![Image from Gyazo](https://i.gyazo.com/a47b59bfbf181c1e910b3eefda7c31c1.png)](https://gyazo.com/a47b59bfbf181c1e910b3eefda7c31c1)
#### 処理
[![Image from Gyazo](https://i.gyazo.com/ae706fba9607e0493ad98e31d4a3559c.gif)](https://gyazo.com/ae706fba9607e0493ad98e31d4a3559c)

### データ更新失敗
#### 更新前
[![Image from Gyazo](https://i.gyazo.com/47e8a175a0f9003dfc9f75d7f62f926e.png)](https://gyazo.com/47e8a175a0f9003dfc9f75d7f62f926e)
#### 処理
[![Image from Gyazo](https://i.gyazo.com/708f119c5625a52bfee312201bfc5b9a.gif)](https://gyazo.com/708f119c5625a52bfee312201bfc5b9a)

# 手順
1. アプリを作成
    ```
    % cd ~/[アプリ作成ディレクトリ]
    % rails _7.0.0_ new update-all -d mysql
    ```
1. DBを作成
    ```
    % cd update-all
    % rails db:create
    ```
1. ルーティングを設定
    パスにidを設定しないので、resourcesメソッドは使用しない
    ```config/routes.rb
    Rails.application.routes.draw do
      root "students#index"
      get '/students', to: 'students#index', as: 'students'
      get '/students/edit', to: 'students#edit', as: 'edit_student'
      patch '/students', to: 'students#update', as: 'student'
    end
    ```
1. コントローラーの設定
    1. コントローラーを作成
        ```
        rails g controller students
        ```
    1. コントローラーを編集
        ```app/controllers/students_controller.rb
        class StudentsController < ApplicationController
          def index
          end
        
          def edit
              # 一括更新するため、全レコードを取得
              @students = Student.all
          end
        
          def update
            # studentsテーブルのレコード件数を取得
            count = Student.count
        
            count.times do |c|
              # 更新するデータを取得
              c += 1
              student_id = c.to_s
              student = Student.find(student_id)
              student_data = params.require(:"student").require(:"students").require(student_id).permit(:age)
              
              # データを更新
              student.update!(age: student_data[:age])
              # データ更新に失敗した場合
              rescue ActiveRecord::RecordInvalid => e
                flash[:alert] = "#{e.record.errors.full_messages} ※#{Student.find(e.record.id).name}以降のデータは更新されていません"
                redirect_to edit_student_path
                return
            end
            # 全件更新成功した場合
            redirect_to root_path
          end
        end
        ```
1. ビューの設定
    1. ビューを手動作成
        app/views/students/index.html.erb
        app/views/students/edit.html.erb
    1. ビュー(index.html.erb)を編集
        ```index.html.erb
        <%= link_to '一括データ更新', edit_student_path %>
        ```
    1. ビュー(edit.html.erb)を編集
        ```edit.html.erb
        <%# CSS・・・assets/stylesheets/common/table.css %>
        <%= form_with model: @students, url: student_path, method: :patch do |form| %>
          <div class="table_name">生徒一覧</div>
          <%= alert %></br>
          <table class="table_sticky">
            <thead>
              <tr>
                <th>id</th>
                <th>生徒名</th>
                <th>年齢</th>
              </tr>
            </thead>
            <tbody>
              <%= form.submit "Update Students" %>
        
            <% @students.each do |student| %>
              <%= form.fields_for "students[#{student.id}]", student do |student_form| %>
                <tr>
                  <td><%= student.id %></td>
                  <td><%= student.name %></td>
                  <td><%= student_form.text_field :age %></td>
                </tr>
              <% end %>
            <% end %>
            </tbody>
            <tfoot></tfoot>
          </table>
        <% end %>
        ```
1. CSSの設定
    1. CSSを手動作成
        app/assets/stylesheets/common/table.css
    1. CSSを編集
        ```table.css
        /*
          テーブル
        */
        
        /* テーブル名 */
        .table_name{
          font-size: 20px;
          margin-top: 10px;
          margin-left: 10px;
          font-weight: bold;
        }
        
        /* テーブルの枠 */
        table th ,td {
          border:1px solid;
          }
           
        /* スクロールバーの実装 */
        .table_sticky {
          display: block;
          overflow-y: scroll;
          height: calc(100% - 130px);
          border-collapse: collapse;
          margin: 20px 10px;
        }
        .table_sticky thead th {
          position: sticky;
          top: 0;
          z-index: 1;
          background: #79E2F2;
          border-top:#FFFFFF;
          border:1px solid;
        }
        ```
1. モデルの設定
    1. モデルを作成
        ```
        % rails g model student
        ```
    1. モデルを編集
        ```student.rb
        class Student < ApplicationRecord
          validates :name, presence: true
          validates :age,  presence: true,  numericality: true
        end
        ```
1. テーブルを設定
    1. テーブルを編集
        ```20241116134951_create_students.rb
        class CreateStudents < ActiveRecord::Migration[7.0]
          def change
            create_table :students do |t|
              t.string :name
              t.integer :age
              t.timestamps
            end
          end
        end
        ```
    1. マイグレーション
        ```
        rails db:migrate
        ```
1. データを作成
    ```
    % rails c
    irb(main):001> Student.create([{name: 'Taro', age: 20}, {name: 'Jiro', age: 19}, {name: 'Saburo', age: 17}])
    irb(main):002> exit
    ```


# 備考
- 同じ原理で一括作成機能を作成できるはず
- `render :edit, status: :unprocessable_entity`でエラーメッセージの表示を試みたが、失敗
    - `@students=nil`であることが原因
    - 2024/11/24 `update!`と`ActiveRecord::RecordInvalid`を使用し、エラー原因を表示するように修正