memberとcollectionオプション(ルーティング)
resourcesを用いると7つのルーティングが一度に作成される。memberとcollectionオプションはどちらも新たにルーティングを加えたい場合に用いる。
member…特定のデータ(:idが入る)に対するアクションを追加する際に用いる。
collection…ユーザー検索や商品検索等、全てのデータを対象とするアクションに対し用いる。
resources :photos do
collection do
get 'search'
end
end
resources :photos do
get 'search', on: :collection
end
on: :collectionという書き方も可能。
resources :photos do
get 'preview', on: :member
end
memberについてもon: :memberの書き方が可能。
Railsのconfigについて
Railsではconfigディレクトリ下のファイルで設定を変更する。
♡config/application.rb
全環境で共通する設定ファイル
rails generateする際に生成したくないファイルがあった場合、このファイルに変更を加えていく。
assetsを生成したくない場合は、
config.generators do |g|
g.assets false
end
とすると、assetsを除外して他の必要なファイルを生成することができる。
♡①environment/development.rb
開発環境における設定ファイル
②environment/test.rb
テスト環境における設定ファイル
↑上記2つを開発時は用いる
③environment/production.rb
本番環境における設定ファイル
♡locales/
国際化する際に用いるディレクトリ
i18nを用いて日本語化したい文言を書き込むja.ymlなどが入るディレクトリ
掲示板の検索機能を実装(ransack)
<実現したいこと>
検索フォームの作成をする
♡検索フォームに入力された文言が掲示板のタイトルか本文に含まれている掲示板のみ表紙させる
♡ブックマーク一覧のページで検索した場合、「ブックマークした掲示板の中から」検索条件に合致したものを表示させる
<手順>
♡ransackの導入
♡コントローラーの編集
♡検索フォームの表記を使い回せるようパーシャルを作成
♡検索フォームを表記したいところにrenderで埋め込んでいく
①ransackの導入
Gemfileに
gem 'ransack'
を書き込み、bundle installする。
②コントローラの編集
今回はboard一覧から検索する処理、bookmark一覧から検索する処理を追加するため、
app/cotrollers/boards_controllersのindexとbookmarksアクションに編集を加える。
indexアクション部分
@q = Board.ransack(params[:q])
@boards = @q.result(distinct: true).includes(%i[user bookmarks])
.order(created_at: :desc).page(params[:page])
bookmarksアクションの部分
@q = current_user.bookmark_boards.ransack(params[:q])
@bookmark_boards = @q.result(distinct: true).includes(:user)
.order(created_at: :desc).page(params[:page])
☆distinct: trueに必要性
コメントで検索をしていく場合、1つの掲示板(Aとする)に2つの「Ruby」(BとCとする)という文言が含まれたコメントがついているとする。コメント検索で「Ruby」と指定して、検索した場合、BとCどちらも検索に引っかかる。結果として”Bで検索したA”と”Cで検索したA”どちらもヒットしてしまい、検索結果に掲示板Aが2つ表示されることになってしまう。
そこでdistinct: true
がしてあることでAという掲示板は重複することなく表示される。
※今回の場合は必須ではないが、癖づけておくと良いということだった。
params[:q]に検索フォームで入力された文言が入ってきて、@qに格納される。
③検索フォームの表記を使い回せるようパーシャルを作成
app/views/boards/_search_form.html.erb
<%= search_form_for q, url: url do |f| %>
<div class='input-group mb-3'>
<%= f.search_field :title_or_body_cont,
class: 'form-control',
placeholder: t('defaults.search_word') %>
<div class="input-group-append">
<%= f.submit class: 'btn btn-primary' %>
</div>
</div>
<% end %>
search_form_forはransackで用意されているヘルパー。
placeholder
で検索フォームにデフォルトで表示されている文言を表示している。
title_or_body_cont
で掲示板のtitle(タイトル)とbody(本文)を最後の_contで検索できるようにしている。cont = contain 部分一致の検索が可能になるLIKE演算子。含まれているものを検索する。
f.submit
のあとにすぐclassの指定をしているが、何もボタン表記の文言を指定しなければ自動で「検索」という表記になるため。
url: url
でurlオプションを設定し、リクエストするurlを指定している。④でrenderする際に、呼び出し側で指定したパスをローカル変数に渡す。どこからでも呼び出せる汎用性の高いパーシャルファイルにすることができるため、このような表記になっている。
④検索フォームを表記したいところにrenderで埋め込んでいく
掲示板一覧(app/views/boards/index.html.erb)
<%= render 'search_form', url: boards_path, q: @q %>
ブックマーク一覧(app/views/boards/bookmarks.html.erb)
<%= render 'search_form', url: bookmarks_boards_path,q: @q %>
☆同じboards内にパーシャルを作成しているため、'boards/search_form'
のboards部分を省略。
☆パーシャル内でインスタンス変数を用いてしまうと汎用性が低下してしまうため、ローカル変数を用いる。そこでq: @q
はパーシャル内のローカル変数qに値を渡す。
☆url部分でリクエストするurlを渡している。
掲示板のページネーション
♡実現したいこと
ページネーションとはそのまま、ページ機能を作ること。google検索をしたときに下にある、1.2.3...のようなもの。
今回の場合は掲示板を何件か表示したら(例えで今回は10件)、次のページに移動して、また10件表示させる。
♡手順
①gemのkaminariをインストール
②kaminariの設定ファイルを作成後、設定する
③ページネーションしたいビューに関連するコントローラーの修正(今回はboard一覧なのでapp/controllers/boards_controller.rb)
④実際に表示したいビューに追記して反映していく
手順は以下にも詳細に載っている↓
https://github.com/kaminari/kaminari#configuring-kaminari
①gemのkaminariをインストール
Gemfileに
gem 'kaminari'
を追記し、bundle installする。
②kaminariの設定ファイルを作成後、設定する
ターミナルで
rails g kaminari:config
すると、config/initializers下にkaminari_config.rbが作成される。
config/initializers/kaminari_config.rbを開いて、実際に設定をしていく。
全てコメントアウトされた状態なので、使いたい部分(今回はページ毎に何個表示したいか)をコメントアウトして設定をしていく。
今回は例として10件にしたいので以下のように記載する。
config.default_per_page = 10
③ページネーションしたいビューに関連するコントローラーの修正(今回はboard一覧なのでapp/controllers/boards_controller.rb)
すでに作成しているapp/controllers/boards_controller.rbに追記をしていく。
今回は.page(params[:page])
のみ追記する。表示の都合上折り返すが、一文になっている。
def index
@boards = Board.all.includes(%i[user bookmarks])
.order(created_at: :desc).page(params[:page])
end
④実際に表示したいビューに追記して反映していく
今回は掲示板一覧を10件ごとに表示していきたいので、app/views/boards/index.html.erbに以下の内容を追加していく。
<%= paginate @boards %>
表示位置は実際にブラウザ上で反映されたものを確認しながら、調整していく。
ブックマーク機能の実装(モデル)
<実現したいこと>
♡bookmarkを通さずuserとboardを関連性を維持しつつ、
bookmarkを通してuserとboardのテーブルを関連付ける。
♡コントローラーの記述量を減らすこと、汎用性を高めるためのモデルへの記載をする。
♡掲示板を作ったユーザーがお気に入りをしてしまわないように一意性制約をかける。
中間テーブルを通じてデータのやりとりをしている。
<手順>
①app/models/user.rbへの記載(テーブル同士の関連づけ)
②コントローラーへの記載を減らすため、app/models/user.rbに定義をしておく
③掲示板作成者が自分の掲示板にお気に入りを付けてしまうことを防ぐため、app/models/bookmark.rbに一意性制約をかける
①app/models/user.rbへの記載(テーブル同士の関連づけ)
app/models/user.rb
has_many :boards, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :bookmarks, dependent: :destroy
has_many :bookmark_boards, through: :bookmarks, source: :board
# has many :boards, through: :bookmarksだが,名前が重複するためbookmark_boardsを用いる
ブックマークをしていないとき→userとboardは繋がっている必要がある。
has many :boards
が必要。
ブックマークをしているとき→
has_many :bookmark_boards, through: :bookmarks, source: :board
through: :bookmarks
= ブックマークを通して`
source: :board = board
を参照する
has_many :boards
としても良いように思われるが、ブックマークをしていないときの記述と被ってしまうため、has_many :bookmark_boardsとする。
②コントローラーへの記載を減らすため、app/models/user.rbに定義をしておく
def bookmark(board)
bookmark_boards << board
# <<で引数を渡した掲示板の情報がbookmark_boardsに入ってる
end
# <<は指定されたオブジェクトの末尾に破壊的に追加できる
# 強制的に追加されて保存もされているのでsaveメソッドがいらない
def unbookmark(board)
bookmark_boards.delete(board)
# ブックマークを通して取得した特定のboardを削除する
end
def bookmark?(board)
bookmark_boards.include?(board)
# ブックマークがboardを含んでいるか
# (ブックマークをしていればbookmark_boardsを通してboardが参照される)
end
③app/models/bookmark.rbに一意性制約をかける
app/models/bookmark.rb
belongs_to :user
belongs_to :board
validates :user_id, uniqueness: { scope: :board_id }
# 掲示板idと同じユーザidがお気に入り関係にならないよう、一意性制約を付けている
scopeで一意性チェックの範囲を限定している。今回の場合board_id
scopeは複数指定も可能
掲示板の編集と削除機能の実装(view)
<実現したいこと>
編集と削除ボタンは掲示板を作成した本人にのみ表示させる
①編集・削除ボタンの部分テンプレートを作成
<ul class='crud-menu-btn list-inline float-right'>
<li class="list-inline-item">
<%= link_to edit_board_path(board), id: "button-edit-#{board.id}" do %>
<%= icon 'fa', 'pen' %>
<% end %>
</li>
<li class="list-inline-item">
<%= link_to board_path(board), id: "button-delete-#{board.id}", method: :delete, data: {confirm: t('defaults.message.delete_confirm')} do %>
<%= icon 'fas', 'trash' %>
<% end %>
</li>
</ul>
idは今後、jsを使ってボタンを操作するときのために割り振っている。
app/views/boards/show.html.erb
<%= render 'crud_menus', board: @board if current_user.own?(@board) %>
でviewファイルから部分テンプレート(パーシャル)を呼び出して、編集、削除ボタンを表示する。
呼び出したいパーシャルと呼び出すファイルが同じディレクトリ内にあるため、ディレクトリ名を指定 = 'boards/crud_menus'としなくても呼び出すことができる。また、board : @boardはレンダー先でboardを@boardとして扱うためである。基本的にパーシャルでインスタンス変数は用いず、ローカル変数を用いる。if current_user own?(@board)で掲示板を作成した本人かの判定をしている。
current_user.own?(@board)
の部分は、app/models/user.rb内に記載した
def own?(object)
id == object.user_id
end
を呼び出している。
掲示板の編集と削除機能の実装(ルーティングとコントローラー)
<実現したいこと>
♡編集機能と削除機能の追加
♡掲示板を作成した本人のみが、編集と削除をできるようにしたい
<手順>
♡config/routes.rbに編集と削除機能の定義を追加する
♡app/controllers/boards.controller.rbに定義を追加する
①config/routes.rbに編集と削除機能の定義を追加する
resourcesで一括管理できるアクションindex・show・new・create・edit・update・destroy7つあり、一つずつ定義した場合以下のようになる。
Rails.application.routes.draw do
get 'boards' => 'boards#index'
get 'boards/:id' => 'boards#show'
get 'boards/new' => 'boards#new'
post 'boards' => 'boards#create'
get 'boards/:id/edit' => 'boards#edit'
patch 'boards/:id' => 'boards#update'
delete 'boards/:id' => 'boards#destroy'
end
☆resouceの場合…
show・new・create・edit・update・destroyになり、単数なのでindexがなくなり、識別する必要がないためidも生成されない。
今回の編集と削除機能の実装により、boardsは全てのアクションを用いることになったため、config/routes.rbは以下のようになる。
resources :boards do
resources :comments, only: %i[create], shallow: true
end
②app/controllers/boards.controller.rbに定義を追加する
before_action :set_board, only: %i[edit update destroy]
(省略)
def edit; end
def update
if @board.update(board_params)
redirect_to @board, success: t('defaults.updated', item: Board.model_name.human)
else
flash.now[:danger] = t('defaults.no_updated', item: Board.model_name.human)
render :edit
end
end
def destroy
@board.destroy!
redirect_to boards_path, success: t('defaults.deleted', item: Board.model_name.human)
end
private
def set_board
@board = current_user.boards.find(params[:id])
end
☆current_user.boards.find(params[:id])
とすることで、実際に掲示板を作成したユーザーのみがデータを取得することができるようになる。
☆!を付けるか否かは例外を発生させるかどうかで異なる
!のを付ける場合→バリデーションに引っかかると、ActiveRecord::RecordInvalid例外が発生する。
!を付けない前者の場合→falseが返ってくる。
⇨例えば、saveを用いてDBに保存できたかどうかによって処理を分岐したい場合、失敗することも想定しており、falseが返ってきた場合の処理を決めておく。
detroyの場合、失敗することが想定されないため、例外を発生させたいため、destroy!を用いる。
☆最後の
def set_board
@board = current_user.boards.find(params[:id])
end
は、edit・update・destroyに共通しているコードなので、最初に
before_action :set_board, only: %i[edit update destroy]
とbefore_actionにまとめて記載することができる。