【问题标题】:Rails validation over redirect通过重定向进行 Rails 验证
【发布时间】:2010-12-04 22:03:11
【问题描述】:

我正在试用用 rails 编写的 beast 论坛,并将以此作为我一直面临的问题的示例。

论坛有一个主题/显示操作和视图,底部有一个表单,用于在主题内创建新帖子。

提交表单进入帖子/创建,如果验证通过重定向回主题/显示并且工作正常,但是如果验证失败(省略正文字段),您将被重定向到相同的主题/显示并返回到表单,没有验证错误...通常如果验证失败,您将留在任何/创建渲染:action => new。

在重定向中是否丢失了验证,让它工作的最佳方法是什么?

见下面的代码:

PostsController.rb

  def create
    @post = current_user.reply @topic, params[:post][:body]

    respond_to do |format|
      if @post.new_record?
        format.html { redirect_to forum_topic_path(@forum, @topic) }
        format.xml  { render :xml  => @post.errors, :status => :unprocessable_entity }
      else
        flash[:notice] = 'Post was successfully created.'
        format.html { redirect_to(forum_topic_post_path(@forum, @topic, @post, :anchor => dom_id(@post))) }
        format.xml  { render :xml  => @post, :status => :created, :location => forum_topic_post_url(@forum, @topic, @post) }
      end
    end
  end

TopicsController.rb

  def show
    respond_to do |format|
      format.html do
        if logged_in?
          current_user.seen!
          (session[:topics] ||= {})[@topic.id] = Time.now.utc
        end
        @topic.hit! unless logged_in? && @topic.user_id == current_user.id
        @posts = @topic.posts.paginate :page => current_page
        @post  = Post.new
      end
      format.xml  { render :xml  => @topic }
    end
  end

主题/显示视图

  <% form_for :post, :url => forum_topic_posts_path(@forum, @topic, :page => @topic.last_page) do |f| %>

  <%= f.error_messages %>

  <table width="100%" border="0" cellpadding="0" cellspacing="0">
    <tr>
      <td rowspan="2" width="70%">
        <%= f.text_area :body, :rows => 8 %>
      </td>
      <td valign="top">
        <%= render :partial => "posts/formatting" %>
      </td>
    </tr>
    <tr>
      <td valign="bottom" style="padding-bottom:15px;">
       <%= submit_tag I18n.t('txt.views_topics.save_reply', :default => 'Save reply') %>
     </td>
   </tr>
  </table>
  <% end %>

非常感谢。

【问题讨论】:

  • 工作流程本身看起来很奇怪
  • 你是什么意思?如果没有这个例子,我可能会更简单地问它。基本上,如果你重定向而不是渲染,错误消息会发生什么,在哪里可以访问它们?
  • 主要问题是使用重定向而不是渲染。通常人们会在创建/新控制器操作中使用render 'new' 来保留对象和object.errors。使用重定向需要复杂的代码来保持对象数据的活跃。

标签: ruby-on-rails validation


【解决方案1】:

My answera very similar question 最近在 StackOverflow 上发布,涵盖了 redirect_torender 辩论的许多优缺点。我很想知道是否有人有任何其他的优点/缺点要添加到讨论中。

【讨论】:

  • 我试过渲染。它有效,简单而愚蠢的解决方案。
【解决方案2】:

我认为你有两个问题。

  1. 通过重定向保留验证错误
  2. 重新填充表单字段,以便用户不必再次输入所有信息。

这两件事是相互关联的。

验证错误通常通过作用于对象的 error_msg_for 方法显示。通常由控制器作为无法保存的对象的实例变量提供。相同的实例变量用于重新填充表单。

在重定向期间,控制器通常会使用 params 哈希实例化一个实例变量。因此,任何用于确定保存失败原因的信息都会丢失。正常资源将在保存失败时呈现并在成功时重定向,这会导致两件事发生。

  1. 该对象的实例被传递给error_msg_用于创建漂亮的统一错误框。
  2. 该对象的实例用于填充表单的字段,允许您的用户只编辑必要的内容。

