RailsアプリケーションのデータをHubotを介してSlackに通知させる


つくったもの

Railsで開発しているアプリケーションのデータ、例えば日次・週次・月次の新規会員登録数のような値を、チャットボットを介してSlackに通知する仕組みを導入した。今までは同様の数値を管理画面のダッシュボードに表示していたが、チーム全員が毎日見るものなので、チャットボットに毎朝つぶやかせるようにした。

動作環境

  • Amazon Linux AMI release 2016.03
  • Rails 4.2.4
  • Node.js 0.10.46
  • Hubot 2.19.0

実装内容

RailsからDBのデータを出力

最も単純な方法として、Hubotがデータベースへ直接アクセスしてデータを取得することが考えられるが、複雑な条件で取得したい場合にはSQLの作成が面倒。Rails側にはモデルがあるのでこれを利用したい。今回はRakeタスクでデータ取得タスクを作成し、Hubotがこれを実行するようにする。

lib/tasks/report.rake
namespace :report do
  task :yesterday => :environment do
    message = <<-"EOS"
ユーザー登録数:#{User.yesterday.count}
記事投稿数:#{Post.yesterday.count}
コメント数:#{Comment.yesterday.count}
    EOS
    puts message
  end
end
app/models/user.rb
class User < ActiveRecord::Base
  include Reportable
end
app/models/concerns/reportable.rb
module Reportable
  extend ActiveSupport::Concern

  included do
    # 昨日作成されたレコード
    scope :yesterday, -> do
      range = Time.yesterday.beginning_of_day .. Time.yesterday.end_of_day
      where(created_at: range)
    end
  end
end

ここでは「昨日作成されたレコード」を表す条件をconcernにまとめて、これを利用する各モデルでincludeしている。この辺りはアプリケーションに合わせてご自由に。今回はyesterdayだけだが、先週とか先月とか条件が増えていった場合、動的にメソッド生成できたら良さそう。

HubotでRailsからデータを取得

たとえばボットに対して”report”というメッセージが送られてきた時に、レポーティングを返すコマンドをHubotに実装する。child_process.execで先ほど作成したRakeタスクを実行し、標準出力にメッセージを添えて返信している。ここもボットのキャラに合わせてご自由に。

scripts/report.coffee
child_process = require 'child_process'

module.exports = (robot) ->
  robot.respond /report/i, (res) ->
    child_process.exec "cd /path/to/rails-app && rake report:yesterday RAILS_ENV=production", (error, stdout, stderr) ->
      if !error
        output = "昨日の数字をお知らせします!\r\n"+stdout
        res.reply output
      else
        res.reply 'error'

この段階で、ボットは我々の"report"という呼びかけに対してレポーティングをしてくれるようになった。

Hubotでレポートを定期投稿

実際の運用では、我々がボットに呼びかけた時だけでなく、自動で定期的にレポートを投稿してもらいたい。ここでは"cron"というパッケージを利用する。下記記事が参考になった。
http://qiita.com/kon_yu/items/ff884d81f9c1db9b574b

投稿するタイミングの指定は通常のcrontabと同じ。ここでは平日の9:30に投稿するようにした。Railsからレポートを取得する部分は前節のコードと同じ。

scripts/cron.coffee
cronJob = require('cron').CronJob
child_process = require 'child_process'
channel_id = '*********'

module.exports = (robot) ->
  new cronJob('30 9 * * 1-5', () ->
    child_process.exec "cd /path/to/rails-app && rake report:yesterday RAILS_ENV=production", (error, stdout, stderr) ->
      if !error
        output = "昨日の数字をお知らせします!\r\n"+stdout
        robot.send {room: channel_id}, output
      else
        robot.send {room: channel_id}, 'error'
  ).start()

注意点がひとつ。SlackのAPIのバージョンアップによって、投稿するroomを指定する際は、channel名ではなくIDを渡すようになったらしい。下記URLから予め目的のchannel IDを調べておく必要がある。
https://api.slack.com/methods/channels.list

感想

ボットに投稿させることによってチーム全員が同時に数字を見ることになるので、ダッシュボードに比べてその数字についての議論が生まれやすいと思う。

今回はHubotが能動的にデータを取得してレポーティングする仕組みを構築したが、Railsで特定のDB操作があった時(問い合わせが送信されたときなど)にリアルタイムで通知できる仕組みを考えたい。