Rails-最佳实践:如何创建依赖的has_one关系


75

您能告诉我创建has_one关系的最佳实践是什么吗?

fe,如果我有一个用户模型,并且必须有一个配置文件...

我该怎么办呢?

一种解决方案是:

# user.rb
class User << ActiveRecord::Base
  after_create :set_default_association

  def set_default_association
    self.create_profile
  end
end

但这似乎并不干净……有什么建议吗?

Answers:


125

创建has_one关系的最佳实践是使用ActiveRecord回调before_create而不是after_create。或者使用更早的回调并处理子代未通过其自己的验证步骤的问题(如果有)。

因为:

  • 通过良好的编码,如果验证失败,则您有机会将子记录的验证显示给用户
  • 它更干净,并由ActiveRecord明确支持-AR在保存父记录(创建时)后自动在子记录中填充外键。然后,AR将子记录保存为创建父记录的一部分。

怎么做:

# in your User model...
has_one :profile
before_create :build_default_profile

private
def build_default_profile
  # build default profile instance. Will use default params.
  # The foreign key to the owning User model is set automatically
  build_profile
  true # Always return true in callbacks as the normal 'continue' state
       # Assumes that the default_profile can **always** be created.
       # or
       # Check the validation of the profile. If it is not valid, then
       # return false from the callback. Best to use a before_validation 
       # if doing this. View code should check the errors of the child.
       # Or add the child's errors to the User model's error array of the :base
       # error item
end

可以单行管理吗?-> before_filter:build_profile?
BvuRVKyUVlViVIc7

2
@Lichtamberg:是的,但是我要添加一条评论:“构建默认配置文件。必须始终进行验证。” 注意:它将是“ before_create:build_profile”而不是“ before_filter”。如果未通过验证,则用户会收到一个非常混乱的错误消息。否则,实际上不会创建它,这意味着您最终将获得一个没有个人资料的用户。您还应该在测试中测试极端情况。
拉里K

我尝试了这段代码,但失败了,我不得不执行profile = build_profile
jakeonrails

1
那么,如何将这个对象添加到新对象userbefore_create呢?
Meekohi,2016年

9
请注意,使用Rails 5使用before_create来创建从属记录时,如果不覆盖belongs_to记录的默认值,则是不可能的。现在,默认值期望belongs_to记录存在,否则引发错误。
越南

28

您的解决方案绝对是一种不错的解决方案(至少在您不再使用之前),但是您可以简化它:

# user.rb
class User < ActiveRecord::Base
  has_one      :profile
  after_create :create_profile
end

24

如果这是现有大型数据库中的新关联,则将按以下方式管理过渡:

class User < ActiveRecord::Base
  has_one :profile
  before_create :build_associations

  def profile
    super || build_profile(avatar: "anon.jpg")
  end

private
  def build_associations
    profile || true
  end
end

以便现有用户记录在需要时获得一个配置文件,并使用它创建新的配置文件。这还将默认属性放在一个位置,并且可以在Rails 4及更高版本中与accepts_nested_attributes_for一起正常使用。


这是一个很棒的解决方案,但我们使用的是超级||。create_profile(avatar:“ anon.jpg”)以确保新配置文件立即保留。
sandre89 '18

1
@ sandre89冒着回调噩梦,验证异常和错误保存的记录的风险,此外,在before_create回调中使用持久性方法还会带来更多风险。Avdi Grimm在RubyTapas上写了一个由四部分组成的系列,涵盖了许多灾难,这些灾难等待您让模型层进行任何形式的自我保存。祝您一切顺利,但我担心最坏的情况。
inopinatus

9

可能不是最干净的解决方案,但我们已经有一个拥有50万条记录的数据库,其中一些已经创建了“个人档案”模型,而有些则没有。我们采用了这种方法,该方法可确保在任何时候都存在Profile模型,而无需经历并追溯生成所有Profile模型。

alias_method :db_profile, :profile
def profile
  self.profile = Profile.create(:user => self) if self.db_profile.nil?
  self.db_profile
end

5

这是我的方法。不确定这是什么标准,但是它工作得很好,并且很懒惰,除非有必要建立新的关联,否则它不会产生额外的开销(我很乐意对此进行纠正):

def profile_with_auto_build
  build_profile unless profile_without_auto_build
  profile_without_auto_build
end

alias_method_chain :profile, :auto_build

这也意味着该关联会在您需要时立即存在。我猜是替代方法是挂在after_initialize上,但这似乎增加了相当大的开销,因为每次初始化对象时都会运行它,并且有时您不需要访问该关联。检查它的存在似乎是浪费。


我认为这个答案是比其他答案更好的解决方案,因为避免了Profile模型中的验证问题。谢谢
2013年

您也可以自动保存:has_one :profile, :autosave => true
montrealmike 2014年

@montrealmike,这样是否可以处理丢失的个人资料?也就是说,如果尚未执行build_profile,是否可以在保存时创建一个?我也遇到过这个问题:github.com/phildionne/associates可能提供另一种解决多模型形式的方法。
布伦登·缪尔

@BrendonMuir不,它不会建立缺少的个人资料。您可以在上面的解决方案中添加这些内容,以便在保存用户时自动保存配置文件(并验证配置文件)
montrealmike 2014年


0

我对此有一个问题,并且accepts_nested_attributes_for存在问题,因为如果传递了嵌套属性,则会在此处创建关联的模型。我最终做了

after_create :ensure_profile_exists
has_one :profile
accepts_nested_attributes_for :profile


def ensure_profile_exists
  profile || create_profile
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.