Creating Multiple Objects (of different Models) in one form.

Yikes! Did I ever dig hole for myself, so let me tell you how I got out of it.

Long story short: I have a model architecture like this:

document
|
--> document_content_groups[]
|
-->document_contents

In other words, document_contents are name value pairs...think of them as questions (name) and answers (value). one or more document_contents are grouped into a document_content_group...think of that as a chunk of questions. in other words, a document_content_group has a collection of document_contents. then, a document has a collection of document_content_groups.

So, on a form where a user creates a new document really we're creating many things:

1 new document object
1 or more document_content_group objects
1 or more document_content objects

and, all of these must be associated with their parent properly.

On p. 499 the Rails book (Agile Web Development with Rails) it is suggested that something like this be done:

<% for_tag do %>
<% for @product in @products %>
<%= text_field("product[]", 'image_url') %><br />
<% end %>
<% end %>


and what this syntax is supposed to do is make the ID of the object be inserted into the form "name" field, e.g. name="product_ID[image_url]". But (sharing this is the purpose of this post), this syntax does not work with a newly created object, it only works with editing existing objects. I solved the problem like this:


@dcg_index = 0
@dc_index = 0
for document_content_group in @document_content_groups %>
<p><%= document_content_group.name %><br />
<%= text_field("document_content_group", 'name', :index => @dcg_index) %></p>
<%
for document_content in document_content_group.document_contents %>
<p><%= document_content.name %><br />
<%= hidden_field ("document_content", "document_content_group", :value => @dcg_index.to_s, :index => @dc_index) %>
<%= hidden_field ("document_content", "name", :value => document_content.name, :index => @dc_index) %>
<%= text_field("document_content", "value", :index => @dc_index) %></p>
<% @dc_index += 1%>
<% end %>
<% @dcg_index += 1%>
<% end %>


Now, the result is that when the form is posted I get two hashes in the params: document_content_groups and document_contents each of which contains hashes representing the objects I want to create.

I can easily create the document_content_groups by passing those hashes in to create, but I have to do some munging on the document_contents to prepare them:

@dcg_index = 0 # this must be the same initial index from the "new" form
for document_content_group_hash in params[:document_content_group].values do
document_content_group = @document.document_content_groups.create(document_content_group_hash)
for document_content_hash in params[:document_content].values do
if (document_content_hash["document_content_group"].to_i == @dcg_index)
# this is removed from the hash because it's not really the document_content_group_id...
# it's just a temporary ID that was used to group the items together from the _new form
document_content_hash.delete("document_content_group")
document_content_group.document_contents.create(document_content_hash)
end
end
@dcg_index += 1
end

Anyhow, not particularly pretty, but it gets the job done for now. Suggestions on how to make it more graceful are welcome.

1 comment:

Anonymous said...

I have a nearly identical document structure and I solved the problem in a very similar way, there just isn't a good was in rails yet to handle these complicated model structures when it's a new object or set of objects.