将多级嵌套表单标记为“脏”表单在 Rails 中

发布于 2024-08-25 11:51:59 字数 2688 浏览 2 评论 0原文

我在 Rails 中有一个三级多重嵌套表单。设置是这样的:项目有很多里程碑,里程碑有很多注释。目标是使用 JavaScript 使页面内的所有内容都可编辑,我们可以在页面内向项目添加多个新的里程碑,并向新的和现有的里程碑添加新的注释。

一切都按预期工作,除了当我向现有里程碑添加新注释时(向其中添加注释时新里程碑工作正常),新注释将不会保存,除非我编辑任何字段实际上属于里程碑来标记表单“脏”/已编辑。

有没有办法标记里程碑,以便保存已添加的新注释?

编辑:抱歉,很难粘贴所有代码,因为有太多部分,但这里是:

模型

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

我正在使用 Ryan Bates 的组合助手/JS 的基于 jQuery 的、不显眼的版本代码来完成这项工作。

应用程序助手

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

我在隐藏的 div 中呈现关联表单,然后使用以下 JavaScript 找到它并根据需要添加它。

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;
}

我猜我能想到的唯一相关的代码片段是 NotesController 中的 create 方法:

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

@owner ivar 是在过滤器之前的以下内容中创建的:

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

def determine_owner
  @owner = load_milestone || @project
end

事情是,所有这些似乎工作正常,除非我向现有里程碑添加新注释。必须“触及”里程碑才能保存新笔记,否则 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, :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 || @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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

情泪▽动烟 2024-09-01 11:51:59

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

红玫瑰 2024-09-01 11:51:59

我认为你的模型是错误的。这些注释与项目没有直接关系。他们正在经历里程碑。

尝试这些

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

更新:这是基于新信息对我有用的代码:

## 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 %>

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 %>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文