在Ruby on Rails中,重写setter方法的正确方式是什么?

203

我正在使用 Ruby on Rails 3.2.2,想知道下面的代码是否是覆盖类属性 setter 方法的 "proper" / "correct" / "sure" 方式。

attr_accessible :attribute_name

def attribute_name=(value)
  ... # Some custom operation.

  self[:attribute_name] = value
end
上面的代码似乎按预期工作。然而,我想知道,通过使用上述代码,将来我是否会遇到问题,或者至少"应该期望"/"可能会发生"哪些问题在Ruby on Rails方面。如果这不是覆盖setter方法的正确方法,那么正确的方法是什么?
attr_accessible :attribute_name

def attribute_name=(value)
  ... # Some custom operation.

  self.attribute_name = value
end

我遇到了以下错误:

SystemStackError (stack level too deep):
  actionpack (3.2.2) lib/action_dispatch/middleware/reloader.rb:70

6
我喜欢使用“正确的”、“准确的”和“确定的”这些术语,当你用这三种方式表达时,确保没有误解。干得好! - Jay
5
@Jay - “Fineness Italianisms” ; - ) 的意思是“精致的意大利语言习惯用语”;-) - Backo
3
只是为了明确一点,“堆栈层数过深”是指递归调用——它在调用自身。 - Nippysaurus
5个回答

325

=========================================================================== 更新:2017年7月19日

现在Rails文档也建议使用以下方式的super

class Model < ActiveRecord::Base

  def attribute_name=(value)
    # custom actions
    ###
    super(value)
  end

end

===========================================================================

原始回答

如果您想在通过模型访问表的列时覆盖setter方法,这就是实现方式。

class Model < ActiveRecord::Base
  attr_accessible :attribute_name

  def attribute_name=(value)
    # custom actions
    ###
    write_attribute(:attribute_name, value)
    # this is same as self[:attribute_name] = value
  end

end

请参阅 Rails 文档中的 Overriding default accessors。这是关于编程的内容,讲述了如何更改 Ruby on Rails 模型中的列设置器。Rails 已经提供了这些访问器,用于将表列作为模型属性进行访问,这就是 ActiveRecord ORM 映射。请注意,模型顶部的 attr_accessible 与访问器无关,它具有完全不同的功能(请参见 this question)。但是,在纯 Ruby 中,如果您已经为类定义了访问器并且想要覆盖设置器,则必须像这样使用实例变量:
class Person
  attr_accessor :name
end

class NewPerson < Person
  def name=(value)
    # do something
    @name = value
  end
end

这段内容需要了解 attr_accessor 的作用才能更容易理解。代码 attr_accessor :name 相当于以下两个方法(getter 和 setter)。
def name # getter
  @name
end

def name=(value) #  setter
  @name = value
end

您的第二种方法也失败了,因为它会导致无限循环,因为您在该方法内调用了相同的方法attribute_name=

9
对于Rails 4,只需跳过attr_accessible,因为它不再存在,这样应该就可以正常工作了。 - zigomir
11
为什么不调用 super - Nathan Lilienthal
1
我曾经认为由于访问器和写入器是动态创建的,所以 super 可能不起作用。但是,事实并非如此。我刚刚检查了一下,它对我有效。此外,这个问题也提到了同样的内容。 - rubyprince
4
write_attribute 存在一个很大的问题。它会跳过转换。请注意,write_attribute 会跳过日期的时区转换,这几乎总是不期望的。 - Tim Scott
2
super也可以工作,但是有一些原因你可能不想使用它。例如,在mongoid gem中,如果你使用super getter方法,就无法将其推送到数组中。这是一个bug,因为它们在内存中管理数组的方式。此外,@name也会返回设置的值,而不是调用你重写的方法。然而,在上面的解决方案中,两者都可以正常工作。 - User128848244
显示剩余2条评论

50

使用 super 关键字:

def attribute_name=(value)
  super(value.some_custom_encode)
end

相反,要覆盖读者:

def attribute_name
  super.some_custom_decode
end

1
在我看来,这个答案比被接受的答案更好,因为它将方法调用限制在相同的名称上。这可以保留继承的重写行为,以attribute_name=为例。 - Andrew Schwartz
由于这个变化:https://github.com/rails/rails/commit/787e22bb491bd8c36db1e9734261c4ce02c5c5fd,在Rails 4.2中覆盖getter方法已经变得危险。以前的表单助手会调用字段的未类型转换值而不是调用您的自定义getter。现在它们调用您的方法,因此根据您如何覆盖该值,将在表单中产生令人困惑的结果。 - Brendon Muir

16

在Rails 4中:

假设你的表中有一个age属性。

def age=(dob)   
    now = Time.now.utc.to_date
    age = now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
    super(age) #must add this otherwise you need to add this thing and place the value which you want to save. 
  end

注意: 对于Rails 4中的新手,您不需要在模型中指定attr_accessible。相反,您需要在控制器级别使用permit方法来白名单您的属性。


3

我发现(至少对于ActiveRecord关系集合),以下模式是有效的:

has_many :specialties

def specialty_ids=(values)
  super values.uniq.first(3)
end

(这会获取传递数组中的前3个不重复条目。)


0

使用attr_writer重写setter attr_writer :attribute_name

  def attribute_name=(value)
    # manipulate value
    # then send result to the default setter
    super(result)
  end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接