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


184

我正在使用Ruby on Rails 3.2.2,并且我想知道以下内容是否是“正确” /“正确” /“确定”方式来覆盖我的class属性的setter方法。

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

4
我喜欢用术语“正确” /“正确” /“确定”'。当您提供3种方式时,它确实可以确保没有误解。做得好!
周杰伦

5
@杰伊-“中国人的意大利主义”;-)
Backo 2012年

2
需要明确的是,“堆栈级别太深”是指以下事实:它是递归调用,它本身就是调用。
Nippysaurus,2014年

Answers:


294

=================================================== ========================= 更新: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文档中的覆盖默认访问器

因此,第一种方法是重写Ruby on Rails模型中列设置器的正确方法。Rails已经提供了这些访问器来访问表的列作为模型的属性。这就是我们所谓的ActiveRecord ORM映射。

还请记住,attr_accessible模型顶部的与访问器无关。它具有完全不同的功能(请参阅此问题

但是在纯Ruby中,如果您为类定义了访问器,并且想覆盖setter,则必须使用实例变量,如下所示:

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 2014年

11
为什么不打电话super
内森·利莲塔尔

1
我的印象是,由于访问器和编写器是动态创建的,因此super可能无法正常工作。但是,似乎并非如此。我刚刚检查了一下,它对我有用。另外,这个问题也要问同样的问题
rubyprince 2014年

4
有一个巨大的陷阱write_attribute。转换将被跳过。请注意,这write_attribute将跳过带日期的时区转换,这几乎总是不希望的。
蒂姆·斯科特

2
super也可以工作,但是由于某些原因您可能不希望我们这样做。例如在mongoid gem中,有一个bug,如果您使用getter方法,则无法将其压入数组。因为存在这种方式来管理内存中的阵列,所以它是错误的。@name还将返回设置的值,而不是调用您覆盖的方法。但是,在上述解决方案中,两者都可以正常工作。
newdark-it

44

使用super关键字:

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

相反,要覆盖读者,请执行以下操作:

def attribute_name
  super.some_custom_decode
end

1
比接受的IMO更好的答案,因为它将方法调用限制为相同的名称。这会将继承的重写行为保留为attribute_name =
Andrew Schwartz

由于以下更改,覆盖getter方法在Rails 4.2中变得很危险:github.com/rails/rails/commit/…以前,表单助手将调用该字段的untypecast值,而不调用您的自定义getter。现在他们调用了您的方法,因此将根据您覆盖值的方式在表单中产生混乱的结果。
布伦登·缪尔

16

在轨道4

假设您的表格中有年龄属性

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。相反,您必须使用允许方法在控制器级别将您的属性列入白名单。


3

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

has_many :specialties

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

(这将获取所传递数组中的前3个非重复条目。)


0

利用attr_writer覆盖二传手attr_writer:ATTRIBUTE_NAME

  def attribute_name=(value)
    # manipulate value
    # then send result to the default setter
    super(result)
  end
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.