計算処理があると関連するActiveRecordモデルにメソッドを作りますよね1。
product.rb
class Product < ApplicationRecord
def billing_price
price * consumption_tax_rate
end
def consumption_tax_rate
# 消費税率を返す
end
end
税込金額を計算するぐらいなら、そこまで問題を感じません。ただ、キャンペーンで値引きするようになったり、値引き条件が複雑になってくると、商品クラスが金額計算クラスのように思えてきます。
product.rb
class Product < ApplicationRecord
def billing_price
campaign_discounted_price * consumption_tax_rate
end
def consumption_tax_rate
# 消費税率を返す
end
def campaign_discounted_price
# キャンペーンを適用した値段を返す
# キャンペーンが複雑になるとこのメソッドが複雑になったり、
# このメソッドからしか呼ばれないメソッドが増えていってしまう!
end
end
このままではFat Modelになってしまいます。
計算のためのクラスを作ろう
消費税の計算をするクラスとキャンペーン値引きの計算をするクラスを作ります。
consumption_tax_calc_rule.rb
class ConsumptionTaxCalcRule
attr_accessor :price, :product_type
# product_typeは軽減税率を適用するか判断する情報(ということにしておいてください)
def initialize(price, product_type)
self.price = price
self.product_type = product_type
end
def tax_included_price
price * tax_rate
end
def tax_rate
# 消費税率を返す
end
end
campaign_discount_calc_rule.rb
class CampaignDiscountCalcRule
attr_accessor :price, :campaigns
# campaignsはキャンペーン値引きのための情報(ということにしておいてください)
def initialize(price, campaigns)
self.price = price
self.campaigns = campaigns
end
def discounted_price
# このメソッドからしか呼ばれないメソッドが増えても、
# キャンペーン値引き計算クラスに
# キャンペーン値引きのためのメソッドが増えるだけだから何もおかしくない!
end
end
商品クラスは、CalcRuleクラスを使うだけです。
product.rb
class Product < ApplicationRecord
def billing_price
discount_rule = CampaignDiscountCalcRule.new(price, campaigns)
tax_rule = ConsumptionTaxCalcRule.new(discount_rule.discounted_price, product_type)
tax_rule.tax_included_price
end
end
これで、消費税が変わったり、キャンペーンが変更になってもCalcRuleクラスだけ確認すればよくなりました!
このエントリは要求分析駆動設計のビジネスルールの計算について具体例を示し汎用的な内容に書き換えたものです。
めっちゃ長いけど、RailsでDDDっぽいことする話です。
-
本題ではないので省略してるけど、浮動小数点数の計算はちゃんとやろう!=>[Ruby]消費税計算にはBigDecimalを使いましょう ↩