動的タイトル表示の実装

ヘルパーを使ってタイトルを動的に表示させる。

ここでいうタイトルとはブラウザのタブに表示される文字のこと↓



実装内容

今回はヘルパーを使い、以下の各ページごとに動的にタイトルを表示させる。
content_forを使い、「ページごとのタイトル名 | タイトル名」のように表示させる。
トップページは「タイトル名」のみ表示。

  • トップページ
  • ログインページ
  • ユーザー登録ページ
  • 掲示板作成ページ
  • 掲示板一覧ページ
  • 掲示板詳細ページ

実装手順

  • app/helpers/application_helper.rbにメソッドを作成する。
  • 各ページのviewファイルへ設定を読み込ませる。

ヘルパーの作成。

empty?メソッドを使い、真偽判定をする。
変数の値が""(文字列の場合)や値が空白の場合、真となる。 条件演算子(?:)
条件式 ? 真の時の値 : 偽の時の値で記述。
page_titleの引数に''を入れて例外が発生するのを防ぐ。

module ApplicationHelper
  def page_title(page_title = '')
    base_title = 'SAMPLE APP'
    page_title.empty? ? base_title : page_title + ' | ' + base_title
  end 
end

webサイト全体のviewへ適応する。

i18n翻訳ファイルの編集。

ja:
  defaults:
    login: 'ログイン'
    register: '登録'
    title: 'ユーザー登録'
    to_login_page: 'ログインページへ'
  user_sessions:
     new:
       Back: '戻る'
       title: 'ログイン'
       email: 'メールアドレス'
       password: 'パスワード'
       to_register_page: '登録ページへ'
  users:
     new:
       title: 'ユーザー登録'  
  boards:
     title: '掲示板作成'
     index: '掲示板一覧'

ヘルパーで作成したメソッドを読み込む。
yieldメソッド
ビューを挿入すべき場所を指定。

〜
<title><%= page_title(yield(:title)) %></title>

これでトップページにbase_titleが適応される。

各viewファイルに読み込ませていく

content_forメソッド
コンテンツを名前付きのyieldとしてレンダリング

<%= content_for(:title, t('.title')) %>
<%= content_for(:title, t('boards.title')) %>
<%= content_for(:title, t('boards.index')) %>

掲示板詳細画面は閲覧中の掲示板名を表示させる。

<% content_for(:title, @board.title) %>

と記述。

Rails 掲示板にコメント機能を追加 part2

part1からの続き。
掲示板の詳細画面を実装したので、そこにコメント機能を実装していく。

投稿詳細画面へコメントの入力フォーム、エリアを追加する。
# Controllerへ追記。

boards_controllerにインスタンス変数@commentと@commentsを定義し、view側で呼び出す。

class BoardsController < ApplicationController
  skip_before_action :require_login, only: %i[index create show]
  def show
    if logged_in?
      @board = Board.find(params[:id])
      @comment = Comment.new
      @comments = @board.comments.includes(:user).order(created_at: :desc)
    else
      redirect_back_or_to login_path, danger: 'ログインしてください'
    end
  end

logged_in?メソッドを使い、ログインしていない場合 ログインページにリダイレクトされるようにした。
ここは単純にskip_before_action :require_login, only: %i[index create]で良かった。
コメントを習得する際はN+1問題に注意し、includesメソッドを使用しユーザーを呼び出す。
created_at: :descで作成された順に降順で表示。

コメントフォームのview

パーシャルで作成する。
ルーティングをネスト(board/board_id/comment)している場合はurlを2つ指定する。

<!-- コメントフォーム -->
<div class="row mb-3">
  <div class="col-lg-8 offset-lg-2">
    <%= form_with model: comment, url: [board, comment], local: true do |f| %>
      <%= render 'shared/error_messages', object: f.object %>    
      <%= f.label :body %>
      <%= f.text_area :body, class: 'form-control mb-3', rows: 4,  placeholder: 'コメント' %>
      <%= f.submit class: 'btn btn-primary', value: '投稿' %>
    <% end %>
  </div>
</div>   

コメントエリアのview

こちらもパーシャルで作成。
コメントを作成したユーザーにだけアイコンを表示。

<!-- コメントエリア -->
<%= comment.user.decorate.full_name %>
<%= simple_format(comment.body) %>
#入力値の改行に対応する
〜
 <% if current_user.own?(comment) %>  
