Catch-22 in association through join model

I have a many-to-many relationship defined by a join model:
class Product < ActiveRecord::Base
has_many :bundlings
has_many :bundles, :through => :bundlings

end

class Bundling < ActiveRecord::Base
belongs_to :product
belongs_to :bundle

end

class Bundle < ActiveRecord::Base
has_many :bundlings
has_many :products, :through => :bundlings

end

Additionally, Bundle validates that it contains at least two products.

Then I tried to create a new Bundle like this:

fixtures :products

bundle = Bundle.new(…) # can’t save bundle yet since
# I haven’t yet added two products
bundle.products << products(:product_one)
bundle.products << products(:product_two)
bundle.save!

The “<<” operation raises an*
*ActiveRecord::HasManyThroughCantAssociateNewRecords
exception, saying that both bundle and the product need to have an ID to
use
“<<” on a through association. However, in order to obtain an ID, I
need to
save bundle
, but bundle cannot be saved (i.e. pass validation) unless
I
have added two products to it through “<<” – Catch 22.

Any thoughts?

Thanks,
Eric

Eric L. wrote:

I have a many-to-many relationship defined by a join model:

Additionally, Bundle validates that it contains at least two products.

Then I tried to create a new Bundle like this:

fixtures :products

bundle = Bundle.new(…) # can’t save bundle yet since
# I haven’t yet added two products
bundle.products << products(:product_one)
bundle.products << products(:product_two)
bundle.save!

The “<<” operation raises an*
*ActiveRecord::HasManyThroughCantAssociateNewRecords
exception, saying that both bundle and the product need to have an ID to
use
“<<” on a through association. However, in order to obtain an ID, I
need to
save bundle
, but bundle cannot be saved (i.e. pass validation) unless
I
have added two products to it through “<<” – Catch 22.

You can use a very simple state machine to control that the validation
only runs when it’s ready. You’d create a bundle as unprepared, add the
products, then save it as prepared.


Josh S.
http://blog.hasmanythrough.com

Do you mean:

  1. Create a unprepared bundled. Mark the unprepared status in one of
    its DB
    columns.
  2. Save the unprepared bundle to DB to generate an ID for it
  3. Add the products to the saved bundle.
  4. When the second product is added, unmark the unprepared status in the
    DB
    column.

This seems like a good solution, even though it would bypass the
validation
mechanism of ActiveRecord, and has a little bit more maintenance of
states…

Eric

Eric L. wrote:

Do you mean:

  1. Create a unprepared bundled. Mark the unprepared status in one of
    its DB
    columns.
  2. Save the unprepared bundle to DB to generate an ID for it
  3. Add the products to the saved bundle.
  4. When the second product is added, unmark the unprepared status in the
    DB
    column.

This seems like a good solution, even though it would bypass the
validation
mechanism of ActiveRecord, and has a little bit more maintenance of
states…

Yes, that’s what I mean. You don’t have to bypass validation though.
You can include the condition of being prepared in the validation.
Something like this:

def validate
!prepared || products.size >= 2
end


Josh S.
http://blog.hasmanythrough.com