如何在多模型表单中使用error_messages_for?

时间:2010-02-27 02:28:02

标签: ruby-on-rails multi-model-forms

我有两个模特:专辑和曲目。专辑有很多曲目,而Track属于专辑。

我希望能够在创建相册时根据需要创建尽可能多的曲目,类似于railscasts episode 197。然而,与railscasts剧集不同,Track表格包含标题和描述 - 两者都是必需的。

现在,表单看起来像这样:

Create New Album

Name: [      ]

    Track (remove link)
          Name:        [      ]
          Description: [      ]

    Track (remove link)
          Name:        [      ]
          Description: [      ]

(add track link)

如果我决定将表单提交为空白,我会在表单顶部收到以下错误消息:

Description can't be blank
Title can't be blank
Title can't be blank

这些错误消息并非特定于模型,全部位于页面顶部,并且每个模型只显示一次(请注意,我将两个的字段留空并显示错误消息只出现一次 - 不具体到哪个曲目。)


要生成初始曲目字段,我在album_controller的新操作中添加了以下行:2.times { @album.tracks.build }

我的表格看起来像这样的要点:

<% form_for @album do |f| %>
<%= f.error_messages %>

<%= f.label :title %><br />
<%= f.text_field :title %>

<% f.fields_for :tracks do |f, track| %>
  <%= render :partial => 'tracks/fields', :locals => {:f => f} %>
<% end %>

<%= f.submit "Submit" %>
<% end %>

我尝试将<%= f.error_messages %>替换为<%= error_messages_for @album %>(仅显示相册的消息),然后添加<%= error_messages_for track %>(以显示特定于每个曲目的错误消息) - 但这并没有成功。 有人知道怎么做吗?

谢谢!

4 个答案:

答案 0 :(得分:3)

如果要为父对象和子对象分隔错误消息,可能会有点复杂。因为当您保存父对象时,它还会验证子对象,并且它包含子对象的错误。所以你可以这样做:

<% form_for @album do |f| %>
<%= custom_error_messages_helper(@album) %>

<%= f.label :title %><br />
<%= f.text_field :title %>

<% f.fields_for :tracks do |t| %>
  <%= t.error_messages message => nil, :header_message => nil %>
  <%= render :partial => 'tracks/fields', :locals => {:f => t} %>
<% end %>

<%= f.submit "Submit" %>
<% end %>

或者您可以在t.error_messages部分'tracks/fields'中添加f这一行(我将表单构建器对象形式t重命名为fieldWithErrors,因为它很混乱)。它应该显示(至少它对我有用)只有特定子对象的错误(所以你可以看到哪个标题错误是针对哪个对象)。还要记住,Rails会自动将css类.fieldWithErrors { border: 1px solid red; } 添加到包含错误的字段中,例如添加到css:

@album.errors

对于父对象有错误,因为def custom_error_messages_helper(album) html = "" html << '<div class="errors">' album.errors.each_error do |attr, error| if !(attr =~ /\./) html << '<div class="error">' html << error.full_message html << '</div>' end end html << '</div>' end 包含子对象的错误,所以它更复杂。我没有找到任何好的和简单的方法来删除一些错误或只显示与父对象相关的错误,所以我的想法是编写自定义助手来处理它:

attr

它将跳过所有名称为conatins'的属性的错误。 - 因此它应该打印与父对象关联的所有错误。唯一的问题是添加到base的错误 - 因为它们base的值是attr而且我不确定如何将错误添加到父对象中添加到父对象的子对象的基础。可能他们base的值也是add_to_base所以他们将被打印在这个助手中。但如果您不使用{{1}},则不会有问题。

答案 1 :(得分:1)

首先,f和f对范围感到困惑。使用“g”或其他东西作为您的fields_for部分,它将理解范围。

然后尝试:

<% form_for @album do |f| %>
  <%= f.error_messages %>

  <%= f.label :title %><br />
  <%= f.text_field :title %>

  <% f.fields_for :tracks do |g, track| %>
    <%= g.error_messages -%>
    <%= render :partial => 'tracks/fields', :locals => {:f => g} %>
  <% end %>

  <%= f.submit 'Create' %>
<% end %>

在相册模型上使用此accepts_nested_attributes_for :tracks

我自己测试了这个并且它有效。各曲目的错误出现在曲目的部分中。

我使用的是最新版本的Rails。

答案 2 :(得分:0)

你尝试过这个吗?

<% f.fields_for :tracks do |f, track| %>
  <%= error_messages_for "track" %>
  <%= render :partial => 'tracks/fields', :locals => {:f => f} %>
<% end %>

答案 3 :(得分:0)

与上面类似,但是,使用下面的代码,我可以测试任何对象或对象数组是否有错误:

<%= show_errors( [@company, @estabs], :header_ref => 'Company' ) %>

def show_errors(objarray, opt)

    return '' if objarray.blank?

    err = [ ]
    html  = ""

    objarray.each { |obj|  
            if obj.is_a?(Array)
                obj.each { |oo| html << show_errors([oo], opt) }
            elsif (obj.errors.count > 0 ) 
                err << obj.errors.full_messages #didnt worked with gem i18n 0.5.0
            end
              }

    return '' if (err.count==0) and (html.blank?)

    if err.count > 0
        err.flatten!            #remove sub-arrays
        err.each { |x| x.strip! }   #strip:remove espaces
        err.uniq!               #remove duplicated messages
        err.sort!

        header_ref = opt[:header_ref].nil?  ?  ""  :  opt[:header_ref]

        erro_str = (err.count == 1) ? 'erro' : 'erros'
        erro_verb = (err.count == 1) ? 'ocorreu' : 'ocorreram'

        html << '<div class="erros"  id="erros"> '
        html <<  "<b> #{header_ref}: #{err.count.to_s}  #{erro_str} #{erro_verb}: </b> "
        html << '<ul> ' 

        err.each  { |e| html << '<li> ' + e + ' </li>' }

        html << '</ul> </div>'
    end
    return html
end