概要
みんなー、雑にバッチ書いて遊んでるー!? (ゝω・)v
ところでそういったバッチやスクリプトはみんなの好意によって公開してもらっているけど、その大半は走り書きで実際に業務で書くような書き方ではないよね。当然だけど。
**だから今日は簡単なバッチを、少し丁寧に書いてみたい。**業務レベルではない趣味エンジニアの参考になれば嬉しい。みんなが知っているもっと良い書き方をコメントで教えてくれるともっと嬉しい!
バッチの内容
今回書くのは本当にとっても簡単なバッチだ。 毎朝ネコの画像を見たくない? 見たいよね!
そこで猫の画像urlをAPIで取得してslackの特定チャンネルへ投稿する。これだけだ。
猫の画像をAPIで取得するとは何か。そのままの意味で猫の画像を取得するWebAPIがあるんだ。
http://thecatapi.com/
走り書きとの違い
真面目にバッチを書く、と言っているが複雑なことをやろうとしている訳じゃない。
走り書きのスクリプトと違うのは以下の点だけだ。
- クラスを丁寧に分離する
- ログファイルを残す
- 例外処理をする
- 設定ファイルを利用する
- テストを書く(今回はスキップ)
楽しいコーディングタイム
こうして出来上がったものがこちらです。
https://github.com/owlworks/slack-bot-sample
最初にslackバッチ全体の処理を書いてみる。
require 'bundler'
Bundler.require
class SlackBatch
attr_reader :logger
ROOT_PATH = File.expand_path(File.dirname(__FILE__))
CONFIG = Hashie::Mash.new YAML.load_file File.join(ROOT_PATH, '/config.yml')
def initialize
init_logger
end
def self.execute
batch = new
batch.logger.info "=== #{batch.name} Start"
begin
batch.execute
rescue => e
batch.logger.error [e.class, e.message, e.backtrace].join("\n")
end
batch.logger.info "=== #{batch.name} End"
end
def post_message(channel: nil, text: nil)
@logger.info HTTP.post(CONFIG.slack.api_url.post_message, params: {
token: CONFIG.slack.token,
channel: channel,
text: text,
as_user: true
})
end
def name
self.class.name
end
private
def init_logger
log_path = File.join(ROOT_PATH, '/log/')
log_name = File.join(log_path, "#{name}.log")
FileUtils.mkdir_p(log_path) unless FileTest.exist?(log_path)
@logger = Logger.new(log_name, 3)
end
end
SlackBatchクラスはslackへ投稿をするバッチの基底クラスだ。
slackへ投稿するpost_messageメソッドとロギングをするための処理を持つ。
実際にどんなことをどこへ投稿するか?といったことはこのクラスの関心ではない。
ロギング
ruby標準のライブラリであるloggerを利用する。
また基本的に利用されたAPIは全て出力する。デバッグを行う際には非常に重要な情報となるし
一見して上手く動いている場合にも無駄なAPIが発行されているのを発見できたりする。
SlackBatchを継承するバッチの方では可能な限りロガーについて意識したくない。
そこでクラス名をログファイル名にして勝手に作成してくれるようにする。
self.executeメソッドも同様だ。継承したバッチの方は単にexecuteメソッドだけを記述すれば開始と終了のロギングやエラー発生時の記録を基底クラス側に書かれた処理でやってくれる。複数の様々なバッチを追加で書いて行ってもいちいちこの共通処理について考える必要はなくなった訳だ。
コンフィグ
設定ファイル(config.yml)を作成しておこう。slackのトークンなど変更されることが考えられるものは別ファイル化だ。今回は単純にyamlファイルとして書き出してHashieにすることで少し使いやすくしているがコンフィグ管理に特化したgemを使ったりするといいかもしれない。思うに、本来ならテストや開発時と本番で設定を分離したいので環境に応じた値を使うようにしたいところだが…。
バッチ側の処理
class CatBatch < SlackBatch
CATAPI_URL = "http://thecatapi.com/api/images/get?format=xml"
def execute
doc = Nokogiri::XML(open(CATAPI_URL).read)
@logger.info doc
img_url = doc.xpath("//url").text
post_message(channel: CONFIG.catbatch.channel, text: img_url)
end
end
ばんざい!お陰でバッチ側の関心毎はどこのチャンネルにどんな投稿をするのかだけに絞り込まれた。特にここでは解説することもないだろう。ここでもAPIの取得結果はログへ出しておく。出力の方法については改善の余地がありそうだ。
runnerもどき
さて、気が付いたと思うがこのままbatches.rbを実行したとしても何も起きない。CatBatch.executeをどこかで呼び出さなければならない。単純にbatches.rbの末尾へ書き加えてもいいが、crontabで管理したり環境指定することを想定するとちょっと問題がありそうだ。
そこでrailsのrunnerみたいに必要なスクリプトを読み込んだ上で引数の処理を実行してくれるコードを書いてみた。
require_relative 'batches'
eval ARGV[0]
本当に何のひねりもないが動く。さっそくバッチを起動してみよう。
$ ruby runner.rb "CatBatch.execute"
無事に実行されて可愛いネコの画像がslackへ投稿されれば成功だ。何となくもう少し良いやり方か既存のgemなどがある気がするなぁ。ご存知の方がいたらコメントで教えてくれると嬉しい。
現場から以上です。╭( ・ㅂ・)و