入荷から一定期間経過した商品には割引(30日以上→500円・60日以上→1000円・90日以上→1500円)を適用する為、以下の様なロジックを書きました。
items = Item.where("created_at < ?", Time.zone.now - 30.days)
items.each do |item|
created_at = item.created_at
today = Time.zone.today
three_month = 90.days
two_month = 60.days
one_month = 30.days
if today >= created_at + three_month
discount = -1500
elsif today >= created_at + two_month && today < created_at + three_month
discount = -1000
elsif today >= created_at + one_month && today < created_at + two_month
discount = -500
end
item.discount = discount
item.save
end
しかし上記のコードでは、items
の数の分処理が走ってしまう所謂 N+1問題 が発生し、処理速度やメモリに大きな影響を与えてしまいます。
そこで活躍するのがupdate_all
。
以下の様に条件をhash
に纏めて配列に格納、各条件に当てはまるデータを纏めて更新します。
today = Time.zone.now
conditions = [
{ upper_limit: today - 30.days, lower_limit: today - 59.days, amount: -500 },
{ upper_limit: today - 60.days, lower_limit: today - 89.days, amount: -1000 },
{ upper_limit: today - 90.days, lower_limit: today - 365.years, amount: -1500 },
]
conditions.each do |condition|
Item.
where("created_at between ? and ?", condition[:lower_limit], condition[:upper_limit]).
update_all(discount: condition[:amount])
end
するとクエリは条件分のたった3つだけに収まります🎉
Item Update All (8.8ms) UPDATE `items` SET `items`.`discount` = -500 WHERE (created_at between '2020-04-27 00:00:00' and '2020-05-26 00:00:00')
Item Update All (3.2ms) UPDATE `items` SET `items`.`discount` = -1000 WHERE (created_at between '2020-03-28 00:00:00' and '2020-04-26 00:00:00')
Item Update All (19.4ms) UPDATE `items` SET `items`.`discount` = -1500 WHERE (created_at between '1920-06-25 00:00:00' and '2020-03-27 00:00:00')
※ActiveRecordオブジェクトを経由しない更新方法のため、バリデーションやコールバックは実行されない点にご注意ください。