Sidekiq with Padrino


[Sidekiq](http://sidekiq.org/) を [Padrino](http://www.padrinorb.com/) で使う場合のメモです

```
$ ruby -v
ruby 2.0.0p353 (2013-11-22 revision 43784) [x86_64-darwin13.0.0]
$ padrino -v
Padrino v. 0.11.4
$ sidekiq -V
Sidekiq 2.17.0
```

## 準備　
Sidekiq はジョブキューの管理に [Redis](http://redis.io/)を使うのでインストールしておきます
インストール方法は割愛(mac なら `brew install redis` とか)

```
$ redis-server -v
Redis server v=2.8.1 sha=00000000:0 malloc=libc bits=64 build=b8cc45f60db4b294
```

あとは適当に Padrino プロジェクトを作成したり既存の Padrino プロジェクトに移動します

```
$ padrino g project sidekiq_with_padrino_sample -d minirecord -t rspec
$ cd sidekiq_with_padrino_sample
```
## インストール
```
$ vim Gemfile
```
```ruby:Gemfile
gem 'sidekiq'
```
```
$ bundle install
```
もし `Bundler could not find compatible versions for gem "activesupport":` のようなエラーが出た場合は Gemfile におまじないを追記します

```ruby:Gemfile
gem 'activesupport’, '>= 3.2', '< 4.0'
```

## Worker

### workerを追加する
バックグラウンドで働かせたいクラスを workers ディレクトリ以下に配置して管理する方針で進めます

```
$ mkdir workers
$ vim worders/hard_worker.rb
```
```ruby:workers/hard_worker.rb
class HardWorker
  include Sidekiq::Worker

  def perform(name, count)
    raise name if name == 'crash'
    logger.info "Doing hard work: #{name}"
    sleep count
  end

end
```
### workerを使う
上で作った hard_worker をコントローラの中で使ってみます

```
$ bundle exec padrino g controller sample get:index
$ vim app/controllers/sample.rb
```
```ruby:app/controllers/sample.rb
SidekiqWithPadrinoSample::App.controllers :sample, map: '/' do
  get :index do
    @count = rand(10)
    logger.info "Adding #{@count} jobs"
    @count.times do |i|
      HardWorker.perform_async("Job_#{i}", 1)
    end
  end
end
```

では動かしてみます

```
$ bundle exec padrino s
```
```
$ curl localhost:3000
   INFO -  Adding 4 jobs
NameError - uninitialized constant HardWorker:
```
worker が読み込めてないのでエラーになります

config/boot.rb を編集して workers ディレクトリの中身を読み込むようにします

```ruby:config/boot.rb
Padrino.before_load do
  Padrino.require_dependencies Dir[Padrino.root('workers', '**', '*.rb')]
end
```
再度確認

```
$ bundle exec padrino s
```
```
$ curl localhost:3000
   INFO -  Adding 5 jobs
```
```
$ redis-cli
127.0.0.1:6379> LLEN queue:default
(integer) 5
```
ちゃんとキューに入ってるっぽいですね

### sidekiqを起動してジョブを実行させる
このままだとジョブがキューに溜まったままで実行されないので sidekiq コマンドを叩いてジョブを処理させます

```
$ bundle exec sidekiq -r ./config/boot.rb
HardWorker JID-964a803489a72625b4a4562b INFO: start
HardWorker JID-964a803489a72625b4a4562b INFO: Doing hard work: job_1
HardWorker JID-43088873fa56b66813c5e6db INFO: start
HardWorker JID-43088873fa56b66813c5e6db INFO: Doing hard work: job_0
HardWorker JID-4425acb6e3b41d96b1b9c6b5 INFO: start
HardWorker JID-4425acb6e3b41d96b1b9c6b5 INFO: Doing hard work: job_2
HardWorker JID-a190428084bd67332ff6114e INFO: start
HardWorker JID-a190428084bd67332ff6114e INFO: Doing hard work: job_3
HardWorker JID-2cc0feff0ee5880daab2558f INFO: start
HardWorker JID-2cc0feff0ee5880daab2558f INFO: Doing hard work: job_4
HardWorker JID-43088873fa56b66813c5e6db INFO: done: 1.001 sec
HardWorker JID-964a803489a72625b4a4562b INFO: done: 1.004 sec
HardWorker JID-a190428084bd67332ff6114e INFO: done: 1.002 sec
HardWorker JID-2cc0feff0ee5880daab2558f INFO: done: 1.001 sec
HardWorker JID-4425acb6e3b41d96b1b9c6b5 INFO: done: 1.004 sec
```
キューに入っていたジョブのログがドドッと出力されます

## ActiveRecord

### 確認用のモデルを作る

```
$ bundle exec padrino g model post title:string body:text
$ vim models/post.rb 
```

```ruby:models/post.rb 
class Post < ActiveRecord::Base
  # Fields
  field :title, :as => :string
  field :body,  :as => :text

  def long_method
    puts "#Long method [#{self.id}] start"
    sleep 5
    puts "#Long method [#{self.id}] finished"
  end
end
```

### ActiveRecord 用の extension を設定する

```ruby:config/boot.rb
Padrino.after_load do
  # - omit -
  ActiveRecord::Base.send(:include, Sidekiq::Extensions::ActiveRecord)
end
```
### バックグラウンド実行してみる

`Post#long_method` を sidekiq の delay メソッド経由で実行させます

```ruby:app/controllers/sample.rb
get :delayed_post do
  post  = Post.where(title: 'First post', body: 'body').first_or_create
  post.delay.long_method
  'enqueued'
end
```
確認してみます

```
$ bundle exec padrino s
```
```
$ curl localhost:3000/delayed_post
enqueued
```
```
$ bundle exec sidekiq -r ./config/boot.rb
#Long method [1] start
#Long method [1] finished
```
できました

※この手順通りにやって `unable to open database file` って怒られた場合は、手動で db ディレクトリを作ってみてください

## Mailer

### 確認用のメーラーを作る

```
$ bundle exec padrino g mailer notifier greeting
$ vim app/mailers/notifier.rb
```
```ruby:app/mailers/notifier.rb
SidekiqWithPadrinoSample::App.mailer :notifier do

  email :greeting do |now|
    from    'sender@example.com'
    to      'recipient@example.com'
    subject 'Hello!'

    locals now: now, hostname: `hostname`.strip
    render 'notifier/greeting'
  end

end
```
```app/views/mailers/notifier/greeting.slim
p Hi!, it's #{now} and I'm at #{hostname}.
```

###  メーラーの設定

確認用なので、実際にメールを飛ばさないように適当に設定しておきます

```ruby:app/app.rb
set :delivery_method, :test
```
### メール送信ジョブをキューに溜める
```ruby:app/controllers/sample.rb
get :email do
  SidekiqWithPadrinoSample::App.delay_for(10.seconds).deliver(:notifier, :greetings, Time.now)
  'enqueued'
end
```
```
$ bundle exec padrino s
```
```
$ curl localhost:3000/email
enqueued
```

### sidekiq 起動用のファイルを用意する

```ruby:config/sidekiq.rb
require File.expand_path("../boot.rb", __FILE__)

# Loads the Padrino applications mounted within the project.
Padrino.mounted_apps.each do |app|
  logger.info "=> Loading Padrino Application : #{app.app_class}"
  app.app_obj.setup_application!
end
```

### キューに溜まったジョブを実行させる

```
$ bundle exec sidekiq -r ./config/sidekiq.rb
  …
  DEBUG -  Sending email to: recipient@example.com
  …
```
できました

## Monitoring

`sidekiq/web`をマウントする

```ruby:config/apps.rb
# Mount the Sidekiq monitoring application
require 'sidekiq/web'
Padrino.mount('Sidekiq::Web', app_class: 'Sidekiq::Web', app_root: Sidekiq::Web.root).to('/sidekiq')
```
おまじない

```ruby:lib/sidekiq-web.rb
require 'sidekiq/web'
class Sidekiq::Web < ::Sinatra::Base
  class << self
    def dependencies; []; end
    def setup_application!; end
    def reload!; end
  end
end
```
これでOK

```
$ bundle exec padrino s
```
```
$ open http://localhost:3000/sidekiq
```

## ロガーの設定
```ruby:config/boot.rb
Sidekiq.logger = Padrino.logger
```

## 開発環境等では非同期にしない
```ruby:config/boot.rb
require 'sidekiq/testing/inline' if PADRINO_ENV == 'development'
```

## テスト

RSpec  を使っているのであれば [rspec-sidekiq](https://github.com/philostler/rspec-sidekiq) を使うのがいいのかな？

```ruby:Gemfile
gem 'rspec-sidekiq', :group => 'test'
```

```ruby:spec/workers/hard_worker_spec.rb
require 'spec_helper'

describe HardWorker do
  it { expect(HardWorker).to be_processed_in :default }
end
```

## この記事のコード
[Github に置いてあります](https://github.com/tyabe/sidekiq_with_padrino_sample)

## 参考
https://github.com/mperham/sidekiq/pull/760/files
http://qiita.com/nysalor/items/94ecd53c2141d1c27d1f
http://qiita.com/mat_aki/items/840b63817ea858fa09b7