【Rails】FatControllerをスッキリさせる
実装したいこと
記事を投稿するアプリの編集画面で、記事のステータスを「下書き」「公開」「公開待ち」に分類したい。
ステータスと公開日時は編集時に選択可能。ただし、公開日時によって記事のステータスを「公開」「公開待ち」に自動で判定して変更する。
ステータスを「下書き」に指定したときは、「下書き」のままにする。
前提
enum
で記事のステータスを定義しています。
enum state: { draft: 0, published: 1, publish_wait: 2 }
コントローラーは2つに分けています。
articles_controller.rb
記事全般の挙動を持つコントローラ
publishes_controller.rb
記事を公開するためのコントローラ
FatController
私が誤ったコード 反省と復習用にみていきます。
def update authorize(@article) if @article.update(article_params) if @article.state == 'draft' #下書きのときはそのまま @article.draft! elsif Time.current >= @article.published_at #公開日時が現在〜過去のとき @article.published! #公開 elsif Time.current < @article.published_at #公開日時が未来のとき @article.publish_wait! #公開待ち end flash[:notice] = '更新しました' redirect_to edit_article_path(@article.uuid) else render :edit end end
Time.current
とpublished_at(公開日時)
を比較し、公開日時が現在〜過去のときは、article
のステータスをpublished
に更新しています。
@article.draft!
のように直接属性を繋げて書けるのは、enum
で定義しているためです。
def update @article.published_at = Time.current unless @article.published_at? if @article.valid? 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_article_path(@article.uuid) else flash.now[:alert] = 'エラーがあります。確認してください。' @article.state = @article.state_was if @article.state_changed? render 'admin/articles/edit' end end
こちらも、選択した公開日時を現在日時と比較して、「公開」or 「公開待ち」のステータスに変更しています。
何がダメなのか
- ステータスの判定でenum用のメソッドを使っていない。
(例)@article.draft?
- コントローラで判定の処理をしていて、FatControllerになっている。
改善
日時で判定してステータスを変更する処理はmodel
に切り出していきます。
諸々をモデルへ切り分ける
コントローラで判定していたものを以下のメソッドに移していきます。
- 日時で公開可能か判定 #1
- 判定結果ごとのメッセージ分け #2
- ステータス(
state
)の調整 #3
def publishable? #1 Time.current >= published_at end def message_on_published #2 if published? '公開しました' elsif publish_wait? '公開待ちにしました。' end end def adjust_state #3 return if draft? self.state = if publishable? :published else :publish_wait end end
#3
のメソッドについて詳しくみていきます。
return if draft?
draft
だった場合はreturn
メソッドで式を抜けます。
self.state = 〜
self
をつけないと、state
に代入することになってしまうので、省略不可です。
article.state
のように使われます。
if~else文
の戻り値をself.state
に代入しています。
修正したコントローラ
def update authorize(@article) @article.assign_attributes(article_params) @article.adjust_state #3でステータスを調整 if @article.save flash[:notice] = '更新しました' redirect_to edit_article_path(@article.uuid) else render :edit end end
assign_attributes
で送られたパラメーターの属性をまとめて上書きする。DBは更新されないので、save
は必要。
ActiveModel::AttributeAssignment
まとめてオブジェクトの属性を変更したい時に便利!assign_attributesメソッド - その辺にいるWebエンジニアの備忘録
def update @article.published_at = Time.current unless @article.published_at? @article.state = @article.publishable? ? :published : :publish_wait if @article.valid? 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
@article.state = @article.publishable? ? :published : :publish_wait
三項演算子です。
@article.publishable?
が真ならpublished
を、偽ならpublish_wait
を@article.state
に代入します。
どちらのコントローラもまず、ステータス判定処理をしてから、保存処理をしています。
最初に実装したコードはif文をネストさせており、かなり読みにくかったです。
コードを読むことはできても、いざ自分で実装!となると汚くなってしまいます。。
英語の勉強と同じで、反復あるのみだと思うのでがんばります。