DIVX テックブログ

catch-img

Ruby on Railsではなぜprivateメソッドは下から呼び出せるのか?


目次[非表示]

  1. 1.はじめに
    1. 1.1.privateメソッドとは?
    2. 1.2.Rubyのprivateは特殊?
  2. 2.調査
    1. 2.1.検証する環境
    2. 2.2.検証した内容
    3. 2.3.実験及び結果
      1. 2.3.1.1.コードは上から下へと実行されるは嘘?
      2. 2.3.2.2.クラス内で定義されたメソッドはどの順番で実行されるのか?
        1. 2.3.2.1.1.クラスの情報が最初に実行されるのか?
        2. 2.3.2.2.2.privateメソッドだけが下から呼び出せるのか?
        3. 2.3.2.3.3.クラスの中のコードもはじめに上から実行しているのか?
        4. 2.3.2.4.4.クラス内のメソッドが認識されるタイミングは?
      3. 2.3.3.3.定義したメソッドのさらに中にメソッドを記述した場合はどうなるか?
      4. 2.3.4.4.クラスを継承した際に、スーパークラスとサブクラスのメソッドが実行されるタイミングは?
      5. 2.3.5.5.Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか検証
  3. 3.まとめ
  4. 4.感想

はじめに

こんにちは。株式会社divxのエンジニア金子です。

エンジニアとしてRubyおよびRuby on Rails言語を中心としたwebアプリケーションの保守・運用をメインに携わってます。

皆さんもエラーが発生した際に、実行されるコードを追う為、数千行あるコードからメソッドを見つけ出したり、別ページのファイルを探しにく経験はあるかと思います。私もその1人です。

そんなコードを追う日々でふと原点に帰りRubyやRuby on Railの実行ルールを振り返ってみるとある疑問が湧きました。

それは「privateメソッドはなぜ下でメソッド定義をしているのに上でメソッドを実行してもエラーにならないのだろう?」という疑問です。

普段当たり前に使っていたので、気にしたことがありませんでした。原則RubyやRuby on Railのコードは上から下へ実行されるはずなのにエラーにならない理由が気になりました。

そこで今回、記述したコードはどの順番で実行されたのか調査しようと思いました。

privateメソッドとは?

メソッドの呼び出し制限し、同じクラスやサブクラス内のメソッドの中だけ呼び出せるようにする制約です。

(参考) https://railsdoc.com/ruby_base

Rubyでは、privateと記述したコードの下部分がprivateメソッドになります。

privateメソッドとは?


またprivateメソッドはクラスの外側で使用すると以下のようにエラーになります。

privateメソッドとは?


この様にクラス内でのみ使用できるようにするのがprivateメソッドです。

Rubyのprivateは特殊?

こちらは本編とは少し別の話になります。ただRuby言語の特徴を知る事ができると思います。

ぜひ知識としてご参考にして頂けると幸いです。

先ほど、privateメソッドの説明をいたしました。ただ実はRubyのprivateメソッドは少し特殊でございます。

何が特殊かと言いますと、スーパークラスで使用したprivateメソッドをサブクラスでも使用できる点です。

本来、privateは自分のクラスのメソッド以外からはアクセスできないので、一般的にはスーパークラスで使用したprivateメソッドはサブクラスで使用するとエラーになります。

(例)Javaの場合

Rubyのprivateは特殊?


上記のようにエラーとなります。

しかしRubyの場合はスーパークラスのprivateメソッドをサブクラスでも使用できます。

(例)Rubyの場合

Rubyのprivateは特殊?


この様にエラーにならずに実行できます。

Rubyのプライベートメソッドは少し特殊なところがございます。

ぜひ知識として今後ご参考にしてください。

では、本編に戻ろうと思います。

調査

検証する環境

