Search code examples
ruby-on-railsmodel-view-controllerlocal-variablesrenderpartialpartials

shared partials error undefined method `empty?' for nil:NilClass


I've create a partial (views/cart/_cart.html.erb) for my applications shopping cart. I'm rendering it in only in my views/layouts/application.html.erb file. But when I run it I'm getting a NoMethodError on every page other than the views/cart/_cart.index.erb, which I suspect is because it shares the same controller as the partial being rendered.

I've been looking everywhere for an answer & believe it has something to do with how I'm passing variables when rendering the partial, I'll really appreciate it if someone can have a look?

All code can be found here: https://github.com/rossmc/topsnowboards

My code for my partial (views/cart/_cart.html.erb) begins:

<% @cart = cart%>

<h1>Your Cart</h1>

<% if @cart.empty? %>
    <p>There is nothing in your shopping Cart</p>
<% end %>

<% total = 0 %>

<!-- More code displaying the rest of the cart omitted for brevity 

The partial is rendered in views/layouts/application.html.erb with:

<%= render :partial => "/cart/cart", :locals => { :cart => @cart } 

The error message Rails is throwing back at me when I run it is:

NoMethodError in Site#home

Showing Server Side/PROJ-Server Side/app/views/cart/_cart.html.erb where line #5 raised:

undefined method `empty?' for nil:NilClass

Extracted source (around line #5):

2: 
3: <h1>Your Cart</h1>
4: 
5: <% if @cart.empty? %>
6:  <p>There is nothing in your shopping Cart</p>
7: <% end %>
8: 

Solution

  • There is a bit of redundancy in the code you mentioned here. @cart is an instance variable and it should be available to your partials without any need to pass it as local (depending on where you defined it in the first place of course), so you don't need the first line in your cart partial. Since you render the cart partial in your application layout, it will be rendered in every page that uses this layout (typically, the whole site).

    What you need is to make @cart variable available to every page (that is, every controller) of your site. Since every controller inherits from your ApplicationController, it makes sense to define @cart there. Simply add a before_filter to ApplicationController that defines @cart. Skimming quickly through your code, I say this should solve the problem:

    # app/controllers/application_controller.rb
    class ApplicationController < ActionController::Base
      protect_from_forgery
      before_filter :the_cart
    
      private
    
      def the_cart
        @cart = session[:cart] || {}
      end
    
      # the rest of the code ...
    end