【Rails】【cron】1時間ごとにタスクを実行する
実装したいこと
記事公開アプリがあります。
記事の状態が「公開待ち」で公開日時を指定している記事を毎時間確認し、公開日時がきたら自動で公開したい。
新しい用語が色々出てきて混乱するのでまとめていきます。
実装の流れ
- Rakeタスクに、『記事のステータスと公開日時を判定して、「公開」ステータスに変えるタスク』を用意する。
- cronで、上記タスクを1時間毎に実行する
Rakeとは
Rake
とは…Rubyのタスクランナー。
タスク…例えばテストを実行する・データベースを定期的に更新する…などのタスクを実行してくれる。
Rake
なしでこのような小さなタスクを実行しようと思うと、違うファイルを行き来するし、コードが散らかってしまう。
パRails P20にも説明あり。
cronとは
The software utility cron also known as cron job is a time-based job scheduler in Unix-like computer operating systems. Users that set up and maintain software environments use cron to schedule jobs[3] (commands or shell scripts) to run periodically at fixed times, dates, or intervals. cron - Wikipedia
定時や定期的にjobを実行してくれる。DBの管理などで使う。
Rakeタスクの実装
前提
enum
で記事の状態を「下書き」「公開」「公開待ち」に分類しています。
今回のRakeタスクでは、「公開待ち」の記事について、公開日時を越えたら記事の状態を「公開」に変える処理を行います。
enum state: { draft: 0, published: 1, publish_wait: 2 }
lib/tasks
以下ディレクトリにrake
ファイルを作ります。
$ rails g task ファイル名
私のNGコード
namespace :classify_article_status do desc '公開待ちの中で、公開日時が過去のもの:ステータスを「公開」に変更する' task :manage_status => :environment do Article.where(state: :publish_wait).find_each do |article| #publish_waitの状態のものを取得してeachで回している。 if article.published_at <= Time.current #公開日時が現時刻より過去だったら article.published! #enumで定義しているので、このような書き方が可能 end end end end
desc
は、タスクの説明文です。タスク一覧を出力すると、表示されます。
task :タスク名
:environment
でモデルへアクセスします。必要ないときは省略可能。
do
以下に実行したい処理を書いていきます。
find_each
はレコードを1,000件単位で分割で取得してブロック内の処理をループします。
大量のレコードがあったとき、一度に処理を行おうとするとメモリの大量確保が必要となり、サーバの負担となってしまいます。そのため、find_each
で処理を分割して行います。
NG部分
- 不要なデータも
each
で回してしまっている each
文の中で公開日時の判定をしてしまっている- ファイル名とTask名が分かりにくい。今回は、
update
というニュアンスが必要。
OKコード
公開日時が現在〜過去の記事を取得するscope
を準備します。
scope :past_published, ->{ where('published_at <= ?', Time.current) }
ファイル名・タスク名も分かりやすいものにする。
namespace :article_state do desc '公開待ちの中で、公開日時が過去のもの:ステータスを「公開」に変更する' task update_article_state: :environment do Article.publish_wait.past_published.find_each(&:published!) #ここ end end
Article
から「公開待ち」の状態で公開日時が現在〜過去のものを取ってきてから、find_each
でループさせます。
必要なデータだけ先に抽出してから繰り返し処理をしています。
(&:メソッド名)
でpublished!
を実行しています。
Rubyチェリー本P98に詳細の説明あり
Rakeタスクの実行
Rakeタスクの一覧表示
desc
で記入した説明文が表示されています。descriptionの略。
$ bundle exec rake --tasks
Rakeタスクの実行
$ bundle exec rake article_state:update_article_state
gem wheneverの導入
cron
jobsを実行してくれるgem
GitHub - javan/whenever: Cron jobs in Ruby
公式に沿って導入していきます。
gem 'whenever', require: false
$ bundle install $ bundle exec wheneverize # config/schedule.rbファイルが生成される。
suchedule.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
Railsで定期的にバッチ回す「Whenever」 - Qiita
Crontabへの書き込み
$ bundle exec whenever --update-crontab
crontabコマンドの使い方: UNIX/Linuxの部屋
crontab -l
で現在設定されているタスクの一覧が表示される。