1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-05-16

経緯

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

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

Ruby 1.9.2 リファレンスマニュアル Time.parse

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

結論

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

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で定義しても可能です。

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 アプリケーションにおけるモンキーパッチの当て方

Timeの拡張クラスを用意して以下の様に定義しました。
※拡張クラスの作り方についてはクラスの拡張の記事を参照させていただきました。


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

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


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を呼ぶ方法がないか調べて以下記事を参考にさせていだきました。
既存メソッドのオーバーライド

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


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が適用できませんでした。

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

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

クラスメソッドをalias_methodする方法について以下参考にさせていただきました。
クラスメソッドに alias を付ける

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

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は不要です。

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

Ruby on Rails アプリケーションにおけるモンキーパッチの当て方

既存メソッドのオーバーライド

クラスメソッドに alias を付ける

クラスの拡張

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?