親モデルのメソッドよりも子モデルのメソッドを先に動作させる


初期状態

子モデルに書いてあるメソッドの結果が、親モデルのプロパティに代入されるという場面がありました。

例えば。。。。
User 1----* Book
の状況で、bookの中にpriceというカラムがあり、userが所有しているbookのprice合計を、userの中のcostというカラムに代入するという仕様があったとします。また、costに入れる前にbookモデル内で消費税を自動で計算してbookのpriceに入れます。その合計をcostに入れます

(not_include_tax_priceは消費税抜きの額)
そして、そのcostのカラムが計算された後に、userのインスタンスが保存されなければならないとします。
カラムには正しい値を入れるため、before_validationをかけます。

問題

user.rb
before_validation :sum_price
def sum_price
  self.cost = self.books.map{|b| b.price}.sum
end

book.rb
before_validation :sales_tax
def sales_tax
  self.price = not_include_tax_price * 1.08
end

spec書いてましたので、そこでやりますが、、、

spec.rb
user = User.first
user.books.new(price: 100)
user.save

上のやり方でかくと、userのcostは消費税が勘案されていない状態で計算されます。
なぜかというと、

Railsのcallbackについて調べた

こちらをみていただくと、親のbefore_validaitonが働いてから、子のbefore_validaitonが働くようになっています。
ということは、def sale_taxよりもsum_priceが先に計算されることとなり、消費税が働かなくなる。。。

解決策

もうほぼ上のURLに書いてありますが、親before_saveは子before_validationの後に来ます。
ということは

user.rb
before_save :sum_price
def sum_price
  self.cost = self.books.map{|b| b.price}.sum
end

book.rb
before_validation :sales_tax
def sales_tax
  self.price = not_include_tax_price * 1.08
end

としてあげれば(sum_priceをbefore_saveに変更)、消費税がしっかり計算されてcostに入ります。

まとめ

callbackの順序を知った上で、どう実際のコーディングに落とし込むかの良い練習になりました。