5

我的 rails 3.1.6 应用程序中有一个自定义访问器方法,它为属性分配一个值,即使该值不存在。my_attr 属性是一个序列化哈希,除非指定了空白值,否则它应该与给定值合并,在这种情况下,它会将当前值设置为空白值。(添加了检查以确保值是它们应有的值,但为简洁起见将其删除,因为它们不是我的问题的一部分。)我的 setter 定义为:

def my_attr=(new_val)
  cur_val = read_attribute(:my_attr)  #store current value

  #make sure we are working with a hash, and reset value if a blank value is given
  write_attribute(:my_attr, {}) if (new_val.nil? || new_val.blank? || cur_val.blank?)

  #merge value with new 
  if cur_val.blank?
    write_attribute(:my_attr, new_val)
  else
    write_attribute(:my_attr,cur_val.deep_merge(new_val))
  end
  read_attribute(:my_attr)
end

此代码按原样运行良好,但在我使用 self.write_attribute() 时却不行。然后我收到以下错误:

NoMethodError:
       private method `write_attribute' called for #<MyModel:0x00000004f10528>

因此,我的问题是:让 write_attribute 可用于实例似乎更合乎逻辑,那么为什么它仅可用于类而不可用于实例?我在 Ruby 或 Rails(或两者兼有)中对自我的基本了解是否有所欠缺?

4

1 回答 1

11

实例方法的内部self是该实例。但是,当您使用显式接收器调用方法时,ruby 的可见性控制会启动并禁止调用私有方法。

class Foo
  def implicit
    self # => #<Foo:0x007fc019091060>
    private_method
  end

  def explicit
    self # => #<Foo:0x007fc019091060>
    self.private_method
  end

  private
  def private_method
    "bar"
  end
end

f = Foo.new
f.implicit # => "bar"
f.explicit # => 
# ~> -:9:in `explicit': private method `private_method' called for #<Foo:0x007fc019091060> (NoMethodError)
# ~>    from -:25:in `<main>'

如果要调用私有方法,请使用隐式接收器或send.

self.send :private_method

更新

Ruby 元编程书籍的摘录。

私人的真正含义

现在您已经了解了 self,您可以对 Ruby 的 private 关键字重新认识。私有方法受一个简单的规则控制:您不能使用显式接收者调用私有方法。换句话说,每次调用私有方法时,它都必须在隐式接收者——self 上。让我们看一个极端案例:

class C
  def public_method
    self.private_method 
  end
  private
  def private_method; end
end
C.new.public_method

⇒ NoMethodError: private method ‘private_method' called [...]

您可以通过删除 self 关键字使此代码工作。

这个人为的例子表明,私有方法来自两个协同工作的规则:首先,您需要一个显式接收器来调用不是您自己的对象上的方法,其次,私有方法只能通过隐式接收器调用。把这两条规则放在一起,你会发现你只能调用自己的私有方法。你可以称之为“私人规则”。</p>

您可能会发现 Ruby 的私有方法令人困惑——特别是如果您来自 Java 或 C#,其中私有的行为非常不同。当您有疑问时,只需回到私人规则,一切都会变得有意义。如果两个对象共享同一个类,对象 x 可以在对象 y 上调用私有方法吗?答案是否定的,因为无论你属于哪个类,你仍然需要一个显式的接收者来调用另一个对象的方法。您可以调用从超类继承的私有方法吗?答案是肯定的,因为您不需要显式接收器来调用自己的继承方法。

于 2012-07-12T21:57:41.103 回答