Rails 掲示板一覧の作成
モデルの作成
- Boardモデルの作成しtitle,bodyカラムを追加する。
- Boardモデルにuser_idを外部キーとして設定する。
紐づくモデル名+「_id」で外部キーと呼ぶ。
reference型を使いモデルと同時に作成。
$ rails g model board title:string body:text user:references invoke active_record create db/migrate/20210506055856_create_boards.rb create app/models/board.rb
マイグレーションファイルとモデルファイルができる。
UserモデルとBoardモデルにアソシエーションを設定し、バリデーションを追加。
class Board < ApplicationRecord validates :title, presence: true, length: { maximum: 200 } validates :body, presence: true, length: { maximum: 60_000 } belongs_to :user end
dependent: :destroyオプション
has_manyにdependent: :destroyを追加すると、親モデル(user)を削除した時に、その親モデルに紐づく子モデル(board)も同時に削除されるようになる。これを定義しないとUserを削除したときに、boardに投稿した内容が残ってしまうので必須である。
class User < ApplicationRecord authenticates_with_sorcery! validates :password, length: { minimum: 3 }, if: -> #... has_many :boards, dependent: :destroy end
データベースに未入力データの登録を排除するための NOT NULL 制約を追加。
class CreateBoards < ActiveRecord::Migration[5.2] def change create_table :boards do |t| t.string :title, null: false t.text :body, null: false t.references :user, foreign_key: true t.timestamps end end end
マイグレーションを実行。
$ rails db:migrate
gem Fakerで作成したダミーデータをデータベースへ投入する。
$ rails db:seed
掲示板一覧画面の作成
ルーティングを定義
Rails.application.routes.draw do root to: 'users#index' get '/login', to: 'user_sessions#new' post '/login', to: 'user_sessions#create' post '/logout', to: 'user_sessions#destroy' resources :users resources :boards end
コントローラを作成しindexアクションを定義
$ rails g controller boards create app/controllers/boards_controller.rb invoke erb create app/views/boards invoke decorator create app/decorators/board_decorator.rb
includesメソッドとN+1問題
N+1問題とは、データベースからデータを取得する際、必要以上にSQLが発行されてしまいパフォーマンスが低下してしまう問題である。アソシエーションが定義されている場合に発生する。
@boards = Board.allと定義した場合、掲示板一覧ページ遷移時(1回)にユーザーとその掲示板の数(N回)だけSQLが発行されてしまう。
以下のようになる
対策
includesメソッドで関連するテーブルをまとめて取得。
orderメソッドは昇順 ASC、降順 DESCの並び替えができる。
created_atは、作成された日時なので 降順で新しい投稿が上にくるようにできる。
descはdescendingの略で降順という意味。
class BoardsController < ApplicationController skip_before_action :require_login, only: %i[index create] def index if logged_in? @boards = Board.all.includes(:user).order(created_at: :desc) else redirect_to login_path, danger: 'ログインしてください' end end
viewの作成。
パーシャルを使用し掲示板一覧画面を表示させる。
1.eachを使う場合
<%= @boards .each do |board| %> <div class="col-sm-12 col-lg-4 mb-3"> <div id="board-id-<%= board.id %>"> <div class="card"> <%= image_tag 'board_placeholder.png', class: 'card-img-top', size: '300x200' %> #... <% end %>
パーシャルをindex.htmlで読み込む。
部分テンプレート内のboardという変数に@boardが代入される。localsオプションが省略されてる。
<%= render partial: 'board', locals: {board: @board} %>
*localsオプションを使用した場合、partialは省略できない。
#... <% if @boards.present? %> <%= render 'board', {board: @board} %> <%else%> <p><%= '掲示板がありません' %></p> <%end%>
2.eachを使わない場合
<div class="col-sm-12 col-lg-4 mb-3"> <div id="board-id-<%= board.id %>"> <div class="card"> <%= image_tag 'board_placeholder.png', class: 'card-img-top', size: '300x200' %> #...Railsでは、@boardsという変数からよしなに「_board.html.erb」を探してくれる。
@boardsにrenderメソッドを使うことで「_board.html.erb」をboardの数だけ繰り返し表示することができる。<%= render @boards %>は、collectionオプションが省略されている。
<%= render partial: 'board', collection: @boards %>
省略できる条件
- 呼び出す部分テンプレートがviewフォルダ内のboardsフォルダに存在する
- 部分テンプレート名が_board.html.erbである
- 部分テンプレート内で使う変数がboardである
#... <% if @boards.present? %> <%= render @boards %> <%else%> <p><%= '掲示板がありません' %></p> <%end%><%= render @boards %>
このコードは以下の処理とまったく同じである。
<%= render partial: 'board', collection: @boards %> <!--下のコードと全く同じ --> <% @boards.each do |board| %> <%= render partial: 'board', board: board %> <% end %>
タイムゾーンを日本時間にする。
config.time_zone = 'Tokyo' config.active_record.default_timezone = :local