Threadクラスを利用した例外処理の方法に苦戦したので、自分用としてアウトプットします。
Threadクラスとは?
スレッドとはメモリ空間を共有して同時に実行される制御の流れです。 Thread を使うことで並行プログラミングが可能になります。
プログラムは通常処理を記述した順に実行されるので、複数の処理を同時に実行することができません。
しかし、AとBという二つに処理を同時に実行した場合ってありますよね?
そんなとき使用できるのがThreadクラスです。
メインスレッドとカレントスレッド
プログラム開始時に生成されるスレッドはメインスレッド、現在実行中のスレッドはカレントスレッドと呼ばれます。
RubyではThread#mainを用いる事でメインスレッドを確認できます。
また、Thread#listでプログラム上に存在するスレッドが配列で表示されます。
joinメソッド
Thread#joinは、 スレッド self の実行が終了するまで、カレントスレッドを停止させるメソッドです。 limit を指定して、limit 秒過ぎても自身が終了しない場合、nil を返します。
要するに、AとBという二つの処理を同時に実行した際に、片方の処理が終わるまでもう片方の処理を停止させることが出来るメソッドです。
Aという処理が終わるまで、Bという処理を停止させるようなイメージ。
コード
テストの前提条件
・画像が21枚以上は保存できない
・既に19枚が保存されている
・2枚同時に投稿した際に片方をエラーにしたい
使用方法
①Threadのインスタンスを生成(t1,t2)
②それぞれのThreadに対してrescue文を使用し例外処理を行う
(どちらが先に実行されるのかわからないため)
③rescue文で定義した例外処理のeにe1を代入。
④どちらか片方が例外処理によって抜け出した際の、エラー文とクラス(ActiveRecord::RecordInvalid)が一致しているか?のテストを実施。
require 'rails_helper'
RSpec.describe UserService do
context 'with 2 images uploading at the same time' do
let(:user) { FactoryBot.create(:user) }
let(:user_images_nearest_max) { FactoryBot.create_list(:user_image, 19, user: user) }
let(:params){ ActionController::Parameters.new( user_id: user.id,
files:[{ file: File.open(Rails.root.join('spec', 'fixtures', 'sample_image_01.jpg')),
order: 2}])}
before do
user_images_nearest_max
# ①
t1 = Thread.new {
ActiveRecord::Base.connection_pool.with_connection {
service = described_class.new(params)
service.run!
}
}
# ①
t2 = Thread.new {
ActiveRecord::Base.connection_pool.with_connection {
service = described_class.new(params)
service.run!
}
}
end
it 'returns invalid with the 21st image' do
e1 = nil
e2 = nil
# ②
begin
t1.join # JoinメソッドはThreadクラスを拡張したクラスのインスタンスから使用できるメソッド。別のスレッドの処理が終了するまで待機させたい処理がある場合に使用
rescue => e
# ③
e1 = e
end
# ②
begin
t2.join
rescue => e
# ③
e2 = e
end
# ④
if e1.nil?
expect(e2.class).to eq(ActiveRecord::RecordInvalid)
expect(e2.record.errors.messages).to match({:file=>["は20枚までしか登録できません。"]})
expect(user.user_images.count).to eq 20
end
# ④
if e2.nil?
expect(e1.class).to eq(ActiveRecord::RecordInvalid)
expect(e1.record.errors.messages).to match({:file=>["は20枚までしか登録できません。"]})
expect(user.user_images.count).to eq 20
end
end
end
end
【参考記事】
https://docs.ruby-lang.org/ja/latest/class/Thread.html
https://qiita.com/k-penguin-sato/items/1326882c400cac8c109b