RubyのTime.parseに空文字列が指定された時に現時刻を返すようにモンキーパッチする

# 経緯

rubyを1.8.7から1.9.3にバージョン上げる時にTime.parseの引数に空文字列を指定すると例外が発生するようになっていました。

>[EXCEPTION] ArgumentError:
>dateに空文字列を与えた場合、発生します。 なお、1.9.2より前は例外は発生せず、現在時刻を表す Time のインスタンスを返していました。

[Ruby 1.9.2 リファレンスマニュアル Time.parse](http://miyamae.github.io/rubydoc-ja/1.9.2/method/-time/s/parse.html)

Time.parseで空文字を指定している箇所が多すぎて修正が大変であったため、Time.parseにモンキーパッチを当てて解決することにしました。

# 結論

空文字列が指定されたらTime.nowを返して、それ以外は組み込みクラスのparseメソッドを呼ぶようなオーバライド的な処理にしました。

```rb
class Time
  class << self
    alias_method :__parse__, :parse
    private :__parse__

    def parse(time)
      if time.empty?
        # 現時刻を返す
        Time.now
      else
        # 組み込みクラスTime.parseを呼ぶ
        __parse__(time)
      end
    end
  end
end
```

self.parseで定義しても可能です。

```rb
class Time
  class << self
    alias_method :__parse__, :parse
    private :__parse__
  end

  def self.parse(time)
    if time.empty?
      # 現時刻を返す
      Time.now
    else
      # 組み込みクラスTime.parseを呼ぶ
      __parse__(time)
    end
  end
end
```

# 結論に至るまで

## Timeのオーバライド

組み込みクラスのTimeを活かしつつ空文字列のチェックをしたかったので、Timeを継承してオーバライドすれば良いかなと思いました。
ですが、継承する時にクラス名を別名にして、Time.parse(arg)が使われている箇所をすべて新しいクラス名に変更する必要があります。
これでは本末転倒なので断念しました。

## Time.parseのモンキーパッチ
次にモンキーパッチを検討しました。
クックパッドさんでもモンキーパッチの対応があったため、こちらを参考にさせていただきました。（なるべくやるなと書いてありましたが。。）
[Ruby on Rails アプリケーションにおけるモンキーパッチの当て方](https://techlife.cookpad.com/entry/a-guide-to-monkey-patchers)

Timeの拡張クラスを用意して以下の様に定義しました。
※拡張クラスの作り方については[クラスの拡張](https://www.ruby.or.jp/ja/tech/development/ruby/tutorial/090_class_extension.html)の記事を参照させていただきました。

```rb

class Time
  def self.parse(time)
    if time.empty?
      Time.now
    else
      Time.parse(time)
    end
  end
end
```

ですが、これではエラー。
拡張クラスのTime.parseがずっと呼ばれ無限ループ状況になってしまったので、これもNG。

```bash

irb(main):002:0> Time.parse('2020-01-01 12:34:56')
SystemStackError: stack level too deep
	from /usr/local/lib/ruby/1.9.1/irb/workspace.rb:80
Maybe IRB bug!
```

## Time.parseのモンキーパッチ & alias_methodの適用

先程のコードから拡張クラスのTime.parseを呼ぶ方法がないか調べて以下記事を参考にさせていだきました。
[既存メソッドのオーバーライド
](https://qiita.com/umanoda/items/33613b94ccabf7b1f851)

`alias_method`を使って拡張クラスTimeの`parse`のメソッド名を`__parse__`に変更することで、先程の無限ループを回避できそうでした。
念の為、外部から呼ばれないように`private`で定義します。

```rb

class Time

  alias_method :__parse__, :parse
  private :__parse__

  def self.parse(time)
    if time.empty?
      Time.now
    else
      Time.parse(time)
    end
  end
end
```

すると今度は`parse`メソッドがないとエラーが出ます。
parseはクラスメソッドであるため、alias_medhodが適用できませんでした。

```bash
bundle exec rails c
`alias_method': undefined method `parse' for class `Time' (NameError)
```

## Time.parseのモンキーパッチ & alias_methodの適用（クラスメソッド）

クラスメソッドをalias_methodする方法について以下参考にさせていただきました。
[クラスメソッドに alias を付ける
](https://qiita.com/akishin/items/41f03a513c7ffd51a014)

class << self内で`alias_method`を定義すれば適用できました。
最終形はこちらです。

```rb
class Time
  class << self
    alias_method :__parse__, :parse
    private :__parse__
  end

  def self.parse(time)
    if time.empty?
      Time.now
    else
      __parse__(time)
    end
  end
end
```

以下の様にparseメソッドをclass << self内で定義しても可能です。
その際はselfは不要です。

```rb
class Time
  class << self
    alias_method :__parse__, :parse
    private :__parse__

    def parse(time)
      if time.empty?
        Time.now
      else
        __parse__(time)
      end
    end
  end
end
```

# 最後に

いまさらrubyを1.8.7を使っている環境は少ないと思いますが、レガシーコードのバージョンアップ等で役に立てていただければと思います。
また、組み込みクラスのモンキーパッチの方法について参考にはなるかと思いますので、お役に立てたら幸いです。

# 参考

[Ruby 1.9.2 リファレンスマニュアル Time.parse](http://miyamae.github.io/rubydoc-ja/1.9.2/method/-time/s/parse.html)

[Ruby on Rails アプリケーションにおけるモンキーパッチの当て方](https://techlife.cookpad.com/entry/a-guide-to-monkey-patchers)

[既存メソッドのオーバーライド
](https://qiita.com/umanoda/items/33613b94ccabf7b1f851)

[クラスメソッドに alias を付ける
](https://qiita.com/akishin/items/41f03a513c7ffd51a014)

[クラスの拡張](https://www.ruby.or.jp/ja/tech/development/ruby/tutorial/090_class_extension.html)