rails runnerをdelayed_jobで実行する

以前、delayed_job と rails runner について 2 つ記事を書きました。

これらの記事で、それぞれを個別に扱ったのですが、組み合わせられないか確かめたのでメモです。

目次

参考

今回実装するもの

今回は、コントローラーから rails runner を実行して、指定したオブジェクト一覧のログファイルを出力してみます。

実装

呼び出されるバッチスクリプトを作る

まずは、コントローラーから呼び出すスクリプトを作ります。
lib/script/getlog.rb を用意します。

lib/script/getlog.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module Script
class GetLog
def self.get_log
classs = ["User","Food"]

lists = Array.new

classs.each do |c|
#safe_constantizeは該当するものがなければnilを返す
obj = c.safe_constantize
if obj.present?
lists += obj.all.to_a
end
end

# 作成時刻で並べ替え
@lists_sorted = lists.sort{|a,b| a.created_at <=> b.created_at}

File.open("log/log.txt", "w"){|f|
@lists_sorted.each do |l|
f.write("#{l.class},#{l.name},#{l.updated_at}\n")
end
}
end
end
end

Script::GetLog.get_log

複数のモデルをまたいで created_at を元に並べ変えています。
詳細な処理は、文字列からクラスにアクセスするで解説しています。

lib/script/getlog.rb が用意できたら、bundle exec rails runner lib/script/getlog.rbで実行します。
log/log.txtが作られていることを確認します。
以下のようになっています。

log/log.txt
1
2
3
4
5
6
7
8
9
10
User,UserA,2020-03-14 16:24:16 UTC
Food,FoodC,2020-03-14 16:24:02 UTC
Food,FoodA,2020-03-14 16:24:47 UTC
Food,FoodD,2020-02-28 15:00:51 UTC
User,UserB,2020-02-28 16:26:11 UTC
Food,FoodE,2020-02-28 16:26:34 UTC
User,UserC,2020-02-29 01:52:55 UTC
User,UserD,2020-03-07 13:30:49 UTC
User,UserE,2020-03-07 14:09:11 UTC
User,UserF,2020-03-07 14:09:22 UTC

呼び出すコントローラを用意する

app/controllers/mix_lists_controller.rb に、ログ書き出しするアクションを追加します。

app/controllers/mix_lists_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class MixListsController < ApplicationController
def index
classs = ["User","Food"]

lists = Array.new

classs.each do |c|
#safe_constantizeは該当するものがなければnilを返す
obj = c.safe_constantize
if obj.present?
lists += obj.all.to_a
end
end

# 作成時刻で並べ替え
@lists_sorted = lists.sort{|a,b| a.created_at <=> b.created_at}
end

def get_log
Delayed::Job.enqueue LogJob.new
redirect_to mixlists_path, notice: 'Start_Log'
end

private

class LogJob
def perform
system("bundle exec rails runner lib/script/getlog.rb")
end
end
end

get_log が実行されると、呼び出し元の get_log を押したmixlistsにリダイレクトします。
ポイントは、Delayed::Job.enqueue LogJob.newの部分なのですが、Delayed_job に.delayを使わずキューにタスクを積む方法がこちらです。
performメソッドを持つオブジェクトを渡すことでperformメソッドに書かれた処理を遅延実行できます。

ルーティングの実装

config\routes.rbを編集して、作成したget_logを呼び出せるようにします。
config\routes.rbを以下のようにします。

config\routes.rb(抜粋)
1
2
3
4
5
6
Rails.application.routes.draw do
# 省略
get '/mixlists', to: 'mix_lists#index'
get '/getlog', to: 'mix_lists#get_log'
# 省略
end

ビューの設定

ルーティングができたので、ビューを用意します。
app/views/mix_lists/index.html.erbを編集します。

app/views/mix_lists/index.html.erb(抜粋)
1
2
3
<!-- 省略 -->
<%= link_to 'ログを出力する。', getlog_path %>
<!-- 省略 -->

確認

