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 のルーティング - Railsガイド

DIVE INTO CODE | Railsのルーティングを学ぼう②

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などが入るディレクト

 

設定ファイル(config) | Railsドキュメント

I18n入門書

掲示板の検索機能を実装(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にまとめて記載することができる。