目次
今回利用するサンプルコード
lib/tasks/example.rake
namespace :batch_example do
task task_example: :environment do |task|
# ログを標準出力する
logger = ActiveSupport::Logger.new(STDOUT)
# ログのフォーマッタをセットする
logger.formatter = Logger::Formatter.new
# タイムスタンプのフォーマットを設定する
logger.formatter.datetime_format = '%Y-%m-%d %H:%M:%S'
# ログにタグを出力させる
Rails.logger = ActiveSupport::TaggedLogging.new(logger)
# ログに出力するタグをtask.nameとする
Rails.logger.tagged("#{task.name}") do
Rails.logger.info("start")
Author.all.each do |author|
Rails.logger.info("author_id:#{author.id} is being processed")
# 処理
end
Rails.logger.info("end")
end
end
end
実行結果
$ rails batch_example:task_example
I, [2021-07-09 12:07:16#315] INFO -- : [batch_example:task_example] start
D, [2021-07-09 12:07:16#315] DEBUG -- : [batch_example:task_example] (0.4ms) SET NAMES utf8mb4, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
D, [2021-07-09 12:07:16#315] DEBUG -- : [batch_example:task_example] ↳ lib/tasks/example.rake:118:in `block (3 levels) in <main>'
D, [2021-07-09 12:07:16#315] DEBUG -- : [batch_example:task_example] Author Load (0.5ms) SELECT `authors`.* FROM `authors`
D, [2021-07-09 12:07:16#315] DEBUG -- : [batch_example:task_example] ↳ lib/tasks/example.rake:118:in `block (3 levels) in <main>'
I, [2021-07-09 12:07:16#315] INFO -- : [batch_example:task_example] author_id:1 is being processed
I, [2021-07-09 12:07:16#315] INFO -- : [batch_example:task_example] author_id:2 is being processed
I, [2021-07-09 12:07:16#315] INFO -- : [batch_example:task_example] author_id:3 is being processed
I, [2021-07-09 12:07:16#315] INFO -- : [batch_example:task_example] author_id:4 is being processed
I, [2021-07-09 12:07:16#315] INFO -- : [batch_example:task_example] author_id:5 is being processed
I, [2021-07-09 12:07:16#315] INFO -- : [batch_example:task_example] author_id:6 is being processed
I, [2021-07-09 12:07:16#315] INFO -- : [batch_example:task_example] end
サンプルとなるrakeタスクではログの設定を行ったあと、ブロック内でバッチ処理を行っています。
以下では上記のコードをリファクタリングする手順について紹介します。
ブロック引数を利用してバッチのコードとログの設定準備を別メソッドに分ける
バッチのコードとログ出力に関する設定が手続き的に書かれているのでまずはそれぞれ別のメソッドに分けるところから始めます。
ブロックをブロック引数として渡し、メソッド内でブロック引数を利用する場合
ブロック引数を利用することで、以下のように書き換えられます。
lib/tasks/example.rake
def batch_logger(task_name, &block)
logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = Logger::Formatter.new
logger.formatter.datetime_format = '%Y-%m-%d %H:%M:%S'
Rails.logger = ActiveSupport::TaggedLogging.new(logger)
Rails.logger.tagged("#{task_name}") do
Rails.logger.info("start")
# ブロックを呼び出してバッチ処理を実行
block.call
Rails.logger.info("end")
end
end
namespace :batch_example do
task task_example: :environment do |task|
batch_logger(task.name) do
# ここのブロックは batch_loggerのblock.callで実行される
Author.all.each do |author|
Rails.logger.info("author_id:#{author.id} is being processed")
# 処理
end
end
end
end
block.call
の代わりにyield
を利用すると以下のようになります。yield
を利用した場合、ブロック引数(&block
)は省略できます。
lib/tasks/example.rake
def batch_logger(task_name)
logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = Logger::Formatter.new
logger.formatter.datetime_format = '%Y-%m-%d %H:%M:%S'
Rails.logger = ActiveSupport::TaggedLogging.new(logger)
Rails.logger.tagged("#{task_name}") do
Rails.logger.info("start")
yield
Rails.logger.info("end")
end
end
namespace :batch_example do
task task_example: :environment do |task|
batch_logger(task.name) do
Author.all.each do |author|
Rails.logger.info("author_id:#{author.id} is being processed")
# 処理
end
end
end
end
Procオブジェクトを利用する場合
ブロック引数を利用するメソッドはProcオブジェクトでも代替可能です。
Procオブジェクトを利用してバッチのコードとログの設定準備を別メソッドに分ける場合は以下のようになります。
lib/tasks/example.rake
# 『lamda do...end』でProcオブジェクトを作成する
batch_logger = lambda do |task_name, &block|
logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = Logger::Formatter.new
logger.formatter.datetime_format = '%Y-%m-%d %H:%M:%S'
Rails.logger = ActiveSupport::TaggedLogging.new(logger)
Rails.logger.tagged("#{task_name}") do
Rails.logger.info("start")
block.call
Rails.logger.info("end")
end
end
namespace :batch_example do
task task_example: :environment do |task|
# callメソッドでProcオブジェクトを実行する
batch_logger.call(task.name) do
Author.all.each do |author|
Rails.logger.info("author_id:#{author.id} is being processed")
# 処理
end
end
end
end
今回はlambda
を利用してProcオブジェクトを作成していますが、ほかの方法でProcオブジェクトを作成しても問題ありません。
Procオブジェクトの作成・実行方法の詳細解説は【Ruby】Procオブジェクトの作成・実行方法まとめで紹介しています。
ログの設定を管理する独自Loggerクラスを作成する
rakeタスクに実装されているログの設定を別クラスで管理することで、バッチ処理のコードを簡潔にします。
既存のコードを独自Loggerクラスに移動する
ログ設定を管理するクラスを新しく作成・利用する場合、以下のようになります。
lib/my_logger.rb
class MyLogger
def self.logger
@logger = ActiveSupport::Logger.new(STDOUT)
@logger.formatter = Logger::Formatter.new
@logger.formatter.datetime_format = '%Y-%m-%d %H:%M:%S'
@logger
end
end
lib/tasks/example.rake
require 'my_logger'
batch_logger = lambda do |task_name, &block|
# MyLogger.loggerでログの設定をlogger変数にセットする
logger = MyLogger.logger
Rails.logger = ActiveSupport::TaggedLogging.new(logger)
Rails.logger.tagged("#{task_name}") do
Rails.logger.info("start")
block.call
Rails.logger.info("end")
end
end
クラスメソッドをtapで書き換える
インスタンスの生成とインスタンスメソッドの実行をまとめて行う場合、tap
を利用することで簡潔に書き換えられます。
今回作成したクラスメソッドをtap
で書き換えると以下のようになります。
lib/my_logger.rb
class MyLogger
def self.logger
@logger ||= ActiveSupport::Logger.new(STDOUT).tap do |logger|
logger.formatter = Logger::Formatter.new
logger.formatter.datetime_format = '%Y-%m-%d %H:%M:%S'
end
end
end
なお、tap
以外にもProcオブジェクトやbegin式でも書き換え可能です。
tap
、Procオブジェクト、begin式の違いについてはProc/begin式/tapによるインスタンス生成方法の比較で紹介しています。
ログの設定準備を別メソッドに分ける
複数のバッチで共通利用できるようにするため、ログの設定を別メソッドで定義します。
新たにメソッドを定義する場合
ログ設定用のメソッドを定義する場合、以下のようになります。
lib/tasks/example.rake
def set_logger
logger = MyLogger.logger
Rails.logger = ActiveSupport::TaggedLogging.new(logger)
end
def batch_logger(task_name, &block)
# ログの設定をするset_loggerメソッドを作成
set_logger
Rails.logger.tagged("#{task_name}") do
Rails.logger.info("start")
block.call
Rails.logger.info("end")
end
end
Procオブジェクトを利用する場合
メソッドの定義と実行はProcオブジェクトの生成と呼び出しでも表現可能です。
Procオブジェクトを利用した場合、以下のようになります。
lib/tasks/example.rake
set_logger = lambda do
logger = MyLogger.logger
Rails.logger = ActiveSupport::TaggedLogging.new(logger)
end
batch_logger = lambda do |task_name, &block|
# callメソッドでProcオブジェクトを実行
set_logger.call
Rails.logger.tagged("#{task_name}") do
Rails.logger.info("start")
block.call
Rails.logger.info("end")
end
end
最終的なコード
最終的なコードは以下の通りです。
lib/my_logger.rb
class MyLogger
def self.logger
@logger ||= ActiveSupport::Logger.new(STDOUT).tap do |logger|
logger.formatter = Logger::Formatter.new
logger.formatter.datetime_format = '%Y-%m-%d %H:%M:%S'
end
end
end
lib/tasks/example.rake
require 'my_logger'
set_logger = lambda do
logger = MyLogger.logger
Rails.logger = ActiveSupport::TaggedLogging.new(logger)
end
batch_logger = lambda do |task_name, &block|
set_logger.call
Rails.logger.tagged("#{task_name}") do
Rails.logger.info("start")
block.call
Rails.logger.info("end")
end
end
namespace :batch_example do
task task_example: :environment do |task|
### callメソッドでProcオブジェクトを実行する
batch_logger.call(task.name) do
Author.all.each do |author|
Rails.logger.info("author_id:#{author.id} is being processed")
# 処理
end
end
end
end
さいごに
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!