1

I'm trying to add ajax to my comment form submission and I've run into an error when I'm trying to render a partial and I don't know how to solve it. I have everything set up properly and comments get created fine. But then I try to render the partial for the comments and I get this error:

undefined local variable or method `each' for #<#<Class:0xae4d760>:0xae59a78>

My create.js.erb

$("#comm_form_wrap").html("<%= escape_javascript(render :partial => "statuses/comment_form") %>");
$('#comment_box').val('');
$("#comments_wrap").html("<%= escape_javascript(render :partial => "statuses/comments") %>")

When I try to render statuses/comments is causing the error.

Here's my partial:

<% @comments.each do |comment| %>
    <div class="com_con">
        <%= Rinku.auto_link(comment.content).html_safe %>
    </div>
<% end %>

So then I tried passing the variables like this

$("#comments_wrap").html("<%= escape_javascript(render :partial => "statuses/comments", :locals => {:comment => comment}) %>")

but it gives this error

undefined local variable or method `each' for #<#<Class:0xae4d760>:0xae59a78>

Not sure what I'm missing here, I'm sure it's something small. Can anyone help me?

View

<% if member_signed_in? %>
    <div id="comm_form_wrap">
        <%= render "comment_form" %>
    </div>

    <div id="comments_wrap">
        <%= render "comments" %>
    </div>
<% end %>

**Edit**

comments_controller.rb

class CommentsController < ApplicationController

before_filter :authenticate_member!
before_filter :load_commentable
before_filter :find_member

def index
   redirect_to root_path
end

def new
    @comment = @commentable.comments.new
end

def create
    @comment = @commentable.comments.new(params[:comment])
    @comment.member = current_member
    respond_to do |format|
        if @comment.save
          format.html { redirect_to :back }
          format.json
          format.js
        else
          format.html { redirect_to :back }
          format.json
          format.js
        end
    end 
end

def destroy
    @comment = Comment.find(params[:id])
    respond_to do |format|
        if @comment.member == current_member || @commentable.member == current_member
          @comment.destroy
          format.html { redirect_to :back }
        else
          format.html { redirect_to :back, alert: 'You can\'t delete this comment.' }
        end
    end 
end

private

def load_commentable
    klass = [Status, Medium, Project, Event, Listing].detect { |c| params["#{c.name.underscore}_id"] }
    @commentable = klass.find(params["#{klass.name.underscore}_id"])
end

def find_member
    @member = Member.find_by_user_name(params[:user_name])
end 

end

statuses_controller

def show
    @status = Status.find(params[:id])
    @commentable = @status
    @comments = @commentable.comments.order('created_at desc').page(params[:page]).per_page(15)
    @comment = Comment.new
    respond_to do |format|
      format.html # show.html.erb
      format.json { redirect_to profile_path(current_member) }
      format.js
    end
end

Logs

