Active Jobの簡単な導入手順とRSpecの書き方について


Active Jobの導入手順と、Active Job周りのRSpecの書き方をちゃんと理解していなかったので、英語の勉強も兼ねて、
Active Job Basics — Ruby on Rails Guides
を読んでみました!


😼 Active Jobとは

Active Jobはジョブを宣言し、バックエンドでキュー操作を抽象化して実行するためのフレームワーク。

👽 ジョブの作成

ジョブの作成コマンドはこちら。

$ bin/rails generate job guests_cleanup
invoke test_unit
create test/jobs/guests_cleanup_job_test.rb
create app/jobs/guests_cleanup_job.rb

作成されたJobはこんな感じ。

class GuestsCleanupJob < ActiveJob::Base
queue_as :default # キューの名前
# 例外発生時の処理を書く
rescue_from(ActiveRecord::RecordNotFound) do |exception|
end
# ジョブをキューにセットする前に実行される
before_enqueue do |job|
end
around_perform do |job, block|
# ジョブが実行される前の処理
block.call
# ジョブが実行された後の処理
end
def perform(*guests)
# 実際の処理を行う
end
end

🤔 Callbackの種別

- before_enqueue => キューにセットする前に呼び出される 
- around_enqueue => キューにセットする前後で block を受け取り、呼び出す
- after_enqueue => キューにセットされた後に呼び出される 
- before_perform => ジョブが実行される前に呼び出される
- around_perform => ジョブが実行される前後で block を受け取り、呼び出す
- after_perform => ジョブが実行された後で呼び出される

🎂 ジョブの登録方法

# キューが無ければ、ジョブをすぐに実行する
GuestsCleanupJob.perform_later(guest)
# 明日の午後ジョブに登録する
GuestsCleanupJob.set(wait_until: Date.tomorrow.noon).perform_later(guest)
# 1週間後にジョブを実行する
GuestsCleanupJob.set(wait: 1.week).perform_later(guest)
# perform_later も perform_now

🐯 設定

# config/application.rb
module YourApp
class Application < Rails::Application
# キューの名前の prefix
config.active_job.queue_name_prefix = Rails.env
# sidekiq や delayed_job 等、利用する job キューの仕組みを指定
config.active_job.queue_adapter = :sidekiq
end
end

🐠 RSpecの書き方

次のようにテストできるそうです。

# spec/rails_helper.rb
RSpec.configure do |config|
config.include ActiveJob::TestHelper
config.include ActiveSupport::Testing::TimeHelpers
...
end
# app/jobs/notification_job.rb
class NotificationJob < ActiveJob::Base
queue_as :default
def perform(msg)
# notification を送付
end
end
# app/models/message.rb
class Message < ActiveRecord::Base
def notification
NotificationJob.set(wait: 5.seconds).perform_later body
end
end
# spec/models/message_spec.rb
require 'rails_helper'
describe Message, '#notification' do
let(:message) { Message.new(body: 'Hello world!') }
it '5秒後に NotificationJob のキューにセットされる' do
time = Time.current
travel_to(time) do
assertion = {
job: NotificationJob,
args: ['Hello world!'],
at: (time + 10.seconds).to_i,
}
assert_enqueued_with(assertion) { message.ping }
end
end
end

😸 (参考) GlobalID

Active Job以前は次のようにclasss, idを渡してActive Recordのオブジェクトを取得して、
からメソッドを実行していた。

class TrashableCleanupJob < ActiveJob::Base
def perform(trashable_class, trashable_id, depth)
trashable = trashable_class.constantize.find(trashable_id)
trashable.cleanup(depth)
end
end

現在はこんな風に書いておくだけでActive Job側で良しなに対応してくれる。

class TrashableCleanupJob < ActiveJob::Base
def perform(trashable, depth)
trashable.cleanup(depth)
end
end

これは、Rails 4.2から実装された #gid#sgid の効果によるもの。

gid = Friend.first.gid
=> #<GlobalID:0x007fa9add041f8 ...>
gid.app
=> "activejob-example"
gid.model_name
=> "Friend"
gid.model_class
=> Friend(id: integer, name: string, email: string...)
gid.model_id
=> "1"
gid.to_s
=> "gid://activejob-example/Friend/1"
sgid = Friend.first.sgid
=> #<SignedGlobalID:0x007fa9add15e58 ...>
sgid.to_s
=> "BAh7CEkiCGdpZAY6BkVUSSIlZ2lkOi8vYWN0aXZl..."

これは知らなかったけど便利な機能。

🐝 (参考) Active Job adapters

Active Job adaptersの例。

|-------------------|-------|--------|------------|------------|---------|---------|
| | Async | Queues | Delayed | Priorities | Timeout | Retries |
|-------------------|-------|--------|------------|------------|---------|---------|
| Backburner | Yes | Yes | Yes | Yes | Job | Global |
| Delayed Job | Yes | Yes | Yes | Job | Global | Global |
| Qu | Yes | Yes | No | No | No | Global |
| Que | Yes | Yes | Yes | Job | No | Job |
| queue_classic | Yes | Yes | Yes* | No | No | No |
| Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
| Sidekiq | Yes | Yes | Yes | Queue | No | Job |
| Sneakers | Yes | Yes | No | Queue | Queue | No |
| Sucker Punch | Yes | Yes | No | No | No | No |
| Active Job Inline | No | Yes | N/A | N/A | N/A | N/A |
|-------------------|-------|--------|------------|------------|---------|---------|

🎃 (参考) Action Mailerとの連携

Action Mailerの #deliver_later は内部でActive Jobを使っている。

# すぐに送付する場合は、 #deliver_now
UserMailer.welcome(@user).deliver_now
# ActiveJobを使って後ほど送る場合は、 #deliver_later
UserMailer.welcome(@user).deliver_later

🍄 (参考) Action Mailerの #deliver_laterに関する処理

rails/message_delivery.rb at v5.0.0.beta3 - rails/rails

🎉 参考リンク

📚 おすすめの書籍

🖥 サーバについて

このブログでは「Cloud Garage」さんのDev Assist Program(開発者向けインスタンス無償提供制度)でお借りしたサーバで技術検証しています。 Dev Assist Programは、開発者や開発コミュニティ、スタートアップ企業の方が1GBメモリのインスタンス3台を1年間無料で借りれる心強い制度です!(有償でも1,480円/月と格安)