This isn't going to be an exhaustive tutorial on multi-model forms, but more of some observations from my learning to work with them over the past week. For a great introduction to multi-model forms see Railscasts. The major issue I've found if that the error messages when things go wrong, particularly at the view layer are almost meaningless or at worse utterly and totally misleading.
I have been working on a multi-model form in my application today and ran into the following error:
`@...' is not allowed as an instance variable name
This is coming from the following markup:
<% fields_for "...", la.contact_mechanism do |ta_la_f| %>
<%= ta_la_f.text_field_row :email_address, :size => 40 %>
<% end %>
Over the course of the evening I used several permutations of a string to index into my multi-model data, but to no avail. It just wouldn't work and kept throwing that error. In the end it turned out that la.contact_mechanism was nil. A simple la.build_contact_mechanism in the method that prepares my model and all seems to be well. Now we'll just see if I can persist all of the values back to the right place.
When dealing with multi-model forms it is important to present user errors next to the field where the error occurred. You don't want the user to need to move up and down the page to look at errors and then go find the place where the error occurred. To solve this issue I created the following FormBuilder. It outputs errors next to the input field where the error occurred.
class TabledBuilder < ActionView::Helpers::FormBuilder
include ActionView::Helpers::ActiveRecordHelper
include ActionView::Helpers::TagHelper
def self.create_helper_method(method_name)
define_method(method_name) do |label, *args|
# Determine if the user wants to override
# the label.
alternate_label = args.last.is_a?(Hash) ? (args.last[:label] != nil ? args.last[:label] : label) : label
@template.content_tag ("span",
@template.content_tag("td",
@template.content_tag("label",
alternate_label ? alternate_label.to_s.humanize : label.to_s.humanize + ":",
:for => "#{@object_name}_#{label}")
) +
@template.content_tag("td", super + error_message_on (:object, label) )
)
end
end
def self.create_helper_row_method (method_name)
define_method(method_name + "_row") do |label, *args|
@template.content_tag ("tr", send (method_name.to_sym, label, *args) )
end
end
field_helpers.each do |name|
create_helper_method(name)
create_helper_row_method(name)
end
end
Now, I'm really new to Rails. I've been at this for two weeks today in my spare hours, which are very few.
If you want to use this helper simply place it in app/helpers, then add the following to your app/helpers/application_helper.rb file:
module ApplicationHelper
def tabled_form_for(name,*args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
options = options.merge(:builder => TabledBuilder)
args = (args << options)
form_for(name, *args, &block)
end
def tabled_fields_for(name, *args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
options = options.merge(:builder => TabledBuilder)
args = (args << options)
fields_for(name, *args, &block)
end
end
Then to use it in your rhtml files:
<% tabled_form_for :project, @project, :url => { :action => :create_project } do |f| %>
<table>
<%= f.text_field_row :name, :size => '42' %>
<%= f.text_area_row :description %>
</table>
<% end %>
You're right -- what a misleading error message. I had the same problem today. Thanks for saving me a lot of time!
thank you
you was the solution for my problem! very good!
note: `@...' is not allowed as an instance variable name
=D