【问题标题】:Render template and change url string in browser?在浏览器中渲染模板并更改 url 字符串?
【发布时间】:2011-01-16 19:14:16
【问题描述】:

我有 2 个操作 - 编辑和更新。编辑中的表单将值提交给更新操作。当保存模型失败时,我会渲染编辑模板,用户会在其中看到错误,并且字段已预先填充了他之前填写的内容。有一个巨大的但对我来说 - 在用户浏览器的 URL 面板中有 /user/update,即使(并且因为)我渲染编辑模板。我可以通过在更新操作中将一些参数传递给渲染方法来以某种方式改变它吗?我不希望用户看到除了编辑之外还有任何(更新)操作。有可能吗?

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3


    【解决方案1】:

    这是解决这个问题的第三种方法:

    在你的 routes.rb 中

    resources :users
    match 'users/:id/edit' => 'users#update', :via => :put, :as => :put_user
    

    在您看来(例如edit.html.erb)

    <%= form_for @user, :url => put_user_path do |f| %>
      ...
    <%  end %>
    

    在您的控制器中(例如 users_controller.rb)

    def update
      @user = User.find(params[:id])
      if @user.update_attributes(params[:user])
        ...
      else
        render 'edit'
      end
    end
    

    【讨论】:

      【解决方案2】:

      有两种解决方法:

      1) 从更新操作重定向回编辑操作,而不是仅呈现模板,并传递您要用于填充正在编辑的对象的错误消息和属性。这将导致 URL 为 /user/edit。

      定义更新 @user = User.find(params[:id]) 如果@user.update_attributes 参数[:user] ... 别的 redirect_to 编辑用户路径(@user, :messages => @user.errors) 结尾 结尾

      2) 发布到编辑操作而不是更新操作并完全删除更新操作。你可以使用 request.post 吗?在您的编辑操作中检查请求是发布请求还是获取请求,然后使用相同的函数定义执行更新和编辑操作。

      定义编辑 @user = User.find(params[:id]) 如果 request.post? @user.update_attributes 参数[:user] ... 别的 ... 结尾 结尾

      注意:请记住,尽管您永远无法真正对客户端隐藏 POST 操作,因为他们始终可以查看您的源代码并查看您在表单中发布的操作。

      【讨论】:

      • 在执行选项 1 时,我最终得到一个类似 articles/1/edit?messages=%23&lt;ActiveModel%3A%3AErrors%3A0x007f81b14c5d08&gt; 的 URL,并且没有显示错误。
      【解决方案3】:

      我想说你根本不了解 REST 如何以及更重要的是为什么 REST 在 Rails 中以这种方式工作,你应该在将自己的想法强加于它之前尝试学习该框架。

      首先,惯用的铁轨路线中没有动作。您可以通过向 things/:id 发送 PATCH 或 PUT 请求来执行更新。 newedit 例外,因为这些操作用于呈现表单。

                 Prefix Verb   URI Pattern                                                                              Controller#Action
                 things GET    /things(.:format)                                                                        things#index
                        POST   /things(.:format)                                                                        things#create
              new_thing GET    /things/new(.:format)                                                                    things#new
             edit_thing GET    /things/:id/edit(.:format)                                                               things#edit
                  thing GET    /things/:id(.:format)                                                                    things#show
                        PATCH  /things/:id(.:format)                                                                    things#update
                        PUT    /things/:id(.:format)                                                                    things#update
                        DELETE /things/:id(.:format)                                                                    things#destroy
      

      编辑操作 (GET /things/:id/edit) 显示用于编辑资源的表单。这是一个幂等操作,因为它应该返回相同的结果并且不会改变资源。

      更新操作 (PATCH|PUT /things/:id) 呈现对资源执行非幂等转换的结果。

      您还应该注意,Rails 中的渲染与重定向无关。这是一个常见的误解。

      render :edit
      

      实际上只是以下的简写:

      render "things/edit"
      

      它不调用编辑动作——两者只是共享一个视图,但在概念上是完全不同的动作。

      重新加载该页面当然不会显示与 /things/1 的 GET 请求相同的结果 - 不是 PATCH/PUT。请记住,GET 请求应该始终是幂等的。

      重定向会在历史记录中创建一个条目,因为它是一个单独的 GET 请求,而且您需要将整个表单正文作为 GET 参数传递,这并不理想。另外,您基本上是在抛弃 Rails 的真正力量,这是您从接受其惯例中获得的生产力。

      【讨论】:

      • @Evan 错误 - render :edit 不会更改浏览器 URL,因为它不是重定向。当用户单击表单上的提交按钮时,URL 已经更改。 render :edit 只是渲染模板并设置响应正文。
      • @Evan 您需要更改表单的 action 属性,以便它提交给/things/1/edit,这在 REST 中是不习惯的。这一切都归结为您所缺少的幂等性。
      • @evan - 你还是不明白。不,我不会说这有点迂腐,因为大多数新程序员都在努力理解 HTTP 的基础知识以及客户端/服务器关系。在客户端单击链接并提交表单会发送请求 - 服务器只是响应。它实际上无法更改浏览器的位置。是否要遵循重定向取决于客户端。非常重要的目的地。
      • 并且有很多篇文章介绍了为什么在 REST 中使用 HTTP 动词以及成员路由来更新资源。与 idempency 相比,整个“但是如果我刷新浏览器......”这件事是一个非常夸张的问题,并且它对缓存之类的东西有影响。如果您认为这确实是个问题,请使用 onunload 处理程序。
      • 正如我在回答中已经解释的那样,PATCH|PUT /things/1 是非幂等的,因此响应不是“编辑页面”,而是尝试更新某些内容的结果。 /new/edit 是仅呈现表单的幂等操作。随心所欲地争论,但几乎整个 Rails 社区都不同意你的观点。使用PATCH /things/1/edit 也无法解决任何问题,因为无论如何刷新都会丢失所有用户输入的数据,除非您破坏幂等性。
      【解决方案4】:

      基于 CL Chang 回答的稍微现代一点的版本

      resources :jobs, except: [:update] do
          member do
              patch 'edit', action: :update, :as => :update_edit
          end
      end
      

      这会生成所有标准的 restful 路由,标准更新路由除外。 (如果你不想要所有的动作,那么你可以使用only: [&lt;your routes not including :update&gt;]

      另外,它生成

      update_edit_job PATCH  /jobs/:id/edit(.:format) 
      

      现在您只需更新表单以指定路径

      <%= form_for @job, :url => update_edit_job_path do |f| %>
        ...
      <%  end %>
      

      所以,它不是通过补丁发送到 /update,而是通过补丁发送到 /jobs//edit,这会影响您的更新操作

      现在如果出现错误,您可以渲染编辑,所有错误都将可见 - 但用户不会注意到 url 与编辑的不同(因为只是方法不同)

      def update
        @job = Job.find(params[:id])
        if @job.update_attributes(user_params)
          #redirect somewhere???
        else
          render 'edit'
        end
      end
      

      【讨论】:

        【解决方案5】:

        可以使用此处描述的 pushState() 方法:https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method

        例如,您可以将其放入一个 javascript 函数中,当验证错误发生时调用该函数:

          var url = document.referrer;
          window.history.pushState(null, null, url);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-02-17
          • 1970-01-01
          • 2021-10-03
          • 2015-03-28
          • 2021-09-15
          • 2018-07-29
          • 2018-10-09
          • 2016-01-07
          相关资源
          最近更新 更多