QA@IT

rubyのメソッドをオーバライド時、引数がnilの場合の動かし方

2805 PV

現在rails3.2上でActiverecordを使っているのですが、saveメソッドのオーバライドがうまく実装できません。

具体的にソースで説明させていただきます。
今、Hogeクラスのsaveメソッドの前に何らかの処理を実行後、superのsaveメソッドを実行したいと考えています。

class Hoge < ActiveRecord::Base
         def save(fuga = nil,*)
                super
         end
end

このようなModelがあったとして、呼び出し側は

fuga = Fuga.last
hoge = Hoge.new
hoge.save(fuga)

このようにsaveメソッドを呼び出します。
このソースはfugaがnil意外なら問題なく動作しますが、nilがはいったり、hoge.saveのように引数をとらない場合はエラーを起こします。
以下はエラー内容です。

/Users/lain/.rvm/gems/ruby-1.9.3-p194/gems/activerecord-3.2.7/lib/active_record/validations.rb:76:in `perform_validations': undefined method `[]' for nil:NilClass (NoMethodError)

おそらくこれはsuper.saveが呼びだされたときにnilを* (配列)と認識して、[]を行うためエラーが発生するのだと思います。

しかし私としてはfugaにnilが入っていた場合でも問題なくsave メソッドが実行されることを期待しています。
この場合どのように実装したらいいのでしょうか?ご回答いただけると幸いです。

回答

まずエラーの見方ですけど、ファイル位置と行番号が出てるのでちゃんと追いましょう(gem-openなどを使うと楽かもしれません)。エラーが起きてるのは直接的には

https://github.com/rails/rails/blob/3-2-stable/activerecord/lib/active_record/validations.rb#L76

ですから、そういうことではなくてハッシュが期待されてるはずのoptionsがnilになってるということですね。で、今度はperform_validationsの呼び出し元を探すと、同じファイルに

def save(options={})
  perform_validations(options) ? super : false
end

が見つかります。つまり、引数を省略すれば空のハッシュになるところ、わざわざnilを渡している、ということですね。

次に、superについて、

http://doc.ruby-lang.org/ja/1.9.3/doc/spec=2fcall.html

括弧と引数が省略された場合には現在のメソッドの引数がそのまま引き 渡されます。引数を渡さずにオーバーライドしたメソッドを呼び出すには super() と括弧を明示します。

とあるように、このやりかただと、superによって本来のsaveに対してsave(fuga)が呼ばれてしまいます。nil以外だと動いてたのは、おそらくfugaがたまたま[]に応答するからで、まったく想定外の挙動をしていることになります。

というわけで、superを使うときには、親の引数の型をそのまま引っ張ってくるのが原則だと思っておいたほうが安全だと思います。

目的が「Hogeクラスのsaveメソッドの前に何らかの処理を実行後、superのsaveメソッドを実行したい」ならば、Rails標準のbefore_savebefore_validation(:on => :create)などのコールバックを使いましょう。

http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

編集 履歴 (0)
  • ご丁寧にありがとうございます。たしかにエラーがおこっているメソッドの処理をちゃんとみてみるべきでした。superもそのような定義だったんですね、あやふやでした。ありがとうございます。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。