2012-05-08 7 views
2

一般的なメソッドをモジュールまたはクラスに移動し、別のモジュールの名前空間に含まれる新しいクラスにインクルード/継承させることで問題を解決しようとしています。同じモジュールの下に2つのクラスの名前空間がある場合、同じ名前空間の下にある限り、モジュール名を含めずに呼び出すことができます。しかし、私の名前空間の範囲の変更とは別のモジュールに含まれているメソッドがある場合、それを避ける理由や方法はわかりません。モジュールのスコープが正しくありません

たとえば、このコードの動作を返す「バー」:

module Foo 
    class Bar 
    def test_it 
     Helper.new.foo 
    end 
    end 
end 

module Foo 
    class Helper 
    def foo 
     'bar' 
    end 
    end 
end 

Foo::Bar.new.test_it 

が、私はモジュールにメソッドのtest_itを移動した場合、それはもはや仕事doesntの:NameError:初期化されていない一定のミックスイン:: ::ヘルパー。

module Mixins; end 

module Mixins::A 
    def self.included(base) 
    base.class_eval do 
     def test_it 
     Helper.new.foo 
     end 
    end 
    end 
end 

module Foo 
    class Bar 
    include Mixins::A 
    end 
end 

module Foo 
    class Helper 
    def foo 
     'bar' 
    end 
    end 
end 

Foo::Bar.new.test_it 

さらに、class_evalがblockではなくstringを評価している場合、scopeはFooではなくFoo :: Barになります。

module Mixins; end 

module Mixins::A 
    def self.included(base) 
    base.class_eval %q{ 
     def test_it 
     Helper.new.foo 
     end 
    } 
    end 
end 

module Foo 
    class Bar 
    include Mixins::A 
    end 
end 

module Foo 
    class Helper 
    def foo 
     'bar' 
    end 
    end 
end 

Foo::Bar.new.test_it 

誰でも知っていますか?

EDIT:

module Mixins; end 

module Mixins::A 
    def self.included(base) 
    base.class_eval do 
     def test_it 
     _nesting::Helper 
     end 
     def _nesting 
     @_nesting ||= self.class.name.split('::')[0..-2].join('::').constantize 
     end 
    end 
    end 
end 

module Foo 
    class Helper 
    end 

    class Bar 
    include Mixins::A 
    end 
end 

module Foo2 
    class Helper 
    end 

    class Bar 
    include Mixins::A 
    end 
end 

Foo::Bar.new.test_it #=> returns Foo::Helper 
Foo2::Bar.new.test_it #=> returns Foo2::Helper 
+0

嬉しいです! –

答えて

1

この問題を理解するには、Rubyで定数検索がどのように機能するのかを理解する必要があります。メソッドルックアップと同じではありません。このコードで:

module Mixins::A 
    def self.included(base) 
    base.class_eval do 
     def test_it 
     Helper.new.foo 
     end 
    end 
    end 
end 

Helperのいずれかである「ヘルパー」と呼ばれる定数を指すAMixins、またはトップレベルで定義され、ない「ヘルパー」と呼ばれる定数(すなわちObjectインチ)これはBarで定義されています。ちょうどあなたクラスBarとこのコードは、それを変更しません。 "字句結合"と "動的結合"の違いを知っていれば、Rubyの定数解決法は字句バインディングを使用していると言えます。動的バインディングを使用することを期待しています。

Helperへの参照を含む)は、同じプリコンパイルされたブロックは別のクラスで実行されていることを、あなたはbase.class_evalに渡しているブロックが一度をバイトコードにコンパイルされ、その後、すべての時間がincludedフックが呼び出されることに注意してください(base)をselfとする。 base.class_evalを実行するたびに、インタプリタはブロックを新しく解析してコンパイルしません。一方

あなたはclass_eval文字列を渡すと、その文字列がincludedフックを実行するたびにを解析され、新たにコンパイルされます。重要:文字列evalであるコードは、ヌル語彙環境で評価されます。つまり、周囲のメソッドからのローカル変数はのコードから利用可能なではなくであることを意味します。あなたのためには、周囲のスコープがevalコード内からの定数検索に影響しないことを意味します。

定数参照を動的に解決する場合は、明示的な定数参照は動作しません。それは単に言語が(そして正当な理由で)働くことを意図した方法ではありません。それについて考えてみましょう:一定の参照が動的に解決された場合、selfのクラスに応じて、実行時にArrayまたはHashなどの参照がどのように解決されるかは予測できません。あなたは、モジュール内でこのようなコード...

hash = Hash[array.map { |x| ... }] 

を持っている...そしてモジュールは、ネストされたHashクラスにクラスに混合した場合、Hash.[]入れ子になったクラスを参照してくださいではなく、Hashからでしょう標準ライブラリ!明らかに、定数参照を動的に解決すると、ネーム・クラッシュや関連するバグの可能性があまりにも高くなります。

メソッドルックアップでは、これは別のことです。 OOPの全体概念(少なくともOOPのRubyフレーバー)は、メソッド呼び出し(つまりメッセージ)がレシーバーのクラスに依存するということです。

動的に定数を検索する場合は、受信者のクラスに応じてself.class.const_getを使用します。これは、同じ効果を達成するために、おそらくeval文字列よりもきれいです。

+0

ありがとう。それはたくさん説明します。私はまだブロックの代わりに文字列を使ってclass_evalをやっているなら、入れ子の変更が混乱しているのです。私はクラスclass_evalと[Mixins :: A]という文字列を持つ[Mixins :: A、Foo :: Bar]をブロックclass_evalで取得します。 –

+0

素晴らしい情報をありがとう。スコープは理にかなっていて、それについて考えるだけで周りの道を探す必要がありました。 –

+0

@ luriG、これに戻るにはかなり時間がかかりました。追加された情報は、ブロックと文字列の評価が異なる結果が得られる理由を理解するのに役立ちます。 –

0
module Mixins::A 
    def self.included(base) 
    base.class_eval do 
     def test_it 
     Foo::Helper.new.foo 
:ウィザードとアレックスに

おかげで、私は綺麗ではないですが、仕事をしていません。このコード(は、それがRailsのヘルパーconstantizeを使用している注意してください)になってしまいました


EDIT:

茶を取得した後コードで少し遊んでみてください。もう少し問題が見えます。私はあなたがしようとして正確に何をすることができるでしょうとは思わないが、これは近いです:ヘルパークラスはFooの下に名前空間するために必要なことを

module Mixins::A 
    def self.included(base) 
    base.class_eval do 
     def test_it 
     self.class.const_get(:Helper).new.foo 
     end 
    end 
    end 
end 

module Foo 
    class Bar 
    include Mixins::A 
    end 
end 

module Foo 
    class Bar::Helper 
    def foo 
     'bar' 
    end 
    end 
end 

注::バーによる方法に定数が解決を取得しますRubyで

+0

コードが複雑すぎるわけではないので、問題が複雑すぎるように私のケースを単純化しました。あなたの答えは私の問題を解決しません。私もFoo2 :: Helperの代わりにFoo :: Helperを呼び出すので、あなたのmixinをモジュール "Foo2"に含めると、明示的にトップレベルの名前空間を指定して動的に解決したくない理由があります。 –

+0

私は参照してください。 'Mixens :: A'を含める前に、あなたのモジュールで' Helper'を定義してみてください。 –

+0

ちょうど試しました。同じ問題。まだMixins :: A :: Helperが定義されていない... –

関連する問題