はじめに
掲示板一覧機能を実装していきましょう。
作業の流れ
- Boardモデルを作成
- Fakerを使ってダミーデータを作成する
- 掲示板一覧のルーティングを設定
- 掲示板一覧のコントローラーを作成
- 掲示板一覧画面のビューファイルを作成
- ログインしていなければ掲示板一覧を表示できなくする
- ログイン時の処理を修正する
- タイムゾーンを日本時刻に設定する
Boardモデルを作成
掲示板のモデルを生成しましょう。今回、「title」「body」「userと紐づけるための外部キー」の3つのカラムが欲しいとします。
references型
user has_many bords
の関係を実現するにはbords
テーブルに user_id
が必要です。
外部キーを設定する際は下記の書き方をします。
$ bundle exec rails g model board title:string body:text user:references
なぜなら、普通のrails g model board title:string body:text user_id:integer
という書き方では、
- インデックスが貼られない。
- 外部キー制約が貼られない。
などの問題が生じるからです。
外部キーを作るときは基本的にはreferences型を使うということを肝に銘じておきましょう!
マイグレーションファイルを確認し、NOT NULL制約を追記
マイグレーションファイルと、modelファイルが作成されます。
ついでにNOT NULL制約を追加しておきます。
class CreateBoards < ActiveRecord::Migration[5.2]
def change
create_table :boards do |t|
t.references :user, foreign_key: true
t.string :title, null: false
t.text :body, null: false
t.timestamps
end
end
end
references
型を指定して、rails g model
を実行するとモデルの他に、マイグレーションファイルにも特別な記述が入ります。
それが、t.references
、foreign_key: true
です。
t.references :テーブル名, foreign_key: true
t.references
カラムが他のテーブルを参照していることを示します。カラム名は、指定したテーブルの前にid_
がつきます。(t.references :client であれば、カラム名は id_client になる)foreign_key: true
外部キーであることを示しています。親テーブルに対象が存在しない場合、子テーブルへの保存ができないようにします。
また、reference型で作成すると、インデックスを自動で貼ってくれます。
BoardモデルにUserモデルとのアソシエーションと、バリデーションを設定
掲示板を作成したユーザーとの関連を、belongs_to
で設定します。
また、タイトルと本文の必須入力と、文字数制限のバリデーションを設定します。
class Board < ApplocationRecord
belongs_to :user
validates :title, presence: true, length: { maximum: 255 }
validates :body, presence: true, length: { maximum: 65_535 }
end
マイグレーションします。
$ bundle exec rails db:migrate
アソシエーションの使い方
このようにモデルを関連付けした事で、必要なオブジェクトを呼び出すメソッドが使えるようになります。具体的には、あるユーザーの投稿一覧が欲しい時には、
user.boards
というメソッドを使えば掲示板オブジェクトを取得できます。
また、この投稿に紐づいてるユーザをオブジェクトとして欲しい時には、boards.user
というメソッドで取得することができるようになります。
Fakerを使ってダミーデータを作成する
10.times do
User.create(
first_name: Faker::Name.first_name,
last_name: Faker::Name.last_name,
email: Faker::Internet.email,
password: '12345678',
password_confirmation: '12345678'
)
end
20.times do |index|
Board.create(
user: User.offset(rand(User.count)).first,
title: "タイトル#{index}",
body: "本文#{index}"
)
end
こちら別記事にしましたのでご参照ください。
掲示板一覧画面のルーティングを設定
掲示板一覧画面のルーティングを追記します。
Rails.application.routes.draw do
root 'static_pages#top'
get 'login', to: 'user_sessions#new'
post 'login', to: 'user_sessions#create'
delete 'logout', to: 'user_sessions#destroy'
resources :users, only: %i[new create]
resources :boards, only: %i[index]
end
掲示板一覧のコントローラーを作成
$ bundle exec rails g controller boards
生成されたBoardsController
に、掲示板の一覧をDBから取得する処理を追加します。
class BoardController < ApplicationController
def index
@boards = Board.all.includes(:user).order(created_at: :desc)
end
end
includes
メソッドは、関連付いたモデルのデータを先に取得するメソッドです。このメソッドを使うことで、Boardモデルからデータを取得する際に、関連するUserモデルのデータもまとめて取得してくれます。なぜこのような書き方をするのかというと、N+1問題を起こさないためです。N+1問題については別記事で説明しています。
掲示板一覧画面のビューファイルを作成
掲示板一覧画面の表示方法は、少々複雑なことをします。大本のindex.html.erb
を作成して、単一の掲示板表示部分は_board.html.erb
というパーシャルファイルを作成し、index.html.erbから繰り返し呼び出して掲示板一覧を表示する設計にします。
このときN +1問題が起きないように注意して実装していきましょう。
N+1問題を回避する具体的な方法は別の記事にまとめましたので、こちらを参照してください。
大本の掲示板一覧画面を作成
<div class="container pt-3">
<div class="row">
<div class="col-lg-10 offset-lg-1">
<!-- 検索フォーム --!>
<form>
<div class="input-group mb-3">
<input class="form-control" placeholder="検索ワード" type="search"/>
<div class="input-group-append">
<input type="submit" value="検索" class="btn btn-primary"/>
</div>
</div>
</form>
</div>
</div>
<!-- 掲示板一覧 --!>
<div class="row">
<div class="col-12">
<div class="row">
<% if @boards.present? %>
<%= render @boards %>
<% else %>
<p><% t('.no_result') %></p>
<% end %>
</div>
</div>
</div>
</div>
コードを紐解く
<% if @boards.present? %>
<%= render @boards %>
<% else %>
<p><% t('.no_result') %></p>
<% end %>
掲示板が存在しない場合「掲示板がありません。」と表示させています。
単一の掲示板を表示するパーシャルを作成
<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' %>
<div class="card-body">
<h4 class="card-title">
<a href="#">
<%= board.title %>
</a>
</h4>
<div class='mr10 float-right'>
<a href="#"><%= icon 'fas', 'trash', class: 'pr-1' %></a>
<a href="#"><%= icon 'fa', 'pen' %></a>
</div>
<ul class="list-inline">
<li class="list-inline-item">
<%= icon 'far', 'user' %>
<%= board.user.decorate.full_name %>
</li>
<li class="list-inline-item">
<%= icon 'far', 'calendar' %>
<%= l board.created_at, format: :long %>
</li>
</ul>
<p class="card-text"><%= board.body %></p>
</div>
</div>
</div>
</div>
ヘッダーに掲示板一覧画面へのリンクを追記
<div class="dropdown-menu dropdown-menu-right">
<%= link_to t('boards.index.title'), boards_path, class: 'dropdown-item' %>
<%= link_to '掲示板作成', '#', class: 'dropdown-item' %>
</div>
ロケールファイルに追記
ja:
activerecord:
attributes:
board:
title: 'タイトル'
body: '本文'
ログインしていなければ掲示板一覧を表示できなくする
ユーザーがログインしていなければ、掲示板一覧を表示できないようにして、ログイン画面へリダイレクトさせましょう。
ここでは、require_login
というメソッドをアクションの前に呼ばれるフィルタとして登録します。アクションの前に呼ばれるフィルタを登録するには、before_action
メソッドを使います。
class ApplicationController < ActionController::Base
add_flash_types :success, :info, :warning, :danger
before_action :require_login
private
def not_authenticated
redirect_to login_path, warning: t('defaults.message.require_login')
end
end
require_login
メソッド
sorceryで定義されています。ログイン状態を判定し、ログインしていなければ結果的にnot_authenticated
メソッドを実行します。
not_authenticated
メソッド
このメソッドもsorceryで定義されているメソッドです。
デフォルトではroot_path
へリダイレクトさせているので、login_path
へと設定し直しています。
また、warning
を使って「ログインしてください」というフラッシュメッセージが出るようにします。
未ログイン状態でもトップページ、ユーザー新規登録画面、ログイン画面を表示できるようにする
ApplicationController
のbefore_action
で、require_login
のフィルタを登録したため、未ログインでもアクセス可能なアクションでは、require_action
をskipするように設定します。
トップページのフィルタをスキップする
class StaticPagesController < ApplicationController
skip_before_action :require_login, only: %i[top]
def top; end
end
ログイン画面のフィルタをスキップする
ログイン後の遷移先を掲示板一覧に変更しておきます。
class UserSessionsController < ApplicationController
skip_before_action :require_login, only: %i[new create]
def new; end
def create
@user = login(params[:email], params[:password])
if @user
redirect_back_or_to boards_path, success: t('.success')
else
新規登録画面のフィルタをスキップする
class UsersController < ApplicationController
skip_before_action :require_login, only: %i[new create]
これでログイン成功後は掲示板一覧ページに遷移し、非ログイン時はログイン画面にダイレクトされるようになりました。
タイムゾーンを日本時刻に設定する
config.time_zone = "Tokyo"
config.active_record.default_timezone = :local
config.time_zone
Rails自体のアプリケーションの時刻の設定
config.active_record.default_timezone
DBを読み書きする際に、DBに記録されている時間をどのタイムゾーンで読み込むかの設定
終わりに
非常に実装料が多かったですね。今回のポイントは以下3点です。お疲れさまです。
- Fakerの使い方
- N+1問題
- renderの省略記法
参考記事

コメント