2011-07-19 38 views
4

我有一个模型,比如Cat,我想创建一个新模型,Owner。 A Cathas_one :owner,但是当我创建Cat模型时,Owner模型尚不存在。懒惰创建Rails关联的最佳模式是什么?

没有求助于回填新Owner模型为每个Cat,我想有一个简单的方法,所以如果我打电话@cat.owner.something,并且@cat.owner还不存在,它会调用在飞行@cat.create_owner并将其返回。

我已经看到这样做有几种不同的方式,但我想知道什么是最棘手的方式来解决这个问题,因为我需要经常这样做。

回答

3

我以前没有见过这件事,但决定无论如何给它一个镜头。

我首先在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

假设它不具有所有者,它会建立,当你调用一个新的所有者对象: cat.owner.name

它将返回零,但仍然在链中的cat.owner部分上构建所有者对象,而不调用method_missing。

1

与第一次访问所有者时创建的所有者不同,我将使用回调来创建所有者。这确保了所有者永远不会为零,并且如果回调失败将自动回滚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 
+0

这是行不通的,因为我在那里有很多猫的对象已经创建了一个情况是,我不希望有每当我引用它时,都要检查是否存在所有者关联。 –

+0

明白了。添加了另一个选项,并对原始解决方案做了一个小改动 –

+0

试过了你的第二个解决方案,这似乎也没有工作。没有超类方法'拥有者'为#' –

1

在一般的方式对这种的问题,我觉得这是最“rubyish”

def owner 
    @owner ||= create_owner 
end 

在轨,我会做这样的事情

的方式
def owner 
    @owner ||= Owner.find_or_create(cat: self) 
end 

但通过一般方法,我会尝试找出一种方法来使用Cat#create_owner或Owner#create_cat,并尝试在第一个位置避免整个问题。

0

根据我的经验,覆盖来自ActiveRecord::Base的默认属性获取者/设置者是一种危险的做法 - 有龙。我会用例子来解释哪一个让我失望。

我使用this answer中建议的super || create_association模式。你可以用这样一个棘手的错误结束:

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的魔法更新在手边(self)记录,其新建的ledger纪录。最后我重新写我的重载#ledger方法如下:

def ledger 
    super || begin 
    ledger = create_ledger(description: "#{name} Ledger") 
    update_column(:ledger_id, ledger.id) 
    ledger 
    end 
end 
相关问题