初心者向け本格的なrubyバッチの書き方

2017-10-03



概要

みんなー、雑にバッチ書いて遊んでるー!? (ゝω・)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などがある気がするなぁ。ご存知の方がいたらコメントで教えてくれると嬉しい。

現場から以上です。╭( ・ㅂ・)و


 このエントリーをはてなブックマークに追加


<< スニペットの実行結果を投稿するSlackBot   TTYでクールなコンソールアプリを作ろう >>

[16]