1

我正在使用 Ruby on Rails v3.2.2。在一个模块中,我试图“动态”打开一个类,以便向它添加一个使用局部变量的 Ruby on Rails“范围方法”,这样:

module MyModule
  extend ActiveSupport::Concern

  included do
    # Note: The `CLASS_NAME` is not the class where `MyModule` is included. That
    # is, for instance, if the including class of `MyModule` is `Article` then
    # the `CLASS_NAME` is `User`.
    CLASS_NAME           = self.get_class_name.constantize # => User
    counter_cache_column = self.get_counter_cache          # => "counter_count"

    class CLASS_NAME
      def self.order_by_counter
        order("#{counter_cache_column} DESC")
      end
    end
  end
end

如果我运行上面的代码,我会收到以下错误:

NameError
undefined local variable or method `counter_cache_column' for #<Class:0x0000010775c558>

发生这种情况是因为counter_cache_columnin 未在模块的上下文中调用。我应该如何正确说明order_by_counter范围方法?


奖励:您对上述“如此动态”的实施有何建议?

4

4 回答 4

3

提供的includedActiveSupport::Concern在包含类的范围内进行评估。换句话说,你已经“重新打开”了这个块中的类。如果包含类继承自ActiveRecord::Base,您可以使用任何 AR 类宏,例如scopehas_manyattr_accessible等:

module MyModule
  extend ActiveSupport::Concern

  included do
    scope :order_by_counter, order("#{self.get_counter_cache} DESC")
  end

end

这假设“get_counter_cache”已经定义为包含类中的类方法(尽管从您显示的代码中并不清楚)。

于 2012-10-16T12:08:14.080 回答
1

counter_cache_column是一个局部变量。局部变量在它们定义的范围内是局部的(这就是它们被称为局部变量的原因)。

在这种情况下,范围是传递给的块included

类定义和方法定义创建一个新的空范围。只有块创建嵌套范围,因此,您需要使用块来定义您的方法。值得庆幸的是,有一种方法可以做到这一点:通过将块传递给define_method

module MyModule
  extend ActiveSupport::Concern

  included do
    klass                = get_class_name.constantize # => User
    counter_cache_column = get_counter_cache          # => "counter_count"

    klass.define_singleton_method(:order_by_counter) {
      order("#{counter_cache_column} DESC")
    }
  end
end

我做了一些其他的风格改进:

  • self是 Ruby 中的隐式接收器,无需指定
  • CLASS_NAME具有误导性:它不包含类的名称,它包含类本身
  • 另外,我不明白为什么它需要是一个常数
于 2012-10-16T12:42:10.390 回答
0

局部变量不会传递给重新打开的类。

module MyModule
  extend ActiveSupport::Concern

  included do
    counter_cache_column = self.get_counter_cache # => "counter_count"

    class_eval <<-RUBY, __FILE__, __LINE__+1
      def self.order_by_counter               # def self.order_by_counter
        order("#{counter_cache_column} DESC") #   order("counter_count DESC")
      end                                     # end
    RUBY
  end
end
于 2012-10-16T12:09:47.350 回答
-1

有许多快速而肮脏的方法可以实现您想要的。例如,如果您希望符号 'counter_cache_column' 表示其范围之外的内容,则可以将其声明为方法而不是局部变量:

included do
  CLASS_NAME           = self.get_class_name.constantize # => User
  def counter_cache_column; get_counter_cache end        # => "counter_count"

  class CLASS_NAME
    def self.order_by_counter
      order("#{counter_cache_column} DESC")
    end
  end
end
于 2012-10-16T12:10:27.490 回答