Tertiary has_many association forms, difficult! (SOLUTION)

I've got three relationships:

Package, PackageItemGroup, and PackageItem

[Package] has_many :package_item_groups, has_many :package_items, :through => :package_item_groups [PackageItemGroup] has_many :package_items, belongs_to :package

Ok, I need a form that will build params like this upon submit

:package => {   :name => "Package Name",   :package_item_groups => [     {:package_items => [{:item_id => "2"}, {:item_id => "3"}]},     {:package_items => [{:item_id => "4"}, {:item_id => "5"}]}   ] }

Using fields for on package_item_groups with a form name like "package[package_item_groups]" works fine. The params returned from that look ok. It's when you try and add the third level of association with package_items fields_for, becuase "package[package_item_groups][package_items]" does not work, as it is a completely invalid array.

Here is what I did to get params that I could use, I created a counter, that would serve as an index for the package_item_groups array. This counter would create key values that didn't mean anything, aside from the form associating the correct package_items and package_item_groups together.

This is an example:   <% fields_for "package[package_item_groups]", PackageItemGroup.new, :index => Counter.index %>

Counter.index being a integer value that I can store in session, keep track of, and increment / decrement when needed.

This solution WORKS, but it SUCKS. It's very ugly, and essentially a hack. Also, the above solution is only for the new form, the edit form has to be different, which makes it a pain in the neck to maintain.

Has anyone else tried this before? Any luck with a more elegant solution that mine? I've googled, but had a very difficult time finding people in the same boat, perhaps this is just too much to expect from a simple web application, I dunno. Some comments would be very welcome and helpful.

Matt Swasey wrote:

I've got three relationships:

Package, PackageItemGroup, and PackageItem

[Package] has_many :package_item_groups, has_many :package_items, :through => :package_item_groups [PackageItemGroup] has_many :package_items, belongs_to :package

Ok, I need a form that will build params like this upon submit

:package => {   :name => "Package Name",   :package_item_groups => [     {:package_items => [{:item_id => "2"}, {:item_id => "3"}]},     {:package_items => [{:item_id => "4"}, {:item_id => "5"}]}   ] }

Using fields for on package_item_groups with a form name like "package[package_item_groups]" works fine. The params returned from that look ok. It's when you try and add the third level of association with package_items fields_for, becuase "package[package_item_groups][package_items]" does not work, as it is a completely invalid array.

Here is what I did to get params that I could use, I created a counter, that would serve as an index for the package_item_groups array. This counter would create key values that didn't mean anything, aside from the form associating the correct package_items and package_item_groups together.

This is an example:   <% fields_for "package[package_item_groups]", PackageItemGroup.new, :index => Counter.index %>

Counter.index being a integer value that I can store in session, keep track of, and increment / decrement when needed.

This solution WORKS, but it SUCKS. It's very ugly, and essentially a hack. Also, the above solution is only for the new form, the edit form has to be different, which makes it a pain in the neck to maintain.

Has anyone else tried this before? Any luck with a more elegant solution that mine? I've googled, but had a very difficult time finding people in the same boat, perhaps this is just too much to expect from a simple web application, I dunno. Some comments would be very welcome and helpful.

In my experience, the way you've done it is essentially the best way.

But it can be made to work for both new and old records simultaneously. You use record ids as the index, and set the ids of new records to a negative value sequence in your controller or model. You can then identify which params correspond to new records.

That sounds like a better solution than using a cached counter index. Would you mind explaining the "negative value sequence" a little more?

I'm guessing that you are talking about overwriting the id methods so that if the object is new_record? true, then it would product a number in it's place.

Also, right now I am messing around with having some params have a new_ or existing_ prefix, but that is turning out to be nightmarish. It would be much simpler if they didn't have a prefix, and my custom setter methods (ie. package_item_groups=() ) could figure out if the incoming was to be created or updated.

Matt Swasey wrote:

That sounds like a better solution than using a cached counter index. Would you mind explaining the "negative value sequence" a little more?

I'm guessing that you are talking about overwriting the id methods so that if the object is new_record? true, then it would product a number in it's place.

You just set the ids of any new records you create during a page render to -1, -2, -3, ... These then go out to the form helpers and automatically receive these parameter indices, just as existing records receive positive indices when the auto-index "" notation is used.

Also, right now I am messing around with having some params have a new_ or existing_ prefix, but that is turning out to be nightmarish. It would be much simpler if they didn't have a prefix, and my custom setter methods (ie. package_item_groups=() ) could figure out if the incoming was to be created or updated.

Negative vs. positive does the job.