1

I want people to define a course name before writing a spotlight. I did this by adding following code to the spotlight model

class Spotlight < ActiveRecord::Base
  validates :name, presence: true
end

Before adding the validation I could write spotlights without any name. If I try that now I get following error message:

undefined method `map' for nil:NilClass
Extracted source (around line #29):

     </div>
     <div class="field">
       <%= f.label :name, "Opleiding" %><br>
       <%= f.collection_select(:name,  @colli,  :name, :name, {prompt: 'Selecteer een opleiding'}, {id: 'collis_select'}) %>
     </div>
     <div class="field">
       <%= f.label :teaser %><br>

What is going on here? The collection select is the base for an ajax call I do to fill up other fields.

View

<%= form_for(@spotlight) do |f| %>
  <% if @spotlight.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@spotlight.errors.count, "error") %> prohibited this spotlight from being saved:</h2>

      <ul>
      <% @spotlight.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :spotlight, "In de kijker" %><br>
    <%= f.check_box :spotlight %>
  </div>
  <div class="field">
    <%= f.label :start, "Start in de kijker" %><br>
    <%= f.datetime_select :start %>
  </div>
  <div class="field">
ruby-on-rails
    <%= f.label :stop, "Stop in de kijker" %><br>
    <%= f.datetime_select :stop %>
  </div>
  <div class="field">
    <%= f.label :name, "Opleiding" %><br>
    <%= f.collection_select(:name,  @colli,  :name, :name, {prompt: 'Selecteer een opleiding'}, {id: 'collis_select'}) %>
  </div>
  <div class="field">
    <%= f.label :teaser %><br>
    <%= f.text_area :teaser, size: "85x10", id: 'teasers_select' %>
  </div>
  <div class="field">
    <%= f.label :coursedate, "Startdatum opleiding" %><br>
    <%= f.datetime_select :coursedate, id: 'startdate_select' %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

<script>
  $(document).ready(function() {
    $('#collis_select').change(function() {
      $.ajax({
        url: "<%= update_teasers_path %>",
        data: {
          name : $('#collis_select').val()
        },
        dataType: "script"
      });
    });
  });
</script>

Update teaser view

$('#teasers_select').val("<%= escape_javascript(@teaser) %>");

Controller

class SpotlightsController < ApplicationController
  before_action :set_spotlight, only: [:show, :edit, :update, :destroy]
  before_action :load_colli, only: [:new, :edit]

  def index
    @spotlights = Spotlight.all.order('spotlight DESC, start, stop')
  end

  def show
  end

  def new
    @spotlight = Spotlight.new
  end

  def edit
  end

  def create
    @spotlight = Spotlight.new(spotlight_params)

    respond_to do |format|
      if @spotlight.save
        format.html { redirect_to @spotlight, notice: 'Spotlight was successfully created.' }
        format.json { render action: 'show', status: :created, location: @spotlight }
      else
        format.html { render action: 'new' }
        format.json { render json: @spotlight.errors, status: :unprocessable_entity }
      end
    end
  end

  def update
    respond_to do |format|
      if @spotlight.update(spotlight_params)
        format.html { redirect_to @spotlight, notice: 'Spotlight was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @spotlight.errors, status: :unprocessable_entity }
      end
    end
  end

  def update_teasers
    # updates artists and songs based on genre selected
    colli = Colli.where(name: params[:name])

    # map to name and id for use in our options_for_select
    @teaser = colli.first.teaser
  end

  def destroy
    @spotlight.destroy
    respond_to do |format|
      format.html { redirect_to spotlights_url }
      format.json { head :no_content }
    end
  end

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

    # Never trust parameters from the scary internet, only allow the white list through.
    def spotlight_params
      params.require(:spotlight).permit(:spotlight, :start, :stop, :name, :teaser, :coursedate)
    end

    def load_colli
      @colli = Colli.select(:name).distinct.order('name')
    end

end

Can somebody explain what seems to be the problem? What is the "map" function the error is referring to?

Christoph
  • 1,347
  • 2
  • 19
  • 36
  • Could you please explain that the error started coming up when you added validation or select? – Nikita Singh Oct 13 '14 at 17:33
  • The error came when I added the validation. – Christoph Oct 13 '14 at 18:04
  • Take a look at the usage of collection_select http://stackoverflow.com/questions/8907867/can-someone-explain-collection-select-to-me-in-clear-simple-terms and make sure your instance obj. in your view is not nil. – bkdir Oct 13 '14 at 18:06
  • That instance is nil but that is what I am trying to prevent by using the validation. If people don't use the collection select it should give a validation error. The problem is I do not know how to fix this. I want to catch this error in a flash message like all the other errors so I can message the user that he is obliged to select a course (colli). – Christoph Oct 13 '14 at 18:09

2 Answers2

0

It looks like your @colli object is nil.

<%= f.collection_select(:name,  @colli,  :name, :name, {prompt: 'Selecteer een opleiding'}, {id: 'collis_select'}) %>

This has nothing to do with the presence validation. Make sure the collection_select method is receiving a @colli instance variable. Right now it's receiving nil.

The map function is an instance method in the class Array. It receives a block, iterates over an array, and returns a new array with elements returned from the block. You can't call map on nil; you will get the error you are seeing above.

You can check if @colli is nil by raising it:

def load_colli
  @colli = Colli.select(:name).distinct.order('name')
  raise @colli.to_s
end
Mohamad
  • 34,731
  • 32
  • 140
  • 219
  • Hi Mohamad your raise @colli.to_S function does not work. Probably because colli is an array? I am getting a runtime error RuntimeError in SpotlightsController#new – Christoph Oct 13 '14 at 19:00
  • Should I add a new record to Colli which states "no selection" and then validate that the "no selection" record is not valid so that the user gets an error message? Is that how it is done? The user should get an flash error message when the colli is not selected using the selectbox. – Christoph Oct 13 '14 at 19:02
  • @Christoph `raise` raises an error. So yes, it does work. The whole point of raise is to see a visual output of what `@colli` is by converting it to a string. If you see a `runtime error nil` it means `@colli` is nil. If you see a `runtime error` follow by a bunch of output showing you an instance and properties of `@colli` then it is not nil. `raise` is just a quick way to see the value of something. It helps when trying to figure out the value of an object. You can try `raise @colli.nil?.to_s` to see if you get `true` or `false`, for example. – Mohamad Oct 13 '14 at 19:44
  • As far as the select box concerned, Rails should handle that automatically for you. Just add a validation to check for the presence of a value for the attribute that `colli` represents. If `colli` is an object, you can validate its presence: `validates :colli, presence: true` and you will have normal error messages like you do for the other attributes. – Mohamad Oct 13 '14 at 19:45
  • Thanks for your clear explanation. I think I already have that with: validates :name, presence: true . The output of the colli select box is the name for a spotlight. Why doesn't rails handles that for me? Sorry I am new in this :) – Christoph Oct 13 '14 at 19:53
  • @Christoph No worries. You must make sure that `@colli` is an array of objects. That's the bottom line. So `@colli` has to have something when it gets to the `collection_select`, otherwise `collection_select` wont work. Does that make sense? It may strike me that your data is setup incorrectly. It sounds like `@colli` should be associated with `Spotlight`? Kind of like `a Post belongs to a category` ? – Mohamad Oct 13 '14 at 21:07
  • I understand what you mean. I deliberately didn't make an association with spotlight because the collis are imported. The id's can change every time. The collis have a unique id in the import (a guid) but I do not know how to setup rails so it uses the imported guid instead of a normal integer id. – Christoph Oct 14 '14 at 07:06
0

If you change the update_teasers function to:

def update_teasers
 # updates artists and songs based on genre selected
 @colli = Colli.where(name: params[:name])

 # map to name and id for use in our options_for_select
 @teaser = @colli.first.teaser
end

that should fix the issue. This isn't an issue with the presence validation.

EDIT:Sorry, that didn't work. Next I would try to set the @colli variable in the create function as follows. That way the variable is still set when it renders the new action upon a failed save.

def create
 @spotlight = Spotlight.new(spotlight_params)
 @colli = Colli.where(name: params[:name])


 respond_to do |format|
  if @spotlight.save
    format.html { redirect_to @spotlight, notice: 'Spotlight was successfully created.' }
    format.json { render action: 'show', status: :created, location: @spotlight }
  else
    format.html { render action: 'new' }
    format.json { render json: @spotlight.errors, status: :unprocessable_entity }
  end
 end
end