クラスメソッドの中でプライベートメソッドは呼べない
前回アプトプットシリーズとしてRubyで「ビンゴカード作成問題」に挑戦したのですが、そのときにメソッドのことで勘違いがあったので忘備録として書いておきたいと思います。
どんなことを勘違いしていたのかというとクラスメソッドの中ではプライベートメソッドを呼べないということです。 言葉だけではわかりにくいので、例を使って示します。
勘違い
# こんなふうに勘違いしていた class Bingo def self.generate_card hoge # privateメソッド呼び出し end private def hoge puts 'hoge' end end Bingo.generate_card # => `generate_card': undefined local variable or method `hoge' for Bingo:Class (NameError)
ビンゴカードの生成をBingo.generate_card
というふうにクラスメソッドで呼び出したいと思いgenerate_card
というメソッドを定義しました。
最初はこのgenerate_card
メソッドに処理の全てを記述していたのですが、冗長性もあり共通部分を切り出したりメソッド化することに。
メソッド化した部分は外から呼び出されたくなかったので、private
キーワードを使ってメソッドを定義しています。
この状態で実行するとgenerate_card': undefined local variable or method
hoge' for Bingo:Class (NameError)`というエラーが出ます。
hogeっていうメソッドなんて定義されてないよ〜!と怒られてしまいます。
何がいけなかったのでしょうか?
原因 - なんでためだったのか
class Bingo def self.generate_card hoge # hogeのレシーバがBingoになっている end private def hoge puts 'hoge' end end
クラスメソッドの中で呼び出されているhogeというメソッドのレシーバはBingoなので、クラスメソッドのhogeを探しにいっています。 当然クラスメソッドのhogeは定義していないので、NameErrorとなるわけです。
ではこれならどうだ - 書き方の工夫
クラスメソッドを探さないようにメソッドの中ででインスタンスを生成し呼び出してみます。
class Bingo def self.generate_card bingo = Bingo.new bingo.hoge end private def hoge puts 'hoge' end end Bingo.generate_card => `generate_card': private method `hoge' called for #<Bingo:0x00007f817512be68> (NoMethodError)
ありがとうございます。はい。エラーです。 根本的にprivateメソッドを理解していないのが原因でした。
そもそもprivateメソッドってなんぞ?
『プロを目指す人のためのRuby入門』(以下チェリー本)をみてみます。
p.247
厳密にいうとpriveteメソッドは「レシーバを指定して呼び出すことができないメソッドになります。」
と書いてあります。ruby2.7より前のバージョンではselfがついている場合も呼び出せなかったのですが、Ruby2.7以降はselfの場合は呼び出せるようになっています。
ということでレシーバを指定して実験してみます。
class Sample def public_method_without_self private_method end def public_method_with_self self.private_method end private def private_method 'プライベートメソッドが呼び出されたよ' end end sample = Sample.new # レシーバを指定して呼び出す sample.private_method => private method `private_method` called for #<Sample:0x00007fdd21a38440> (NoMethodError) # レシーバなし(selfなし)で呼び出す sample.public_method_without_self => "プライベートメソッドが呼び出されたよ" # レシーバあり(self)で呼び出す Ruby2.7より前のバージョン sample.public_method_with_self => private method `private_method` called for #<Sample:0x00007fdd21a38440> (NoMethodError) # レシーバあり(self)で呼び出す Ruby2.7以降のバージョン sample.public_method_with_self => "プライベートメソッドが呼び出されたよ"
ややこしいことは抜きにして一旦整理すると以下のことがわかります。
プライベートメソッドはレシーバ(selfを除く)を指定して呼び出すことができないメソッドである。
最終的に行きついた形
色々試行錯誤した結果以下のように落ち着きました。
class Bingo def self.generate_card new.generate_card end def generate_acrd hoge end private def hoge;end end Bingo.generate_card
今回のグダグダでprivateメソッドについて深く知ることができてよかったです。
参考
るりま https://docs.ruby-lang.org/ja/latest/doc/spec=2fdef.html#limit
Ruby2.7の変更点 サンプルコードでわかる!Ruby 2.7の主な新機能と変更点 Part 3 - 新機能と変更点の総まとめ