Programming Journal

学習したことの整理用です。

【Rails】【cron】1時間ごとにタスクを実行する

実装したいこと

記事公開アプリがあります。
記事の状態が「公開待ち」で公開日時を指定している記事を毎時間確認し、公開日時がきたら自動で公開したい。

新しい用語が色々出てきて混乱するのでまとめていきます。

実装の流れ

  • Rakeタスクに、『記事のステータスと公開日時を判定して、「公開」ステータスに変えるタスク』を用意する。
  • cronで、上記タスクを1時間毎に実行する

Rakeとは

Rakeとは…Rubyのタスクランナー。
タスク…例えばテストを実行する・データベースを定期的に更新する…などのタスクを実行してくれる。
Rakeなしでこのような小さなタスクを実行しようと思うと、違うファイルを行き来するし、コードが散らかってしまう。

https://www.rubyguides.com/2019/02/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

f:id:Study-Diary:20201009085044p:plain
タスク一覧

Rakeタスクの実行

$ bundle exec rake article_state:update_article_state

gem wheneverの導入

cronjobsを実行してくれる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

gemのwheneverを導入してみた - まっしろけっけ

Railsで定期的にバッチ回す「Whenever」 - Qiita

Crontabへの書き込み

$ bundle exec whenever --update-crontab

crontabコマンドの使い方: UNIX/Linuxの部屋

crontab -lで現在設定されているタスクの一覧が表示される。