プログラミング漫遊記

思ったことや、勉強したことをつらつらと。

クラスメソッドの中でプライベートメソッドは呼べない

前回アプトプットシリーズとしてRubyで「ビンゴカード作成問題」に挑戦したのですが、そのときにメソッドのことで勘違いがあったので忘備録として書いておきたいと思います。

haruguchi-yuma.hatenablog.com

どんなことを勘違いしていたのかというとクラスメソッドの中ではプライベートメソッドを呼べないということです。 言葉だけではわかりにくいので、例を使って示します。

勘違い

# こんなふうに勘違いしていた
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 methodhoge' 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 - 新機能と変更点の総まとめ

ブログ Ruby Classメソッドのアクセス権