我不太了解Beast,所以我不确定创建线程的表单是否是活动记录模型。但以下内容将让您了解如何解决您的问题。这将涉及修改 Beast 插件的本地副本,因此如果您使用工具保持更新,您的更改可能会丢失。

我一直在使用以下这些方法来解决您的验证问题。假设您正在谈论的表单基于 nmodel 他们应该为您提供重新填充表单所需的一切。

本质上,您使用 clone_with_errors 在闪存哈希中存储带有错误的对象的浅表副本。您必须使用浅拷贝,否则在显示具有多个关联的记录的错误时会遇到问题。

然后我使用 my_error_msg_for,它采用与标准 error_msg_for 相同的选项来构建错误消息 html。我之所以写它,是因为由于某种原因,标准的 error_msg_for 方法不适用于存储在散列中的对象。它与error_msg_for的官方源代码版本几乎相同。

/app/controllers/examples_controller.rb

class ExamplesController < ApplicationController
  def update
    ...

    if @example.save 
      regular action
    else
      flash[:errors] = clone_with_errors(@example)
      respond_to do |format|
        format.html redirect_to(@example)
      end
    end
end

/app/views/examples/show.html.erb

<div id="error">
        <% if flash[:errors] && !flash[:errors].empty? then -%>

        <p ><%= my_error_msg_for flash[:errors] %></p>

        <% end -%>
</div>
...

这是您需要的代码。

application_controller.rb

 def clone_with_errors(object)
    clone = object.clone
    object.errors.each{|field,msg| clone.errors.add_to_base(msg)}
    return clone
  end

application_helper.rb

 def _error_msg(*params)

    options = params.extract_options!.symbolize_keys
    if object = options.delete(:object)
      objects = [object].flatten
    else
      objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
    end
    count   = objects.inject(0) {|sum, this| sum + this.errors.count }
    unless count.zero?
      html = {}
      [:id, :class].each do |key|
        if options.include?(key)
          value = options[key]
          html[key] = value unless value.blank?
        else
          html[key] = 'errorExplanation'
        end
      end
      options[:object_name] ||= params.first
      options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message) && !options[:header_messag].nil?
      options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message) && !options[:message].nil?
      error_messages = objects.sum {|this| this.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join

      contents = ''
      contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank?
      contents << content_tag(:p, options[:message]) unless options[:message].blank?
      contents << content_tag(:ul, error_messages)

      content_tag(:div, contents, html)
    else
                                        ''
    end

  end

  def my_error_msg_for(params)
    _error_msg_test :object_name => params[:object].class.name.gsub(/([a-z])([A-Z])/,'\1 \2').gsub(/_/, " "),
    :object => params[:object], :header_message => params[:header_message], :message => params[:message]
  end

【讨论】:

  • 请注意,add_to_base 方法已从 rails 3 中删除。您应该使用 errors[:base]
【解决方案3】:

恐怕我对 Beast 一无所知,但一般来说,当您重定向时,一切都会丢失。这是一个新的页面请求,除非它被存储在某个地方(通常是数据库或会话),否则一切都会重置。

您在表单中看到的正常流程是在保存对象时重定向,但在保存失败时呈现。然后,视图文件可以获取控制器中设置的任何变量 - 通常包括未保存的对象及其验证消息。

很抱歉,这并不能解决您的问题,但希望它能给您一些线索。

【讨论】:

  • 感谢您的解释。我知道它通常是如何工作的,但是当您在显示主题页面上有一个帖子表单(这很正常)时,您无法在帖子控制器上真正呈现 :action => new 因为您将无权访问这些对象除非您复制它并为 /posts/new.erb.html 添加额外的视图,否则在显示主题操作中可用。如果您可以在帖子控制器中呈现 :action => "topics/show" 那就太好了。谢谢你的帮助。
猜你喜欢
  • 2021-12-18
  • 1970-01-01
  • 1970-01-01
  • 2012-04-11
  • 1970-01-01
  • 2016-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多