How do I nest two resources that are both polymorphic associations?
In my app, I have several models for various product types, as each product has different attribute types and I wanted to avoid using STI. However, I am using the same Review and Comment system for each of the Product resources. So for example, a path would be accessories/1/reviews/1/
and the New Comment form (and all comments for that review) will display under the specific Review.
I am taking the same approach as used in the following video: https://gorails.com/episodes/comments-with-polymorphic-associations?autoplay=1.
Right now, the New Comment form displays properly below the show review page, but when I try to create a new comment I receive the following error:
NameError at /accessories/1/reviews/1/comments
uninitialized constant Accessories::Reviews
activesupport (6.0.2.1) lib/active_support/inflector/methods.rb
279 280 281 282 283 284 285 286 287 288 289
names.inject(Object) do |constant, name|
if constant == Object
constant.const_get(name)
else
candidate = constant.const_get(name)
next candidate if constant.const_defined?(name, false)
next candidate unless Object.const_defined?(name)
Here is all the relevant code I have:
views/reviews/show.html.erb:
...
<%= render partial: "comments/form", locals: { reviewable: @reviewable, commentable: @review } %>
<%= render partial: "comments/comments", locals: { reviewable: @reviewable, commentable: @review } %>
...
views/comments/_form.html.erb :
<%= form_for([reviewable, commentable, Comment.new]) do |f| %>
<%= f.form_group :body, class: 'row' do |f| %>
<div class='col-12 col-md-12'>
<%= f.text_area :body, { class: 'form-control', placeholder: "Add a comment..." } %>
<%= f.error_messages %>
</div>
<% end %>
<div class="d-flex">
<div class="ml-auto">
<%= f.submit class: "btn btn-primary pull-right" %>
</div>
</div>
<% end %>
controllers/accessories/reviews_controller.rb:
class Accessories::ReviewsController < ReviewsController
before_action :set_reviewable
before_action :show_reviewable
# GET /reviews
def index
@reviews = Review.where(reviewable_type: "Accessory", reviewable_id: params[:accessory_id])
end
private
def set_reviewable
@reviewable = Accessory.find(params[:accessory_id])
end
def show_reviewable
@show_reviewable = Review.find_by_reviewable_type_and_reviewable_id("Accessory", params[:accessory_id])
end
end
/controllers/accessories/comments_controller.rb:
class Reviews::CommentsController < CommentsController
before_action :set_commentable
private
def set_commentable
@commentable = Review.find(params[:review_id])
end
end
/controllers/comments_controller.rb:
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
@comment = @commentable.comments.new(comment_params)
@comment.user = current_user
@comment.save
redirect_to @commentable, notice: "Your comment was successfully posted!"
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
routes.rb:
...
resources :accessories do
resources :reviews, module: :accessories do
resources :comments, module: :reviews
end
end
...
accessory.rb:
class Accessory < ApplicationRecord
belongs_to :user
#has_rich_text :body
has_many :reviews, as: :reviewable, dependent: :destroy
#has_many :comments, as: :commentable, through: :reviews
accepts_nested_attributes_for :reviews
#accepts_nested_attributes_for :comments
end
review.rb:
class Review < ApplicationRecord
belongs_to :user
belongs_to :reviewable, polymorphic: true
has_rich_text :body
has_many :comments, as: :commentable
...
end
comment.rb:
class Comment < ApplicationRecord
belongs_to :user
belongs_to :commentable, polymorphic: true
end
Appreciate any guidance. Also, if there is a more efficient way to do this please let me know. Thanks!