学習記録

アウトプット用に作りました

一時間ごとにRakeタスクを実行する

今回行いたいことは、公開待ちになっているブログの記事を公開日時になったら自動で公開してくれるような実装をすることです。
ここで使う機能として3つあります。この3つを使って実装していくので少しややこしく感じますが、一つずつ説明していきたいと思います。

  1. Rakeタスク

  2. whenever

  3. cron


Rakeタスクとは

Rakeは、Ruby および Rails アプリケーションで人気のあるタスクランナーです。例: Rails は、データベースの作成、移行の実行、テストの実行のための事前定義された Rake タスクを提供します。カスタムタスクを作成して特定のアクションを自動化することもできます。コード分析ツールの実行、データベースのバックアップなど。

Rake タスクの実行 | RubyMine

そしてこのRakeが実行する処理内容を「Rakeタスク」と呼び、定義する場所を「Rakefile」と呼びます。


Rakeタスクの使用方法

Rakeタスクに「公開日時になっている公開待ちの記事を公開に変更する」を定義していきます。enumで記事の状態を「公開(published)」「公開待ち(future_publish)」に分類しています。

models/article.rb

enum state: { published: 0, future_publish: 1 }


まず最初にタスクファイルを生成します。
ファイル名は何を行うファイルなのかわかりやすい名前をつけると良いです。今回は「article_state」にしました。

$ rails g task ファイル名

lib/tasks/配下にarticle_state.rakeというファイルが生成されました。
ファイルの中身は以下のようになっていると思います。

namespace :article_state do
end


作成されたファイルに実行したい処理を記入していきます。

lib/tasks/article_state.rake

namespace :article_state do
  desc '公開待ちの中で、公開日時が過去になっているものがあれば、ステータスを「公開」に変更されるようにする。'
  task change_to_be_published: :environment do
    Article.future_publish.past_published.find_each(&:published!)
  end
end

descには処理の説明を記入します。
taskにはタスク名をつけます。
:environmentはデータベースに接続する必要がある場合につけます。今回はstateカラムのデータが変わるので必要です。
do以下に処理内容を記入します。

do以下のコードは少し難しいので一つずつ読み解いていきます。

past_publishedは下記のarticleモデルで定義したscopeのメソッドです。
事前に公開日時が現在からみて過去の記事を取得するためのscopeを準備します。
参考 : 部分一致とか日付の範囲で検索したい - Qiita

models/article.rb

scope :past_published, -> { where('published_at <= ?', Time.current) }


find_eachは大量のデータを一度に取得してループするのではなく決まった単位(デフォルト1,000件)ごとに取得してループしてくれる機能です。データを分割して取得することで少ないメモリで処理することができます。
参考 : [Rails]find_eachが無限ループして本番環境のメモリを食いつぶした話 - Qiita

&:は配列(find_eachやmap、select、pluckなど)の中身を受け取るようなメソッドで使用することができます。

この二つのコードは同じ意味です。
Article.find_each(&:published!)
Article.find_each { |article| article.published! }


published!enumのメソッドです。以下でenumのメソッドについて説明しておきます。

article = Article.new(state: :future_publish)

article.state
=> future_publish

article.future_publish?
=> true

article.published?
=> false

article.published!
=> stateをpublishedに変更することができます。
   article.state = 'published'と同じ意味ですが、コード量が多くなるのでNGですArticle.published
=> publishedになっている記事をまとめて全件検索することができます。 
   Article.where(state: :published)と同じ意味になります。


ファイルの中身が書き終わったら作ったRakeファイルを確認します。

$ rails -T

f:id:kimura34:20210424180006p:plain このようにコマンド上で確認することができます。

これで「公開日時になっている公開待ちの記事を公開に変更する」を定義できました。次は一時間ごとに自動で「公開待ちを公開にする」設定を行っていきたいと思います。


wheneverを導入する

wheneverはcrontab管理ライブラリです。 Gemfileにwheneverのgemを追加してbundle installを行います。

gem 'whenever', require: false

require: falseRailsの内部でwheneverを使うわけではないため、Railsの実行時に読み込まないようにするためです。

wheneverの設定ファイルを作成します。

$ bundle exec wheneverize .

このコマンドによってconfig/schedule.rbファイルが生成されました。このファイルの中で、cronを設定していきます。

config/schedule.rb

# Rails.rootを使用するために必要。なぜなら、wheneverは読み込まれるときにrailsを起動する必要がある
require File.expand_path(File.dirname(__FILE__) + "/environment")

# cronを実行する環境変数
rails_env = ENV['RAILS_ENV'] || :development

# cronを実行する環境変数をセット
set :environment, rails_env

# cronのログの吐き出し場所。ここでエラー内容を確認する
set :output, "#{Rails.root}/log/cron.log"

# 1時間ごとにarticle_state.rakeのchange_to_be_publishedを実行する
every 1.hour do
  rake "article_state:change_to_be_published"
end

参考 : [初学者]whenever を使って定期的にバッチ処理を行う(公開設定編) - Qiita


crontabを実行する

cronとは「○時になったら○○のコマンドを実行」などといった具合に、定期的にコマンドを実行するためにメモリ場で常に命令を待機しているプロセスです。このcronに対して命令を行うには、crontabというコマンドを実行して登録・確認・管理しています。

# wheneverの設定更新
$ bundle exec whenever --update-crontab

このコマンドを使うことでcronにデータを反映して、schedule.rbファイルに記載したコードを実行することができます。
他にも下記のようなcrontabコマンドがあります。

# 設定内容にエラーがないか確認
$ bundle exec whenever

# 設定されているcronを見る
$ crontab -l

# crontabの設定削除
$ bundle exec whenever --clear-crontab

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