#自分のコメントだけ編集、削除アイコンを表示する
<td class="action">
  <ul class="list-inline justify-content-center" style="float: right;">
    <li class="list-inline-item">
      <a href="#" class='js-edit-comment-button' data-comment-id="<%= comment.id %>">
        <%= icon 'fa', 'pen' %>
      </a>
    </li>
    <li class="list-inline-item">
      <a href="#" class='js-delete-comment-button' data-comment-id="<%= comment.id %>">
        <%= icon 'fas', 'trash' %>
      </a>
    </li>
   </ul>
 </td>
<% end %>
  • コメントエリア_comment.html.erbにbootstrapを適応。
    render commentsでよしなに_comment.html.erbを呼び出している。
    render @commnetsでも同様。
<div class="row">
  <div class="col-lg-8 offset-lg-2">
    <table id="js-table-comment" class="table">
      <%= render comments %>
    </table>
  </div>
</div>

掲示板一覧画面と掲示板詳細画面において、コメントを作成したユーザーにのみ編集、削除ボタンを表示させる。

  • モデル側にユーザーのコメントであるか判定するメソッドを定義。
  def own?(object)
    id == object.user_id
  end
  • 削除、編集ボタンのパーシャルを作成。
    掲示板の編集と削除のボタンは、掲示板の一覧と詳細ページで同じものを表示する。
<ul class='crud-menu-btn list-inline float-right'>
  <li class="list-inline-item">
      <%= link_to '#', id: "button-edit-#{board.id}" do %>
      <%= icon 'fa', 'pen' %>
    <% end %>
  </li>
  <li class="list-inline-item">
    <%= link_to '#', id: "button-delete-#{board.id}" do %>
      <%= icon 'fas', 'trash' %>
    <% end %>
  </li>
</ul>

掲示板詳細画面show.html.erb

  • 編集削除ボタン
  • コメントフォーム
  • コメントエリア
    を読み込む。
    フォームは、comments/_form.html.erbをレンダーし、@boardと@commentを引数として渡している。
    エリアは、comments /_comments.html.erbをレンダーし、@commentsをcommentsという変数で使えるようにしている。
<!-- 編集、削除ボタン-->
<%= render 'crud_menus', board: @board %>
<!-- コメントフォーム -->
<%= render 'comments/form',{board: @board, comment: @comment} %> 

<!-- コメントエリア -->
<%= render "comments/comments", {comments: @comments} %>
  • 掲示板一覧画面に編集、削除ボタンを読み込む。
<%= render 'crud_menus', board: board %>

comments_controllerの作成。

$ rails g controller comments
userとcommentでアソシエーションが定義されてるのでその点に注意する。

class CommentsController < ApplicationController
  def create
    comment = current_user.comments.build(comment_params)
    if comment.save
      redirect_to board_path(comment.board), success: 'コメントを作成しました'
    else
      redirect_to board_path(comment.board), danger: 'コメントを作成できませんでした'
    end
  end
end

ログインユーザーに関連したコメントを表示させる。
current_userメソッドでログイン中のユーザーを返す。
アソシエーションで定義されたオブジェクトを初期化する際はnewではなくbuildを使う。

 comment = current_user.comments.build(comment_params)

ストロングパラメータの追加。

コメントをデータベースへ保存するにはuser_idとboard_idが必要でcommentの値(body)とboard_idが保存される。
(board_id: params[:board_id])はURLであるboards/:board_id/commentsからboard_idをとってきている。
mergeはハッシュを合体させるメソッド。

  private

  def comment_params
    params.require(:comment).permit(:body).merge(board_id: params[:board_id])
  end

Rails 掲示板にコメント機能を追加 part1

概要

  • 掲示板詳細画面を追加し、その中でコメントをできるようにする。
  • 掲示板一覧画面から掲示板のタイトル名を押すと掲示板詳細画面へ行けるようにする。
  • 書き込んだコメントが一番上に表示されるようにコメントの並び順を指定
  • コメントした本人のみに削除・編集ボタンを表示させる
  • コメントを投稿したユーザーが削除されると、ユーザーが投稿したコメントも削除される
  • コメントが投稿された掲示板が削除されると、掲示板に投稿されたコメントも削除される

モデルの作成

