RubyMoneyを使ってお金の管理をしているプロダクトは多いのではないでしょうか。
RubyMoneyはデフォルトで通貨の設定を提供してくれていますが、要件に応じてその設定を上書きすることもできます。
多くのシステムの場合、アプリのビルド時に一度設定をしてしまえばその後設定を変更することはないと思いますが、ことテストの文脈においては、一度変更してしまった設定を初期化したいことがあるのではないでしょうか。
この記事では、そんなときどうやってRubyMoneyの通貨設定を初期化するかを解説します。
前提として、通貨の設定は下記のように行います。
cad = Money::Currency.find(:cad)
cad.name # => Canadian Dollar
Money::Currency.register(
:priority => cad.priority,
:iso_code => cad.iso_code,
:name => 'Something Different',
:subunit => cad.subunit,
:subunit_to_unit => cad.subunit_to_unit,
:thousands_separator => cad.thousands_separator,
:decimal_mark => cad.decimal_mark
)
cad = Money::Currency.find(:cad)
cad.name # => Something Different
Money::Currency
クラスは @table
というインスタンス変数で通貨の設定を管理しています。よってこのインスタンス変数をリセットできればよさそうです。
しかし仕様上直接 @table
にアクセスはできないので、下記のようにして初期化を行います。
Money::Currency.instance_variable_set('@table', nil)
Money::Currency.table # => reloading the initial setting
簡単ですね。と思いきや上記の初期化を行ってもまだ上書きされた値を受け取ってしまいます。
cad = Money::Currency.find(:cad)
cad.name # => Something Different
Money::Currency.instance_variable_set('@table', nil)
Money::Currency.table
cad = Money::Currency.find(:cad)
cad.name # => Something Different. 初期化されていない!
詳しくライブラリの実装を追うと、その答えが分かります。一度変更された設定は、アプリケーションが起動されてから、つまりRubyプロセスが立ち上がってから、そのプロセスが終了するまで、一貫性を持っている必要があります。
その仕様を実現するため、さらに Money::Currency
はインスタンス自体をクラス変数として管理しています。
そうすることで、ライブラリユーザーである我々は、開発中設定について気にする必要がなくなると言うわけです。
ということを踏まえた上で、改良したコードが以下です。
cad.name # => Something Different
Money::Currency.class_variable_set('@@instances', {})
Money::Currency.instance_variable_set('@table', nil)
Money::Currency.table
cad = Money::Currency.find(:cad)
cad.name # => Canadian Dollar
無事初期化されました、このコードをspec_helperなどに書いておけば、テスト中の Money::Currency
の設定上書きに依存しないテストを担保することができます。
でもわざわざこの設定を書くのって面倒くさいですよね。
実は最新のRubyMoney::Money
のmasterはこの初期化を行うAPI Money::Currency.reset!
を提供しています。
これがその変更のプルリクエストです。
もしこの機能を使ってみたい方がいれば最新のmasterをインストールしてみてください。
RubyMoney::Money
の次のアップデートバージョンが早くリリースされることを願っています。
英語版はこちら