Sidekiq をバックエンドに ActiveJob を導入

Heroku に 30 秒でレスポンスを返さないといけないルールがあったのを忘れていたので、 急遽 Rails アプリで時間がかかる処理を非同期にすることにした。

Rails で非同期というと Resque や Sidekiq が今のところ人気だけど、 今回は Rails 4.2 で追加予定の ActiveJob を使うことにした。 バックエンドには Sidekiq。

Redis をインストール

バックエンドに使う Sidekiq は Redis が必要なので、 Vagrant + Chef + Berkshelf で構築している開発環境にインストールする。

Berksfile に

cookbook "redisio"

を追加し

rm -rf cookbooks
berks vendor cookbooks

で Cookbook をインストール。

redisio のレシピを使うように Vagrantfile を

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # ...

  config.vm.provision "chef_solo" do |chef|
    chef.cookbooks_path = ["./chef/cookbooks", "./chef/site-cookbooks"]

    # ...

    # Redis のレシピを追加
    chef.add_recipe "redisio::install"
    chef.add_recipe "redisio::enable"
  end
end

という風に修正したら、vagrant provision で chef-solo を実行。

ActiveJob と Sidekiq をインストール

ここからは vagrant ssh で開発環境に入っての作業。

Gemfile に

gem "activejob", "4.2.0.beta1", require: "active_job/railtie"
gem "sidekiq"
gem "sinatra"

を追加して bundle install

ActiveJob は gem を直接使う。 Rails 4.1 のアプリで使いたかったので、activesuport 4.2.0.beta2 が不要な、4.2.0.beta1 を使用。 active_job/railtie を require しないと、ActiveJob が Rails に読み込まれなかった。

あとは config/environment/development.rb で ActiveJob の設定を記述。 バックエンドで Sidekiq を使うように指定する。

Rails.application.configure do
  # ...

  # ActiveJob のバックエンドに Sidekiq を指定
  config.active_job.queue_adapter = :sidekiq
end

ローカルにインストールした Redis を使うので、Sidekiq の Redis 設定は省略可能。

Sidekiq の Web コンソールをマウント

ちゃんとジョブが実行されたか確認したいので、Sidekiq の Web コンソールをマウントする。 sinatra もインストールしたのは、これが理由。 Sidekiq の Web コンソールは Sinatra を別にインストールしないといけない。

config/routes.rb を次のように修正。

Rails.application.routes.draw do
  # ...

  # Sidekiq の Web コンソールをマウント
  mount Sidekiq::Web => "/sidekiq"
end

ジョブを作成

次のコマンドでジョブを生成。

bundle exec railg generate job calc_score

すると

class CalcScoreJob < ActiveJob::Base
  def perform(*args)
  end
end

みたいな雛形が app/jobs/calc_score_job.rb に生成されるので、 perform メソッドに非同期で実行したい処理を書く。 こんな感じ。

class CalcScoreJob < ActiveJob::Base
  def perform(*args)
    project_id = args[0]
    project = Project.find(project_id)
    project.calc_score # 30 秒以上かかる集計処理
  end
end

コントローラーでジョブを使う

コントローラーで作成したジョブを使ってみる。

ドキュメントには perform_later を使うと書いてあったけど、 RubyGems.org からインストールした 4.2.0.beta1 にはまだ実装されていないので、NoMethodError。 代わりに古い APIenqueue を呼び出す。

class ProjectsController < ApplicationController

  # ...

  def calc_score
    # 本当は
    # CalcScoreJob.perform_later(data)

    # ジョブ実行
    CalcScoreJob.enqueue(@project.id)
    respond_to do |format|
      format.html { redirect_to @project }
    end
  end
end

ジョブを実行

Rails プロジェクト直下に、次の内容を書いた Procfile を作成する。

web: bundle exec unicorn -p $PORT -c config/unicorn.rb
worker: bundle exec sidekiq

Foreman を使って Rails サーバーと Sidekiq ワーカーを起動。

web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb
worker: bundle exec sidekiq

コントローラーのアクションを呼び出してジョブを実行したら、 Sidekiq の Web コンソールで確認してみる。

ブラウザで http://localhost:8080/sidekiq にアクセスして

f:id:griefworker:20140928122506p:plain

Processed の数が増えていれば、ActiveJob のバックエンドとして Sidekiq がちゃんと動いたことになる。

プロフェッショナルのための実践Heroku入門

趣味レベルで Heroku にはさわってきたけど、 公式のドキュメントを隅から隅まで読んだわけではないので、 知らない情報を本書から得ることができた。

例えば Herokuのアーキテクチャ、特に Slug。 .slugignore に Slug に含めないファイルを指定して、 Slug コンパイル高速化&サイズ縮小する手法なんかは即試してみた。

本番環境へ移行するときに役立つであろう情報がまとまっているのもありがたい。 SSL の導入手順や、 Heroku Postgres でのデータベースのバックアップ方法と移行方法は、 「なんとなく知ってるけどきちんと調べなきゃいけないな」と思っていただけにタイムリーだった。

欲を言えば、アドオンの解説がもっと欲しいところ。 Logging 系のアドオンは必要って何度も書いてあるから、 Papertrail とか紹介してもよかったと思う。 あと NewRelic はもはや必須アドオンといっても過言じゃないので、 他の説明のついでではなく、ちゃんと専用にページを割いて欲しかった。

最初、買おうかどうか迷っていたけど、 新しい情報を得ることができたし、本番環境へ移行するための方法もゲットできたので、 今では買ってよかったと思っている。

