Search code examples
ruby-on-railsrubymodel-view-controllermapsruby-on-rails-5

NoMethodError - undefined method `map' for nil:NilClass


I'm pretty new to Rails and building a store locator app using a maps API. I have a Store model and a Toilet model that are associated so that users can selected if a store has a toilet or not when they are adding a store. However, I keep getting the following error when I try and add a store and select "no" for toilet_available:

NoMethodError in Stores#create
Showing /mnt/c/Users/Me/Dropbox/coding/SpaetiApp/app/views/stores/_form.html.erb where line #7 raised:

undefined method `map' for nil:NilClass
Extracted source (around line #7):
5
6
7
8
9
10

    <%= f.input :address_line3, label: "Country", input_html: { value: "Germany"} %>
    <%= f.input :beer_cost, label: "Cost of a Berliner Pilsner" %>
    <%= select_tag(:toilet_id, options_for_select(@toilets), :prompt => "Toilet available?" ) %>
    <%= f.input :facilities, label: "Facilities" %>

    <%= f.button :submit %>

Trace of template inclusion: app/views/stores/new.html.erb

I don't get this error when I select "yes" for toilet_available, however. Here are the relevant files.

stores controller:

class StoresController < ApplicationController

    before_action :find_store, only: [:show, :edit, :update, :destroy]

    def index
        if params[:toilet].blank?
            @stores = Store.all.order("created_at DESC")
        else
            @toilet_id = Toilet.find_by(toilet_available: params[:toilet]).id
          @stores = Store.where(:toilet_id => @toilet_id).order("created_at DESC")
        end
    end

    def show
    end

    def edit
        @toilets = Toilet.all.map{ |t| [t.toilet_available, t.id] }
    end

    def update
        @store.toilet_id = params[:toilet_id] 
        if @store.update(store_params)
            redirect_to store_path(@store)
        else
            render 'edit'
        end
    end

    def new
        @store = current_user.stores.build
        @toilets = Toilet.all.map{ |t| [t.toilet_available, t.id] }
    end

    def create
        @store = current_user.stores.build(store_params)
        @store.toilet_id = params[:toilet_id] 

        if @store.save 
            redirect_to root_path
        else
            render 'new'
        end
    end

    def destroy
        @store.destroy
        redirect_to root_path
    end

    private

        def store_params
            params.require(:store).permit(:name, :address, :address_line2, :address_line3, :beer_cost, :toilet_id)
        end

        def find_store
            @store = Store.find(params[:id])
        end
end

Schema.rb:

ActiveRecord::Schema.define(version: 20180814220216) do

  create_table "stores", force: :cascade do |t|
    t.string "name"
    t.string "address"
    t.float "beer_cost"
    t.text "facilities"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.float "latitude"
    t.float "longitude"
    t.boolean "toilet"
    t.string "address_line2"
    t.integer "user_id"
    t.integer "toilet_id"
    t.string "address_line3"
  end

  create_table "toilets", force: :cascade do |t|
    t.string "toilet_available"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string "current_sign_in_ip"
    t.string "last_sign_in_ip"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

end

form:

<%= simple_form_for @store,:html => { :multipart => true } do |f| %>
    <%= f.input :name, label: "Spaeti Name" %>
    <%= f.input :address, label: "Address" %>
    <%= f.input :address_line2, label: "City", input_html: { value: "Berlin" } %>
    <%= f.input :address_line3, label: "Country", input_html: { value: "Germany"} %>
    <%= f.input :beer_cost, label: "Cost of a Berliner Pilsner" %>
    <%= select_tag(:toilet_id, options_for_select(@toilets), :prompt => "Toilet available?" ) %>
    <%= f.input :facilities, label: "Facilities" %>

    <%= f.button :submit %>
<% end %>

Solution

  • When it hits the create action it looks like '@store.save' is returning false and so it's rendering the 'new' template. However, the @toilets variable is not being set in the create action so the template sees it as nil.

    Add this line to your create action as well and that will remove the error you're seeing:

    @toilets = Toilet.pluck(:toilet_available, :id) 
    

    You could also set it in a before_action for the actions you wanted:

    before_action :set_toilets, except: [:show, :index]
    
    private
    
    def set_toilets 
      @toilets = Toilet.pluck(:toilet_available, :id) 
    end