开发者

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

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, :reje开发者_开发百科ct_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 || @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.


This is bug #4242 in Rails 2.3.5 and it has been fixed in Rails 2.3.8.


i think your models are wrong. the notes have no direct relationship to project. they are through milestones.

try these

class Project < ActiveRecord::Base
  has_many :milestones, :dependent => :destroy
  has_many :notes, :through => :milestones
  accepts_nested_attr ibutes_for :milestones, :allow_destroy => true
end

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

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

class Note < ActiveRecord::Base
  belongs_to :milestone
end

Update: here is the code that worked for me based on the new info:

## project controller

# PUT /projects/1
def update
  @project = Project.find(params[:id])

  if @project.update_attributes(params[:project])
    redirect_to(@project)
  else
    render :action => "edit"
  end
end

# GET /projects/1/edit
def edit
  @project = Project.find(params[:id])
  @project.milestones.build
  for m in @project.milestones
    m.notes.build
  end
  @project.notes.build
end

## edit.html.erb
<% form_for(@project) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
  <% f.fields_for :notes do |n| %>
      <p>
        <div>
          <%= n.label :content, 'Project Notes:' %>
          <%= n.text_area :content, :rows => 3 %>
        </div>
      </p>
  <% end %>
  <% f.fields_for :milestones do |m| %>
      <p>
        <div>
          <%= m.label :name, 'Milestone:' %>
          <%= m.text_field :name %>
        </div>
      </p>
      <% m.fields_for :notes do |n| %>
          <p>
            <div>
              <%= n.label :content, 'Milestone Notes:' %>
              <%= n.text_area :content, :rows => 3 %>
            </div>
          </p>
      <% end %>
  <% end %>
  <p>
    <%= f.submit 'Update' %>
  </p>
<% end %>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