【问题标题】:Rails: cattr_accessor and class variablesRails:cattr_accessor 和类变量
【发布时间】:2010-07-16 10:18:52
【问题描述】:

运行此代码:

module A
  def self.included(klass)
    klass.send(:cattr_accessor, :my_name)
  end

  def set_my_name_var
    @@my_name = 'A' # does NOT work as expected
  end

  def set_my_name_attr
    self.class.my_name = 'A' # works as expected
  end
end

class B
  include A

  cattr_accessor :my_other_name

  def set_my_other_name_var
    @@my_other_name = 'B' # works
  end

  def set_my_other_name_attr
    self.class.my_other_name = 'B' # works
  end
end

b = B.new

b.set_my_other_name_var
puts "My other name is " + B.my_other_name
b.set_my_name_var
puts "My name is " + B.my_name

b.set_my_other_name_attr
puts "My other name is " + B.my_other_name
b.set_my_name_attr
puts "My name is " + B.my_name

像这样中断:

My other name is B
TypeError: (eval):34:in `+': can't convert nil into String

如果我们交换最后两个代码块(这样b.set_my_name_attrb.set_my_name_var 之前被调用),一切正常。

看起来它将@@my_name 视为模块A 的类变量,而不是B 类(正如我所期望的那样)。不是很混乱吗?在哪里可以阅读更多关于模块类变量的信息?

【问题讨论】:

    标签: ruby-on-rails ruby activesupport


    【解决方案1】:

    当您在模块A 中使用set_my_name_var 方法执行@@my_name = 'A' 时,这是在A 中设置模块变量。当通过包含类调用该方法时,此行为不会改变。这也导致了另一个有时会引起人们注意的事实 - 如果您要将 A 包含在多个类中,那么 @@my_name 的实例只有一个,不是每个包含类都有一个实例。以下示例说明了这一点:

    module Example
      def name=(name)
        @@name = name
      end
    
      def name
        @@name
      end
    end
    
    class First
      include Example
    end
    
    class Second
      include Example
    end
    
    irb(main):066:0> f = First.new
    => #<First:0x2d4b80c>
    irb(main):067:0> s = Second.new
    => #<Second:0x2d491d8>
    irb(main):068:0> f.name = 'Set via f'
    => "Set via f"
    irb(main):069:0> s.name
    => "Set via f"
    

    更新

    我想我已经弄清楚发生了什么,这将解释为什么它似乎没有按您期望的方式工作。 cattr_reader(以及扩展名cattr_accessor)包含以下内容:

    class_eval(<<-EOS, __FILE__, __LINE__)
      unless defined? @@#{sym}  # unless defined? @@hair_colors
        @@#{sym} = nil          #   @@hair_colors = nil
      end
    
      # code to define reader method follows...
    

    以下顺序发生:

    • B 已定义
    • 包含模块A
    • included 回调执行klass.send(:cattr_accessor, :my_name)
    • @@my_name 是在设置为 nil 的类 B 中创建的。

    如果没有cattr_accessor,那么在调用set_my_name_var 之后,当您在B 中说@@my_name 时,它将引用模块的变量。但是使用cattr_accessor 后,类中现在存在同名的变量,因此如果我们在B 中说@@my_name,我们会得到B 的变量值,而不是A 的值。这就是我说的掩饰的意思。 (B 的变量妨碍我们看到 A 的)

    也许下面会说明。想象一下,我们刚刚到达您的b = B.new,我们会执行以下操作:

    >> A.class_variables
    => [] # No methods called on A yet so no module variables initialised
    >> B.class_variables
    => ["@@my_other_name", "@@my_name"] # these exist and both set to nil by cattr_accessor
    >> B.send(:class_variable_get, '@@my_name')
    => nil # B's @@my_name is set to nil
    >> b.set_my_name_var # we call set_my_name_var as you did in the question
    => "A"
    >> A.send(:class_variable_get, '@@my_name')
    => "A" # the variable in the module is to to 'A' as you expect
    >> B.send(:class_variable_get, '@@my_name')
    => nil # but the variable in the class is set to nil
    >> B.my_name
    => nil # B.my_name accessor has returned the variable from the class i.e. nil
    

    如果您尝试在 setter 之前使用 getter,我认为 cattr_reader 这样做是为了避免 uninitialized class variable 错误。 (类变量不像实例变量那样默认为nil。)

    【讨论】:

    • 我想到了,但是,这又不是令人困惑吗?我希望模块中的实例方法表现得好像它们是包含模块的类的实例方法。而且,到目前为止,情况是这样的:self、@ivar 符合我的期望。
    • ... 如果需要模块变量,可以从模块类方法访问它们。一切似乎都合乎逻辑。
    • @artemave 我对此进行了更多思考,并对您所看到的内容提出了更新的解释,我已添加到答案中。请让我知道这现在是否更有意义。谢谢。
    • @mikej 我不明白 masking access to @@my_name in the module 位。
    • @artemave 我试图澄清并添加更多代码来说明。让我知道这是否更好。
    猜你喜欢
    • 2012-01-31
    • 2019-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-16
    相关资源
    最近更新 更多