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.