Webuilder240

Railsのクラスに定義した定数がいつ読み込まれるのか

2020-08-08 00:42:36 +0900

定数 Rails Ruby
Railsのモデルにこんなコードを書いてちょっと問題になった。
class Post < ApplicationRecord
  PUBLISH_TIME = Time.now
   
  def set_datetime
    self.publish_date = PUBLISH_DATE
  end
end
まぁ見る人からみれば火を見るよりも明らかなのだろうだけど、そもそも論は最後に書いた。
ちょっと待ってほしい。

Railsの場合、モデルクラスに定義した定数はいつ読み込まれるのか?

上記のコードを下記に書き換えて、実行タイミングがいつなのかを実験してみた。

class Post < ApplicationRecord  
  PUBLISH_TIME = lambda { puts "=========== execute =============="; Time.now }.call
end

さらにView(`GET /posts`)で Post::PUBLISH_DATE を呼び出すようにしてみた。

development環境の場合

この場合は、`/posts` にアクセスしたとき、つまり、定数がCallされたタイミングで代入された。
また複数プロセスでPumaを動かして実験した時には、プロセスで実行結果が違っているので、各プロセスで保持されている定数データは異なるようだった。もちろん一度代入されてからは、定数の中が更新されることはなかった。詳しくは調べていないけど、一度代入された定数については、再代入もできないし、実行しているRubyプロセスが終了しない限りは変更されることはないと思われる。


production環境で実行した場合

ubuntu@DESKTOP-7F3HAF0:~/tmp/teisuu_of_rails$ SECRET_KEY_BASE=secret_key_base be rails s -e production                            │[204b9f2a-9559-4796-8d4b-3a68cbd4328e] puma (4.3.5) lib/puma/server.rb:713:in `handle_request'
=> Booting Puma                                                                                                                   │[204b9f2a-9559-4796-8d4b-3a68cbd4328e] puma (4.3.5) lib/puma/server.rb:472:in `process_client'
=> Rails 6.0.3.2 application starting in production                                                                               │[204b9f2a-9559-4796-8d4b-3a68cbd4328e] puma (4.3.5) lib/puma/server.rb:328:in `block in run'
=> Run `rails server --help` for more startup options                                                                             │[204b9f2a-9559-4796-8d4b-3a68cbd4328e] puma (4.3.5) lib/puma/thread_pool.rb:134:in `block in spawn_thread'
=========== execute ==============                                                                                                │I, [2020-08-07T22:42:09.615991 #16652]  INFO -- : [1331bd8a-8082-4ed3-86a9-bd90d2f2427b] Started GET "/packs/js/application-dd6e8
[16596] Puma starting in cluster mode...                                                                                          │8065a32b23f8e21.js" for 127.0.0.1 at 2020-08-07 22:42:09 +0900
[16596] * Version 4.3.5 (ruby 2.6.6-p146), codename: Mysterious Traveller                                                         │F, [2020-08-07T22:42:09.616356 #16652] FATAL -- : [1331bd8a-8082-4ed3-86a9-bd90d2f2427b]
[16596] * Min threads: 5, max threads: 5                                                                                          │[1331bd8a-8082-4ed3-86a9-bd90d2f2427b] ActionController::RoutingError (No route matches [GET] "/packs/js/application-dd6e88065a32
[16596] * Environment: production                                                                                                 │b23f8e21.js"):
[16596] * Process workers: 2                                                                                                      │[1331bd8a-8082-4ed3-86a9-bd90d2f2427b]
[16596] * Phased restart available                                                                                                │[1331bd8a-8082-4ed3-86a9-bd90d2f2427b] actionpack (6.0.3.2) lib/action_dispatch/middleware/debug_exceptions.rb:36:in `call'
[16596] * Listening on tcp://0.0.0.0:3000                                                                                         │[1331bd8a-8082-4ed3-86a9-bd90d2f2427b] actionpack (6.0.3.2) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
[16596] Use Ctrl-C to stop
上記のログのように、Pumaが「起動したタイミング」で、`Post::PUBLISH_TIME` は処理され、「Pumaの起動時間」が代入された。

このようにproductionとdevelopmentで挙動が異なるのには理由がある。

config.eager_load

こちらにある、Railsガイドの説明を引用する。

https://railsguides.jp/configuring.html#rails%E5%85%A8%E8%88%AC%E3%81%AE%E8%A8%AD%E5%AE%9A
config.eager_load: trueにすると、config.eager_load_namespacesに登録された事前一括読み込み(eager loading)用の名前空間をすべて読み込みます。ここにはアプリケーション、エンジン、Railsフレームワークを含むあらゆる登録済み名前空間が含まれます。
ざっくり言ってしまうと、
Productionの場合は、アプリケーションサーバを起動する前にすべてのapp以下に存在するClassを読み込む...というような理解をしています。

クラスに定義した定数に関してもその例外ではなく、クラスを読み込むタイミングで読み込まれているため、Production環境では「Pumaが実行した時間」に設定されるわけです。

development環境では、このconfig.eaher_loadが `false` に設定されているので、コードが実行されたタイミングで読み込まれています。

下記の参考URLを見るとこのあたりの挙動についてかなり深堀されているので、参考にするとよいと思います。

https://railsguides.jp/constant_autoloading_and_reloading.html

そもそも論

基本的に定数はプロセスが終了するまで不変なので、定数に「Time.now」などの実行タイミングに依存する値の代入をそもそもするべきではないと思いました。レビューする際もこのあたりの挙動や性質を抑えてレビューするとよいでしょう。

関連しそうなブログ