消費税改正に備えて

## はじめに
最近、COVID-19の流行で本当に大変なことになっています。
経済への影響もかなり大きく、その対策として消費税改正の話が出たり引っ込んだりしています。

消費税改正…もし行われることがあれば、速やかに従いたいところです。
自分の関わっているサービスでは、サイト内いくつかの場所で税込価格を表示していますが、消費税の処理の記述も複数箇所に書かれており、速やかな対応をするのがちょっと大変そうです…。

…というわけで、こうやったら税処理の部分をうまく書けるのではないか？ということをまとめました。
(テストの話もありますが、長いと大変なので[こちら](https://qiita.com/ken1flan/items/ce03785b20a254edb4fc)に分割しています。)

ソースは[こちら](https://github.com/ken1flan/with_tax_sample)。

### 理想の姿
消費税を扱うクラスで`attr_with_tax`によって属性を指定してあげると、`属性_with_tax`メソッドが使えるようになり、これが処理日なり指定日で自動で税率が変わってくれるようにしたいです。

```ruby
class SampleItem
  extend WithTax

  attr_accessor :price
  attr_with_tax :price

  def initialize(price)
    @price = price
  end
end

sample_item = SampleItem.new(123)
sample_item.price                                    #=> 123
sample_item.price_with_tax                           #=> 135
sample_item.price_with_tax(Date.parse('2019/09/30')) #=> 132
```

## 試していった順序
こんなふうに順番に試していきました。`irb`や`pry`などに貼り付けて試せるようになっているはずです。

### 動的にメソッドを追加
Rubyは[Module#define_method](https://docs.ruby-lang.org/ja/latest/method/Module/i/define_method.html)を使って動的にメソッドを定義することができるので、それでよろしくできそうです。

下の例では、`.attr_with_tax`というクラスメソッドで`#price_with_method`を作っています。

```ruby
class SampleItem
  attr_accessor :price

  def initialize(price)
    @price = price
  end

  def self.attr_with_tax(attr_name)
    method_name = "#{attr_name}_with_tax"
    define_method(method_name) do
      (send(attr_name) * 1.10).floor
    end
  end

  attr_with_tax :price
end

sample_item = SampleItem.new(123)
sample_item.price          #=> 123
sample_item.price_with_tax #=> 135
```

### モジュールでまとめる
いちいち全部のクラスでメソッドを定義すると大変なので、モジュールにまとめて、必要なクラスに`extend`して、税込み計算をさせたい属性を指定します。

```ruby
module WithTax
  def attr_with_tax(attr_name)
    method_name = "#{attr_name}_with_tax"
    define_method(method_name) do
      (send(attr_name) * 1.10).floor
    end
  end
end

class SampleItem
  extend WithTax

  attr_accessor :price
  attr_with_tax :price

  def initialize(price)
    @price = price
  end
end

sample_item = SampleItem.new(123)
sample_item.price          #=> 123
sample_item.price_with_tax #=> 135
```

### 適用される日によって税率が変化
消費税が改正されることになったとき、できれば何もせず待っているくらいに済ませたいです。
ですので、施行日と税率のハッシュを作り、実行日や指定日で検索するようにします。


```ruby
require 'date'

module WithTax
  TAX_TABLE = {
    '2019/10/01': 0.10,
    '2014/04/01': 0.08,
    '1997/04/01': 0.05,
    '1989/04/01': 0.03,
  }

  def attr_with_tax(attr_name)
    method_name = "#{attr_name}_with_tax"
    define_method(method_name) do |effective_date = nil|
      effective_date ||= Date.today
      _k, rate = TAX_TABLE.find { |k, _v| Date.parse(k.to_s) <= effective_date }
      rate ||= 0.00
      (send(attr_name) * (1 + rate)).floor
    end
  end
end

class SampleItem
  extend WithTax

  attr_accessor :price
  attr_with_tax :price

  def initialize(price)
    @price = price
  end
end

sample_item = SampleItem.new(123)
sample_item.price                                    #=> 123
sample_item.price_with_tax                           #=> 135
sample_item.price_with_tax(Date.parse('2019/09/30')) #=> 132
```

### Railsのアプリケーションに入れるには
上で作った`WithTax`モジュールを`app/models/concerns/with_tax.rb`に保存、消費税の計算が必要なモデルで先の例と同じように`attr_with_tax`を定義すればよいです。

## おわりに
Rubyは動的にメソッド定義できたり、DSLっぽい書き方ができて好きなんですが、普段の仕事ではあんまりしないので、楽しかったですヽ(=´▽`=)ﾉ

## おまけ（gem）
ここにまとめたことを[with_taxというgem](https://rubygems.org/gems/with_tax)にしました。
ソースは[github](https://github.com/ken1flan/with_tax)にあります。
興味がありましたらどうぞー。