ruby-on-rails - rails : cattr_accessor and class variables

标签 ruby-on-rails ruby activesupport

运行这段代码:

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 类(正如我所期望的那样) .是不是很困惑?在哪里可以阅读更多关于模块类变量的信息?

最佳答案

当您在模块 A 中使用 set_my_name_var 方法执行 @@my_name = '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)
  • 在类 B 中创建一个 @@my_name 并设置为 nil

如果没有 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

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

关于ruby-on-rails - rails : cattr_accessor and class variables,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3263829/

相关文章:

mysql - 由于 "bundle update"gem,我无法运行 "mysql2"

ruby - 如何基于 capybara 中的元素发送 JavaScript

ruby - 如何更改 RSpec 描述 block 的模块上下文?

ruby-on-rails - 在 ActiveSupport::LogSubscriber 子类中访问 Rails 请求

ruby - 如何在 Rails 3.0.x 上使用可读性 gem?

ruby-on-rails - 带有RVM的Mac OSX上的Rails Segmentation Fault

ruby-on-rails - 自定义 Javascript Rails 管理员

ruby-on-rails - 破解 i18n 系统来生成 <p> 标签是个坏主意吗?

php - 如何从传递和接收参数的 PHP 内部运行 Ruby/Python 脚本?

Ruby Mechanize 如何发送多个同名参数