Need help building complex form in Rails
I am trying to put together a somewhat complex form in Rails and am hitting a bit of a wall. Here is the general jist of what I am trying to do. I have the following objects:
[source:ruby]
class ExpenseReport
# :project
# :submitted_date
# :comments
has_many :line_items
end
class LineItem
# :amount
belongs_to :expense_report
belongs_to :expense_category
end
class ExpenseCategory
# :category
has_many :line_items
end
[/source]
What I would like to do is to create a form for the ExpenseReport that contains 5 rows of LineItems and each LineItem row would allow the user to select the ExpenseCategory.
This is what I have for the form:
[source:ruby]
< % form_for :report, :url => { :action => ‘create’} do |form| %>
< %= text_field 'report', :project, :size => 40 %>
< %= datetime_select 'report', 'submit_date' %>
Report Line Item
< % fields_for :line_items do |line_item_form| %>
< % @line_items.each_with_index do |line_item, index| %>
< % fields_for :expense_categories do |expense_category| %>
< %= expense_category.collection_select(:id, @expense_category, :id, :name) %>
< % end %>
< %= text_field :line_item_form, :amount, :index => index %>
< % end %>
< % end %>
< %= text_area 'report', 'comments', :rows => 5, :cols => 60 %>
< %= submit_tag "Create" %>
< % end %>
[/source]
I am getting two errors when I do this, first is when I go to retrieve the line_items from the params, I am getting a ‘nil object’ error and second, I only get one instance of the report_category being returned in the params instead of 5 (I create an array of 5 new LineItems for the @line_items collection.
If anyone has any hints, tricks, suggestions, or comments, please leave them in the comments sections or email via my contact page. Unfortunately, the Agile Web Development with Rails books only has simple examples and nothing that uses two or three model objects.
Hey, the quick and easy solution is this:
new.rhtml:
{ :action => 'create'} do |form| %>
Name
40 %>
Report Submitted on
Report Line
Comments
5, :cols => 60 %>
expense_reports_controller.rb:
class ExpenseReportsController "Create", "report"=>{"expense_category_0"=>"1", "submit_date(3i)"=>"9",
# "submit_date(4i)"=>"02", "expense_category_1"=>"1", "submit_date(5i)"=>"41",
# "comments"=>"This was an awesome trip.", "amount_0"=>"100", "expense_category_2"=>"4",
# "project"=>"Everybody party", "amount_1"=>"200", "expense_category_3"=>"2", "amount_2"=>"300",
# "expense_category_4"=>"3", "amount_3"=>"400", "amount_4"=>"500", "submit_date(1i)"=>"2007", "submit_date(2i)"=>"6"}}
#
# nab the params you need based on the index (expense_category_x with amount_x) and save each.
end
end
(pardon the possibly messy comment, I’m a little special when it comes to textile)
In short, I eschewed the collection_select thing in favor of just a plain ol’ select that gets mapped in the controller action. You’re going to need that to be populated with every expense_category you’ve got. I got rid of the fields_for stuff because the expense_categorIES instance variable doesn’t actually hang off of line_item. Although you have to assign a single expense_categorY to the line_item, you’ll just need a select * of the categories table.
As to your two problems stated above, ‘line_items’ won’t actually appear as a key in the params hash; line_item_form will. The best way to inspect your params is to just do
def create
raise params.inspect
end
Or to use the TextMate Footnotes plugin, which displays all your state at the bottom of the page when in dev mode.
As to your second problem, you’re giving the text_field for amount the same name every time (:amount), so the form parameters for the five fields are being overwritten in series, since you’ll only be getting one :amount key in the params. You’ll need to stick that id on the end to get unique names.
If it could be possible, it would be nice to somehow massage fields_for to break your parameters into sub-hashes to keep from having to do as much parsing of params, but I’m not sure how to do it without a little more prodding. Anyway, HTH, fellow TWer.
Clint Bishop
Ok that code totally didn’t post like I thought it would. Hate to say I told you so on the Textile.
The template:
{ :action => 'create'} do |form| %>
Name
40 %>
Report Submitted on
Report Line
Comments
5, :cols => 60 %>
And the controller:
class ExpenseReportsController "Create", "report"=>{"expense_category_0"=>"1", "submit_date(3i)"=>"9",
# "submit_date(4i)"=>"02", "expense_category_1"=>"1", "submit_date(5i)"=>"41",
# "comments"=>"This was an awesome trip.", "amount_0"=>"100", "expense_category_2"=>"4",
# "project"=>"Everybody party", "amount_1"=>"200", "expense_category_3"=>"2", "amount_2"=>"300",
# "expense_category_4"=>"3", "amount_3"=>"400", "amount_4"=>"500", "submit_date(1i)"=>"2007", "submit_date(2i)"=>"6"}}
#
# nab the params you need based on the index (expense_category_x with amount_x) and save each.
end
end
Ok so pre and code tags don’t work at all and this is becoming a fiasco. Suffice it to say that I’m not going to try to copy code again, so for the amount field, I’d have this in ERb tags:
form.text_field :”amount_#{index}”
and for the expense_report field, this:
form.select(”expense_category_#{index}”,@expense_categories)
where @expense_categories is found in the new action with:
@expense_categories = ExpenseCategory.find(:all).map {|cat| [cat.category, cat.id]}
Man, not my night for posting blog comments, apparently.
Thanks for trying. I have been having some weird things happen when people try and post comments with Textile getting in the way as well as the restrictions placed on comments to help battle spam. Maybe it is time I turned off Textile, hmmm?
I will muddle through what you have posted Clint and probably post follow up questions. Thanks for the suggestions.