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