Heroku Postgres のデータベースをリセットする方法メモ

忘れて毎回ネットで検索しているので、ブログにメモしておく。

heroku pg:reset DATABASE

上記コマンドをそのままターミナルにコピペして実行すればいい。DATABASE のところを置き換える必要なし。

ステージング用とプロダクション用に複数のアプリがある場合は

heroku pg:reset DATABASE --app <your_app_name>

を実行する。<your_app_name> は Heroku アプリの名前で置き換える。

Capybara + Poltergeist を使ってテストするための環境を Vagrant + Chef で構築

プライベートで開発に関わっている Rails アプリが完成に近づいてきたので、 Capybara と RSpec を使ってインテグレーションテストを書くことにした。

JavaScript で動きをつけたページもきちんとテストしたいので、 JavaScript ドライバに Poltergeist を選択。

以下、作業メモ。

Chef で Phantomjs をインストール

まずは、Poltergeist が依存している、ヘッドレスブラウザの Phantomjs のインストールが必要。

Phantomjs はコミュニティ Cookbook を使ってインストールする。

Berkfile に

cookbook "phantomjs"

の1行を追加し、

berks vendor cookbooks

でインストール。既に cookbooks フォルダが存在する場合は、先に削除しておくこと。

開発環境は Vagrant で構築しているので、Vagrantfile に

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # ...

  config.vm.provision "chef_solo" do |chef|
    chef.cookbooks_path = ["./chef/cookbooks", "./chef/site-cookbooks"]

    # ...

    # Phantomjs のレシピを追加
    chef.add_recipe "phantomjs::default"
  end
end

を記述して

vagrant provision

を実行。

Capybara と Poltergeist をインストール

ここからは vagrant ssh仮想マシンに入って作業する。

Rails プロジェクトの Gemfile に

group :test do
  # 下記を追加
  gem "capybara"
  gem "database_cleaner"
  gem "poltergeist"
end

を記述。投入したテストデータがちゃんと削除されるように、database_cleaner もあわせて使う。

bundle install

を実行してインストール。

Capybara と Poltergeist と DatabaseCleaner を有効にする

spec_helper.rb に、Capybara と Poltergeist と DatabaseCleaner の設定を記述。

# ...

# Capybara の設定
require 'capybara/rspec'
require 'capybara/rails'
require 'capybara/poltergeist'
Capybara.javascript_driver = :poltergeist

RSpec.configure do |config|
  # ...

  # database_cleaner の設定
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end
  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
  end
end

これで準備完了

あとは spec/features フォルダ下にフィーチャーを書いて、

bin/rake spec:features

でテストを走らせればいい。

下記のように、js: true オプションを指定した箇所は Poltergeist を使って実行されるようになる。

describe "JavaScript を使ったページのテスト", js: true do
  it "JavaScript が実行されたかのテスト" do
    # テスト内容を書く
  end
end

Pow をアンインストール

だいぶ前に、マルチテナントの Rails アプリをテストするために Pow を導入した。

便利に使っていたけど、もう用済みになったのでアンインストール。

$ curl get.pow.cx/uninstall.sh | sh

Pow は Mac 以外で使えないのがもったいない。 Node ではなく Go で作られていたら Windows でも使えたりしたかもな。

環境や開発者ごとに異なる設定を記述するのに dotenv が便利だった

Rails アプリ開発中、メール送信機能をテストするには GmailSMTP サーバーを使うのが手っ取り早い。

その際、ActionMailer の設定に Gmail のアカウントとパスワードを書く必要があるけど、 開発者ごとに違うし、そもそもアカウントとパスワードをソースコードに直接書きたくない。

そういった環境や開発者ごとに異なる情報を記述するために、 dotenv(dotenv-rails) を導入してみた。

Gemfile に

gem "dotenv-rails", groups: [:development, :test]

を追加して bundle install

プロジェクトのルートディレクトリに .env ファイルを作成し、環境変数にセットしたい情報を記述する。

GMAIL_ADDRESS="your-address@gmail.com"
GMAIL_PASSWORD="your-password"

すると、config/environment/development.rb の ActionMailer の設定が次のように書ける。

config.action_mailer.delivery_method = :smtp
config.action_mailer.raise_delivery_errors = true
config.action_mailer.smtp_settings = {
  enable_starttls_auto: true,
  address: "smtp.gmail.com",
  port: 587,
  domain: "smtp.gmail.com",
  authentication: "plain",
  user_name: ENV["GMAIL_ADDRESS"],
  password: ENV["GMAIL_PASSWORD"]
}

bin/rails server で開発サーバーを起動すれば、.env ファイルに書いた環境変数が自動で読み込まれて ENV にセットされる。

.env ファイルをリポジトリから除外するように .gitignore に追加しておけば、 Gmail のアカウントとパスワードをソースコードに記述せずに、 心置きなくメール送信をテストできる。

RubyMotion をアンインストール

Apple の新言語 Swift を使い始めていて、思いのほか書き心地が良かったので、 iOS アプリ開発は RubyMotion から Swift に移行することにした。

そこで、後戻りできないように、MacBook から RubyMotion をアンインストール。

sudo rm -rf /Library/RubyMotion
sudo rm /usr/bin/motion
rm ~/Library/RubyMotion

書き心地というか、楽しさでは RubyMotion も負けていないんだけど、 ライセンス更新に毎年 10000 円ほどかかるのがツライ。 iOS Developer Program で 7800 円かかるから余計にね。