在 Rails 中,当资源创建操作失败并调用 render :new 时,为啥 URL 必须更改为资源的索引 url?

Posted

技术标签:

【中文标题】在 Rails 中,当资源创建操作失败并调用 render :new 时,为啥 URL 必须更改为资源的索引 url?【英文标题】:In Rails when a resource create action fails and calls render :new, why must the URL change to the resource's index url?在 Rails 中,当资源创建操作失败并调用 render :new 时,为什么 URL 必须更改为资源的索引 url? 【发布时间】:2013-01-07 13:05:08 【问题描述】:

我有一个名为 Books 的资源。它在我的路由文件中正确列为资源。

我有一个新动作,它为新视图提供了标准:

@book = Book.new

在模型上,有一些属性是通过存在来验证的,所以如果保存操作失败,就会产生错误。

在我的控制器中:

@book = Book.create
...  # some logic
if @book.save
  redirect_to(@book)
else
  render :new
end

这是相当标准的;并且使用 render:new 的基本原理是将对象传递回视图,并且可以报告错误、重新填写表单条目等。

这可行,除非每次我被发送回表单(通过渲染:new),我的错误都会出现,但我的 URL 是 INDEX URL,即

/books

而不是

/books/new

我最初是从那里开始的。我看过其他几个关于这个问题的帖子,但没有答案。至少,有人会假设它会将您带到 /books/create,我也有一个视图文件(在这种情况下与 new 相同)。

我可以这样做:

# if the book isn't saved then
flash[:error] = "Errors!"
redirect_to new_book_path

但是随后@book 数据以及错误消息都会丢失,这就是拥有表单和操作等的全部意义。

为什么当通常该 URL 调用列出所有书籍的 INDEX 方法时,render :new 将我登录到 /books,我的索引操作?

【问题讨论】:

它不会让您进入 /books/new,因为您是通过发布到 /books/ 创建资源的,当它失败时它只是呈现新操作,而不是将您重定向到新操作跨度> 试试redirect_to new_book_path(book: params[:book]) @Doon 是对的(他应该把它作为一个答案,这样我就可以投票了:))。在 REST 的“Rails 视图”中创建操作没有可视化,因此是正常的重定向。这里的错误网址有什么问题? @MrYoshiji 关闭,但没有雪茄......这给了我一个带有 /books/new/book%skldjalkdja 的 URL(我相信与图书 ID 本身有关的乱码)。 /book/new 怎么样? 【参考方案1】:

它实际上 将您发送到创建路径。它在create 操作中,其路径为/books,使用HTTP 方法POST。这看起来与索引路径/books 相同,但索引路径使用的是HTTP 方法GET。 rails 路由代码在确定调用哪个操作时会考虑该方法。验证失败后,您仍处于创建操作中,但您正在呈现 new 视图。这有点令人困惑,但像render :new 这样的行实际上根本不会调用新操作;它仍在运行 create 操作,它告诉 Rails 渲染新的 view

【讨论】:

有没有办法渲染 :new,以便错误传递给表单,同时登陆 URL 中的 /books/new?考虑到 /books/new 是我开始的地方,也是用户期望的地方,似乎这样看起来会更好,也更有意义。 为此,您必须重定向(请参阅上面的@MrYoshiji 评论)。当您提交表单时,浏览器会向create_book_path 发出请求。除非您随后重定向到另一个页面,否则浏览器仍然认为它位于创建 URL。不过,要使其真正成为 RESTful 还需要做一些工作;如果您只是使用 GET 重定向到 new_book_path,则必须对 URL 中的表单参数进行编码(并不总是理想的,有时对于更复杂/更大的表单数据是不可能的)。 RESTful 方式是为/books/new 添加一个 POST 路由来填充模型/表单数据。 @Doon 建议使用 javascript 更新浏览器中显示的 URL 是一种更有效的解决方案。我不知道如何更改显示的 URL。浏览器可能不会支持它。【参考方案2】:

我刚开始使用Rails-Tutorial 并遇到了同样的问题。 解决方案很简单:如果您在提交表单后想要相同的 URL(有错误),只需将 new 和 create 操作合并到一个操作中。