コンソールを 2 つ用意し、一方は、bundle exec rails jobs:workもう一方はbundle exec rails sを実行します。
localhost:3000/mixlistsにアクセスします。
すると以下の画面になります。

ここで、「ログを出力する」をクリックすると、’localhost:3000/mixlists’にリダイレクトして、表面では何も起きません。
log/log.txtが作られます。

画面から入力して、バッチ処理を delayed_job で呼び出すことができました。

少し改修

log/log.txtは、作られましたが遅延実行するとはいえブラウザから取得するようにしてみたいところです。
少し改修します。

呼び出されるバッチスクリプトを改修

まずは、コントローラーから呼び出すスクリプトをlib/script/getlog.rb を改修します。

lib/script/getlog.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module Script
class GetLog
def self.get_log
classs = ["User","Food"]

lists = Array.new

classs.each do |c|
#safe_constantizeは該当するものがなければnilを返す
obj = c.safe_constantize
if obj.present?
lists += obj.all.to_a
end
end

# 作成時刻で並べ替え
@lists_sorted = lists.sort{|a,b| a.created_at <=> b.created_at}

File.open("public/log/log#{Time.now.strftime("%Y-%m%d-%H%M%S") }.txt", "w"){|f|
@lists_sorted.each do |l|
f.write("#{l.class},#{l.name},#{l.updated_at}\n")
end
}
end
end
end

Script::GetLog.get_log

ディレクトリの追加

public の下に log ディレクトリを作ります。

呼びだすコントローラを改修

app/controllers/mix_lists_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class MixListsController < ApplicationController
def index
classs = ["User","Food"]

lists = Array.new

classs.each do |c|
#safe_constantizeは該当するものがなければnilを返す
obj = c.safe_constantize
if obj.present?
lists += obj.all.to_a
end
end

# 作成時刻で並べ替え
@lists_sorted = lists.sort{|a,b| a.created_at <=> b.created_at}

# public/logにあるファイルをしてファイル名とパスのペアを作る
@files = Array.new
Dir.foreach('public/log') do |item|
if item != "." && item != ".."
hash={:name => "#{item}", :path => "log/#{item}"}
#ハッシュに.でアクセスするしたいのでStructに入れる
@files.push( Struct.new(*(hash.keys)).new(*(hash.values)) )
end
end
end

def get_log
Delayed::Job.enqueue LogJob.new
redirect_to mixlists_path, notice: 'Start_Log'
end

private

class LogJob
def perform
system("bundle exec rails runner lib/script/getlog.rb")
end
end
end

public/logにある log ファイルを出力するので、こちらからファイル一覧を取得して、パスとのペアを作ります。

ビューの改修

取得したファイルの一覧を表示するために link_to でファイルへのリンクを作ります。

app/views/mix_lists/index.html.erb(抜粋)
1
2
3
4
5
6
7
8
<!--省略-->
<%= link_to 'ログを出力する', getlog_path %>
<ul>
<% @files.each do |file| %>
<li><%= link_to file.name, file.path,{download:file.name} %></li>
<% end %>
</ul>
<!--省略-->

改修後確認

コンソールを 2 つ用意し、一方は、bundle exec rails jobs:workもう一方はbundle exec rails sを実行します。
localhost:3000/mixlistsにアクセスします。
すると以下の画面になります。

ここで、「ログを出力する」をクリックすると、’localhost:3000/mixlists’にリダイレクトして、表面では何も起きません。

ここまでは同じですが、ブラウザのリロードをしているとそのうちリンクが現れます。
画像では、3 回「ログを出力する」をクリックしています。

表示されたリンクにアクセスすると、作成されたログファイルをダウンロードできます。

Delayed_job で遅延実行したログ作成バッチで、作成したログファイルをブラウザで入手できるようになりました。


今回は、delayed_job と rails runner を組み合わせました。
最近、これまでやってきたことが噛み合うようになってきて、複合的に扱って作りたいものが実装できるようになってきました。
学習の効果で出てきていると感じます。

ではでは。