2

我试图通过将常用方法移动到模块或类中并将其包含/继承在不同模块下命名空间的新类中。如果我在同一个模块下有两个类命名空间,那么只要我在同一个命名空间下,我就可以在不包括模块名称的情况下调用它们。但是,如果我有一个从不同模块中包含的方法,而不是我的命名空间范围更改,我不知道为什么或如何避免它。

例如。此代码有效并返回“bar”:

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 移到一个模块中,那么它就不再起作用了:NameError: uninitialized constant Mixins::A::Helper.

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 正在评估字符串而不是块,则范围变为 Foo::Bar 而不是 Foo。

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

有人有想法吗?

编辑:

多亏了 Wizard 和 Alex,我最终得到了这段代码,它并不漂亮,但可以完成工作(注意它使用的是 Rails helper constantize):

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
4

3 回答 3

1

要理解这个问题,您需要了解常量查找在 Ruby 中是如何工作的。它与方法查找不同。在这段代码中:

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

Helper指的是一个名为“Helper”的常量,它位于AMixins或在顶层(即 in Object)中定义,而不是定义在 中的称为“Helper”的常量Bar。仅仅因为你class_eval这个带有 class 的代码Bar,并没有改变它。如果您知道“词法绑定”和“动态绑定”之间的区别,那么您可以说 Ruby 中的常量解析使用词法绑定。您期望它使用动态绑定。

请记住,您传递给的块base.class_eval被编译为字节码一次,随后,每次included调用钩子时,相同的预编译块(包括对 的引用Helper)将使用不同的类(base)作为self. 每次执行时,解释器都不会重新解析和编译块base.class_eval

另一方面,如果您将String传递给,则每次钩子运行时都会class_eval重新解析和编译该字符串。重要提示:从 String 编辑的代码在null lexical environment中进行评估。这意味着来自周围方法的局部变量不可用于从字符串编辑的代码。更重要的是,这也意味着周围的范围不会影响ed 代码中的持续查找。includedevalevaleval

如果您确实希望动态解析常量引用,则显式常量引用将永远无法工作。这根本不是该语言的工作方式(并且有充分的理由)。想一想:如果常量引用是动态解析的,取决于 的类,self你永远无法预测对事物的引用是如何在运行时解析的。如果您在模块中有这样的代码......ArrayHash

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

...并且模块与嵌套类混合到一个类中HashHash.[]将引用嵌套类而不是Hash标准库!显然,动态解析常量引用有太多的名称冲突和相关错误的可能性。

现在使用方法查找,这是另一回事。OOP 的整个概念(至少是 OOP 的 Ruby 风格)是方法调用(即消息)的作用取决于接收者的类。

如果您确实想动态查找常量,具体取决于接收器的类,您可以使用self.class.const_get. 这可以说比eval使用 String 来实现相同的效果更干净。

于 2012-05-09T15:01:53.823 回答
0
module Mixins::A
  def self.included(base)
    base.class_eval do
      def test_it
        Foo::Helper.new.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 中解析常量的方式,Helper 类需要在 Foo::Bar 下命名。

于 2012-05-08T23:09:40.393 回答
0

在过去的几个主要版本中,Ruby 中的常量查找相对于#class_eval 发生了变化。有关更多信息,请参阅此帖子:http: //jfire.posterous.com/constant-lookup-in-ruby

于 2012-05-09T16:22:33.990 回答