😪

Rails scaffoldについて

2023/05/11に公開

はじめに

YouTubeでRailsの基本について復習してたら知らなかったことが出てきたのでメモっときます📝
やっぱり色々なところから知識をいいとこ取りしていくのがいいね🌱

https://www.youtube.com/watch?v=IyEcu9e1YgM&ab_channel=せお丸のプログラマー養成講座

scaffoldとは

CRUD(クラッド)機能をコマンド一発で簡単に作れる!!!

Create = 新規追加
Read = 一覧、詳細
Update = 編集
Delete = 削除

CRUDとは、データ作成(Create)/読み込み(Read)/更新(Update)/削除(Delete)の基本的な機能のこと!

使い方

rails generate scaffold モデル名 [カラム名:型]

例えば、今回は体重管理システムを作りたいので、下記のコマンドを実行する!
rails g scaffold WeightHistroy user:references weight:integer memo:string

reference型を指定したカラム名は テーブル名_id となる。
(user:references なので、 user_id というカラムが生成される)
(referencesを指定したことで、生成されたモデルファイルにbelongs_to :userも追加される。)

weightは体重を整数で格納したいので、integer型、memoには文字列stringを指定!

🏋🏻指定することができるカラムの型一覧🏋🏻

カラムの型 説明
string 文字列
text 長い文字列
integer 整数
float 浮動小数
decimal 精度の高い小数
datetime 日時
time 時間
date 日付
binary バイナリデータ
boolean Boolean

自動生成ファイル

Scaffolding機能を使用すると、こんな感じでたくさんのファイルが自動で生成される!
(基本的な操作(CRUD)を実現するために必要なファイルたち)

  • 一覧(index)
  • 詳細(show)
  • 新規作成(new/create)
  • 編集(edit/update)
  • 削除(destroy)

今回は次のコマンドを実行したので、体重の一覧、体重の詳細、体重の新規作成、体重の編集、体重の削除に必要なファイルが作成された!
rails g scaffold WeightHistroy user:references weight:integer memo:string

ルーティング

ルーティングは、自動生成されるファイルというわけではないが、resources :weight_histroysが追加される!

routes.rb
Rails.application.routes.draw do
  resources :weight_histroys
end

上記のresourcesメソッドによって、7つのルーティングが自動で設定される。
rails routes | grep <文字列>で絞って表示できます!)

ちなみに、resourcesで特定のアクションだけを指定したい時は、、

onlyとexcept

(indexとshowを指定したい場合だと)

resources :weight_histroys, only: [:index, :snow]

もしくは下記のように記述しても同じ!

resources :weight_histroys, except: [:new, :create, :edit, :update, :destroy]

どちらを使っても構わないが、

onlyを使ったほうが使用するアクションを明示的にできるので可読性が上がる!

特定のアクションしか使用しない場合はこのようにonlyexceptを使った方がスッキリする。

※ルーティングがスッキリするだけでonlyexceptを絶対に使わなければいけないわけではない。

https://pikawaka.com/rails/resources#onlyとexcept

コントローラ

自動生成されるコントローラファイルは、次のようにapp/controllers/配下に置かれる。

weight_histroys_controllerの中には、体重の一覧、体重の詳細、体重の新規作成、体重の編集、体重の削除に必要なソースコードが記述される!

