I'm trying to embed a Comment form to a rails Post show view and the only way I can get it to work is by passing this hidden field in the comment form:
<%= form.hidden_field :post_id, value: "#{params[:id]}" %>
Here is my Post show action:
def show
@comment = Comment.new
end
Here is the Comment create action:
def create
@user = current_user
@comment = @user.comments.build(comment_params)
end
I tried adding this to the Comment create action, but it still said the Post ID was missing:
def create
@user = current_user
@post = Post.find(params[:id])
@comment = @user.comments.build(comment_params).merge(post_id: @post.id)
end
I also tried adding the @post = Post.find(params[:id])
to the Post show action, thinking that if rails had that variable then the Comment create action would have access to @post.id
).
The only thing that works is adding the post_id as a hidden field in the Comment form, but this seems dangerous because a malicious user could edit the html in the browser. I don't know why they would want to do this just to change the Post that the comment gets applied to, but it still seems not the right way to do this.
I don't want a "nested form" in the sense that the comment is something that is created via the post form.
It's really just a separate Comment form on the Post show page. I'm assuming this is a common thing in Rails, but having trouble figuring out the "correct" way to do this.
The Rails way to do this is is by declaring a nested resource:
# config/routes.rb
resources :posts do
resources :comments, only: [:create]
end
This creates the route POST /posts/:post_id/comments
which connects the two resources in a RESTful way and makes it very transparent what is going on compared to placing the post id in the request body.
# 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
The only thing that works is adding the post_id as a hidden field in the Comment form, but this seems dangerous because a malicious user could edit the html in the browser. I don't know why they would want to do this just to change the Post that the comment gets applied to, but it still seems not the right way to do this.
You're thinking about the problem wrong. If users should only be able to comment on certain posts you need to enforce authorization on your server (such as by using Pundit or CanCanCan).
What is really bad is passing the current user id in hidden inputs as it makes it very easy for malicous users to create resources as another user.
# This is how you get pwned
<%= form.hidden_field :user_id, value: current_user.id %>
You want to rely on the session storage instead as its encrypted and harder to tamper with.