dreamin' blog

前提・背景

railsにはrails runnerとてもよくできたバッチ機構が存在します。
rails runner 'p "hoge"' のように、rails runnerの後ろにrubyでプログラムが書けます。

大体、サービスのバッチで使用する場合は、下記のようにして、
Batchクラスに実装したrunメソッドを実行することが多いようです。

$ rails runner Batch.run

もちろん、Batch.runの部分はただのrailsですので、

$ rails r User.actives.map(&:kick)

のように、書くこともできます。

ただ、大体はバッチの実行用のクラスを用意し、処理をファイルの中に書き、それを実行すると思います。

自分が考えていること

少しでもDRYにしたいにしたい

バッチを毎回毎回普通に作るのはそれでいいんだけど、
バッチの実行時間や実行ログなどは、大体同じ処理になるはず。
失敗したときの通知とか、正常に終了した時の通知とかもあるはず。

少しでもインターフェースを統一したい

バッチファイルの置き場所をつくっても、人によって実行の仕方が違うと混乱する。
runで実行するひととexecで実行する人とか色々いそう。

バッチを書いた時に、自分がチームのインターフェースと違う形で実行していると気付けるようにしたい。
「BaseBatchクラスを作って、それを継承すればいいじゃん!」って話なのですが、rubyはfinalがないので、自分がオレオレで実装すると、そのオレオレが優先されて、チームのルールから外れていることに気づきにくい。

今回考えたこと

バッチを実行するとき大体は事前処理と事後処理があるはずです。
その内容はサービスやバッチによっていろいろだとは思いますが、
今回は実行時間を計測するために、起動時と終了時の時刻をログに書くものを統一するシンプルな共通処理を書いてみました。

こういうbaseのモジュールを作っておき、

module Batch
  module Base
    def before
      Rails.logger(Time.current)
    end

    def run
      before
      execute
      after
    end

    def after
      Rails.logger(Time.current)
    end
  end
end

実行するバッチファイルの方には、prependで、共通処理を上書きしないように書いておきます。

module Batch
  class Example
    prepend Batch::Base

    def execute

    end
  end
end

一応これで完成。

$ rails r "Batch::Example.new.run"

で実行できるようになります。
runメソッドを実装すればいいんだ!と思ってrunを実装しても、Exampleの方にexecuteがないと言って怒られるので、個人的には気付けると思います。
rubyにはfinalがないので、prependで上書きはできないようにしています。

番外編 (もやもや)

コマンドの書き方が気に食わなかったので、

module Batch
  class Executer
    def self.run(klass_name)
      klass_name.constantize.new.run
    end
  end
end

というラッパーを作ってインターフェースを統一してみました。

$ rails r "Batch::Executer.run('Batch::Example')"

あんまり嬉しくないかな?
なんとなく名前が悪い気がするから、gem化してもいいかもね。

コマンドレベルで、とかすればいいか?

$ rake batch run 'Batch::Example'

うーん。微妙か。