記事ステータスの追加
実装したこと
・記事を投稿するアプリの編集画面で、記事のステータスを「下書き」「公開」「公開待ち」に分類したい。
・ステータスと公開日時は編集時に選択可能。ただし、公開日時によって記事のステータスを「公開」「公開待ち」に自動で判定して変更する。
・ステータスを「下書き」に指定したときは、「下書き」のままにする。
コントローラー
artcles_controller.rb(記事更新用)
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メソッドで変更を受け取る #引数で指定したattributes(属性)を変更するメソッド
②現在時刻が公開日を過ぎているか? いないかでstateを公開、公開待ちと振り分ける。
③ saveメソッドで保存 #DBへの保存はされないので必要。
published_controller.rb(記事公開用)
def update @article.published_at = Time.current unless @article.published_at? @article.state = @article.publishable? ? :published : :publish_wait #上記の一文は三項演算子 if @article.valid? Article.transaction do @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
@article.publishable?が真ならpublishedを 偽ならpublish_waitを@article.stateに代入
モデルへ切り分ける
①日時で公開可能か判定
②判定結果ごとのメッセージ分け
③draft?はstateがdraftかどうか判定する trueならreturnによりここで処理が終わる(nilが返る)
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? self.state = if publishable? # stateがpublishedなら:published(公開)を返す :published else # それ以外(publish_waitしか残ってないのでpublish_wait)なら:publish_wait(公開待ち)を返す :publish_wait end end
公開日を1時間ごとにしか設定できないように変更する
admin.js
format: 'YYYY-MM-DD HH:00'
Rakeタスク
Rakeタスクとは
アプリケーションを起動せず、行いたい処理をCUI(コマンドプロンプトやターミナル)から実行できます。CSVデータのインポートなど、サーバーを起動せず任意の処理を実行する際にこの機能がよく利用されます。
今回でいうと、公開待ちの中で、公開日時が過去になっているものがあれば、ステータスを「公開」に変更されるようにする。
上記の処理をrakefileに記述し、後ほど紹介するwhenneverと組み合わせることにより、自由なタイミングで処理を走らせることができます。
前提
app/models/article.rb
enumで記事の状態を「下書き」「公開」「公開待ち」に分類しています。
今回のRakeタスクでは、「公開待ち」の記事について、公開日時を越えたら記事の状態を「公開」に変える処理を行います。
app/models/article.rb
# draft: 下書き, published: 公開, publish_wait: 公開待ち enum state: { draft: 0, published: 1, publish_wait: 2 }
Rakeファイルの作成
rails g task article_state # rails g task ファイル名
lib/tasks/article_state.rake
namespace :article_state do desc '公開日時が過去の日付の「公開待ち」記事があれば、ステータスを「公開」に変更する' # desc = description(説明) task update_article_state: :environment do # environmentはDBとのやりとりが必要な際に記述します Article.publish_wait.past_published.find_each(&:published!) end end
Articleから「公開待ち」の状態で公開日時が現在〜過去のものを取ってきてから、find_eachでループさせます。 必要なデータだけ先に抽出してから繰り返し処理をしています。 (&:メソッド名)でpublished!を実行
公開日時が現在〜過去の記事を取得するscopeを準備
app/models/article.rb
scope :past_published, ->{ where('published_at <= ?', Time.current) }
Rakeタスクの実行
bundle exec rake article_state:update_article_state
cronとは
UNIX系のOSには、cronと呼ばれる仕組みが標準で備わっています。
cronとは、多くのUNIX系OSで標準的に利用される常駐プログラム(デーモン)の一種で、利用者の設定したスケジュールに従って指定されたプログラムを定期的に起動してくれるもの。
そして、このcronに対して命令を行うには、crontabというコマンドを実行します。
crontab -l で現在設定されている定期実行タスクの一覧を表示させたり、
crontab -r でcronを削除できたりなど、様々な命令を行うことが可能となっております。
gem wheneverの導入
cronjobsを実行してくれるgem
gem 'whenever', require: false
require: falseとするのはこのGem自体がRailsアプリケーションに反映するものではなく、 ターミナル(言わばOS)に反映させるため
$ bundle exec wheneverize
config/schedule.rbファイルが生成
schedule.rb
# Rails.rootを使用する require File.expand_path(File.dirname(__FILE__) + "/environment") # cronを実行する環境変数(RAILS_ENVが指定されていないときはdevelopmentを使用) rails_env = ENV['RAILS_ENV'] || :development # cronの実行環境を指定(上記で作成した変数を指定) set :environment, rails_env # cronのログファイルの出力先指定 set :output, "#{Rails.root}/log/cron.log" #一時間毎に実行する&タスク名の指定 every 1.hours do rake 'article_state:update_article_state' end
Crontabへの書き込み
$ bundle exec whenever --update-crontab
参考文献