Commentモデルを作成し、User、Boardモデルとアソシエーションを定義する。
Commentモデルに外部キー(user_id, board_id)を設定。
Commentモデルにバリデーションを設定。
今回はreferencesでモデルと同時に作成したのでCommentモデル側のアソシエーションは自動で定義される。
(CommentをUserとBoardに関連付けたいので、User、Boardにhas_many、Commentにbelong_toを定義)

$ rails g model Comment body:text user:references board:references
      invoke  active_record
      create    db/migrate/20210522131533_create_comments.rb
      create    app/models/comment.rb

空のデータは登録できないようにNOTNULL制約を追加。
外部キーが記述されていることを確認する。

class CreateComments < ActiveRecord::Migration[5.2]
  def change
    create_table :comments do |t|
      t.text :body, null: false
      t.references :user, foreign_key: true
      t.references :board, foreign_key: true

      t.timestamps
    end
  end
end

ここまで確認できたらrails db:migrateを実行。

== 20210522131533 CreateComments: migrating ===================================
-- create_table(:comments)
   -> 0.0036s
== 20210522131533 CreateComments: migrated (0.0040s) ==========================

Commnetモデルにバリデーションの追加。
空の投稿を防ぐ為、presence: trueをつける。

class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :board

  validates :body, presence: true, length: { maximum: 10_000 }
end

User、Boardモデル側に以下のアソシエーションを定義
has_many :comments, dependent: :destroy
dependent: :destroyオプションを定義しないと、親モデル(user board)を削除した時に、その親モデルに紐づく子モデル(comment)の内容が残ってしまうので注意する。

掲示板詳細画面の作成

ルーティングの作成

今回は、BoardモデルとCommentモデルが紐付いてるので、ルーティングをネスト(入れ子)する必要がある。
ネストする場合はshallowオプションを使用する。
shallowオプションを使用するとルーティングが簡潔になる。

  resources :boards, only: %i[index new create show] do
    resources :comments, only: %i[create destroy], shallow: true
  end

使用前
comments#createはcommentのidではなくboard_idを指定する。

        Prefix Verb   URI Pattern                              Controller#Action
board_comments POST   /boards/:board_id/comments(.:format)     comments#create
 board_comment DELETE /boards/:board_id/comments/:id(.:format) comments#destroy

使用後
comments#destroyはcommentのidを指定するのでboard_idは指定しない。

        Prefix Verb   URI Pattern                          Controller#Action
board_comments POST   /boards/:board_id/comments(.:format) comments#create
       comment DELETE /comments/:id(.:format)              comments#destroy

controllerの作成

@board = Board.find(params[:id]) でリクエストパラメータ(params)経由でidを受け取りBoardオブジェクトをデータベースから取得。
findはidによってそのモデルに対応するレコードをデータベースから検索する。
params[:id]にはURL “localhost:3000/boards/[id]”の[id]の部分が格納されてる。

class BoardsController < ApplicationController
  skip_before_action :require_login, only: %i[index create show]
#...
  def show
    @board = Board.find(params[:id])
#...

viewの作成

boards/show.html.erbを作成。

#...
<%= image_tag @board.board_image.url, class: 'card-img-top img-fluid', size: '300x200' %>
<%= @board.title %>
#掲示板名の表示
<%= @board.user.decorate.full_name %>
#user_decorator.rbのfull_nameメソッドを呼び出し、ユーザー名を表示する。
<%= l @board.created_at, format: :long %>
#...

lメソッドについて

lメソッドはi18nにおいて日付や時刻を表す際に使用する。
config/locales/ja.ymlに書かれた内容を読み込む。
formatオプションを使うことで複数の書式を使い分けることができる。

ja:
  time:
    formats:
      default: "%Y年%m月%d日(%a) %H時%M分%S秒 %z"
      # 2021年05月23日(日) 02時10分39秒 +0900
      long: "%Y/%m/%d %H:%M"
      # 2021/05/23 02:10
      short: "%m/%d %H:%M"
      # 05/23 02:10

掲示板一覧画面から掲示板のタイトル名を押すと掲示板詳細画面へ行けるようにする。

掲示板一覧画面に詳細画面へのリンクを追加。
今回のboard_pathはidが指定されているのでlink_to prefix_path(渡すid)と記述。

   <%= link_to board.title, board_path(board.id) %>

part2へ続く。

掲示板に画像アップロード機能を実装

