Reputation: 43351
I have written a gem that allows Google Spreadsheets to be transformed into Rails models. The sequence of this process involves creating all the models, then hooking up their associations, then saving all the models. It supports all the types of association available, and in every case bar one, creating the models, establishing associations, then saving the models works correctly. The exception is as follows:
I have a simple has_one, through
association (attribute access omitted for brevity) :
class Left < ActiveRecord::Base
has_many :middles, dependent: :destroy
has_many :rights, through: :middles
end
class Right < ActiveRecord::Base
has_one :middle, dependent: :destroy
has_one :left, through: :middle
end
class Middle < ActiveRecord::Base
belongs_to :left
belongs_to :right
end
I'm finding some inconsistent behaviour depending on which side the assignment of the association is made:
Assigning right to left:
left = Left.new
right = Right.new
left.rights << right
left.middles #[]
right.middle #nil
left.save!
left.middles # <Middle theme_id: 1, archive_resource_id: 1 >
Assigning right to left:
left = Left.new
right = Right.new
right.left = left
left.middles #[]
right.middle <Middle theme_id: nil, archive_resource_id: nil >
right.save!
right.middle # <Middle theme_id: nil, archive_resource_id: 1 >
This behaviour seems very inconsistent. Why is this? Why does this work one way and not the other? Is there any way to establish this relationship with both records unsaved?
I appreciate that the obvious solution is to save everything before setting up the relationships, but as explained above, I need the models to be unsaved when the associations are established, and in every other type of association, there is no problem in this regard.
Upvotes: 5
Views: 978
Reputation: 144
I think it's didn't work because you have
has_many - has_one
relation between Left and Right instead of
has_many - belongs_to
On the other hand
belongs_to :left, through: :middle
isn't possible. So you should use delegate for getter and write setter method by youself, something like this
class Right < ActiveRecord::Base
has_one :middle, dependent: :destroy
delegate :left, to: :middle
def left=(left)
#logic
end
end
Upvotes: 0
Reputation: 364
As per the Rails API docs(link)-
If you are using a belongs_to on the join model, it is a good idea to set the :inverse_of option on the belongs_to, which will mean that the following example works correctly (where tags is a has_many :through association):
So the association set up by @Kaleidoscope is infact correct.
Upvotes: 0
Reputation: 3627
I think it's probably working, but not updating immediately. Try passing true
when you call your association methods. Ex. right.middle(true)
. This will force Rails to go back to the database to get the record instead of checking the cache, which is stale. Don't do this on new records though, as you'll blow away the associated object in memory.
To avoid having to force a reload, set inverse_of
on your associations:
class Left < ActiveRecord::Base
has_many :middles, dependent: :destroy, inverse_of: :left
has_many :rights, through: :middles
end
class Right < ActiveRecord::Base
has_one :middle, dependent: :destroy, inverse_of: :right
has_one :left, through: :middle
end
class Middle < ActiveRecord::Base
belongs_to :left, inverse_of: :middles
belongs_to :right, inverse_of: :middle
end
inverse_of
makes a model
aware of an associated model's relationship to itself, which allows it to be "smart" about persisting and loading associated objects.
Upvotes: -1