【问题标题】:Rails: URL after validation fails when creating new records via formRails:通过表单创建新记录时验证后的 URL 失败
【发布时间】:2011-03-18 04:00:28
【问题描述】:

假设我正在使用一个表单和一个标准的 Rails restful 控制器创建一个新的 Foo,它看起来像这样:

class FoosController < ApplicationController
  ...
  def index
    @foos = Foo.all
  end

  def new
    @foo = Foo.new
  end

  def create
    @foo = Foo.create(params[:foo])
    if @foo.save
      redirect_to foos_path, :notice => 'Created a foo.'
    else
      render 'new'
    end
  end
  ...
end

所以,如果我使用标准的 restful 控制器(如上),那么当我创建 Foo 时,我在 example.com/foos/new,如果我提交表单并正确保存,我在 example.com/foos 显示索引动作。但是,如果未正确填写表单,则会再次呈现表单并显示错误消息。这都是普通的香草。

但是,如果显示错误,将呈现表单页面,但 URL 将是 example.com/foos,因为 CREATE 操作发布到该 URL。但是,人们会期望在example.com/foos 找到 Foos#index,而不是他们现在刚刚提交并添加了错误消息的表单。

这似乎是 Rails 的标准行为,但对我来说没有多大意义。显然,我可以重定向回 new 而不是从 create 操作呈现 new,但问题是错误消息等会随着内存中部分完整的 Foos 丢失。

对于这个问题是否有一个干净的解决方案,当他们提交的新 Foo 表单中出现错误时,一种将人们送回 example.com/foos/new 的方法?