weight_histroys_controller.rbの中身
controllers/weight_histroys_controller.rb
class WeightHistroysController < ApplicationController
  before_action :set_weight_histroy, only: %i[ show edit update destroy ]

  # GET /weight_histroys or /weight_histroys.json
  def index
    @weight_histroys = WeightHistroy.all
  end

  # GET /weight_histroys/1 or /weight_histroys/1.json
  def show
  end

  # GET /weight_histroys/new
  def new
    @weight_histroy = WeightHistroy.new
  end

  # GET /weight_histroys/1/edit
  def edit
  end

  # POST /weight_histroys or /weight_histroys.json
  def create
    @weight_histroy = WeightHistroy.new(weight_histroy_params)

    respond_to do |format|
      if @weight_histroy.save
        format.html { redirect_to weight_histroy_url(@weight_histroy), notice: "Weight histroy was successfully created." }
        format.json { render :show, status: :created, location: @weight_histroy }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @weight_histroy.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /weight_histroys/1 or /weight_histroys/1.json
  def update
    respond_to do |format|
      if @weight_histroy.update(weight_histroy_params)
        format.html { redirect_to weight_histroy_url(@weight_histroy), notice: "Weight histroy was successfully updated." }
        format.json { render :show, status: :ok, location: @weight_histroy }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @weight_histroy.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /weight_histroys/1 or /weight_histroys/1.json
  def destroy
    @weight_histroy.destroy

    respond_to do |format|
      format.html { redirect_to weight_histroys_url, notice: "Weight histroy was successfully destroyed." }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_weight_histroy
      @weight_histroy = WeightHistroy.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def weight_histroy_params
      params.require(:weight_histroy).permit(:user_id, :weight, :memo)
    end
end

respond_toメソッド

create,update,destroyアクションには、リクエストされるフォーマットがJSON形式の場合も記述されているので、不要であれば削除しちゃってOK!

https://pikawaka.com/rails/respond_to

モデル

app/models/配下にデータベースのテーブル操作のためのファイルが自動生成される。
この定義によってweight_historyテーブルを操作することができる。

app/models/weight_history.rb
class WeightHistroy < ApplicationRecord
  belongs_to :user
end

(最初の作成時にreferencesを指定したことで、生成されたモデルファイルにbelongs_to :userも追加されてます!)
(Userモデル側には何も定義されていないので自分で記述する!)

必要なマイグレーションファイルも自動生成されています!
rails g scaffoldで指定したカラム名と型が反映される。

db/migrate/20xxxxxx_create_weight_history.rb
class CreateWeightHistroys < ActiveRecord::Migration[7.0]
  def change
    create_table :weight_histroys do |t|
      t.references :user
      t.integer :weight
      t.string :memo

      t.timestamps
    end
  end
end

データベース作成済みの場合はrails db:migrateで、データベースに反映させる。

ビュー

app/views配下に管理画面を表示するためのファイルが自動生成される。
部分テンプレートまで作成してくれてる、、!

自動生成される主要なビューファイル一覧

主要なファイル 説明
_form.html.erb 新規投稿/編集画面の共通フォーム
index.html.erb 一覧画面
edit.html.erb 編集画面
new.html.erb 新規登録画面
show.html.erb 詳細画面

index.html(一覧画面)

index.htmlは、次のようにUser,Weight,Memoの一覧を表します。
(Userは元々user_idが表示されてましたが変更済みの画面です。)

index.html.erbの中身
weight_historys/index.html.erb
<p style="color: green"><%= notice %></p>

<h1>Weight histroys</h1>

<div id="weight_histroys">
  <% @weight_histroys.each do |weight_histroy| %>
    <%= render weight_histroy %>
    <p>
      <%= link_to "Show this weight histroy", weight_histroy %>
    </p>
  <% end %>
</div>

<%= link_to "New weight histroy", new_weight_histroy_path %>

edit.html.erb(編集画面)

edit.html.erbは、次のように編集画面を表示します。

(edit.html.erb)の中身
weight_historys/_form.html.erb
<h1>Editing weight histroy</h1>

<%= render "form", weight_histroy: @weight_histroy %>

<br>

<div>
  <%= link_to "Show this weight histroy", @weight_histroy %> |
  <%= link_to "Back to weight histroys", weight_histroys_path %>
</div>

呼び出し先の(_form.html.erb)の中身(部分テンプレート)
エラーメッセージの表示の記述まで入ってます!⚠️