実装内容

  • 掲示板作成画面から画像を投稿する。
  • gem CarrierWaveとMiniMagickを利用。
  • サムネイルというラベルを追加し、画像の選択をする。
  • 画像を選択せずに投稿した場合、デフォルトの画像を表示。
  • アップロードできるファイルはjpg, jpeg, png, gifのみに制限。
  • 画像投稿時にプレビューとして表示。

    導入

    CarrierWaveRailsアプリケーションに画像アップロード機能をつけることができる。
    MiniMagickはアップロードされた画像のリサイズなどができる。
    MiniMagickを使用するには、ImageMagickが必要なので事前にインストールする。

$ brew install imagemagick
gem 'carrierwave'
gem 'mini_magick'

Boardモデルのboardsテーブルにboard_imageカラムを追加する。

board_imageカラムには画像情報(ファイルそのものではない)が格納される。

$ rails g migration AddBoardImageToBoards board_image:string
Running via Spring preloader in process 10390
      invoke  active_record
      create    db/migrate/20210520113137_add_board_image_to_boards.rb

画像を選択せずに掲示板を作成できるようにしたいので、マイグレーションファイルはそのままでNOTNULL制約はつけない

class AddBoardImageToBoards < ActiveRecord::Migration[5.2]
  def change
    add_column :boards, :board_image, :string
  end
end
$ rails db:migrate
== 20210520113137 AddBoardImageToBoards: migrating ============================
-- add_column(:boards, :board_image, :string)
   -> 0.0013s
== 20210520113137 AddBoardImageToBoards: migrated (0.0016s) ===================

画像uploaderの作成

ジェネレータでuploaderを生成することから始める。

$ rails g uploader image(アップローダー名)

これにより次のファイルが生成される。
ここには画像のリサイズなど、設定を記述する。

 create  app/uploaders/image_uploader.rb

uploaderとモデルを紐付ける。

今回はBoardモデルと紐付ける。

class Board < ApplicationRecord
  mount_uploader :board_image, ImageUploader

image_uploader.rbの編集

このファイルはデフォルトでコードが記述されており、コメントアウトを解除して使用する。 MiniMagickでリサイズするので記述。

class ImageUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

デフォルトの設定

  • storage :file
    public/uploads/に画像が保存される。

  • def store_dir
    画像が保存される先のpathを指定。

  storage :file

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

追加設定

  • 画像を選択せずに投稿した場合、デフォルトとして使う画像を指定する。
  • アップロードできるファイルはjpg, jpeg, png, gifのみに制限。
  def default_url
    'board_placeholder.png'
 # app/assets/images/board_placeholder.png
  end
 
  def extension_allowlist
    %w(jpg jpeg gif png)
  end

viewの作成

掲示板作成フォームを編集する。
画像投稿時にプレビューとして表示するには、画像はまだデータベースへ登録されていないので、選択した画像はjavascriptを使って表示させる必要がある。

#...
 <div class="form-group">
 <%= f.label :board_image %>
 <%= f.file_field :board_image, onchange: 'previewFile(preview)', class: 'form-control mb-3', accept: 'image/' %>
 <%= f.hidden_field :board_image_cache %>
 </div>
 <div class='mt-3 mb-3'>
 <%= image_tag board.board_image.url, id: 'preview', size: '300x200' %>
 </div>