谢谢!

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 url view controller


    【解决方案1】:

    要回答您对另一个答案的评论:

    我想知道是否有一种方法,根本不重写控制器,告诉 rails 你希望 URL 匹配呈现的模板,而不是调用它的控制器操作。

    我不这么认为; URL 直接绑定到路由,路由绑定到控制器和操作对——渲染层根本不接触它。

    要回答您最初的问题,以下是我回答的来自another similar question 的信息。


    如您所见,默认情况下,当您指定resources :things 时,用于创建新事物的POST 路径为/things。这是rake routes 的输出:

        things GET    /things(.:format)          {:action=>"index", :controller=>"things"}
               POST   /things(.:format)          {:action=>"create", :controller=>"things"}
     new_thing GET    /things/new(.:format)      {:action=>"new", :controller=>"things"}
    edit_thing GET    /things/:id/edit(.:format) {:action=>"edit", :controller=>"things"}
         thing GET    /things/:id(.:format)      {:action=>"show", :controller=>"things"}
               PUT    /things/:id(.:format)      {:action=>"update", :controller=>"things"}
               DELETE /things/:id(.:format)      {:action=>"destroy", :controller=>"things"}
    

    听起来你想要更像这样的东西:

    create_things POST   /things/new(.:format)      {:action=>"create", :controller=>"things"}
           things GET    /things(.:format)          {:action=>"index", :controller=>"things"}
        new_thing GET    /things/new(.:format)      {:action=>"new", :controller=>"things"}
       edit_thing GET    /things/:id/edit(.:format) {:action=>"edit", :controller=>"things"}
            thing GET    /things/:id(.:format)      {:action=>"show", :controller=>"things"}
                  PUT    /things/:id(.:format)      {:action=>"update", :controller=>"things"}
                  DELETE /things/:id(.:format)      {:action=>"destroy", :controller=>"things"}
    

    虽然不推荐,但是可以通过以下途径得到这个结果:

    resources :things, :except => [ :create ] do
      post "create" => "things#create", :as => :create, :path => 'new', :on => :collection
    end
    

    您还需要修改表单以使它们 POST 到正确的路径。

    【讨论】:

    • 好的,这几乎回答了我的问题(不,没有真正干净的方法来做到这一点)。真正困扰我的是,当您的新记录验证失败时获得的 URL 不是您正在查看的页面的正确 URL。如果您重新加载该页面,您将不会得到相同的页面。如果你问我,这在 Rails 内部似乎是一个糟糕的设计决定,但好吧。由于我刚刚获得了赏金,因此我将把它留得更久——以防其他人有任何其他想法。谢谢!
    • 可以理解。我支持 URL 很重要的观点,我可以理解这种挫败感。我想知道选择背后是否有一些原因。但是,还要记住,这并不是 REST 的真正意义所在。有一个很棒的幻灯片,叫做RESTful Best Practices,它确实让我思考。
    • 不幸的是,此解决方案不会保留任何验证错误。
    【解决方案2】:

    您可以通过在初始化程序中添加以下内容来挂钩 Rails 路由: https://gist.github.com/903411

    那就把常规资源放到你的routes.rb中:

    resources :users
    

    它应该创建您正在寻找的路线和行为。

    【讨论】:

    • 这实际上非常有用,因为虽然它是一种戏剧性的 hack,但它确实开辟了测试配置 Rails 应用程序的不同方法的可能性,我以后可能会制作一个 gem 甚至在 Rails 核心中提交以供考虑。我喜欢这个主意,以后我会试试的。谢谢!
    • 非常有用-但是:更新操作不使用编辑路径有什么原因吗?
    【解决方案3】:

    如果您担心要显示的 URL 是什么,您可以手动设置路由。对于您想要的,您可以让 GET/foos/new 呈现您的表单,并在同一 URL 上使用 POST 进行创建:

    map.with_options :controller => :foos do |foo|
        foo.new_foo    '/foos/new', :conditions => {:method => :get},  :action => :new
        foo.create_foo '/foos/new', :conditions => {:method => :post}, :action => :create
        foo.foos       '/foos',     :conditions => {:method => :get},  :action => :index
    end
    

    这应该可以在不需要对您的控制器进行任何更改的情况下工作(耶!) - 您示例中的所有三个操作都已处理。少数免责声明:

    1. 这是基于我对 2.3.8 应用程序的路由 - 可能需要进行一些语法(语义?)更改才能使其进入 Rails 3 路由样式。
    2. 我尝试将这种风格的路由与map.resources 混合使用失败了——除非你比我更熟悉这个,或者 Rails 3 路由更好(两者都很容易),你必须这样做到控制器的每条路由。
    3. 最后,不要忘记将/:id(.:format) 等添加到需要它们的路由中(本例中没有,但请参见#2)。

    希望这会有所帮助!

    编辑:最后一件事 - 您需要在/foos/new.html.erb 上的form_for 助手中硬编码URL。只需添加:url =&gt; create_foo_path,这样Rails 就不会尝试发布到/foos,默认情况下(可能有一种方法可以更改模型中的创建URL,但我不知道,如果有的话是一个)。

    【讨论】:

      【解决方案4】:

      您可以使用Rack::Flash 在用户会话中存储您想要的参数,然后重定向到您的表单网址。

      def create
        @foo = Foo.new(params[:foo])
        if @foo.save
          redirect_to foos_path, :notice => 'Created a foo.'
        else
          flash[:foo] = params[:foo]
          flash[:errors] = @foo.errors
          redirect_to new_foo_path #sorry - can't remember the Rails convention for this route
        end
      end
      
      def new
        # in your view, output the contents of flash[:foo]
        @foo = Foo.new(flash[:foo])
      end
      

      【讨论】:

      • 好吧,我想将这些存储在会话中是可行的,但我真正想知道的是,是否有办法更改此默认 rails 行为以显示您正在呈现的布局的 url,而不是使您返回该布局的操作...
      • 但这就是它的作用。它会重定向,然后您使用 flash 的内容填充视图。
      • 我的意思是,我想知道是否有一种方法,根本不重写控制器,告诉 Rails 你希望 URL 匹配呈现的模板,而不是调用的控制器操作它。这有意义吗?
      • 不,你不能这样做,因为 url 会导致控制器导致模板。车推不了马。
      猜你喜欢
      • 2023-04-09
      • 1970-01-01
      • 2018-04-09
      • 1970-01-01
      • 1970-01-01
      • 2017-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多