I have a three-tier multi-user form in Rails. The setup is this: projects have many steps, and there are many Notes in milestones. The goal is to make everything editable on the page with JavaScript, where we can add several new steps to the project on the page and add new notes to new and existing steps.
Everything works as expected, except that when I add new notes to an existing Milestone (new milestones work fine when adding notes to them), new notes will not be saved if I do not edit any of the fields that really belong Milestone to indicate dirty / edited form.
Is there a way to mark Milestone to keep the new Notes that have been added?
Edit: Sorry, it's hard to paste all the code because there are 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 use a jQuery-based, unobtrusive version of Ryan Bates command assistant / JS code to do this.
Application assistant
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 present the association form 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 assume that the only suitable piece of code that I can imagine will be the create method in 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 initiator is created as follows before the filter:
def load_milestone
@milestone = @project.milestones.find(params[:milestone_id]) if params[:milestone_id]
end
def determine_owner
@owner = load_milestone || @project
end
, , , , , . "", , Rails .