#...
onchangeイベントハンドラでjavascriptを指定
フォームの入力値、選択が変更されたときに処理を行う。
acceptでファイルを指定する。image/
は画像ファイル、video/*は動画ファイル。

<%= f.hidden_field :board_image_cache %>
これは、掲示板作成できなかった場合、アップロードした画像を消えないようにする処理。

javascriptでプレビューを表示させる。

jsファイルの作成。
application.jsは個別のjavascriptを設定するファイルなので、ここには記述してはいけない。

function previewFileWithId(id) {
    const target = this.event.target;
    const file = target.files[0];
    const reader  = new FileReader();
    reader.onloadend = function () {
        preview.src = reader.result;
    }
    if (file) {
        reader.readAsDataURL(file);
    } else {
        preview.src = '';
    }
  }

掲示板一覧画面へアップロードした画像を表示させる。

board.rbのboard_imageからurlをとってくる。

<%= image_tag board.board_image.url, class: 'card-img-top', size: '300x200' %>

ストロングパラメータに画像のフィールドを追加。

  private

def board_params params.require(:board).permit(:title, :body, :board_image, :board_image_cache) end end

ローカルでアップロードした画像をリモートへアップロードしないように設定。

.gitignoreに画像のアップロード先を追加する。

# Ignore vendor
/public/uploads
既にpushしてしまった場合
rmコマンドでリモートリポジトリから削除する。
 $ git rm -r --cached public/uploads
# public/uploadsディレクトリ下のファイルをすべて削除

ログイン中のユーザーで掲示板の新規作成をする

UserモデルとBoardモデルにアソシエーションを設定

Userと関連付いたboardを作成するので、has_manyをUserモデルに定義する。

class User < ApplicationRecord
  authenticates_with_sorcery!
  validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
#...

  has_many :boards, dependent: :destroy
end

controller

UserモデルとBoardモデルにアソシエーションが定義されている場合の記述。
以下のように記述した場合、登録に成功しない。

class BoardsController < ApplicationController
  skip_before_action :require_login, only: %i[index create]
  def create
    @board = Board.new(board_params) ←ここ
    if @board.save
      redirect_back_or_to boards_path, info: '掲示板を作成しました'
    else
      flash.now[:danger] = '掲示板を作成できませんでした'
      render 'new'
    end
  end

  private

  def board_params
    params.require(:board).permit(:title, :body)
  end
end

原因

user_idカラム作成時にNOT NULL制約をつけているので、上記コードだとuser_idがnilになってしまう。

ログインユーザーに関連した掲示板のオブジェクトを作成するには

@board = current_user.boards.build(board_params)

current_user.boards.newとすることで、user_idを登録したboardオブジェクトを初期化できる。引数としてパラメータ(board_params)を渡すことでフォームに入力されたオブジェクトを作成することができる。アソシエーションで定義されたオブジェクトを初期化する際はnewではなくbuildを使う。(どちらを記述しても挙動は変わらない)
また、以下のようにmergeを使ってuser_idを用意することもできるが、

Board.new(board_params.merge(user_id: current_user.id))

アソシエーション関連であるオブジェクトは強調したほうが良いので、モデルのアソシエーションに関連性のない情報を登録する時にmergeは使うと良い。

view

ビューはパーシャルを使い表示させる。

      <%= form_with model:board, local: true do |f| %>
        <%= render 'shared/error_messages', object: f.object %> 
        <div class="form-group">
          <%= f.label :title %>
          <%= f.text_field :title, class: "form-control" %>
        </div>
        <div class="form-group">
          <%=f.label :body %>
          <%=f.text_area :body, class: "form-control", low: "10", style: "height: 200px" %>
        </div>
        <%= f.submit "登録する", class: "btn btn-primary" %>
      <% end %>
    </div>
  </div>
</div>

パーシャルをnew.htmlで読み込む。

<div class="container">
  <div class="row">
    <div class="col-lg-8 offset-lg-2">
      <h1>掲示板作成</h1>
      <%= render 'form', { board: @board } %>
    </div>
  </div>
</div>

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

Fakerを利用しダミーデータを作成

掲示板の一覧にダミーデータを投入する。

gem Fakerのインストール。
Fakerはdevelopmentとtest環境にインストールする。

group :development, :test do
#...
  gem 'faker'
#...
end

bundle installで完了。

データベース上にダミーデータを生成

今回はBoardモデルのダミーデータを20個作る。 データは公式のREADMEからとってくる。

20.times do
  title = Faker::Games::Pokemon.name
  body = Faker::Games::Pokemon.move
  user = User.offset(rand(User.count)).first
  Board.create!(
    title: title,
    body: body,
    user: user
  )
end

seedに書いた内容をデータベースへ反映。

$ rails db:seed

データが作成されたか確認。 f:id:meo2:20210509035116p:plain

なぜcreate!を使うのか。

createとcreate!の違い
createの場合はユーザーが無効なときfalseを返す。一方、create!は例外を発生させる(例外が発生した時点で処理が止まる)。よって、デバッグが安易になるのでcreate!を使用したほうがいい。

user = User.offset(rand(User.count)).first

について

  • モデル.offset(取得開始位置) 指定位置からレコードを取得。
  • rand 引数に整数を渡したら0以上指定した整数未満の整数を返す。
  • first 最初のレコードを取得。

20個のダミーデータから0〜19個間でランダムに数字を生成して、最初のレコードを取得している。 User.offset(18).firstなら18以降から最初のUserを取得する。