导轨、STI 和 'becomes' - f.object.errors 未显示在视图中



我的问题是:为什么.becomes不传递错误到新对象?这不就是预期的行为吗?


我在rails应用程序中有以下单表继承类:

class Document < ActiveRecord::Base
  validates :title, :presence => true
end
class LegalDocument < Document
end
class MarketingDocument < Document
end

我想使用相同的控制器和视图集来编辑LegalDocument s和MarketingDocument s,所以我使用DocumentsController < ApplicationController与以下editupdate动作:

def edit
  @document = Document.find(params[:id])
end
def update
  @document = Document.find(params[:id])
  if @document.update_attributes(params[:document])
    redirect_to documents_path, :notice => "#{t(:Document)} was successfully updated."
  else
    render :action => "edit"
  end
end

和以下在我的edit视图:

<%= form_for @document.becomes(Document) do |f| %>
  <% if f.object.errors.present? %>
    <div class="error_message">
      <h4><%= pluralize(f.object.errors.count, 'error') %> occurred</h4>
    </div>
  <% end %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title, :class => "inputText" %>
  </div>
  <%= f.submit %>
<% end %>
  • 如果填了title,说明文档更新正确。
  • 如果标题为空,我返回到编辑视图,但不显示错误。

从调试,我知道它没有显示,因为f.object.errors是nil。然而,从调试中,我也知道@document.errors不是nil,正如预期的那样。


我的问题是:为什么.becomes不传递错误到新的对象?这不就是预期的行为吗?

是的,我也注意到了。

f.object.errors.present?改成@document.errors.any?(或@document.errors.present?)。

如果你真的想使用f.object.errors.present?,将becomes写入控制器(编辑和更新操作),而不是在视图中:

def edit
  @document = Document.find(params[:id]).becomes(Document)
end
def update
  @document = Document.find(params[:id]).becomes(Document)
  # ....
end

然后在视图中:

<%= form_for @document do |f| %>
  <% if f.object.errors.present? %>
    <p>Errrorsss....</p>
  <% end %>
  #.....

这是因为表单的url是根据@document. gets (Document) (=> PUT document/:id)创建的,而@document是根据它的"true"类(Document的子类)创建的。

如果你有撬(强烈建议),写:

def update
  @document = Document.find(params[:id])
  binding.pry
  # ...
end

然后检查@document。你会看到@document是LegalDocument或其他子类的一个实例,即使你在表单中调用了@document.becomes(Document)

所以最后的f.object@document是不一样的

这解释了为什么当验证失败时看不到f.object.errors

编辑

处理STI和form的"最佳方法"是不使用becomes :

<= form_for @document, url: { controller: 'documents', action: 'update' }, as: :document do |f| %>
  <% if @document.errors.any? %>
  # or if f.object.errors.any?
  # handle validation errors
  <% end %>
  # your form... 
<% end %>

  • 只有一个控制器(documents_controller)

  • 只有一个资源(资源:文档)

  • 它跟踪你的子类:LegalDocument将被存储为LegalDocument。不转换:您不必在转换为Document之前存储它的类,然后在稍后重新分配它。另外,您的子类在表单中可用,因此您可以(让我们想象一下)为类型构建一个select

  • 您的控制器看起来更干净:@document = Document.find params[:id]仅此而已。就像一个经典的资源。

如果您想在不同的操作(通常是editnew)之间共享此表单:

<%= form_for @document, url: { controller: 'media_files', action: action }, as: :media_file do |f| %>%>
# edit.html.erb
<%= render 'form', action: 'update' %>
# new.html.erb
<%= render 'form', action: 'create' %>

这基本上是一个bug,它应该像您最初期望的那样工作。下面的补丁解决了这个问题,看起来它是在10月份被拉回来的

https://github.com/lazyatom/rails/commit/73cb0f98289923c8fa0287bf1cc8857664078d43

最新更新