Rails アソシエーションを活用して、コントローラー処理を短く記載する方法

前提

・UsersモデルとBoardsモデルをアソシエーションしていること(1対多) (has_manyとbelong to)

#app/models/user.rb
class User < ApplicationRecord
  authenticates_with_sorcery!
  has_many :boards, dependent: :destroy
#app/models/board.rb
class Board < ApplicationRecord
  belongs_to :user

実装

実装前にアソシエーションを入れない場合を記入

#app/controllers/boards_controller.rb
# 初期化した後に値を代入
def create
  @board = Board.new(board_params)※
  @board.user_id = current_user.id
  if @board.save
    redirect_to boards_path, success: t('.success')
  else
    flash.now[:danger] = t('.fail')
    render :new
  end
end

次にアソシエーションを活用した場合

#アソシエーションを活用
def create
  @board = current_user.boards.build(board_params)
  if @board.save
    redirect_to boards_path, success: t('.success')
  else
    flash.now[:danger] = t('.fail')
    render :new
  end
end

※引数に(board_params)となっているのは、boards_controller.rbにストロングパラメーターを設定している為。

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

説明

  • モデルにhas_many :boardsを設定することで、userオブジェクトでboardsというメソッドが使えるようになる。 current_user.boards.newと実行することで、user_idを登録したboardオブジェクトを初期化できる。 また、newの引数にパラメータを渡すことで入力フォームから渡ってきた情報のオブジェクトが作成できる。 このように、アソシエーションで関連したオブジェクトを初期化する際は、通常の初期化と区別してnewではなくbuildと記載することがある。 ちなみに、buildはnewのエイリアス(別名)のため、どちらで記載しても挙動は変わらない。

  • 以上の記法は開発現場では当たり前のように使われているため、開発者としてはこの記載に慣れておく必要がある。 この記載を知らなければオブジェクトを初期化してから必要なデータを代入したり、引数のパラメータ情報のmergeなどが必要になる。

  • また、「掲示板のオブジェクトを初期化する」「初期化したオブジェクトにログインユーザーのIDを登録する」と2行に分かれた情報を理解させるより、 「ログインユーザーに関連した掲示板のオブジェクトを作成する」と記載した方が読み手に取ってのコードの理解が早くなる。 このように1行で記載することで、全体のコード量が削減されるので、他のより重要な処理の記載に集中できるようになるメリットがある。

  • パラメータにmergeすることで1行でも記載できるが、アソシエーションで関連しているオブジェクトであることを強調した方が良い。 また、モデルにアソシエーションの関連性がない情報を登録する場合は、mergeを使って短く記載することが可能。

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

追記1 コントローラー側で値を保持しない処理の場合、ローカル変数にする

CommentsControllerのcreateアクションでリダイレクトさせる場合、不要なインスタンス変数を使っていないこと

# 例
  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

成功時、失敗時どちらもredirectするので、インスタンス変数にしてもそれをViewで参照することがない。 今までは登録失敗時にrenderで表示することが多かったため、@commentの保存失敗時にredirect_toを使うことに疑問を持った方もいるかもしれません。

今回は、今までのような入力フォームの再表示とは違い、コメントの作成・失敗後は紐づいている掲示板の詳細画面に遷移して、そこで関連するコメントの一覧を読み込んで表示する仕様になっています。

仮にrenderで表示する場合は、Board#showのアクションと同様に、@boardのインスタンス変数を取得している必要があります。

入力するコメントは1カラムのみで、エラーメッセージも1種類でフォームの入力内容によって出し分けたりしないことから、解答例ではredirect_toを使って実装しました。 この場合にはインスタンス変数を使う必要はないので、ローカル変数を使っています。

追記2 belongs_to, has_manyで追加されるメソッド

railsguides.jp

railsguides.jp