Rails 验证通过重定向 [英] Rails validation over redirect

查看:30
本文介绍了Rails 验证通过重定向的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

提交表单转到帖子/创建,如果验证通过重定向回主题/显示并且工作正常,但是如果验证失败(离开正文字段),您将被重定向到相同的主题/显示并返回到表单,没有验证错误……通常,如果验证失败,您将继续使用 render :action => new 创建任何内容.

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

见下面的代码:

PostsController.rb

 def 创建@post = current_user.reply @topic, params[:post][:body]response_to do |格式|如果@post.new_record?format.html { redirect_to forum_topic_path(@forum, @topic) }format.xml { 渲染:xml =>@post.errors, :status =>:unprocessable_entity }别的flash[:notice] = '帖子已成功创建.'format.html { redirect_to(forum_topic_post_path(@forum, @topic, @post, :anchor => dom_id(@post))) }format.xml { 渲染:xml =>@post, :status =>:created, :location =>forum_topic_post_url(@forum, @topic, @post) }结尾结尾结尾

TopicsController.rb

 def showresponse_to do |格式|format.html 做如果登录?current_user.seen!(session[:topics] ||= {})[@topic.id] = Time.now.utc结尾@topic.hit!除非已登录?&&@topic.user_id == current_user.id@posts = @topic.posts.paginate :page =>当前页面@post = Post.new结尾format.xml { 渲染:xml =>@话题 }结尾结尾

主题/节目视图

 <% form_for :post, :url =>forum_topic_posts_path(@forum, @topic, :page => @topic.last_page) 做 |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"><%=渲染:部分=>帖子/格式" %></td></tr><tr><td valign="bottom" style="padding-bottom:15px;"><%= submit_tag I18n.t('txt.views_topics.save_reply', :default => '保存回复') %></td></tr><%结束%>

非常感谢.

解决方案

我认为您在这里有两个问题.

  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 应用控制器定义更新...如果@example.save定期行动别的flash[:errors] = clone_with_errors(@example)response_to do |格式|format.html redirect_to(@example)结尾结尾结尾

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

<% if flash[:errors] &&!flash[:errors].empty?然后-%><p ><%= my_error_msg_for flash[:errors] %></p><%结束-%>

...

这是使一切正常运行所需的代码.

application_controller.rb

 def clone_with_errors(object)克隆 = object.cloneobject.errors.each{|field,msg|clone.errors.add_to_base(msg)}返回克隆结尾

application_helper.rb

 def _error_msg(*params)选项 = params.extract_options!.symbolize_keysif object = options.delete(:object)对象 = [对象].flatten别的对象 = params.collect {|object_name|instance_variable_get("@#{object_name}") }.compact结尾count = objects.inject(0) {|sum, this|sum + this.errors.count }除非count.zero?html = {}[:id, :class].each do |key|if options.include?(key)值 = 选项[键]html[key] = value 除非 value.blank?别的html[key] = '错误解释'结尾结尾选项[:object_name] ||= params.firstoptions[:header_message] = "#{pluralize(count, 'error')} 禁止保存这个 #{options[:object_name].to_s.gsub('_', ' ')} 除非 options.include?(:header_message) &&!options[:header_messag].nil?options[:message] ||= '以下字段有问题:' 除非 options.include?(:message) &&!options[:message].nil?error_messages = objects.sum {|this|this.errors.full_messages.map {|msg|content_tag(:li, msg) } }.join内容 = ''内容<params[:object].class.name.gsub(/([a-z])([A-Z])/,'\1 \2').gsub(/_/, " "),:对象=>params[:object], :header_message =>参数[:header_message], :message =>参数[:消息]结尾

I'm trying out the beast forum written in rails and will use this as an example of a problem I keep facing.

The forum has a topics/show action and view with a form at the bottom to create a new post within the topic.

Submitting the form goes to posts/create and if the validation passes redirects back to topics/show and works fine, however if the validation fails (leaving out the body field) you're redirected to the same topics/show and back to the form, with no validation errors... normally if validation fails you're left on whatever/create with render :action => new.

Are the validations being lost in the redirect, and what's the best method of getting it working?

See code below:

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

topics/show view

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

Many thanks.

解决方案

I think you have two problems here.

  1. Keeping validation errors through a redirect
  2. Repopulating the form fields so the user doesn't have to enter all the information again.

Both of these things are connected.

Validation errors are usually displayed through the error_msg_for method which acts on an object. Usually provided by the controller as the an instance variable of object that could not be saved. That same instance variable is used to repopulate the form.

During a redirect the controller will usually instantiate an instance variable using the params hash. So any information used to determine why a save failed is lost. Normal resources will render on save failure and redirect on success, this causes two things happen.

  1. The instance of the object is passed to error_msg_for creating that nice uniform error box.
  2. The instance of the object is used to populate the fields of the form, allowing your user to edit only what is necessary.

I don't know Beast so well, so I'm not sure if the form to create threads is an active record model. But the following will give you an idea how to work around your problem. It would involve modifying your local copy of the Beast plugin, so if you're using a tool to keep it updated, your changes might get lost.

I've been using these following methods to get your validation problems. Assuming the form you're talking about is based on a nmodel they should provide you with everything you need to repopulate a form.

Essentially you store a shallow copy of the object with the errors in the flash hash, using clone_with_errors. You have to use a shallow copy or else you'll run into problems when displaying errors for records with multiple associations.

Then I use my_error_msg_for which takes the same options as the standard error_msg_for to build the error messages html. I only wrote it because for some reason the standard error_msg_for method didn't work with objects stored in the hash. It's almost identical to the official source version of error_msg_for which was troubling.

/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>
...

Here's the code you need to make it all work.

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

这篇关于Rails 验证通过重定向的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