3

この回答によると、少なくとも IRB では最上位レベルでグローバル名前空間に定数を取得できます。include単純に、モジュール内で同じトリックを実行できると思いました。

module Foo
  class Bar
    def frob(val)
      "frobbed #{val}"
    end
  end
end

module Baz
  include Foo
  class Qux
    def initialize(val)
      @val = val
      @bar = Bar.new
    end
    def twiddle
      @bar.frob(@val)
    end
  end
end

ただし、これを使用しようとすると、次のようになります。

NameError: uninitialized constant Baz::Qux::Bar

Baz::const_missing次に、 forward toにオーバーライドしようとしましFooたが、呼び出されません。Baz::Qux::const_missingしますが、その時までに、通訳者はBaz.

クラスがそれらを修飾する必要がないように、Foo定数をインポートするにはどうすればよいですか?BazBaz

(ディレクトリ構造とモジュール名の一貫性のため、 の代わりにQuxetc.を宣言したいだけではないことに注意してください。)FooBaz


ETA:別のクラスを作成Baz::CorgeするBaz::Quxと、資格なしで参照できます。したがって、明らかに「ピアスコープ」の感覚があります。Quxがにアクセスできるようにする方法がいくつかあることは承知していますが、 のすべてのクラス(および の他のすべてのクラス)が修飾なしでアクセスできるBar方法を探しています。BazBarFoo

4

4 に答える 4

0

Here's what I eventually came up with. In the same file that initially declares Baz:

module Foo
  def self.included(base)
    constants.each { |c| base.const_set(c, const_get("#{self}::#{c}")) }
  end
end

module Baz
  include Foo
  #... etc.
end

When Baz includes Foo, it will set a corresponding constant Baz::Whatever for every Foo::Whatever, with Foo::Whatever as the value.

If you're worried Foo may already define self.included, you can use alias_method to adjust for that:

module Foo
  alias_method :old_included, :included if self.method_defined? :included
  def self.included(base)
    old_included(base) if method_defined? :old_included
    constants.each { |c| base.const_set(c, const_get("#{self}::#{c}")) }
  end
end

This approach has two limitations --

  1. All the constants (including classes) in Foo that we care about must be defined at the time include Foo is evaluated -- extensions added to Foo later will not be captured.
  2. The file that defines Foo.included here must be required before any file in Baz that uses any of those constants -- simple enough if clients are just using require 'baz' to pull in a baz.rb that in turn uses Dir.glob or similar to load all the other Baz files, but it's important not to require those files directly.

Roko's answer gets around problem (1) above using const_missing, but it still has an analogous problem to (2), in that one has to ensure add_const_missing_to_classes is called after all classes in Baz are defined. It's a shame there's no const_added hook.

I suspect the const_missing approach also suffers performance-wise by depending on const_missing and const_get for every constant reference. This might be mitigated by a hybrid that caches the results, i.e. by calling const_set in const_missing, but I haven't explored that since trying to figure out scoping inside define_singleton_method always gives me a headache.

于 2015-10-06T22:48:03.923 に答える
-1

与えられたエラーからわかるように:NameError: uninitialized constant Baz::Qux::Bar

Bar class内部のスコープを見つけようとしますが、そこに見つからQux classないのはなぜですか?

このスコープ内にないため-使用したBaz modlueスコープ内にありますinclude Foo

したがって、2 つのオプションがあります。1) を呼び出すときに正しいスコープに対処するためBar class、これを変更します。

@bar = Bar.new

これに:

@bar = Baz::Bar.new

そのように:

module Baz
  include Foo
  class Qux
    def initialize(val)
      @val = val
      @bar = Baz::Bar.new
    end
    def twiddle
      @bar.frob(@val)
    end
  end
end

または、

2)をそれ自体に挿入include Fooします。class Qux

module Baz
  class Qux
    include Foo
    def initialize(val)
      @val = val
      @bar = Bar.new
    end
    def twiddle
      @bar.frob(@val)
    end
  end
end

- 編集 -

joanbm が述べたように、これはこの動作を説明していません。Scope of Constants in Ruby Modulesをご覧になることをお勧めします。その投稿は定数 (クラスではない) に関するものですが、原則は同じです。

于 2015-10-06T00:27:18.710 に答える