Ruby 3.1.2 (https://www.ruby-lang.org/ja/downloads/より最新で安定板)
Ruby on Rails 7.0.3.1 (https://railsguides.jp/7_0_release_notes.htmlよりRubyのバージョンと互換性あり)

# Rubyのバージョンの確認
% ruby -v 
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-darwin19]
# Railsのバージョンの確認
% rails -v
Rails 7.0.3.1

検証した内容

  1. コードは上から下へと実行されるは嘘?
  2. クラス内で定義されたメソッドはどの順番で実行されるのか?
    1. クラスの情報が最初に実行されるのか?
    2. privateメソッドだけが下から呼び出せるのか?
    3. クラスの中のコードもはじめに上から実行しているのか?
    4. クラス内のメソッドが認識されるタイミングは?
      1. クラス内のメソッドは本当に認識されていないのか?
      2. newメソッドがクラス内のメソッドを認識したタイミングは?
  3. 定義したメソッドの中にさらにメソッドを記述した場合はどうなるか?
  4. クラスを継承した際に、スーパークラスとサブクラスのメソッドが実行されるタイミングは?
  5. Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか確認

上記の内容は私自身が疑問に思ったことを実際に検証する形で、どのようなルールになっていたのか解明していく構成になっております。また「2 .クラス内で定義されたメソッドはどの順番で実行されるのか?」に関しては、答えを出す為に、a~dの内容を検証し、さらにdに関してはi~iiの内容を検証して答えを結論づけた構成にしております。

実験及び結果

1.コードは上から下へと実行されるは嘘?

まず、この調査を始めるにあたり、 privateメソッドはコードの下側で定義をしても、エラーにならないと冒頭で説明しました。

自分の今までの説明が嘘なのか検証する為、以下の様なシンプルなコードで検証してみました。

 プログラムと実行結果

プログラムと実行結果


当然エラーなく実行されます。

続いて、以下のようにメソッド定義より先にメソッドを呼び出す記述にしました。

プログラムと実行結果

プログラムと実行結果


結果は NameErrorとエラーになりました。

改めて、上記2つの実行結果からRubyのコードは上から下に実行されているのは間違いではないのです。

ではなぜ、上から下に実行されるルールがあるにもかかわらず、privateメソッドは下から呼び出すことができるのでしょうか?

一度ここでprivateメソッドとは何か思い出してみましょう。

「privateメソッドは同じクラスやサブクラス内のメソッドの中だけ呼び出せるようにする制約」です。

つまりクラスで使用するのです。

そこで次はクラスではどのような順番でコードが実行されるのか調査してみます。

2.クラス内で定義されたメソッドはどの順番で実行されるのか?

1.クラスの情報が最初に実行されるのか?

コードは上から下へ実行されるルールを最初に調べました。

しかしprivateメソッドは下から呼び出す事ができたので、実行される優先度があるのでは?と考えました。

そこで「クラスのコードが優先的に実行されるルールがあるのでは?」と仮説を立て、クラスの場合も上から下へ実行されるルールがあるのか同様に検証しました。

プログラムと実行結果

プログラムと実行結果


上から下へ実行されるルール通り、Blogクラスより上でBlogオブジェクトにメソッドを実行した結果エラーになりました。

やはり、クラスでも同様に上から下へ実行されるルールが適応されています。

2.privateメソッドだけが下から呼び出せるのか?

では次にprivateメソッドだけが特別で下から呼び出す事ができたのか検証してみました。

2つのコードでprivateメソッドを使用した時と使用しなかった時で変化が生じるか確認しました。

まずはprivateメソッドを使用した時で検証しました。

プログラムと実行結果

プログラムと実行結果


結果は当然、privateメソッドは下で定義しているにもかかわらず、呼び出すことができました。

では次に、privateメソッドを使用しなかった場合で検証しました。

プログラムと実行結果

プログラムと実行結果


privateメソッドを使用しなくても呼び出せました。

この結果より、クラスの中のメソッドはprivateメソッドかどうかは関係なく、下から呼び出す事が可能だとわかりました。

3.クラスの中のコードもはじめに上から実行しているのか?

ここまでの調査だけでは以下のように考えることも可能です。

クラスの記述が一番上に記述されている為、上から下へ実行のルールよりクラスの中のコードすなわちメソッドがすでに認識されており、newメソッドやインスタンスメソッドで呼び出した時にはメソッドを使用することができた。」という解釈です。

しかし、先ほどの記述でnewメソッドやインスタンスメソッドを使用していた箇所をクラスメソッドに変更すると以下の結果になりました。

プログラムと実行結果

プログラムと実行結果


もしクラス内のコードも上から実行されているなら、クラスメソッド実行時もエラーにならないはずです。

ではクラスの中の記述は上から実行されてなかったということでしょうか?

以下のファイルのようにクラス名のすぐ下と1番最後にputsメソッドを記述しクラスのコードだけを記述して確認しました。

プログラムと実行結果

プログラムと実行結果


今回の結果より、やはり上から下へ実行されており、クラスの中のコードも実行されるとわかります。

しかし、クラスメソッドの実行時にはprivate_blogメソッドが使えなかった為、

クラスの場合、クラス内のメソッドは上から実行された時とは別のタイミングで認識されたと考えることができます。

そこで次はクラス内のメソッドが認識されるタイミングについて調査しました。

4.クラス内のメソッドが認識されるタイミングは?

sample6.rbのファイルでエラーになったのはクラス内で定義されていたprivate_blogメソッドをクラスメソッドでは認識できなかったのが原因でした。

sample5.rbの検証結果よりnewメソッドはクラス内で定義したメソッドを認識し実行できました。

その為、「いつ・どのように」newメソッドはクラス内のメソッドを認識したのか?がポイントになります。

そこで以下の2点を実際に確認する事で調査しました。

ⅰ.クラス内のメソッドは本当に認識されていないのか?
ⅱ.newメソッドがクラス内のメソッドを認識したタイミングは?


ⅰ.クラス内のメソッドは上から実行された時に本当に認識されていないのか?

先ほどの「c.クラスは中のコードも上から実行しているのか?」の最後の考察として「クラス内のメソッドに関しては認識するタイミングが別である」と記述しました。しかし上から下へ実行のルールがあるから、最初に認識されていた可能性があるのでは?と考える方もいるかと思います。

ここは論より証拠ということで、クラス内でクラスメソッドとインスタンスメソッドを定義し、すぐ下で定義したメソッドを呼び出してみるとどうなるか検証して見ました。

プログラムと実行結果

プログラムと実行結果


結果はクラスメソッドは読み込まれ、インスタンスメソッドはエラーになりました。

この事より、クラスメソッドはすぐに認識され、インスタンスメソッドは認識されてないとわかりました。

ⅱ.newメソッドがクラス内のメソッドを認識したタイミングは?
ではnewメソッドはクラス内のインスタンスメソッドをいつ認識したのか考えます。

先ほどの結果よりクラスのコードを上から実行したタイミングでは認識されておりませんでした。

なので次はnewメソッド実行後、つまりインスタンス化した後、クラス内に記述したメソッドが認識されているのかをmethodsメソッドおよびprivate_methodsを使用して確認してみました。

※methodsメソッドはそのオブジェクトに対して呼び出せるメソッド名の一覧を返します。(publicメソッドおよびprotectedメソッドの名前)

private_methodsはそのオブジェクトが理解できるprivateメソッド名の一覧を返します。

(参考)
https://docs.ruby-lang.org/ja/latest/method/Object/i/methods.html
https://docs.ruby-lang.org/ja/latest/method/Object/i/private_methods.html

プログラム

プログラム


実行結果
(1枚目)

実行結果


(2枚目)

実行結果


写真のハイライトの箇所に注目すると、インスタンス化が完了後、クラスの中で定義したgenerate_blog、private_blog、private_blogerメソッドがあると確認できました。 そしてクラスメソッドが含まれていないこともわかります。逆にクラスメソッドは最初に認識されていることになります。sample6.rbはインスタンス化完了前にメソッドを呼び出した為エラーになったと考えられます。
 
ここまで多くの検証をしてきました。

その為、「2.クラス内で定義されたメソッドはどの順番で実行されるのか?」のトピックで分かった事をまとめると以下の順番で実行されます。

①コードは基本的に上から下へ実行される。
②そのままクラスは中のコードも上から下へ実行される。
(クラスメソッドは認識され、インスタンスメソッドは認識されない)
③newメソッド実行後インスタンス化された時にクラスメソッド以外の定義したメソッドをまとめて認識する。
④インスタンスメソッド実行のタイミングでクラス内のメソッド部分が実行される。
(クラスメソッドは最初に認識されているのでいつでも使える)


検証した結果privateメソッドが下から呼び出せたのは「newメソッドを実行しインスタンス化されたタイミングでクラス内のメソッドが認識された為」とわかりました。

上記の結果より今回の記事タイトルの疑問は解消できました。ここからはさらに深ぼってみようと思います。


3.定義したメソッドのさらに中にメソッドを記述した場合はどうなるか?

ここまでの情報で、newメソッドを実行した後にクラスの中のメソッドも使えるようになると分かりました。

では定義したメソッドのさらに中にメソッドを定義した場合、コードはどのように実行されるのでしょうか?

プログラムと実行結果

プログラムと実行結果


エラーにならず実装できました。

では次にcomicメソッドの中で定義したbattle_comicメソッドを定義したすぐ上で呼び出してみました。

プログラムと実行結果

プログラムと実行結果


結果はNameErrorでした。

この結果より、newメソッドは、メソッドの中身のコードまでは認識していないことがわかります。

なお、sample8.rbのファイルが成功したのは、book.comicが実行され、先にメソッド定義が実行された為です。

またこの現象はクラスに限らず、メソッドの中身が実行されないのは以下の検証結果からもわかります。

プログラムと実行結果

プログラムと実行結果


逆に、error_putsメソッドを呼び出すとエラーになります。

プログラムと実行結果

プログラムと実行結果


今回の結果より、メソッドの中身のコードは呼び出されたタイミングではじめて実行されるとわかりました。

4.クラスを継承した際に、スーパークラスとサブクラスのメソッドが実行されるタイミングは?

次にクラスが継承された時に実行される順番を検証してみようと思います。

スーパークラスとサブクラスの定義する順番を変えて検証します。

まずは先にスーパークラスから記述した場合です。

プログラムと実行結果

プログラムと実行結果


では次に先にサブクラスを記述した場合です。

プログラムと実行結果

プログラムと実行結果


結果はエラーでした。

やはりコードは上から下に実行されるルールが適応され、1行目の記述のタイミングまでに登場しなかったBookのところでエラーになりました。

この結果を踏まえ、スーパークラスとサブクラスでは先にスーパークラスから実行する必要があるとわかりました。

5.Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか検証

それでは最後にここまで検証してきた結果がRuby on Railsでも同じことが言えるのか検証して終了にします。

改めてここまで調べたルールをおさらいすると、以下の通りです。

①コードは基本的に上から下へ実行される。
②そのままクラスは中のコードも上から下へ実行される。
(しかし、クラス内のメソッド部分はその時には認識されない)
③newメソッド実行後インスタンス化された時にクラスメソッド以外の定義したメソッドをまとめて認識する。
④インスタンスメソッド実行のタイミングでクラス内のメソッド部分が実行される。
(クラスメソッドはクラス内の呼び出したクラスメソッドのみを認識し実行する)
⑤メソッドの中のコードはメソッドが呼び出されるタイミングではじめて実行される。
⑥クラスはスーパークラスから実行する必要がある。


それでは上記の内容を踏まえて調査に入ります。

まずは以下のコマンドでアプリケーションを作成です。

#バージョン7.0.3.1で作成
% rails 7.0.3.1 new simple -d mysql  
#db 作成
% rails db:create


以下のコマンドで、モデルとコントローラを作成しました。

#articlesコントローラの作成
% rails g controller articles 
#articleモデルの作成
% rails g model article 
#今回はarticleテーブルにtextカラムを作成してます。


今回は以下の動画の簡単な投稿アプリを作成しました。

動画の簡単な投稿アプリ


また以下のファイルにbinding.pryを打ち込み、処理が止まるか確認します。

今回は投稿ページから「コメント」と入力し、createアクション(メソッド)を動かします。

リクエストから投稿完了のビューが表示されるまでの流れの中で、「処理が止まる=コードが実行される」なので、今まで検証したルールで止まる事を確認します。

app/controllers/application_controller.rb

 class ApplicationController < ActionController::Base 
    binding.pry # ①スーパークラスが呼び出される事を確認 
 end


app/controllers/articles_controller.rb

class ArticlesController < ApplicationController 
  binding.pry # ②クラスの中身が実行されることを確認
  def index
    @articles = Article.all
  end
  def new
    binding.pry #③呼び出されないメソッドの中身は実行されないことの確認
    @article = Article.new
  end
  def create 
    binding.pry #④メソッドが呼び出された時、中身が実行されるのを確認 
    @article = Article.create(text_params) 
    if @article.save
    else 
      render :new 
    end 
  end 
  private 
  def text_params 
    binding.pry #⑤createメソッド後に呼び出されることを確認 
    params.require(:article).permit(:text) 
  end
end

実際にコメントを入力してbinding.pryが止まった結果は以下の写真の順番です。

1番目 # ①スーパークラスが呼び出される事を確認

Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか検証


2番目 # ②クラスの中身が実行されることを確認

Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか検証


3番目  #④メソッドが呼び出された時、中身が実行されるのを確認

Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか検証


4番目 #⑤ createメソッド後に呼び出されることを確認

Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか検証


以上を経て投稿が完了しました。
 
この結果を受けて、Ruby on Railsも同様にRubyで検証したルールが適応している事を確認できました。

またこの後サーバーを止めずに、そのままもう一度createアクションが実行されるように再度「2回目のコメント」と入力して、binding.pryを確認すると以下の順番で止まりました。

1番目 #④メソッドが呼び出された時、中身が実行されるのを確認

Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか検証


2番目 #⑤ createメソッド後に呼び出されることを確認

Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか検証


つまり、2回目ではcreateアクション(メソッド)に関係する箇所だけが呼び出されました。
 
さらにサーバーを止めず、クラスのコードを変更しcreateアクション(メソッド)を実行すると、再びスーパークラスから順番にbindig.pryが止まりました。(今回はcreateメソッド内に改行を入れてコードを変更してます)止まった順番は以下の通りでした。

1番目 # ①スーパークラスが呼び出される事を確認

Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか検証


2番目 # ②クラスの中身が実行されることを確認

Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか検証


3番目  #④メソッドが呼び出された時、中身が実行されるのを確認

元の記述からコメントアウトと13行目に改行を入れて変化させてます。

Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか検証


4番目 #⑤ createメソッド後に呼び出されることを確認

Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか検証


この事よりRuby on Railsでは以下の様にして動かしていることがわかりました。

①サーバーを立ち上げ、最初にアクションを実行しようとした時とコードに変化が起こった時はコントローラのクラスをnewメソッドでインスタンス化している。
(インスタンス化された時にスーパークラスから実行され、その上でサブクラスを上から実行)

②インスタンス化されたオブジェクトに対して、インスタンスメソッド(今回はcreateメソッド)を実行した。

③2回目以降はインスタンス化されたオブジェクトに対し、インスタンスメソッドから実行した為、リクエストしたアクションの処理だけが実行された。

まとめ

今回さまざまな検証を行いました。

それぞれの項目で実施した結果は以下の通りでした。

1.コードは上から下へと実行されるは嘘?
A. 上から下へ実行される。

2.クラス内で定義されたメソッドはどの順番で実行されるのか?
a.クラスの情報が最初に実行されるのか?
A.上から下へ実行されるが正しいです。

 b.privateメソッドだけが下から呼び出せるのか?
 A.Noです。private関係なく呼び出せます。

 c.クラスは中のコードも上から実行しているのか?
 A.Yesです。

 d.クラス内のメソッドが認識されるタイミングは?
   ⅰ.クラス内のメソッドは本当に認識されていないのか?
   A.クラスメソッドは認識され、インスタンスメソッドは認識されない

   ⅱ.newメソッドがクラス内のメソッドを認識したタイミングは?
   A.インスタンス化された後にインスタンスメソッドはまとめて認識されます

3.定義したメソッドの中にさらにメソッドを記述した場合はどうなるか?
A.メソッドの中身のコードは呼び出されたタイミングではじめて実行される。
(つまりメソッドの中のメソッドは実行されるまでは呼び出されない。)

4.クラスを継承した際に、スーパークラスとサブクラスのメソッドが実行されるタイミングは?
A.クラスは親クラスから実行する必要があります。

5.Ruby on Railsのコントローラをbinding.pryで止めて、Rubyで検証した結果通りか確認
A.Rubyと同じルールでコードが実行されます。

また以下の法則でnewメソッド(インスタンス化する方のnewメソッド)とアクションに対するインスタンスメソッドの実行が行われる。
・サーバーを立ち上げ、最初にアクションを実行しようとした時とコードに変化が起こった時はコントローラのクラスをnewメソッドでインスタンス化している。 (インスタンス化された時に親クラスから実行し、その上で子クラスを上から実行している)

・インスタンス化されたオブジェクトに対して、インスタンスメソッド(今回はcreateメソッド)を実行した。

・2回目以降はインスタンス化されたオブジェクトに対し、インスタンスメソッドから実行した為、リクエストしたアクションの処理だけが実行される。

感想

今まで当たり前になっていた前提をあえて言語化する為に始めた調査でしたが、改めて自分がわかったつもりでいた事に気づかされました。

また今回実験を繰り返す中で多くの仮説を考えtry & errorを繰り返すことで新たな発見ができた時はプログラミングの面白さに出会えたと思えました。

この記事で皆様の知識にお役に立つことはもちろん、自分で検証しながら発見していく楽しさが伝わると幸いです。今回は以上になります。最後まで目を通して頂きありがとうございます。
 
divxでは一緒に働ける仲間を募集しています。
興味があるかたはぜひ採用ページを御覧ください。


  採用情報 | 株式会社divx(ディブエックス) 可能性を広げる転職を。DIVXの採用情報ページです。 株式会社divx(ディブエックス)



お気軽にご相談ください


ご不明な点はお気軽に
お問い合わせください

サービス資料や
お役立ち資料はこちら

DIVXブログ

テックブログ タグ一覧

人気記事ランキング

GoTopイメージ