大ちゃんの駆け出し技術ブログ

RUNTEQ受講生のかわいいといわれるアウトプットブログ

【Rails】Active Job

Active Jobの概要

こんにちは。大ちゃんの駆け出し技術ブログです。

今回はRuby on Railsの標準機能であるActive Jobについて、簡単に紹介させていただきます。

Railsには代表的な標準機能であるActive Recordがありますよね! SQLを知らなくてもDBを操作することができるとても便利なものです。

Railsには「Active 〜」のような名前の標準機能がいくつもあります。 今回紹介するActive Jobもその一つです。

Active Jobはどんな機能なのかというと、「非同期処理を実行する機能」です。

非同期処理で代表的なAjax通信は、クライアントのレスポンスに対して非同期で処理がサーバ側で行われます。これによりクライアントはレスポンスを待たずに快適に操作を行えます。Ajaxとは言わば、クライアント側のための非同期処理機能です。

それとは別に、Active Jobは言うなればサーバー側だけで完結する非同期処理です。例えば、サーバー側にジョブのリクエストを送信し、一週間後にそのジョブの処理を行なって欲しいといった場合に使われます。

Active Jobの実装

それでは実際にハンズオン形式でActive Jobを体験していきます。なお、Active Jobの実装手順はRailsガイド にありますので、公式を参照したい方はこちらをご覧ください。

本記事ではActive Jobを使って非同期でDBに処理を行うように実装します。

まずは、Active Jobを使うために仮でアプリを作成します。

$ rails new active_job
$ cd active_job

 そして適当なモデルを作成します。今回はモデル名をexample_modelとし、string型でtitleカラムを定義しておきます。モデルを作成したらDBを作成し、モデルをDBに反映します。

$ rails generate model example_model title:string
$ rails db:create
$ rails db:migrate

  ここでジョブクラスを作成します。rails generate job ジョブ名で作成することができます。ジョブ名は自由ですが、今回はexample_jobをジョブ名としてコマンドで実行します。

 $ rails generate job example_job
Running via Spring preloader in process 22919
      invoke  test_unit
      create    test/jobs/example_job_test.rb
      create  app/jobs/example_job.rb

  コマンド実行後、テストファイルと共にapp/jobs/example_job.rbというファイルが作成されているかと思います。このファイルでは非同期処理時に呼ばれるperformメソッドがデフォルトで定義されています。

# app/jobs/example_job.rb
class ExampleJob < ApplicationJob
  queue_as :default

  def perform(*args)
    # Do something later
  end
end

Do something laterとあるように、後で何かしらの処理を実行するように設定することができます。今回はExampleModelの作成処理を後で(非同期で)行なってもらうように設定します。ExampleModelにはtitleカラムがあるので引数を渡したら、渡した引数がtitleカラムに登録されるように実装します。

# app/jobs/example_job.rb
class ExampleJob < ApplicationJob
  queue_as :default

  def perform(title)
    ExampleModel.create(title: title)
  end
end

ジョブの実行

それではターミナル上で非同期で実行してみます。 方法は先ほど定義したExampleJobクラスをクラスメソッドとして使用しターミナル上で実行します。

irb(main):001:0> ExampleJob.perform_later("おはよう")
Enqueued ExampleJob (Job ID: 47dbf1d6-78ee-4a57-89ad-630d210496cd) to Async(default) with arguments: "おはよう"
=> #<ExampleJob:0x00007fd51f0ff8b0 @arguments=["おはよう"], @job_id="47dbf1d6-78ee-4a57-89ad-630d210496cd", @queue_name="default", @priority=nil, @executions=0, @exception_executions={}, @provider_job_id="d3bf4ba7-60d8-456f-94df-6a9b4e55c535">
Performing ExampleJob (Job ID: 47dbf1d6-78ee-4a57-89ad-630d210496cd) from Async(default) enqueued at 2021-01-23T00:00:08Z with arguments: "おはよう"
   (0.1ms)  SELECT sqlite_version(*)
   (0.1ms)  begin transaction
irb(main):005:0>   ExampleModel Create (1.5ms)  INSERT INTO "example_models" ("title", "created_at", "updated_at") VALUES (?, ?, ?)  [["title", "おはよう"], ["created_at", "2021-01-23 00:00:08.976914"], ["updated_at", "2021-01-23 00:00:08.976914"]]
   (0.9ms)  commit transaction
Performed ExampleJob (Job ID: 47dbf1d6-78ee-4a57-89ad-630d210496cd) from Async(default) in 16.53ms

少し長いですが、ExampleModelを作成するSQLが発行されています。

INSERT INTO "example_models" ("title", "created_at", "updated_at") VALUES (?, ?, ?)  [["title", "おはよう"], ["created_at", "2021-01-23 00:00:08.976914"], ["updated_at", "2021-01-23 00:00:08.976914"]]

ExampleModel.firstを実行するとモデルが作成されていることも確認できます。

irb(main):002:0> ExampleModel.last
  ExampleModel Load (0.3ms)  SELECT "example_models".* FROM "example_models" ORDER BY "example_models"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<ExampleModel id: 1, title: "おはよう", created_at: "2021-01-23 00:00:08", updated_at: "2021-01-23 00:00:08">

しかし、ジョブが直ぐに実行されてしまって非同期で行われているかわかりづらいですね、、、。 それでは、30秒後にジョブが実行されるように実装してみましょう。方法は簡単でsetメソッドを使っていつ実行するかを指定するだけです。

irb(main):003:0> ExampleJob.set(wait: 30.seconds).perform_later("30秒後のジョブ")
Enqueued ExampleJob (Job ID: ca09e9e9-7b7f-4e18-b7e2-62f54269f1c0) to Async(default) at 2021-01-23 00:10:29 UTC with arguments: "30秒後のジョブ"
=> #<ExampleJob:0x00007fd52824f978 @arguments=["30秒後のジョブ"], @job_id="ca09e9e9-7b7f-4e18-b7e2-62f54269f1c0", @queue_name="default", @priority=nil, @executions=0, @exception_executions={}, @provider_job_id="25580038-707a-4103-bcd9-e1842d34ebbe", @scheduled_at=1611360629.818372>

先ほどと違い、実行後直ぐにターミナル上にSQLが発行されません。 しかし、30秒後にはSQLが発行されたコードがターミナル上に出現します。

Performing ExampleJob (Job ID: ca09e9e9-7b7f-4e18-b7e2-62f54269f1c0) from Async(default) enqueued at 2021-01-23T00:09:59Z with arguments: "30秒後のジョブ"
   (0.1ms)  SELECT sqlite_version(*)
   (0.1ms)  begin transaction
  ExampleModel Create (0.6ms)  INSERT INTO "example_models" ("title", "created_at", "updated_at") VALUES (?, ?, ?)  [["title", "30秒後のジョブ"], ["created_at", "2021-01-23 00:10:29.823096"], ["updated_at", "2021-01-23 00:10:29.823096"]]
   (1.7ms)  commit transaction
Performed ExampleJob (Job ID: ca09e9e9-7b7f-4e18-b7e2-62f54269f1c0) from Async(default) in 5.73ms

こうしてみると非同期で実行されていることがわかりますね。 Ajax通信とは違い、サーバー側だけで完結する非同期処理ということがよくわかりますね。

終わりに

Active Jobについて簡単に実装してみましたが、まだまだ奥が深そうです。 自分もまだ理解が浅いため、理解を深めたいと思います。

以上、大ちゃんの駆け出し技術ブログでした!!