private メソッドをレシーバ付きで呼ぶと NoMethodError になる
privateを付けたメソッドは、クラスの外からinstance.methodのようにレシーバを明示して呼び出すことができません。呼び出したい場合は、内部でそのメソッドを使うpublicなメソッド経由にする必要があります。
エラーメッセージの読み方
main.rb:13:in `<main>': private method `battery' called for an instance of Robot (NoMethodError)
main.rb- ファイル名
13- 行番号 — 実際にクラッシュした行
<main>- 発生場所 — <main> ならトップレベル、メソッド名ならそのメソッドの中
NoMethodError- 例外クラス — 何が起きたか。ここを検索するのが最短です
private method `battery' called for an instance of Robot- 詳細メッセージ — どの値が問題だったか
このエラーが出る典型パターン
パターン1
1 class Robot 2 def status 3 battery 4 end 5 6 private 7 8 def battery 9 "80%" 10 end 11 end 12 13 puts Robot.new.battery ^
privateメソッドはクラスの外からレシーバ付きで呼び出すことができません。公開されているstatus経由で呼ぶ必要があります。
直し方: battery を status にします。
パターン2
1 class CoffeeMachine 2 def status 3 brew_report 4 end 5 6 private 7 8 def brew_report 9 "ready" 10 end 11 end 12 13 puts CoffeeMachine.new.brew_report ^
外部から直接呼べるのはpublicなメソッドだけです。privateなメソッドは内部の呼び出しからしか使えません。
直し方: brew_report を status にします。
パターン3
1 class Vault 2 def status 3 unlock_code 4 end 5 6 private 7 8 def unlock_code 9 "1234" 10 end 11 end 12 13 puts Vault.new.unlock_code ^
privateはクラスの外に見せたくない実装の詳細を隠すための仕組みです。
直し方: unlock_code を status にします。
パターン4
1 class Printer 2 def status 3 ink_level 4 end 5 6 private 7 8 def ink_level 9 "50%" 10 end 11 end 12 13 puts Printer.new.ink_level ^
privateなメソッドにアクセスしたい場合は、そのメソッドを呼び出すpublicなメソッドを経由する必要があります。
直し方: ink_level を status にします。
パターン5
1 class Server 2 def status 3 uptime 4 end 5 6 private 7 8 def uptime 9 "99%" 10 end 11 end 12 13 puts Server.new.uptime ^
「同じインスタンスならレシーバを付けても呼べるはず」という思い込みは誤りです。レシーバの有無だけで判定されます。
直し方: uptime を status にします。
よくある誤解
「インスタンスさえ取得できれば、privateなメソッドでもレシーバ付きで呼べるはず」という思い込みは誤りです。Ruby 2.7以降、self.を付けた呼び出しだけは例外的に許可されていますが、それ以外の明示的なレシーバ(他のインスタンス経由の呼び出しを含む)は一律で拒否されます。
まとめ
private メソッドをレシーバ付きで呼ぶと NoMethodError になるは中級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。