Ruby On Rails におけるバッチ処理の作り方

Railsでのバッチ処理を普通に検索すると、wheneverがヒットする。
別にこれでもいいんだけど、whenevercronに依存しているのが気に食わない。dockerで環境を作っていたりするとcronデーモンをキックするのに余計な手間がかかる。
次点でsidekiqを使う方法も出てくるけど、sidekiqredisが必須要件となっていて、サーバの構成要件に口出しできない立場だったりするとそもそも導入はできない。

ということで、cronoというgemを使って実現する方法を以下に示す。
多分これが一番手数が少ない。

cronoを使用するには

A time-based background job scheduler daemon (just like Cron) for Rails
Readmeに書いてあることをほとんどそのままやればよろしい。

Gemfileに次の2件を追加

gem 'crono'
gem 'daemons'

gemのインストール、cronoのインストール、migrateし直し

bundle install
rails generate crono:install
rails db:environment:set db:drop db:create db:migrate db:seed

ActiveJobのジェネレータでjobの作成

rails g job aggregate

ActiveJobが作った app/jobs/application_job.rb に、 ログ出力切り替え を追加し・・・

class ApplicationJob < ActiveJob::Base
  def jobLogger
    Crono.logger.nil? ? Rails.logger : Crono.logger
  end
end

app/jobs/aggregate_job.rb に、jobの中身を書いて
(ここには書いてないけど、普通にActiveRecordとか使って処理できる)

class AggregateJob < ApplicationJob
  queue_as :default

  #
  # 請求データの月次バッチ
  #
  def perform(*_args)
    # require 'byebug'; byebug
    jobLogger.info '*** start aggregate ***'
  end
end

config/crontab.rb に起動するタイミングを書く。
(Crono.performに、上記ActiveJobのジェネレータで作ったクラス名を渡す)

# Crono.perform(TestJob).every 2.days, at: '15:30'
#
# Crono.perform(AggregateJob).every 1.minutes
# Crono.perform(AggregateJob).every 10.second # for test

# 月初に実行
Crono.perform(AggregateJob).every 1.month, at: 'start of the month at 0am'

cronoデーモンを実行するのは以下

bundle exec crono start

rails c などで手動実行する場合は AggregateJob.perform_now
(つまり、rails g jobで作ったクラス名とperform_nowメソッド)

# rails c
Running via Spring preloader in process 68
Loading development environment (Rails 5.2.2)
irb(main):001:0> AggregateJob.perform_now
Performing AggregateJob (Job ID: d3283fa8-eafd-4703-a30d-d0eb67b20461) from Async(default)
2019-05-15T11:48:57+09:00  *** start aggregate ***
Performed AggregateJob (Job ID: d3283fa8-eafd-4703-a30d-d0eb67b20461) from Async(default) in 3.82ms
=> true
irb(main):002:0>

cronoデーモンの実行ログは log/crono.log に出力される。

ログ出力切り替えに関して

ApplicationJobログ出力切り替え を用意した理由は、cronoデーモンで実行された場合には Rails.logger を叩いてくれないから。