読者です 読者をやめる 読者になる 読者になる

pblog

pplog.net を作っている @ppworks こと越川直人(Koshikawa Naoto)のブログ

Railsのコントローラーの責務を意識する

rails

Railsのコントローラーの仕事は何か? - スモールスタート」という記事がRailsのコントローラーを設計する際のとても良い指針となっているので、ちょくちょく参考にさせて頂いております。ここからさらに考えたことをまとめてみます。

Railsのコントローラーの責務を意識することが大事です。あくまでもよいコントローラーとなっているかは、URLで表されるリソースに対して、コントローラーのアクションの責務が明確であるか です。

scaffoldで考える

scaffoldを作ってみましょう。

rails g scaffold post title content:text

以下の様なコントローラーが出来上がります。

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  respond_to :html

  def index
    @posts = Post.all
    respond_with(@posts)
  end

  def show
    respond_with(@post)
  end

  def new
    @post = Post.new
    respond_with(@post)
  end

  def edit
  end

  def create
    @post = Post.new(post_params)
    @post.save
    respond_with(@post)
  end

  def update
    @post.update(post_params)
    respond_with(@post)
  end

  def destroy
    @post.destroy
    respond_with(@post)
  end

  private
    def set_post
      @post = Post.find(params[:id])
    end

    def post_params
      params.require(:post).permit(:title, :content)
    end
end

ここで注目したいのは、show, edit, update, destroyset_postbefore_actionで実行される理由です。この理由をどう捉えるか考えましょう。私は2つの理由が思い浮かびました。

  • 同じ処理が複数回あるのでまとめた
  • 各アクションのメインの責務ではないのでフィルターにまとめた

同じ処理が複数回あるのでまとめた

見ての通りDRYのため、と考えることが出来ますね。初めてこのscaffoldの処理を見た時こう思いました。これの考えは正解だと思っています。

各アクションのメインの責務ではないのでフィルターにまとめた

ただ、私はこちらを推します。

  • showは、URLで指定されたリソースを 表示する
  • editは、URLで指定されたリソースを更新するためのフォームを表示する
  • updateは、URLで指定されたリソースを更新する
  • destroyは、URLで指定されたリソースを削除する

大事なのは、強調した部分です。コントローラの責務は、URLで表されるリソースに、どんな処理をするかです。

共通化している部分は、URLで表されるリソースを取得する部分なので、コントローラーで大事な処理というよりも、注目すべき対象となるリソース です。それはメインではないのでフィルターに任せます。メインというよりサブぐらいですかね。

なので、インスタンス変数 として目立たせるのです。「Railsのコントローラーの仕事は何か? - スモールスタート」でも言及されているようにviewにレンダリングするために渡したいというよりも、そのアクションで 注目している対象はなにか? という点が大事です。

そしてアクションの中には大事な処理だけを書くというスタンスです。

この考えで行くと@posts = Post.all@post = Post.newもフィルターで良いと考えます。改修した、scaffoldが以下です。

class PostsController < ApplicationController
  # URLからリソースを取得しインスタンス変数にセットするフィルターを定義
  before_action :set_new_post, only: [:new, :create]
  before_action :set_posts, only: [:index]
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  respond_to :html

  def index
    respond_with(@posts)
  end

  def show
    respond_with(@post)
  end

  def new
    respond_with(@post)
  end

  def edit
  end

  def create
    @post.save
    respond_with(@post)
  end

  def update
    @post.update(post_params)
    respond_with(@post)
  end

  def destroy
    @post.destroy
    respond_with(@post)
  end

  private
    def set_post
      @post = Post.find(params[:id])
    end

    def set_posts
      @posts = Post.all
    end
    
    def set_new_post
      @post = Post.new(post_params)
    end

    def post_params
      params.fetch(:post).permit(:title, :content)
    end
end

このように考えると、複数呼ばれるからset_postを切り出すという方針ではないので、どのコントローラーでも(showだけなどset_postが1度しか呼ばれない場合も)

  • URLから対象となるリソースを決定する処理をフィルターで定義
  • リソースに対して行う処理を各アクションに書く

という流れに統一出来ると思います。良いRailsのコントローラーとは処理が同じようなものとなると考えます。

複雑な処理となった場合は、パーフェクトRailsにもあるように、Service層の導入によりコントローラーをシンプルに保つことができます。また、インスタンス変数を整理するためには、apotonick/cellsdrapergem/draperなどでviewを整理することを検討しましょう。

パーフェクトRuby on Rails

パーフェクトRuby on Rails