Search code examples
ruby-on-railsrubyruby-on-rails-4polymorphic-associations

Undefined Method in 'comments_path' using "Commentable" form_for Polymorphic association


I'm getting an error raised of "undefined method 'comments_path' for..." At this code in app/views/comments/_form.html.erb (line 1).

I recently tried to implement nestable comments via polymorphic associations on a website I'm building; however, I'm running into a problem that's not allowing me to move forward.

I'm trying to implement nested comments on a 'commentable' model (ie the blog in this case) and then when show is clicked, all the nested comments are shown and the individual can comment on the blog or reply to a comment (and hence result in nested comments) without leaving the page.

I've included a few other files to show the setup, but if I've missed one that's necessary, please let me know and I'll promptly add it. Any help is much appreciated. I've been stumped for several hours and I'm sure its something simple.

<%= form_for [@commentable, @comment] do |f| %>
  <%= f.hidden_field :parent_id %></br>
    <%= f.label :content, "New Comment" %></br>
    <%= f.text_area :body, :rows => 4 %></br>
    <%= f.submit "Submit Comment" %>
<% end %>

app/views/blogs/show.html.erb

<div class="content">
  <div class="large-9 columns" role="content">
    <h2>Comments</h2>
    <div id="comments">
      <%= nested_comments @comments %>
      <%= render "comments/form" %>
    </div>
  </div>
</div>

app/controllers/comments_controller

class CommentsController < ApplicationController
      def new
        @parent_id = params.delete(:parent_id)
        @commentable = find_commentable
        @comment = Comment.new( :parent_id => @parent_id, 
                                :commentable_id => @commentable.id,
                                :commentable_type => @commentable.class.to_s)
      end

      def create
        @commentable = find_commentable
        @comment = @commentable.comments.build(params[:comment])
        if @comment.save
          flash[:notice] = "Successfully created comment."
          redirect_to @commentable
        else
          flash[:error] = "Error adding comment."
        end
      end



      private
      def find_commentable
        params.each do |name, value|
          if name =~ /(.+)_id$/
            return $1.classify.constantize.find(value)
          end
        end
        nil
      end

      def comment_params
        params.require(:comment).permit(:parent_id, :body, :commentable_type, :commentable_id)
      end
    end

app/controller/blogs_controller.rb

class BlogsController < ApplicationController
  before_filter :authenticate, :except => [ :index, :show ]
  before_action :set_blog, only: [:show, :edit, :update, :destroy]
  include MyModules::Commentable

  # GET /blogs
  # GET /blogs.json
  def index
    @blogs = Blog.all.order('created_at DESC')

    respond_to do |format|
      format.html 
      format.json
      format.atom
    end
  end

  # GET /blogs/1
  # GET /blogs/1.json
  def show
    @blog = Blog.find(params[:id])
  end

  # GET /blogs/new
  def new
    @blog = Blog.new
  end

  # GET /blogs/1/edit
  def edit
    @blog = Blog.find(params[:id])
  end

  # POST /blogs
  # POST /blogs.json
  def create
    @blog = Blog.new(blog_params)

    respond_to do |format|
      if @blog.save
        flash[:success] = "Blog was sucessfuly created"
        format.html { redirect_to @blog }
        format.json { render :show, status: :created, location: @blog }
      else
        format.html { render :new }
        format.json { render json: @blog.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /blogs/1
  # PATCH/PUT /blogs/1.json
  def update
    respond_to do |format|
      if @blog.update(blog_params)
        flash[:success] = "Blog was successfully updated."
        format.html { redirect_to @blog }
        format.json { render :show, status: :ok, location: @blog }
      else
        format.html { render :edit }
        format.json { render json: @blog.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /blogs/1
  # DELETE /blogs/1.json
  def destroy
    @blog.destroy
    respond_to do |format|
      flash[:success] = "Blog was successfully deleted."
      format.html { redirect_to blogs_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_blog
      @blog = Blog.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def blog_params
      params.require(:blog).permit(:title, :body, :image, :image_cache, :remote_image_url)
    end

  private
  def authenticate
    authenticate_or_request_with_http_basic do |name, password|
      name == "admin" && password == "runfast"
    end
  end
end

my routes file looks like this...

Rails.application.routes.draw do
  resources :blogs do
    resources :comments
  end

  resources :applications

  resources :reviews

  resources :properties

  root to: 'blogs#index'
end

the blog and comment models...

class Blog < ActiveRecord::Base
  validates_presence_of :body, :title
  has_many :comments, :as => :commentable, :dependent => :destroy
  mount_uploader :image, ImageUploader
end

class Comment < ActiveRecord::Base
  has_ancestry
  belongs_to :commentable, :polymorphic => true
  validates_presence_of :body
end

and finally the commentable module in lib/my_modules/commentable.rb

require 'active_support/concern'

module MyModules::Commentable
    extend ActiveSupport::Concern

    included do 
      before_filter :comments, :only => [:show]
    end

    def comments
      @Commentable = find_commentable
      @comments = @Commentable.comments.arrange(:order => :created_at)
      @comment = Comment.new
    end

    private

    def find_commentable
      return params[:controller].singularize.classify.constantize.find(params[:id])
    end

  end

@blog

#<Blog id: 8, title: "New Blog Post about Databases.... Again", body: "RDM, the database management system, was designed ...", created_at: "2015-03-01 22:28:07", updated_at: "2015-03-03 00:11:07", image: "IMG_2210.JPG">

@commentable

#<Blog id: 8, title: "New Blog Post about Databases.... Again", body: "RDM, the database management system, was designed ...", created_at: "2015-03-01 22:28:07", updated_at: "2015-03-03 00:11:07", image: "IMG_2210.JPG">

app/helpers/comments_helper.rb

module CommentsHelper
  def nested_comments(comments)
    comments.map do |comment, sub_comments|
      content_tag(:div, render(comment), :class => "media")
    end.join.html_safe
  end
end

@_params instance variable in better errors

{"utf8"=>"✓", "authenticity_token"=>"OH2tDdI5Kp54hf5J78wXHe//Zsu+0jyeXuG27v1REqjdAec7yBdlrVPLTZKEbLZxgR2L7rGwUwz5BlGTnPcLWg==", "comment"=>{"parent_id"=>"", "body"=>"Hello!\r\n"}, "commit"=>"Submit Comment", "controller"=>"comments", "action"=>"create", "blog_id"=>"8"}

Solution

  • You're getting the error in this view: app/views/blogs/show.html.erb.

    The data for this view has been prepared in blogs#show. So in your view, you should have <%= form_for [@blog, @comment] do |f| %>, since you set @blog, not @commentable.

    You should also do @comment = Comment.new. Not sure where you set this one...

    Do <% raise @commentable.inspect %> in your view (app/views/blogs/show.html.erb). If it's nil, then that's why you're getting the error.