Undistraction
Undistraction

Reputation: 43351

Establishing A Has One Through vs Has Many Through With Unsaved Models

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

Answers (3)

vld
vld

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

Anurag Abbott
Anurag Abbott

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

Kaleidoscope
Kaleidoscope

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

Related Questions