这是我的代码的一部分,它使这成为可能(希望它可以帮助某人^^)

routes.rb(为新动作添加后路由):

...
    resources :books
    post "books/new"
...

控制器:

...
def create
    @book = Book.new(book_params)

    if @book.save
        # save was successful
        print "Book saved!"

        else
        # If we have errors render the form again   
        render 'new'
    end
end

def new 
    if book_params
        # If data submitted already by the form we call the create method
        create
        return
    end

    @book = Book.new

    render 'new' # call it explicit
end

private

def book_params
    if params[:book].nil?  || params[:book].empty?
        return false
    else
        return params.require(:book).permit(:title, :isbn, :price)
    end
end

new.html.erb:

<%= form_for @book, :url => :action => :new do |f| %>
  <%= f.label :title %>
  <%= f.text_field :title %>

  <%= f.label :isbn %>
  <%= f.text_field :isbn %>

  <%= f.label :price %>
  <%= f.password_field :price %>

  <%= f.submit "Save book" %>
<% end %>

【讨论】:

非常好的解决方案,感谢分享,不过我有一个问题,我对newupdate 操作使用相同的“_form”模板,所以我正在尝试:@987654327 @ 然后:= form_for @book, :url =&gt; :action =&gt; action 但它似乎没有正确生成路径。 好的,我想我必须使用url_for @book 来生成 URL,谢谢【参考方案3】:

刚刚有同样的问题,所以也许有一天这可能会对某人有所帮助。您基本上必须进行 3 次调整才能使这件事起作用,尽管我的解决方案仍然不理想。

1) 在创建动作中:

if @book.save
  redirect_to(@book)
else
  flash[:book] = @book
  redirect_to new_book_path
end

2) 在新动作中:

@book = flash[:book] ? Book.new(flash[:book]): Book.new

3) 无论你在哪里解析 flash 哈希,一定要过滤掉 flash[:book]。

--> 显示正确的 URL,保留表单数据。尽管如此,我还是不喜欢将用户对象放入闪存哈希中,我认为这不是它的目的。有没有人知道一个更好的地方放它?

【讨论】:

我用这种方法得到了一个 CookieOverflow,因为它比 cookie 允许的 4k 大 你将如何通过这个传递错误消息? @Gcap 幸运的是,flash 是一个哈希,因此您可以在设置 flash[:book] 后添加其他项目,例如 flash[:error]flash[:alert]。 Action Controller guide 有更多关于 flash 的详细信息。【参考方案4】:

它不会让您登陆/books/new,因为您通过发布到/books/ 来创建资源。当您的创建失败时,它只是呈现新操作,而不是将您重定向到新操作。正如@MrYoshiji 上面所说,您可以尝试将其重定向到新操作,但这确实效率低下,因为您将创建另一个 HTTP 请求并往返于服务器,只是为了更改 url。到那时,如果它很重要,您可能可以使用 javascript 更改它。

【讨论】:

【参考方案5】:

可以通过使用相同的 url 但不同的方法来新建和创建操作来修复它。

在路由文件中可以使用以下代码。

resources :books do
  get :common_path_string, on: :collection, action: :new
  post :common_path_string, on: :collection, action: :create
end

现在你的新页面将在 url 处呈现

书籍/common_path_string

如果验证后出现任何错误,网址仍然相同。

同样在表单中使用

books_path

使用

url: common_path_string_books_path, method: :post

选择您喜欢的common_path_string

【讨论】:

以上是关于在 Rails 中,当资源创建操作失败并调用 render :new 时,为啥 URL 必须更改为资源的索引 url?的主要内容,如果未能解决你的问题,请参考以下文章

列存储索引创建失败并出现此错误“资源池'默认'中没有足够的内存来运行此查询”

在提交失败时保持 jQuery - Rails

Rails 3:取消在 after_save 上的插入

单击时的DataTable锚事件不起作用,并返回加载资源失败

尝试在 Rails 中发布文件时出现 405 错误

加载资源失败:404(未找到)- Rails 自定义字体