ネストされたコメントの場合、親コメントの ID をコメント フォームに渡すにはどうすればよいですか?
概要
コメントが記事を直接参照している場合、次の形式でコメントを作成することに成功しました。
<%= form_with(model: [@article, @article.comments.build]) do |f| %>
<p>Comment:</p>
<%= f.text_area :body, class:'' %>
<%= image_submit_tag("Post.jpg", height: "25", width: "25", class: "mt-5 ms-3") %>
<% end %>
しかし、今、ネストされたコメントを作成しようとしていますが、以前に作成したコメントへの返信としてフォームにアクセスするとき、親コメントのIDをフォームに渡す方法、またはnil親を渡す方法がわからずに立ち往生しています。 -コメント ID。記事自体からフォームに直接アクセスし、その記事を参照している場合。
app/views/articles/show.html.erb では、記事に直接コメントを作成するためにコメント フォームが呼び出されます。これは私が試したことです。
<%= render '/comments/comment_form', locals: {article: @article, parent: nil} %>
app/views/comments/show.html.erb では、以前に作成したコメントへの返信としてフォームが呼び出されますが、これは私が試したことです:
<%= render '/comments/comment_form', locals: {article: comment.article, parent: comment} %>
ネストされたコメントを作成する前に、何も渡さず (ローカル: {etc.} なし)、articles/show.html.erb からフォームをレンダリングしただけで、見事に機能しました。
この件に関して何かご指導をいただければ幸いです。以下は、この問題に関連すると思われるコードの残りの部分です。
app/models/comments.rb 内
class Comment < ApplicationRecord
belongs_to :article
belongs_to :commenter, class_name: 'User', foreign_key: :user_id
belongs_to :parent, class_name: 'Comment', optional: true
has_many :comments, foreign_key: :parent_id
validates_presence_of :body, length: { minimum: 3, maximum: 500 }
end
app/controllers/comments_controller.rb 内
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
@comment = Comment.new(comment_params.merge(commenter: current_user))
@comment.article_id = params[:article_id]
@comment.save
redirect_to article_path(params[:article_id])
end
private
def comment_params
params.require(:comment).permit(:body, :article_id, :user_id, :parent_id)
end
end
解決策
インスタンス変数とローカル変数を混同していますが、これは非常に簡単です。
@ 記号が付いたものはすべて、Ruby のインスタンス変数です。 Rails での動作方法は、コントローラー内のすべてのインスタンス変数が、テンプレート エンジンに渡されるコンテキストであるビュー コンテキストを介してビューで利用できるようにすることです。それは基本的にあなたのビューの中の自己であると考えてください。
一方、ローカル割り当ては、render を呼び出すときに渡され、通常のメソッドとして、または local_assigns メソッドを通じて呼び出されます。 Rails はメタプログラミングを通じてこれらの呼び出しをビュー コンテキストに委任します。これらの呼び出しには @ 記号を含めないでください。また、ビューローカルを言語機能であるローカル変数と混同しないように注意してください。
それでは、実際にローカルを使用するように部分を変更することから始めましょう。
<%= form_with(model: [local_assigns(:parent), comment]) do |f| %>
# ..
<% end %>
local_assigns(:parent) は、ローカルが渡されない場合に NoMethodError を回避する気の利いたちょっとしたトリックで、浅いネストで同じフォームを再利用可能にするために使用できます。
また、フォーム内で @article.comments.build を実行しないでください。モデルのインスタンス化は、ビューではなくコントローラーの仕事です。また、フォームは常に新しいインスタンスにバインドされます。つまり、フォームが無効で編集のためにフォームを壊した場合、ユーザー入力はすべて失われます。
コントローラー側では、複数の異なるリソースにネストできるリソースを処理する 2 つの異なる方法があります。 1 つ目の方法は、すべてを 1 つのコントローラーにルーティングし、「パラメーター スニッフィング アプローチ」を使用することです。
class CommentsController < ApplicationController
before_action :authenticate_user!
before_action :set_parent
# Creates articles nested in a Article or in another Comment
# POST /articles/1/comments
# POST /comments/2/comments
def create
@comment = @parent.comments.new(comment_params) do |c|
c.user = current_user
end
# don't assume that saving will always be successful
if @comment.save
redirect_to @parent
else
render :new
end
end
private
def set_parent
if params[:article_id].present?
@parent = Article.find(params[:article_id])
elsif params[:comment_id].present?
@parent = Comment.find(params[:comment_id])
end
end
def comment_params
# you should not be permitting any of those params!
params.require(:comment).permit(:body)
end
end
これは人気がありますが、複雑さが増すにつれて非常に醜くなるため、私はこのアプローチの大ファンではありません。また、単一責任原則も完全に無視しています。たとえば、親のタイプに応じて異なる方法でリダイレクトすることが必要な場合があります。
2 番目のアプローチは、合成を使用して別のコントローラーを作成することです。
resouces :articles do
resouces :comments, only: :create, module: :articles
end
resouces :comments, only: [] do
resouces :comments, only: :create, module: :comments
end
# Creates Comments on an Article
class CommentsController < ApplicationController
before_action :authenticate_user!
before_action :set_parent
# Creates comments nested in a parent record.
# This parent record should be set by subclasses.
def create
@comment = @parent.comments.new(comment_params) do |c|
c.user = current_user
end
# don't assume that saving will always be successful
if @comment.save
redirect_to @parent
else
render :new
end
end
private
def set_parent
raise "This method must be implemented by subclasses!"
end
def comment_params
# you should not be permitting any of those params!
params.require(:comment).permit(:body)
end
end
module Articles
# Creates Comments on an Article
class CommentsController < ::CommentsController
private
def set_parent
@parent = Article.find(params[:article_id])
end
end
end
module Comments
# Creates Comments nested in another Comment
class CommentsController < ::CommentsController
private
def set_parent
@parent = Comment.find(params[:comment_id])
end
end
end