良い感じ = 以下の二点
- テストしやすいこと
- 工数削減のため
- (ある程度は)エンジニア間での書き方が統一できること
- 例えば
dryrun
の指定方法が書き手によってまちまちだと、商用でのtask実行時に事故が起きる可能性もあるので統一可能であればした方が良い - ログのフォーマットが統一されていないと作業効率が悪い
- 例えば
などなど
コード
lib/tasks/issue_6885.rake
require_relative 'helpers/all_user_name_update_helper.rb'
namespace :issue_6885 do
desc 'これはサンプルです'
task all_user_name_update: :environment do
helper = AllUserNameUpdateHelper.new
helper.main
end
end
lib/tasks/helpers/all_user_name_update_helper.rb
require_relative 'rake_helper_template'
class AllUserNameUpdateHelper < RakeHelperTemplate
NEW_NAME = 'bar'
def main
template do |logger|
all_users = get_all_user
logger.info("対象ユーザー数: #{all_users.size}")
if all_users.blank?
logger.info('対象ユーザー数が0件だったため処理を終了します')
return
end
all_users.each { |user| update_name(user, NEW_NAME) }
end
end
private
def get_all_user
User.all
end
def update_name(user, new_name)
user.update!(name: new_name)
user
end
end
lib/tasks/helpers/rake_helper_template.rb
class RakeHelperTemplate
# 標準出力も行う
def make_logger(log_file_path)
logger = ActiveSupport::Logger.new(log_file_path)
stdout_logger = ActiveSupport::Logger.new(STDOUT)
broadcast_logger = ActiveSupport::Logger.broadcast(stdout_logger)
logger.extend(broadcast_logger)
logger.formatter = Logger::Formatter.new
logger
end
# ログファイル名はtask名と同じにする(コロンは使わない方が良いので置換する)
# rakeタスクの実行以外から呼び出されるケース(例: spec)を考慮しておく
def make_log_file_path
task_name = Rake.try(:application)&.top_level_tasks&.[](0)&.gsub(':', '_') || Rails.env
log_file_name = "#{task_name}.log"
Rails.root.join('log', log_file_name)
end
def template
log_file_path = make_log_file_path
# 明示的に文字列のfalseを渡さない限りは必ずdryrunにする
is_dryrun = ENV['is_dryrun'] != 'false'
logger = make_logger(log_file_path)
logger.info("Start. is_dryrun: #{is_dryrun}")
ActiveRecord::Base.transaction do
yield(logger)
raise ActiveRecord::Rollback if is_dryrun
end
logger.info("Finish. log_file_path: #{log_file_path}")
end
end
実行例
dryrunにする場合
$ is_dryrun=false bundle exec rake issue_6885:all_user_name_update
I, [2020-09-27T13:33:27.229764 #13040] INFO -- : Start. is_dryrun: true
I, [2020-09-27T13:33:27.556890 #13040] INFO -- : 対象ユーザー数: 1
I, [2020-09-27T13:33:27.755933 #13040] INFO -- : Finish. log_file_path: /app/log/issue_6885_all_user_name_update.log
ポイント
- .rakeファイルはhelperクラスを呼び出すだけにして、rakeのDSL?的なお作法を気にせずに普段書き慣れているclassのメソッドをテストするようにした
- yieldを使って
dryrun
,logger
(の一部)を共通化し、rake taskを作成するたびに毎回定義しなくても済むようにした- mainメソッド(名前は何でも良い)には極力そのtaskの処理フローだけを定義するようにし、個別具体的な処理はテストしやすい粒度で別メソッドに切り出すようにする
テンプレートにしてしまえばall_user_name_update_helper.rbのようにやりたいことに集中できる!(^○^)
所感
- rake taskの書き方は久々に書くと忘れてるのでこの記事は実は備忘録的な意味合いもあったり
- yieldは正直わかりにくいので使いたくない派だけど、テンプレートのように毎回書いたり読んだりしないものであれば良いかなという考え
- 他にも良い感じの書き方あるよという方いたら教えてください!