2

コードで奇妙なバグに遭遇しました。ライブラリに次の 2 つのファイルを含む Rails アプリケーションがあります。

lib/module_one/module_two/class_one.rb

module ModuleOne
  module Moduletwo
    class ClassOne
      class << self
        def test
          puts 'Class one'
          ClassTwo.test
        end
      end
    end
  end
end

lib/module_one/module_two/class_two.rb

module ModuleOne
  module ModuleTwo
    class ClassTwo
      def self.test
        puts 'Class two'
      end
    end
  end
end

今私の問題は、コンソールに入って次のように書くときです:

ModuleOne::ModuleTwo::ClassOne.test

次をスローします。NameError: uninitialized constant ClassTwo

奇妙なことに、問題は のclass << self代わりに を使用することに関連しているようですself.method。このように class_one.rb ファイルを変更すると、動作します!:

module ModuleOne
  module ModuleTwo
    class ClassOne
      def self.test
        puts 'Class one'
        ClassTwo.test
      end
    end
  end
end

次のようにapplication.rbにファイルをロードしています:

config.autoload_paths += %W(#{config.root}/lib)

これはレールのバグですか、それとも私が何か間違っているだけですか?

私はレール3.1.3btwを使用しています

4

2 に答える 2

9

定数参照

まず、定数解決の基本的なプロセスは、Ruby が最初にレシーバー (参照を囲むクラスまたはモジュール) のレキシカルスコープを検索し、ClassTwo見つからない場合はレベルを上げます (Module.nestingこの検索パスを返します)。 ) 等々。あなたの場合、ModuleOne::ModuleTwo::ClassOne:ClassTwoそれは、、、、などを探すことを意味します。ModuleOne::ModuleTwo::ClassTwoModuleOne::ClassTwo

それが失敗した場合、ルビーは囲んでいるクラス/モジュールの継承階層を調べます (たとえば、ClassOne のスーパークラスに何かが定義されています。ancestorsモジュールのメソッドはこの検索パスを返します。最後に、最上位の定数が検索されます。

Rails オートローディング

Railsのマジックローディングに戻ります。ここでconst_missingは、Ruby がクラスを見つけられないときに呼び出されるフックが Rails によって追加されます。これは基本的に、この検索ロジックを複製しようとします。各ステップで、欠落している定数を含むファイルがロードされたかどうかを確認します。

理想的には、Ruby は検索パス (つまりネスト) を渡して検索しますが、残念ながらそうではありませClassTwoconst_missing

const_missingRailsは、呼び出されているクラスの名前 (つまり、定数へのアクセスを囲んでいるクラス) を先頭に追加することで、ネストを推測します。たとえば、2 番目の例では、最終的にModuleOne::ModuleTwo::ClassOne::ClassTwo. const_missingと呼ばれるものをログに記録するように定義することで、これを十分に簡単に確認できます

class Object
  def self.const_missing missing_name
    puts "qualified name is #{self.name}::#{missing_name}"
    super
  end
end

その後、Rails は「ClassOne」を削除ModuleOne::ModuleTwo::ClassTwoして、チェーンを上っていきます。

では、なぜclass << self違いが生じるのでしょうか。ログで最初のケースを繰り返すと、const_missingログに記録された修飾名が::ClassTwo. const_missingは現在 ClassOne のメタクラスで呼び出されてclass << selfおり、定数に割り当てられていないため、名前がなく、入れ子をごまかすレールの試みは機能しません。

これにより、恐ろしい回避策への扉が開かれます。

module ModuleOne
  module ModuleTwo
    class ClassOne
      class << self
        def test
          puts 'Class one'
          ClassTwo.test
        end
      end
      FOO = class << self; self; end
    end
  end
end

const_missing が呼び出されるクラスに名前 (ModuleOne::ModuleTwo::ClassOne::FOO) が付けられたため、Rails の回避策が機能するようになりました。

ModuleOne::ModuleTwo::ClassOneデイブの回避策は、匿名の固有クラス/メタクラスではなくconst_missing が呼び出されるためだと思います。

const_missing本当の修正は、Ruby がネストを渡すことです。長い間公開されていましたが、この趣旨の ruby​​ に対してログに記録されたバグがあります。そうです、これはマジック ロードのバグと見なすことができます (他にもエッジ ケースがあります)。

于 2012-05-17T10:02:02.263 に答える
1

(部分的な回答のみですが、フォーマットが必要です。)

それはclass << self働き方によるものです。

たとえば、次のように変更した場合:

class << self
  def test
    self::ClassTwo.test
  end
end

それは正常に動作します。


編集; 合理的なコメントには長すぎます。

私は少し突っついています... 直感的なレベルでは、それは私には理にかなっていますが、その理由はまだわかりません. 本当の理由をかつて知っていたのか、それともでっち上げなのかはわかりません。

selfただし、モジュールを参照しているように見える理由はわかりません。class <<「Programming Ruby 1.9」の本では、セマンティクスについて十分に詳しく説明されていません。何かをツイートしてこの質問を参照すると、より賢い人が本当の答えを作成します。

于 2012-05-16T13:27:11.977 に答える