如何在另一个控制器的上下文中使用rails form partial而不将ID作为隐藏字段传递

Posted

技术标签:

【中文标题】如何在另一个控制器的上下文中使用rails form partial而不将ID作为隐藏字段传递【英文标题】:How to use a rails form partial in the context of another controller without passing the ID as a hidden field 【发布时间】:2021-10-06 05:06:16 【问题描述】:

我正在尝试将评论表单嵌入到 rails Post show 视图,我可以让它工作的唯一方法是在评论表单中传递这个隐藏字段:

<%= form.hidden_field :post_id, value: "#params[:id]" %>

这是我的帖子展示操作:

def show
  @comment = Comment.new
end

这是评论创建操作:

def create
  @user = current_user
  @comment = @user.comments.build(comment_params)
end

我尝试将此添加到评论创建操作中,但它仍然说缺少帖子 ID:

def create
  @user = current_user
  @post = Post.find(params[:id])
  @comment = @user.comments.build(comment_params).merge(post_id: @post.id)
end

我还尝试将 @post = Post.find(params[:id]) 添加到 Post show 操作,认为如果 rails 具有该变量,那么评论创建操作将可以访问 @post.id)。

唯一可行的方法是将 post_id 添加为评论表单中的隐藏字段,但这似乎很危险,因为恶意用户可以在浏览器中编辑 html。我不知道他们为什么要这样做只是为了更改应用评论的帖子,但这似乎仍然不是正确的方法。

我不想要一个“嵌套表单”,因为评论是通过帖子表单创建的。

这实际上只是帖子展示页面上的一个单独的评论表单。我假设这在 Rails 中很常见,但很难找出“正确”的方法。

【问题讨论】:

您可以尝试将表单移动到部分中,然后使用该特定表单将所有变量传递给视图中的部分吗? 如果你想知道为什么你的代码不能工作,因为参数没有命名为:id。它实际上更像params[:comment][:post_id]。您也没有保存评论,因此该方法完全没有任何作用。 【参考方案1】:

Rails 的方法是声明 nested resource:

# config/routes.rb
resources :posts do
  resources :comments, only: [:create]
end

这将创建路由 POST /posts/:post_id/comments,它以 RESTful 方式连接两个资源,与将帖子 ID 放在请求正文中相比,它使正在发生的事情变得非常透明。

# app/views/comments/_form.html.erb
<%= form_with(model: [@post, @comment], local: true) do %>
  # don't create a hidden input since the 
  # post id is passed through the URL
<% end %>
# app/views/posts/show.html.erb
<%= render partial: 'comments/form' %>
# app/views/comments/new.html.erb
<%= render partial: 'comments/form' %>
class CommentsController < ApplicationController
  before_action :set_post, only: [:create]

  # POST /posts/1/comments
  def create
    @comment = @post.comments.new(comment_params) do |c|
      c.user = current_user
    end
    if @comment.save
      redirect_to @post, success: 'Comment Created'
    else
      render :new
    end
  end

  private
  def set_post
    @post = Post.find(params[:post_id])
  end
end

唯一可行的是将 post_id 添加为隐藏字段 评论表单,但这似乎很危险,因为恶意用户 可以在浏览器中编辑html。我不知道他们为什么想要 这样做只是为了更改应用评论的帖子, 但这似乎仍然不是正确的方法。

你对问题的思考是错误的。如果用户只能对某些帖子发表评论,您需要在您的服务器上强制执行授权(例如使用 Pundit 或 CanCanCan)。

真正糟糕的是在隐藏输入中传递当前用户 ID,因为它使恶意用户很容易以其他用户的身份创建资源。

# This is how you get pwned
<%= form.hidden_field :user_id, value: current_user.id %>

您希望依赖会话存储,因为它已加密且更难篡改。

【讨论】:

如果表单中有错误,您可以再次渲染posts/show 视图或单独的comments/new 视图。前者对用户来说似乎更加无缝,但如果表单位于页面下方,则可能会很痛苦。 只有在 Max 的回答中没有我想分享的问题,以防其他人有这个问题:我还需要将 @comment = Comment.new 添加到 Posts 控制器中的 show 操作中,否则添加评论表单不会渲染。 另一种方法是使用本地人render partial: 'comments/form', locals: post: @post, comment: @post.comments.new 。我真的只选择了最少的行数。 感谢您的建议。我想我更喜欢comment: @post.comments.new,因为它在表单上更明确。

以上是关于如何在另一个控制器的上下文中使用rails form partial而不将ID作为隐藏字段传递的主要内容,如果未能解决你的问题,请参考以下文章

Rails 3在另一个控制器中呈现部分形式

Rails 在控制器动作中形成,在另一个视图中呈现?

Rails:2单击一个提交按钮在两个数据库中插入记录(将一个控制器的记录保存在另一个控制器中)

Rails 3 - 在另一个帖子的SHOW视图中无法创建新帖子

Rails 3 - 如何将语句从一个控制器打印到另一个

如何使用 ActionCable 作为 API