validates_presence_of导致使用奇怪的自我调用after_initialize

时间:2009-11-22 10:32:10

标签: ruby-on-rails ruby

我有这个模型工作正常:

class Weight < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :weight, :measured_on
  attr_accessible :weight, :measured_on

  def after_initialize
    self.measured_on ||= Date.today
  end
end

我在这行添加了

validates_uniqueness_of :measured_on, :scope => :user_id

它开始在验证时抛出错误。不是验证错误,而是Ruby错误:

>> w.valid?
ActiveRecord::MissingAttributeError: missing attribute: measured_on
    from /Users/pupeno/Projects/sano/app/models/weight.rb:8:in `after_initialize'

我在after_initialize中放了一个调试器语句,我注意到了一些意想不到的事情。当我创建一个新的权重时,它按预期工作,after_initialize上的self对象是预期的权重:

>> w = Weight.new
/Users/pupeno/Projects/sano/app/models/weight.rb:9
self.measured_on ||= Date.today
(rdb:1) p self
#<Weight id: nil, user_id: nil, weight: nil, measured_on: nil, created_at: nil, updated_at: nil>
(rdb:1) c
=> #<Weight id: nil, user_id: nil, weight: nil, measured_on: "2009-11-22", created_at: nil, updated_at: nil>

当我跑w.valid时?它变得奇怪。再次调用after_initialize,我不知道为什么,自我对象不是我所期待的:

>> w.valid?
/Users/pupeno/Projects/sano/app/models/weight.rb:9
self.measured_on ||= Date.today
(rdb:1) p self
#<Weight id: 1>
(rdb:1) p self.inspect
"#<Weight id: 1>"
(rdb:1) p self.class
Weight(id: integer, user_id: integer, weight: float, measured_on: date, created_at: datetime, updated_at: datetime)
(rdb:1) p self.measured_on
ActiveRecord::MissingAttributeError Exception: missing attribute: measured_on
(rdb:1)

似乎另一个Weight对象创建时没有任何属性,但id设置。有什么想法吗?这是一个错误还是预期的行为?我是通过在after_initialize上设置measured_on来做错事吗?

我目前的解决方法是,如果有人遇到同样的问题,

class Weight < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :weight, :measured_on
  validates_uniqueness_of :measured_on, :scope => :user_id
  attr_accessible :weight, :measured_on

  def after_initialize
    if self.has_attribute? :measured_on
      self.measured_on ||= Date.today
    end
  end
end

但我希望有一个合适的解决方案。

3 个答案:

答案 0 :(得分:6)

我认为你正在遇到我最近与之斗争的铁轨问题。请参阅This blog entry链接到相关的灯塔错误。

我的理解是,正在发生的事情是,某些先前的rails代码执行“select table from tablename”以查看条目是否存在或匹配。然后,该对象缓存表中唯一存在的字段是“id”。然后运行您的代码,然后“属性”值不正确,仅反映id字段。

从我能找到的情况来看,只有当这个特定的代码路径被击中时才会发生这种情况,并且除了进行验证外,通常不会让事情变得烦恼。

我做的是将after_initialise代码包装在begin / rescue ActiveRecord :: MissingAttributeError块中。然后我在应用程序中写了一个大笔记,并在每个项目上面指出了何时发布了新版本的rails,我们可以删除它。

是的,我确信有更优雅的解决方案。

def after_initialize
  begin
    # ... updates here
    # self.unique_reference = UUIDTools::UUID.random_create.to_s
  rescue ActiveRecord::MissingAttributeError
  end
end

或者您也可以这样做:

def after_initialize
  if self.has_attribute? :measured_on
    self.measured_on ||= Date.today
  end
end

答案 1 :(得分:2)

此问题与rails票证#3165有关。如果您对如何以及为何出现感兴趣,请在那里阅读

在我终于找到那张票之前,我只花了半天时间。虽然我很伤心,自报道以来已经差不多整整一年了,而且还没有修复,这是我的简单解决方案:

validates_uniqueness_of :email, :scope => :library_id

def after_initialize
  self.status ||= "Invited"
end

如果validates_uniqueness_of查询返回了记录,则会引发'MissingAttributeError'。我的简单解决方案就是:

def after_initialize
  self.status ||= "Invited" if new_record?
end

虽然其他人遇到了更复杂的问题,但这应该解决一个简单的问题,直到实际的解决方案被提交到rails。

答案 2 :(得分:0)

validates_uniqueness_of需要扫描数据库以查找条目。 ActiveRecord正在将所有其他条目作为模型的实例加载。但是为了减少处理器/内存的使用,它不会为属性添加方法,因为它不需要它们来进行快速存在检查。但是它仍然将所有现有记录实例化为模型的实例,因此调用after_initialize。

解决方法是直接修改@attributes哈希,而不是依赖于访问者:

class Weight < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :weight, :measured_on
  validates_uniqueness_of :measured_on, :scope => :user_id
  attr_accessible :weight, :measured_on

  def after_initialize
    @attributes["measured_on"] ||= Date.today
  end
end