weight_historys/_form.html.erb
<%= form_with(model: weight_histroy) do |form| %>
  <% if weight_histroy.errors.any? %>
    <div style="color: red">
      <h2><%= pluralize(weight_histroy.errors.count, "error") %> prohibited this weight_histroy from being saved:</h2>

      <ul>
        <% weight_histroy.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div>
    <%= form.label :user_id, style: "display: block" %>
    <%= form.text_field :user_id %>
  </div>

  <div>
    <%= form.label :weight, style: "display: block" %>
    <%= form.number_field :weight %>
  </div>

  <div>
    <%= form.label :memo, style: "display: block" %>
    <%= form.text_field :memo %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>


インスタンス変数(@user)に特定のユーザ情報が格納される場合、form_withのコンパイル後は、次のようにupdateアクションが動くパスに変換される。

form_withをコンパイルした場合
<form action="/weight_historys/編集するレコードのid" method="post" data-remote="true">
  <input type="hidden" name="_method" value="patch"><!--他省略-->

そのため、編集画面では選択したユーザ情報を編集して送信ボタンを押すことで、更新処理をリクエストすることができる!
(ここの仕組みよく分かってなかった!!)

new.html.erb(新規登録画面)

new.html.erbは、次のようにユーザの新規登録画面を表示する。

(new.html.erb)の中身
weight_historys/new.html.erb
<h1>New weight histroy</h1>

<%= render "form", weight_histroy: @weight_histroy %>

<br>

<div>
  <%= link_to "Back to weight histroys", weight_histroys_path %>
</div>

新規登録画面でも編集画面と同様に_form.html.erbを呼び出して入力フォームを表示している!
が、@userに格納されているのは特定のユーザ情報ではなく、新しく生成されたUserのインスタンスのこと。

app/controllers/weight_historys_controller.rb
class UsersController < ApplicationController
  def new
    @user = User.new #Userインスタンスを生成して@userへ
  end
end

@userは新規登録画面のフォームの初期値を与えるために必要になる!


@userに特定のユーザ情報がない場合は、次のようにform_withがコンパイルされると、createアクションが動くパスに変換される。

form_withのコンパイル後
<form action="/weight_historys" method="post" data-remote="true">

そのため新規登録画面で入力された内容は更新ではなく、新規登録されることになる!!

show.html.erb(詳細画面)

show.html.erbは、次のように特定の詳細画面を表示する。

( show.html.erb)の中身
weight_historys/new.html.erb
<p style="color: green"><%= notice %></p>

<%= render @weight_histroy %>

<div>
  <%= link_to "Edit this weight histroy", edit_weight_histroy_path(@weight_histroy) %> |
  <%= link_to "Back to weight histroys", weight_histroys_path %>

  <%= button_to "Destroy this weight histroy", @weight_histroy, method: :delete %>
</div>

jbuilderファイル

erb以外にもjbuilderのビューファイルも自動生成されており、
これらのファイルは、非同期通信でJSON形式のデータを返すときによく使用される。

その他にも関連するテストファイルやヘルパーなど自動生成されている!

オプション

scaffoldは、オプションを使用することができる🙆🏻‍♀️

rails generate scaffold モデル名 [カラム名:型] オプション

例えば、デフォルトで自動生成されるjbuilderのビューファイルがいらない場合は、次のように--skip-jbuilderを指定して実行すると、jbuilderファイルが生成されない!!
便利!!

rails generate scaffold User name:string age:integer --skip-jbuilder

主要なオプション一覧(他にもたくさんあるらしい!)

主要なオプション 説明
–-skip-ファイル名 指定したファイルを自動生成させない
–-skip-bundle bundle installを行わない
–-database=DATABASE データベースの種類を指定する
-f, –force ファイルが存在する場合に上書きする

こんなに楽に作れるのね、、!!
だから初心者はなるべくScaffolding機能を使わず、仕組みを理解してからの方がいいってことか〜〜🧐
これやりながら新しい発見もたくさんあって大満足です。
おやすみなさい〜〜🌙

🌱参考にさせていただいた記事

https://pikawaka.com/rails/scaffold

Discussion