Upgrade to Pro — share decks privately, control downloads, hide ads and more …

定数参照のトラップとそれを回避するために行う2つのこと / Kaigi on Rails 2023

hogucc
October 27, 2023

定数参照のトラップとそれを回避するために行う2つのこと / Kaigi on Rails 2023

Kaigi on Rails 2023で発表した「定数参照のトラップとそれを回避するために行う2つのこと」のスライドです

https://kaigionrails.org/2023/talks/hogucc/

hogucc

October 27, 2023
Tweet

More Decks by hogucc

Other Decks in Programming

Transcript

  1. MY_CONSTANT = "I’m from the top level scope" module MyModule

    MY_CONSTANT = "I’m from the my module scope" class ParentClass MY_CONSTANT = "I’m from the super class scope” end class MyClass < ParentClass MY_CONSTANT = "I’m from the current scope” def self.show_constant puts MY_CONSTANT end end end Ruby の定数探索アルゴリズム
  2. MY_CONSTANT = "I’m from the top level scope" module MyModule

    MY_CONSTANT = "I’m from the my module scope" class ParentClass MY_CONSTANT = "I’m from the super class scope” end class MyClass < ParentClass MY_CONSTANT = "I’m from the current scope” def self.show_constant puts MY_CONSTANT end end end Ruby の定数探索アルゴリズム 定数参照している スコープから 探索開始
  3. MY_CONSTANT = "I’m from the top level scope" module MyModule

    MY_CONSTANT = "I’m from the my module scope" class ParentClass MY_CONSTANT = "I’m from the super class scope” end class MyClass < ParentClass MY_CONSTANT = "I’m from the current scope” def self.show_constant puts MY_CONSTANT end end end Ruby の定数探索アルゴリズム 外側のスコープ を探索
  4. MY_CONSTANT = "I’m from the top level scope" module MyModule

    MY_CONSTANT = "I’m from the my module scope" class ParentClass MY_CONSTANT = "I’m from the super class scope” end class MyClass < ParentClass MY_CONSTANT = "I’m from the current scope” def self.show_constant puts MY_CONSTANT end end end Ruby の定数探索アルゴリズム スーパークラス を探索
  5. MY_CONSTANT = "I’m from the top level scope" module MyModule

    MY_CONSTANT = "I’m from the my module scope" class ParentClass MY_CONSTANT = "I’m from the super class scope” end class MyClass < ParentClass MY_CONSTANT = "I’m from the current scope” def self.show_constant puts MY_CONSTANT end end end Ruby の定数探索アルゴリズム トップレベルを探索
  6. MY_CONSTANT = "I’m from the top level scope" module MyModule

    MY_CONSTANT = "I’m from the my module scope" class ParentClass MY_CONSTANT = "I’m from the super class scope” end class MyClass < ParentClass MY_CONSTANT = "I’m from the current scope” def self.show_constant puts MY_CONSTANT end end end Ruby の定数探索アルゴリズム 見つからなかったら エラーを返す
  7. Ruby の定数参照 のパターン :: をつけずに参照する 定数の間に:: をつけて参照 する 先頭に ::

    をつけてトップレ ベルから参照する 例)Zoo 例)Zoo::Animals 例)::Zoo 1 2 3
  8. MY_CONSTANT = "Top Level Constant" module MyModule MY_CONSTANT = "Current

    Level Constant" puts ::MY_CONSTANT end 先頭に:: をつけてトップレベルから探索する “Top Level Constant” が 出力される
  9. require やload は不要 同一ファイル内の定 数を読み込む require やload が必要 別ファイルの定数を 読み込む

    定数の読み込み autoload で定数が初めて参照された ときに定数が定義されたファイルを 読み込むことができる
  10. require "application_controller" require "user" class UsersController < ApplicationController def index

    @users = User.all end end require は 必要ない! Rails のautoload
  11. app 配下に 後から追加した カスタムディレクトリ も対象 デフォルトではapp 配下のassets, javascript, views を除くすべてのサブディレク

    トリが対象 Q. なぜrequire がいらないのか A. Rails のconfig.autoload_paths に設定され たパスは自動読み込みの対象になるため
  12. autoload のメリット 1 2 3 開発環境ではアプリを再起動しなくて もコードの変更がリアルタイムに反映 される ファイル名とクラス名の不整合があれ ばロード時に検出される

    本番環境ではアプリケーション起動時にすべてのコードを読み込 む。都度ロードを挟まないことでパフォーマンスを最適化
  13. autoload のメリット 1 2 3 開発環境ではアプリを再起動しなくて もコードの変更がリアルタイムに反映 される ファイル名とクラス名の不整合があれ ばロード時に検出される

    本番環境ではアプリケーション起動時にすべてのコードを読み込 む。都度ロードを挟まないことでパフォーマンスを最適化 Zeitwerk という コードローダーが 定数のロードを 担っている
  14. # app/controllers/user/notifications_controller.rb class User::NotificationsController < ApplicationController def index roles =

    User::ROLES end end # app/models/user.rb class User < ApplicationRecord ROLES = ["member", "guest"] end 定数参照の失敗例① model のUser::ROLES を 参照したかったのに controller のUser が参照され エラーになってしまった... ~階層化していたcontroller の名前空間とmodel のクラス名が被ってしまったケース~
  15. # app/controllers/user/notifications_controller.rb class User::NotificationsController < ApplicationController def index roles =

    User::ROLES end end # app/models/user.rb class User < ApplicationRecord ROLES = ["member", "guest"] end 定数参照の失敗例① ~階層化していたcontroller の名前空間とmodel のクラス名が被ってしまったケース~ ::User::ROLES のように トップレベルから 参照すればOK
  16. module Wrapper module Aws class S3 def self.upload_file s3 =

    Aws::S3::Client.new(region: ‘xxxx’) end end end end 定数参照の失敗例② 〜名前空間がネストしたモジュールやクラス〜 unitialized constant Wrapper::Aws::S3::Client というエラーが発生🥺 Aws::S3::Client はgem に定義されたクラス
  17. module Wrapper module Aws class S3 def self.upload_file s3 =

    Aws::S3::Client.new(region: ‘xxxx’) end end end end 定数参照の失敗例② 〜名前空間がネストしたモジュールやクラス〜 Wrapper::Aws::S3 と みなされて、Client という 定数は存在しない、と エラーになっていた💦 Wrapper まで辿って Aws::S3 を発見!
  18. マージされた 内容をテスト テスト環境 ユーザーが 使う環境 本番環境 Pull Request を マージ

    開発環境 開発→テスト→エラー発見→開発→またテスト... エラー発生😢 修正 OK! エラーになった箇所はこの環境から参照される
  19. マージされた 内容をテスト テスト環境 ユーザーが 使う環境 本番環境 Pull Request を マージ

    開発環境 開発→テスト→エラー発見→開発→またテスト... エラー発生😢 修正 OK! エラーになった箇所はこの環境から参照される 開発環境の時点で気づきたかった... ! さっき見たこの知識があれば気づけた! ... かも?
  20. class S3 def self.upload_file s3 = Aws::S3::Client.new(region: ‘xxxx’) end end

    絶対に気づける... ? もともとはS3 という クラスで、後から Wrapper::Aws::S3 という 名前空間が切られた としたら気づける... ? 複数箇所の定数を 一度に直した後、 別の定数が参照 されていることに 気づける...? 自分さえ間違えなければ それでいい... ?
  21. いま参照している定数が どの定数としてRuby に 認識されているかを 知りたい gem で実現したいこと 定数の候補を出力してほしい 全ての定数の先頭に ::

    をつけることを強制しても解決はできそうで すが、やりたいことに対してオーバーキルしている感があり、 やめました 意図せず 別の定数参照 しちゃってた 問題を解決 別の名前空間 の定数も検索
  22. いま参照している定数が どの定数としてRuby に 認識されているかを 知りたい 定数の候補を出力してほしい ConstantVision.search("HOGE", "Fuga::Piyo") # =>

    origin: xxx, candidates: ["yyy", "zzz"] constant_vision というgem を作りました https://github.com/hogucc/constant_vision