iamdhunt
iamdhunt

Reputation: 453

Undefined local variable or method ajax render partial rails

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)

Upvotes: 1

Views: 3538

Answers (2)

iamdhunt
iamdhunt

Reputation: 453

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.

Upvotes: 0

Richard Peck
Richard Peck

Reputation: 76784

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

Upvotes: 3

Related Questions