Processing by StatusesController#show as HTML
    Parameters: {"id"=>"86"}
    [1m[35mMember Load (1.0ms)[0m  SELECT "members".* FROM "members" WHERE "members"."user_name" IS NULL LIMIT 1
    [1m[36mStatus Load (0.0ms)[0m  [1mSELECT "statuses".* FROM "statuses" WHERE "statuses"."id" = ? LIMIT 1[0m  [["id", "86"]]
    [1m[35mComment Load (2.0ms)[0m  SELECT "comments".* FROM "comments" WHERE "comments"."commentable_id" = 86 AND "comments"."commentable_type" = 'Status' ORDER BY created_at desc LIMIT 15 OFFSET 0
    [#<Comment id: 82, content: "and why not try again ha", commentable_id: 86, commentable_type: "Status", member_id: 1, created_at: "2014-06-26 06:27:05", updated_at: "2014-06-26 06:27:05">]
    [1m[36mMember Load (1.0ms)[0m  [1mSELECT "members".* FROM "members" WHERE "members"."id" = 1 LIMIT 1[0m
    [1m[35mCACHE (0.0ms)[0m  SELECT "members".* FROM "members" WHERE "members"."id" = 1 LIMIT 1
    [1m[36m (0.0ms)[0m  [1mSELECT COUNT(*) FROM "comments" WHERE "comments"."commentable_id" = 86 AND "comments"."commentable_type" = 'Status'[0m
    Rendered statuses/_comment_form.html.erb (8.0ms)
    [1m[35mCACHE (0.0ms)[0m  SELECT "members".* FROM "members" WHERE "members"."id" = 1 LIMIT 1
    Rendered statuses/_comments.html.erb (95.0ms)
    Rendered statuses/show.html.erb within layouts/application (406.0ms)
    Rendered layouts/_query.html.erb (108.0ms)
    Rendered search/_search.html.erb (22.0ms)
    Rendered layouts/_menu.html.erb (592.0ms)
Completed 200 OK in 2956ms (Views: 2312.1ms | ActiveRecord: 10.0ms | Solr: 0.0ms)
iamdhunt
  • 443
  • 6
  • 16

2 Answers2

3

Problem is your partial is calling @comments.each:

<% @comments.each do |comment| %>

2 issues:

  1. @comments doesn't exist
  2. Partials need to use local variables (they can't rely on @instance vars)

--

Partials

You'll be best doing this:

<%= render partial"statuses/comments", collection: @comments, as: comment %> 

There is a little-known piece of functionality in Rails' partials which allows you to basically "reload" the partial for each member of a collection.

The reason this is important is because it cuts out a LOT of code from your partial. If you use the partial I posted above, you'll only need this code inside the partial:

#app/views/statuses/_comments.html.erb
<div class="com_con">
    <%= Rinku.auto_link(comment.content).html_safe %>
</div>

If you set the correct @instance variable, and pass it into the collection option of the partial, Rails will basically reload the partial in a loop, like you have with the .each loop now

This will also work for singular items:

<%= render partial: "statuses/comments", object: @comment, as: comment %>

--

Instance Variable

The second issue is the setting of your instance variable

In your controller, you are not setting the @comments instance variable. This means you cannot load the contents of this variable into your view, consequently causing an issue like you've got

The way to fix this is very simple - use @instance variables you have set in your controller!

--

Escape

You may also need to look at how to escape quotes in your JS:

$("#comments_wrap").html("<%= escape_javascript(render :partial => \"statuses/comments\", :locals => {:comment => comment}) %>")

I'm not sure if this is applicable in this case, but I do know if you encapsulated quotes inside another set, you'll get errors from your JS

Community
  • 1
  • 1
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • In my statuses_controller I have @comments defined in my show action because that's what my view belongs to. You can see it in my edit. – iamdhunt Jun 25 '14 at 19:21
  • Sorry about that! Have you tried using the `collection:` method? – Richard Peck Jun 26 '14 at 05:49
  • Yeah I tried the collection method but then I get this error `undefined local variable or method 'comment' for #<#:0x6c6e130>` – iamdhunt Jun 26 '14 at 06:13
  • Hmm it looks like although you're setting your `@comments` var - it's only populating with a `class` or something. Can you do a `Rails.logger.info( @comments.inspect() )` in your `statuses_controller` & post what that variable looks like? – Richard Peck Jun 26 '14 at 06:15
  • Hmmm okay thanks for that! It seems you're getting back the variable. This is rather strange. How are you calling the var in the partial? Have you tried setting a local variable for use in the partial, rather than the instance var? – Richard Peck Jun 26 '14 at 09:59
  • Yeah I do this: `<%= render "statuses/comments", :locals => { :comment => @comments } %>` and removed this from the partial: `<% @comments.each do |comment| %>` but I get undefined local variable or method 'comment'. Can't figure out what's wrong. If I use my original code everything displays fine it just throws an error everytime I try to render it using ajax. – iamdhunt Jun 27 '14 at 11:33
  • **Don't use `@instance variables` in partials**. Stop calling `@comments` in your partial. Use the locally defined variable instead: `<%= render "statuses/comments", :locals => { :comments => @comments } %>` -> `<% comments.each do |comment| %>` – Richard Peck Jun 27 '14 at 11:35
  • Getting this: `undefined method 'each' for nil:NilClass` – iamdhunt Jun 27 '14 at 11:40
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/56415/discussion-between-rich-peck-and-iamdhunt). – Richard Peck Jun 27 '14 at 11:51
  • I finally got `collection` to work and now rendering the partial like you said `<%= render partial"statuses/comments", collection: @comments, as: comment %>` However, when I try to render the partial again through ajax like so: `$("#comments_wrap").html("<%= escape_javascript(render :partial => 'shared/comments', :collection => @comments, :as => :comment) %>")` nothing happens. I get no errors but upon inspection it says it's rendering this `$("#comments_wrap").html("")`. Do you know why this may be? Everything in the controllers is the same. – iamdhunt Jun 30 '14 at 10:36
0

The answer above helped me solve this. Rendering the comments as a collection helped me render the partial through ajax and I also needed to define @comments in my create action in my comments_controller as well to ensure it doesn't render blank.

iamdhunt
  • 443
  • 6
  • 16