[Rails] FatControllerの改善

学習記録

はじめに

  • ブログサービスの記事ステータスを「下書き」「公開」「公開待ち」に分類される。
  • ステータスと公開日時は、編集画面で変更可能。
  • ステータスと公開日時によって「更新する」ボタンを押したときの挙動が異なる。
    • ステータスが「公開公開待ち」かつ、公開日時が「現在または過去」の日付に設定されている場合は、ステータスを「公開」に変更する。
    • ステータスが「公開公開待ち」かつ、公開日時が「未来」の日付に設定されている場合は、ステータスを「公開待ち」に変更する。
    • ステータスが「下書き」に設定されている場合は、ステータスを「下書き」のままにする。
  • ステータスと公開日時によって「公開する」ボタンを押したときの挙動が異なる。
    • 公開日時が「過去」に設定されている場合は、記事のステータスを「公開」にする。
    • 公開日時が「未来」に設定されている場合は、記事のステータスを「公開待ち」にする。

enumの追加

articleモデルに、「公開待ち」(publish_wait)のステータスを追加します。

enum state: { draft: 0, published: 1, publish_wait: 2 }

コントローラの作成(FatController改善前)

記事に関する主なCRUD機能はadmin/articles_controller.rbで設定されています。
記事の公開に関する機能はadmin/articles/publishes_controller.rb で設定されています。

articles_controller.rb

「更新する」ボタンを押した場合は 、updateアクションが発動されます。

def update
  authorize(@article)

  if @article.update(article_params)
    if @article.state != 'draft'      # ステータスが下書きではない場合、
    @article.state = if @article.published_at <= Time.current    # 公開日時が現在または過去の時
                       :published                                # 公開
                     else                                        
                       :publish_wait                             # 公開待ち
                     end
    end
    @article.save!
    flash[:notice] = '更新しました'
    redirent_to edit_admin_article_path(@article.uuid)
  else
    render :edit
  end
end

private

def article_params
  params.require(:article).permit(:title, :description, :slug, :state, :published_at, :eye_catch, :category_id, :author_id, tag_ids: [])
end

コードを紐解く

Time.currentpublished_at(公開日時カラム)を比較して、ステータスを更新しています。
enumを設定することで、下記のようなメソッドが使えるようになっています。


[25] pry(main)> Article.states
=> {"draft"=>0, "published"=>1, "publish_wait"=>2}
[26] pry(main)> article.state
=> "draft"
[28] pry(main)> article.draft?
=> true
[31] pry(main)> article.published?
=> false
[32] pry(main)> Article.states
=> {"draft"=>0, "published"=>1, "publish_wait"=>2}
[3] pry(main)> article.draft!    # ステータスをdraftに変更
=> true
[5] pry(main)> article.state_was
=> "draft"
[6] pry(main)> article.state_changed?
=> false

publishes_controller.rb

記事を公開したときのupdateアクションに公開日時によってステータスを変更する条件分岐を書いていきます。

  def update
    @article.published_at = Time.current unless @article.published_at?     # 
    @article.state = :published

    if @article.valid?
      Article.transaction do
        @article.body = @article.build_body(self)
        @article.save!
      end

      if Time.current >= @article.published_at       # 公開日時が現在または過去の時
        @article.published!
        flash[:notice] = '記事を公開しました'
      elsif Time.current < @article.published_at     # 公開日時が未来の時
        @article.publish_wait!
        flash[:notice] = '記事を公開待ちにしました'
      end
      redirect_to edit_admin_article_path(@article.uuid)
    else
      flash.now[:alert] = 'エラーがあります。確認してください。'

      @article.state = @article.state_was if @article.state_changed?    # もしステータスが変わっていれば、ステータスを戻す
      render 'admin/articles/edit'
    end
  end

  private

  def set_article
    @article = Article.find_by!(uuid: params[:article_uuid])
  end

FatController改善後

前項のコントローラの記述でも動作しますが、コントローラ内で判定処理などをしており、FatControllerになってしまっています。
判定ロジックなどはモデルに切り出してしまいましょう。

models/article.rb

def publishable?     # 公開可能か判定するメソッド
  Time.current >= published_at
end

def message_on_published  # ステータスによってフラッシュメッセージを分岐させるメソッド
  if published?
    '公開しました'
  elsif  publish_wait?
    '公開待ちにしました'
  end
end 

def adjust_state
  return if draft?         # もしステータスがdraftならメソッドを抜け出す
  
  self.state = if publishable?        # 公開可能か判定
                 :published           # 公開
               else
                 :publish_wait        # 公開待ち
               end
end

adjust_stateメソッドは三項演算子にしても良いでしょう。

def adjust_state        
  self.state = publishable? ? :published : :publish_wait
end

コードを紐解く

adjust_stateメソッドは、self.stateselfは省略不可であることに注意が必要です。ariticle.stateのように使われるので、省略してしまうとただのローカル変数になってしまいます。

articles_controller.rb

def update
    authorize(@article)

    @article.assign_attributes(article_params)
    @article.adjust_state
    if @article.save
      flash[:notice] = '更新しました'
      redirect_to edit_admin_article_path(@article.uuid)
    else
      render :edit
    end
end

コードを紐解く

assign_attributes メソッド 送られてきた値をまとめて上書きするが、DBに保存はしないメソッドです。なぜupdateメソッドでは無いかと言うと、送られてきた値に対してadjust_stateメソッドでステータスを判定した後にsaveメソッドを使ってDBを保存しているので、updateメソッドで先にDBに保存してしまうと無駄にSQLを発行してしまう事になってしまうからです。

publishes_controller.rb

def update
  @article.published_at = Time.current unless @article.published_at?
  @article.state = @article.publishable? ? :published : :publish_wait      # @article.adjust_state でも良い

  if @article.valid?
    Article.transaction do
      @article.body = @article.build_body(self)
      @article.save!
    end

    flash[:notice] = @article.message_on_published
    redirect_to edit_admin_article_path(@article.uuid)
  else
    flash.now[:alert] = 'エラーがあります。確認してください。'
    @article.state = @article.state_was if @article.state_changed?
    render 'admin/articles/edit'
  end
end

コメント

タイトルとURLをコピーしました