Marking multi-level nested forms as "dirty" in Rails

Posted by Charles Kihe on Stack Overflow See other posts from Stack Overflow or by Charles Kihe
Published on 2010-03-12T20:50:48Z Indexed on 2010/03/12 23:37 UTC
Read the original article Hit count: 476

Filed under:
|
|
|

I have a three-level multi-nested form in Rails. The setup is like this: Projects have many Milestones, and Milestones have many Notes. The goal is to have everything editable within the page with JavaScript, where we can add multiple new Milestones to a Project within the page, and add new Notes to new and existing Milestones.

Everything works as expected, except that when I add new notes to an existing Milestone (new Milestones work fine when adding notes to them), the new notes won't save unless I edit any of the fields that actually belong to the Milestone to mark the form "dirty"/edited.

Is there a way to flag the Milestone so that the new Notes that have been added will save?

Edit: sorry, it's hard to paste in all of the code because there's so many parts, but here goes:

Models

class Project < ActiveRecord::Base
  has_many :notes, :dependent => :destroy
  has_many :milestones, :dependent => :destroy

  accepts_nested_attributes_for :milestones, :allow_destroy => true
  accepts_nested_attributes_for :notes, :allow_destroy => true, :reject_if => proc { |attributes| attributes['content'].blank? }
end

class Milestone < ActiveRecord::Base
  belongs_to :project
  has_many :notes, :dependent => :destroy

  accepts_nested_attributes_for :notes, :allow_destroy => true, :allow_destroy => true, :reject_if => proc { |attributes| attributes['content'].blank? }
end

class Note < ActiveRecord::Base
  belongs_to :milestone
  belongs_to :project

  scope :newest, lambda { |*args| order('created_at DESC').limit(*args.first || 3) }
end

I'm using an jQuery-based, unobtrusive version of Ryan Bates' combo helper/JS code to get this done.

Application Helper

def add_fields_for_association(f, association, partial)
  new_object = f.object.class.reflect_on_association(association).klass.new
  fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
    render(partial, :f => builder)
  end
end

I render the form for the association in a hidden div, and then use the following JavaScript to find it and add it as needed.

JavaScript function addFields(link, association, content, func) { var newID = new Date().getTime(); var regexp = new RegExp("new_" + association, "g"); var form = content.replace(regexp, newID); var link = $(link).parent().next().before(form).prev(); if (func) { func.call(); } return link; }

I'm guessing the only other relevant piece of code that I can think of would be the create method in the NotesController:

def create
  respond_with(@note = @owner.notes.create(params[:note])) do |format|
    format.js   { render :json => @owner.notes.newest(3).all.to_json }
    format.html { redirect_to((@milestone ? [@project, @milestone, @note] : [@project, @note]), :notice => 'Note was successfully created.') }
  end
end

The @owner ivar is created in the following before filter:

def load_milestone
  @milestone = @project.milestones.find(params[:milestone_id]) if params[:milestone_id]
end

def determine_owner
  @owner = load_milestone
  @owner ||= @project
end

Thing is, all this seems to work fine, except when I'm adding new notes to existing milestones. The milestone has to be "touched" in order for new notes to save, or else Rails won't pay attention.

© Stack Overflow or respective owner

Related posts about ruby-on-rails

Related posts about ruby