What is the best template for lazy creating a Rails association?

I have a model, say Cat , and I want to create a new Owner model. A Cat has_one :owner , but when I created the Cat model, the Owner model did not exist yet.

Without having to reload the new Owner model for each Cat , I want to have an easy way, so if I call @cat.owner.something and @cat.owner does not exist yet, it will call @cat.create_owner on the fly and return it .

I saw how this was done in several different ways, but I wonder what the most rail way to solve this problem, since I need to do this quite often.

+4
source share
4 answers

I have not seen this before, but decided to give him a chance.

First, I used the owner association method in the Cat model to keep a backup of the original method. I tried the owner method to call the build_owner method (returns a new owner object through an association) if the original method returns nil. Otherwise, return the original_owner_method object.

 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 

So if you call: cat = Cat.first

Assuming it has no owner, it will create a new Owner object when called: cat.owner.name

It will return zero, but it will still build the owner object in the cat.owner part of the chain without calling the missing method.

+3
source

Instead of having an owner created on first access, I would use a callback to create an owner. This ensures that the owner will never be zero, and will automatically roll back from creating Cat if the callback fails.

 class Cat < ActiveRecord::Base before_create :create_owner private def create_owner return true unless owner.nil? create_owner(:default => 'stuff') end end 

Update. Although I would still recommend the above approach for new applications, since you already have existing entries, you might need something more:

 class Cat < ActiveRecord::Base def owner super || create_owner(:default => 'stuff') end end 
+1
source

In general, for this kind of problem, I think this is the most “ruby”

 def owner @owner ||= create_owner end 

On the rails, how would I do something like this

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

But in general, I would try to figure out a way to use Cat # create_owner or the owner # create_cat and try to avoid the whole problem in the first place, if I could.

+1
source

Overriding these default getter / seters attributes that come from ActiveRecord::Base , in my experience, is a dangerous practice - there will be dragons. I will explain with an example that pushed me.

I used the super || create_association pattern super || create_association suggested in this answer . You may get a complex error like this:

 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 

I mistakenly expected Rails magic to update the record at hand ( self ) with its newly created ledger record. I ended up rewriting the overloaded #ledger method as follows:

 def ledger super || begin ledger = create_ledger(description: "#{name} Ledger") update_column(:ledger_id, ledger.id) ledger end end 
0
source

All Articles