#75 Complex Forms Part 3
Note: This solution is out of date. See episode 196.
Resources
- Complex Forms Part 1
- Complex Forms Part 2
- Generate valid dom ids
- Scroll down for validation info.
Update (3/29/2008): The code for this screencast isn't the greatest, it requires the :index option for the fields and does not work well in Rails 2. Instead I recommend going with the approach I show in Advanced Rails Recipes Here's the code (posted by permission). Check out the recipe for details on how it works.
If you want, you can also view the original code
<% form_for :project, :url => project_path(@project), :html => { :method => 'put' } do |f| %> <%= render :partial => 'fields', :locals => { :f => f } %> <p><%= submit_tag "Update Project" %></p> <% end %>
<% form_for :project, :url => projects_path do |f| %> <%= render :partial => 'fields', :locals => { :f => f } %> <p><%= submit_tag "Create Project" %></p> <% end %>
<p> Name: <%= f.text_field :name %> </p> <div id="tasks"> <%= render :partial => 'task', :collection => @project.tasks %> </div> <p><%= add_task_link "Add a task" %></p>
<div class="task"> <% fields_for_task(task) do |task_form| %> <p> Task: <%= task_form.text_field :name %> <%= link_to_function "remove", "$(this).up('.task').remove()" %> </p> <% end %> </div>
class Project < ActiveRecord::Base has_many :tasks, :dependent => :destroy validates_presence_of :name validates_associated :tasks after_update :save_tasks def new_task_attributes=(task_attributes) task_attributes.each do |attributes| tasks.build(attributes) end end def existing_task_attributes=(task_attributes) tasks.reject(&:new_record?).each do |task| attributes = task_attributes[task.id.to_s] if attributes task.attributes = attributes else tasks.delete(task) end end end def save_tasks tasks.each do |task| task.save(false) end end end
class Task < ActiveRecord::Base belongs_to :project validates_presence_of :name end
def new @project = Project.new @project.tasks.build end def create @project = Project.new(params[:project]) if @project.save flash[:notice] = "Successfully created project and tasks." redirect_to projects_path else render :action => 'new' end end def edit @project = Project.find(params[:id]) end def update params[:project][:existing_task_attributes] ||= {} @project = Project.find(params[:id]) if @project.update_attributes(params[:project]) flash[:notice] = "Successfully updated project and tasks." redirect_to project_path(@project) else render :action => 'edit' end end
def fields_for_task(task, &block) prefix = task.new_record? ? 'new' : 'existing' fields_for("project[#{prefix}_task_attributes][]", task, &block) end def add_task_link(name) link_to_function name do |page| page.insert_html :bottom, :tasks, :partial => 'task', :object => Task.new end end
Validation
Displaying the validation errors can be tricky depending on your needs. It may be as simple as displaying the project validations:
<%= error_messages_for :project %>
This will mention if there are task validation errors, however it will not state exactly what the errors are because these are stored in each task model.
If you need to display the task error messages. One way is to do this:
<% @task = task %> <%= error_messages_for :task %>
The error_messages_for method requires an instance variable with the same name, so that is why it needs to be set right there. This is an ugly work around. If you want to do it in a cleaner way or handle the error messages differently then I recommend creating your own validation helper method instead. I may go into more details in a future episode.
If you want to share your validation troubles feel free to add a comment!