ネストされたクラスが悪い理由とレールマジックの学習方法

私は最近、grapeを使用してAPIを書いているときにおかしな問題に遭遇しました。 グレープはそれとはまったく関係がありません。この記事は、ActiveSupportがどのように機能し、レールで一定の要求を必要としないすべての魔法のおかげで、それを弱める方法について詳しく説明しています。 猫の下で誰が気にしてください。



そのため、クラスがあります-Grape :: Entity、これを使用してAPIでモデルを表示し、継承し、異なるモジュールを接続し、バージョン管理中に異なるモジュールに入ります。 ディレクトリ構造は次のようになります。



./api/ └── path1 ├── entities │  ├── entity1.rb │  └── entity2.rb ├── v1 │  └── entities │  ├── entity1.rb │  └── entity3.rb └── v2 └── entities ├── entity1.rb ├── entity2.rb └── entity3.rb
      
      





::Api::Path1::V2::Entity::Entity1



を使用すると、特定のエントリを簡単に見つけることができます。 そして、すべては順調です。このように、モジュールと最終クラスのみがあります。 ただし、常に単独で作業するわけではなく、ネストされたクラスが発生することもあります。 Module1::Module2::Class1::Class2



、これはカットです。ここでは可能ですが、それも何も問題はありません。 ただし、APIの新しいバージョンを作成し、ゼロから作成するものは何でも、古いクラス::Api::Path1::V3::Entity::Class1::Class2



を継承します。ここで、 V3::Class1 < V1::Class1



です。 そして、ここですべてが突然壊れます。 ::Api::Path1:: V3 ::Entity::Class1::Class2



を取得しようとしていますが、 ::Api::Path1:: V1 ::Entity::Class1::Class2



ます。 Railsの魔法の典型的な例では、エラーは発生しませんでしたが、適切なクラスを取得できませんでしたが、完全に異なるクラスを取得しました。これは、フルパスがすべての名前で記述されているにもかかわらず



幸いなことに、これはirbに代わる非常に強力な代替手段であるだけでなく、実行可能なコードをステップごとに歩き回り、何も知らないすべての補助クラスのすべてのメソッドに飛び込むことができる素晴らしいデバッガーです。実装、通信など 詳細はこちら



だから:



 ... binding.pry '::Api::Path1::V3::Entity::Class1::Class2'.constantize ... > step 65: def constantize => 66: ActiveSupport::Inflector.constantize(self) 67: end > step ... @ line 251 ActiveSupport::Inflector#constantize: 248: # NameError is raised when the name is not in CamelCase or the constant is 249: # unknown. 250: def constantize(camel_cased_word) => 251: names = camel_cased_word.split('::') 252: 253: # Trigger a built-in NameError exception including the ill-formed constant in the message. 254: Object.const_get(camel_cased_word) if names.empty?
      
      





そして、わずか数ステップで、ActiveSupportの世界に飛び込みます。ActiveSupportはクラス名を解析し、その実装を探します。次のようになります。



 250 def constantize(camel_cased_word) 251 names = camel_cased_word.split('::') 252 253 # Trigger a built-in NameError exception including the ill-formed constant in the message. 254 Object.const_get(camel_cased_word) if names.empty? 255 256 # Remove the first blank element in case of '::ClassName' notation. 257 names.shift if names.size > 1 && names.first.empty? 258 259 names.inject(Object) do |constant, name| 260 if constant == Object 261 constant.const_get(name) 262 else 263 candidate = constant.const_get(name) 264 next candidate if constant.const_defined?(name, false) 265 next candidate unless Object.const_defined?(name) 266 267 # Go down the ancestors to check if it is owned directly. The check 268 # stops when we reach Object or the end of ancestors tree. 269 constant = constant.ancestors.inject do |const, ancestor| 270 break const if ancestor == Object 271 break ancestor if ancestor.const_defined?(name, false) 272 const 273 end 274 275 # owner is in Object, so raise 276 constant.const_get(name, false) 277 end 278 end 279 end
      
      





ここで何が起こっていますか:



ActiveSupportは、フォーム::Api::Path1::V2::Entity::Entity1



を別の単語に分割し、それを順番に収集して、親オブジェクトから開始して、次の各名前でconst_getを呼び出し、定義されていることを確認します。



そして、これは、263行目のActiveSupportが::Api::Path1:: V3 ::Entity::Class1.const_get('Class2')



で、2番目のパラメーターfalseなしで、Class2が継承クラス::Api::Path1:: V1 ::Entity::Class1



定義されているときに問題が発生する場所::Api::Path1:: V1 ::Entity::Class1



、そしてそれがメソッドから取得して返すものです。



candidate = constant.const_get(name, false)



を使用する場合、このような問題は存在しませんが、これはバグというよりも機能です。 ActiveSupportは、先祖によって定義されたものを含む定数を見つけようとします。そうしないと、魔法がはるかに少なくなります。



そして、ネストされたクラスを持つクラスを継承しない場合、そのような問題は発生せず、したがって、私たち自身の問題を思い付くことはありません)



追伸 printercuはコメントの中で素晴らしい記事をアドバイスしまし



All Articles