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
=> #
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
=> #
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

🐮 参考リンク

🖥 VULTRおすすめ

VULTR」はVPSサーバのサービスです。日本にリージョンがあり、最安は512MBで2.5ドル/月($0.004/時間)で借りることができます。4GBメモリでも月20ドルです。 最近はVULTRのヘビーユーザーになので、「ここ」から会員登録してもらえるとサービス開発が捗ります!

📚 おすすめの書籍