Rails 3 - 显示多态注释模型的提交错误

时间:2011-02-17 20:43:03

标签: ruby-on-rails-3 error-handling polymorphism

对Rails 3来说相当新,并且谷歌搜索每一种方式无法解决以下问题,大多数教程都没有处理错误。

我创建了一个包含多种内容类型/模型的Rails 3项目,例如文章,博客等。每种内容类型都有注释,所有内容都存储在一个注释表中作为嵌套资源并具有多态关联。只有一个评论操作,即“创建”操作,因为它不需要显示等,因为它属于父内容类型,应该只是在提交时重新显示该页面。

现在我有大部分工作和评论提交和发布就好了,但最后一个问题是当用户没有填写必填字段时显示错误。如果字段未填写,则应返回到父页面并显示验证错误,如Rails通常与MVC一起使用。

我的评论控制器的创建动作看起来像这样,这是我第一次尝试...

def create
   @commentable = find_commentable
   @comment = @commentable.comments.build(params[:comment])

   respond_to do |format|
      if @comment.save
         format.html { redirect_to(@commentable, :notice => 'Comment was successfully created.') }
      else
         format.html { redirect_to @commentable }
         format.xml  { render :xml => @commentable.errors, :status => :unprocessable_entity }
      end
   end
end 

如果您没有填写任何内容并提交评论表单,该页面会重定向回适当的父级,但不会显示任何闪存或任何内容。现在我想出了为什么,根据我的理解,闪存不会在redirect_to上持久存在,只能在渲染上。现在,麻烦就在这里。

评论控制器中只有'创建'动作,所以我需要将渲染指向'blogs / show'(注意:我知道这不是多态的,但是一旦我开始工作,我会担心关于那个)。我在上面代码的“else”块中尝试了这个...

else
   format.html { render 'blogs/show' }
   format.xml  { render :xml => @commentable.errors, :status => :unprocessable_entity }
end

无论如何,当我尝试在博客上提交无效评论时,我收到一条错误消息“显示[...] / app / views / blogs / show.html.erb,其中第1行引发:undefined method 'title'代表nil:NilClass。“

查看网址,我想我知道为什么......而不是直接访问/ blogs / the-title-of-my-article(我正在使用friendly_id),它会转到/ blogs / the-title-的-MY-文章/评论。我认为额外的“评论”会抛出查询并将其返回为零。

那么我怎样才能让页面呈现而不用在那里抛出额外的“评论”?或者有更好的方法来解决这个问题吗?

不确定是否重要或有帮助,但评论/博客的route.rb看起来像这样......

resources :blogs, :only => [:show] do
   resources :comments, :only => [:create]
end

3 个答案:

答案 0 :(得分:3)

在过去的几周里,我一直在关注这个问题,我认为我最终将其拉下来,错误/正确的渲染方向,填写的字段仍然填满。我确实考虑过AJAX,但是如果可能的话,我宁愿以优雅的降级来做这件事。

此外,我承认我不得不采用一种非常麻烦的方式,包括引入一种方式来复制父模型以呈现适当的内容类型的show动作,并且在这个阶段我需要简单的代码工作,不一定看起来很漂亮。

我知道它可以更好地重构,我希望在Rails变得更好的时候这样做。或者,任何其他认为可以改善这一点的人都会受到欢迎。无论如何,这是我的所有代码,只是想分享回来,并希望这可以帮助同一场景中的某些人。

comments_controller.rb

class CommentsController < ApplicationController
    # this include will bring all the Text Helper methods into your Controller
    include ActionView::Helpers::TextHelper

    def create
        @commentable = find_commentable
        @comment = @commentable.comments.build(params[:comment])

        respond_to do |format|
            if @comment.save
                format.html { redirect_to(@commentable, :notice => 'Comment was successfully created.') }
            else

                # Transform class of commentable into pluralized content type
                content_type = find_commentable.class.to_s.downcase.pluralize

                # Choose appropriate instance variable based on @commentable, rendered page won't work without it
                if content_type == 'blogs'
                    @blog = @commentable
                elsif content_type == 'articles'
                    @article = @commentable
                end

                format.html { render "#{content_type}/show" }
                format.xml  { render :xml => @commentable.errors, :status => :unprocessable_entity }
            end
        end
    end 

    private

    # Gets the ID/type of parent model, see Comment#create in controller
    def find_commentable
        params.each do |name, value|
            if name =~ /(.+)_id$/
                return $1.classify.constantize.find(value)
            end
        end
    end
end

articles_controller.rb

class ArticlesController < ApplicationController

  def show
    @article = Article.where(:status => 1).find_by_cached_slug(params[:id])
    @comment = Comment.new

    # On another content type like blogs_controller.rb, replace with appropriate instance variable
    @content = @article

    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @article }
    end
  end

end

show.html.erb for articles(更改适当的博客或其他变量)

<h1><%= @article.title %></h1>

<%= @article.body.html_safe %>

<%= render :partial => 'shared/comments', :locals => { :commentable => @article } %>

shared / _comments.html.erb(为了简化,我在这里省略了发布评论的显示,只是显示了提交它们的表单)

<%= form_for([commentable, @comment]) do |f| %>
    <h3>Post a new comment</h3>

    <%= render :partial => 'shared/errors', :locals => { :content => @comment } %>  

    <div class="field">
        <%= f.label :name, :value => params[:name] %>
        <%= f.text_field :name, :class => 'textfield' %>
    </div>

    <div class="field">
        <%= f.label :mail, :value => params[:mail] %>
        <%= f.text_field :mail, :class => 'textfield'  %>
    </div>

    <div class="field">
        <%= f.text_area :body, :rows => 10, :class => 'textarea full', :value => params[:body] %>
    </div>

    <%= f.submit :class => 'button blue' %>
<% end %>

shared / _errors.html.erb(我将其重构为部分重用文章,博客,评论等,但这只是一个标准的错误代码)

<% if content.errors.any? %>
    <div class="flash error">
        <p><strong><%= pluralize(content.errors.count, "error") %> prohibited this page from being saved:</strong></p>
        <ul>
            <% content.errors.full_messages.each do |msg| %>
                <li><%= msg %></li>
            <% end %>
        </ul>
    </div>
<% end %>

答案 1 :(得分:1)

我稍微重构了@Shannon的答案,使其更具活力。在我的'find_parent'方法中,我抓住了url路径并获取了控制器名称。在'create'方法中,我创建了一个'instance_variable_set',它为Articles(@article)或Blogs(@blog)创建了一个动态变量,或者它可能是什么。

希望你会喜欢我做过的事情?如果您有任何疑问或有什么可以改进的地方,请告诉我?

def create
    @comment = @commentable.comments.new(params[:comment])
    if @comment.save
        redirect_to @commentable, notice: "Comment created."
    else
        content_type = find_parent
        instance_variable_set "@#{content_type.singularize}".to_sym, @commentable
        @comments = @commentable.comments

        render "#{content_type}/show"
    end
end

def find_parent
    resource = request.path.split('/')[1]
    return resource.downcase
end

答案 2 :(得分:0)

您收到错误,因为blogs/show视图可能引用@blog对象,当您在评论控制器中呈现它时,该对象不存在。

你应该回到使用redirect_to而不是渲染。在您发出无效评论时,它没有显示闪光,因为如果没有保存评论,您没有告诉它设置闪光。闪光将持续到下一个请求。