我有一个模型,假设是Cat
,我想创建一个新模型Owner
。A Cat
has_one :owner
,但是当我创建Cat
模型时,Owner
模型还不存在。
不需要为每个Cat
回填新的Owner
模型,我想有一个简单的方法,所以如果我调用@cat.owner.something
, @cat.owner
还不存在,它将动态调用@cat.create_owner
并返回它。
我已经看到了一些不同的方法,但我想知道解决这个问题的最rails风格的方法是什么,因为我需要经常这样做。
我以前没有见过这样做,但还是决定试一试。
我首先在Cat
模型中将关联方法owner
别名化,以保留原始方法的备份。如果原始方法返回nil,我将覆盖owner
方法以调用build_owner
方法(通过关联返回一个新的Owner
对象)。否则,返回original_owner_method
对象。
class Cat < ActiveRecord::Base
has_one :owner
alias :original_owner_method :owner
def owner
if original_owner_method.nil?
build_owner
else
original_owner_method
end
end
现在如果你调用:cat = Cat.first
假设它没有所有者,当你调用它时,它将建立一个新的owner对象:cat.owner.name
它将返回nil,但仍然在链的cat.owner
部分上构建所有者对象,而不调用method_missing
我将使用回调来创建所有者,而不是在第一次访问它时创建所有者。这确保了所有者永远不会为nil,并且如果回调失败,将自动回滚Cat创建。
class Cat < ActiveRecord::Base
before_create :create_owner
private
def create_owner
return true unless owner.nil?
create_owner(:default => 'stuff')
end
end
更新:虽然我仍然推荐上述方法用于新应用程序,但由于您已经有了现有的记录,您可能希望更像这样:
class Cat < ActiveRecord::Base
def owner
super || create_owner(:default => 'stuff')
end
end
对于这类问题,我认为这是最"rubyish"的
def owner
@owner ||= create_owner
end
在rails中,我这样做的方式是
def owner
@owner ||= Owner.find_or_create(cat: self)
end
但一般来说,我会尝试找出一种方法来使用Cat#create_owner或Owner#create_cat,并尽量避免整个问题。
根据我的经验,重写来自ActiveRecord::Base
的默认属性getter/setter是一种危险的做法-有龙。我将举一个使我出错的例子来解释。
我使用了这个答案中建议的super || create_association
模式。你可能会得到一个像这样棘手的bug:
From: /Users/mec/Sites/zipmark/service/spec/models/vendor_application_spec.rb @ line 39 :
34: subject.read_attribute(:ledger_id).should be_blank
35: end
36:
37: it "lazily creates the association" do
38: subject.ledger
=> 39: binding.pry
40: subject.reload.ledger_id.should be_present
41: end
42: end
43: end
44:
[1] pry(#<RSpec::Core::ExampleGroup>)> subject.ledger
#<Ledger:0x007fc3c30ad398> {
:id => "cf0ac70e-ce23-4648-bf3f-85f56fdb123a",
:created_at => Wed, 30 Sep 2015 17:56:18 UTC +00:00,
:updated_at => Wed, 30 Sep 2015 17:56:18 UTC +00:00,
:description => "Freshbooks Ledger"
}
[2] pry(#<RSpec::Core::ExampleGroup>)> subject.reload.ledger_id
nil
我错误地期望Rails魔术用其新创建的ledger
记录更新手头的记录(self
)。我最终重写了重载的#ledger
方法如下:
def ledger
super || begin
ledger = create_ledger(description: "#{name} Ledger")
update_column(:ledger_id, ledger.id)
ledger
end
end