はじめに
- ブログサービスの記事ステータスを「下書き」「公開」「公開待ち」に分類される。
- ステータスと公開日時は、編集画面で変更可能。
- ステータスと公開日時によって「更新する」ボタンを押したときの挙動が異なる。
- ステータスが「公開、公開待ち」かつ、公開日時が「現在または過去」の日付に設定されている場合は、ステータスを「公開」に変更する。
- ステータスが「公開、公開待ち」かつ、公開日時が「未来」の日付に設定されている場合は、ステータスを「公開待ち」に変更する。
- ステータスが「下書き」に設定されている場合は、ステータスを「下書き」のままにする。
- ステータスと公開日時によって「公開する」ボタンを押したときの挙動が異なる。
- 公開日時が「過去」に設定されている場合は、記事のステータスを「公開」にする。
- 公開日時が「未来」に設定されている場合は、記事のステータスを「公開待ち」にする。
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.current
と published_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.state
のself
は省略不可であることに注意が必要です。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
コメント