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

时间:2010-09-28 00:07:51

标签: ruby-on-rails ruby activerecord ruby-on-rails-3 associations

你能告诉我创建has_one关系的最佳做法吗?

f.e。如果我有一个用户模型,它必须有个人资料......

我怎么能做到这一点?

一种解决方案是:

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

  def set_default_association
    self.create_profile
  end
end

但这看起来不是很干净......有什么建议吗?

7 个答案:

答案 0 :(得分:118)

创建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

答案 1 :(得分:27)

你的解决方案绝对是一种不错的方式(至少在你不能实现之前),但你可以简化它:

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

答案 2 :(得分:22)

如果这是现有大型数据库中的新关联,我将按照以下方式管理转换:

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一起正常工作。

答案 3 :(得分:7)

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

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

答案 4 :(得分:4)

我是这样做的。不确定这是多么标准,但它工作得很好而且它很懒,因为它不会产生额外的开销,除非有必要建立新的关联(我很乐意对此进行纠正):

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,但这似乎增加了相当多的开销,因为每次初始化对象时它都会运行,并且有时你可能不关心访问关联。检查它的存在似乎是一种浪费。

答案 5 :(得分:1)

有一个宝石:

https://github.com/jqr/has_one_autocreate

现在看起来有点老了。 (不适用于rails3)

答案 6 :(得分: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