user6087399
user6087399

Reputation:

undefined method `map' error on should create model test

app/controllers/products_controller.rb:

class ProductsController < ApplicationController
    before_action :set_product, only: [:show, :edit, :update, :destroy]
    before_action :authenticate_user!, except: [:index, :show]

    # GET /products
    # GET /products.json
    def index
        #@products = Product.all
        @products = Product.all
        if params[:search]
            @products = Product.search(params[:search]).order("created_at DESC").paginate(page: params[:page], per_page: 5)
        else
            @products = Product.all.order('created_at DESC').paginate(page: params[:page], per_page: 5)
        end

        if session[:cart] then
            @cart = session[:cart]
        else
            @cart = {}
        end

    end

    # GET /products/1
    # GET /products/1.json
    def show
    end

    # GET /products/new
    def new
        if current_user.admin?
            #@product = Product.new
            @product = Product.new
            @categories = Category.all.map{|c| [ c.name, c.id ] }
        end
    end

    # GET /products/1/edit
    def edit
        if current_user.admin?
             @categories = Category.all.map{|c| [ c.name, c.id ] }
        end
    end

    # POST /products
    # POST /products.json
    def create
        if current_user.admin?
            @product = Product.new(product_params)
            @product.category_id = params[:category_id]

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

    # PATCH/PUT /products/1
    # PATCH/PUT /products/1.json
    def update
        if current_user.admin?
            @product.category_id = params[:category_id]
            respond_to do |format|
                if @product.update(product_params)
                format.html { redirect_to @product, notice: 'Product was successfully updated.' }
                format.json { render :show, status: :ok, location: @product }
                else
                format.html { render :edit }
                format.json { render json: @product.errors, status: :unprocessable_entity }
                end
            end
        end
    end

    # DELETE /products/1
    # DELETE /products/1.json
    def destroy
        if current_user.admin?
            @product.destroy
            respond_to do |format|
                format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
                format.json { head :no_content }
            end
        end
    end

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

    # Never trust parameters from the scary internet, only allow the white list through.
    def product_params
        params.require(:product).permit(:title, :description, :photo, :price, :category, :subcategory)
    end
end

app/views/products/new.html.erb:

<h1>New Product</h1>

<%= render 'form' %>

<%= link_to 'Back', products_path %>

app/views/products/_form.html.erb:

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

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

  <div class="field">
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :description %><br>
    <%= f.text_area :description %>
  </div>
  <div class="field">
    <%= f.label :image %><br>
    <%= f.file_field :photo %>
  </div>
  <div class="field">
    <%= f.label :price %><br>
    <%= f.number_field :price, :step => "0.01" %>
  </div>
  <div class="field">
    <%= f.label :category %><br>
    <%= select_tag(:category_id, options_for_select(@categories), :prompt => "Select one!") %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

My test in test/controllers/products_controller_test.rb:

  test "should create product" do
    sign_in users(:admin)
    assert_difference('Product.count') do
      post :create, product: { category_id: @product.category, description: @product.description, photo_content_type: @product.photo_content_type, photo_file_name: @product.photo_file_name, photo_file_size: @product.photo_file_size, photo_updated_at: @product.photo_updated_at, price: @product.price, title: @product.title }
    end

    assert_redirected_to product_path(assigns(:product))
  end

Causing this error:

Finished in 0.816541s, 24.4936 runs/s, 35.5157 assertions/s.

  1) Error:
ProductsControllerTest#test_should_create_product:
ActionView::Template::Error: undefined method `map' for nil:NilClass
    app/views/products/_form.html.erb:32:in `block in _app_views_products__form_html_erb__4489093195482743578_70189257075560'
    app/views/products/_form.html.erb:1:in `_app_views_products__form_html_erb__4489093195482743578_70189257075560'
    app/views/products/new.html.erb:3:in `_app_views_products_new_html_erb__3438187527367239596_70189257283820'
    app/controllers/products_controller.rb:57:in `block (2 levels) in create'
    app/controllers/products_controller.rb:52:in `create'
    test/controllers/products_controller_test.rb:29:in `block (2 levels) in <class:ProductsControllerTest>'
    test/controllers/products_controller_test.rb:28:in `block in <class:ProductsControllerTest>'

20 runs, 29 assertions, 0 failures, 1 errors, 0 skips

I'm running my tests with rake test:functionals. The other tests are fine but only this test is problematic.

There is even no any method named map in app/views/products/_form.html.erb:. So I don't have any idea for cause of this error.

If you want to look to the whole project: https://github.com/mertyildiran/SCOR

CHANGES:

According to answers:

In form I made this change: <%= f.collection_select(:category_id, Category.all, :id, :name) %>

In products controller: params.require(:product).permit(:title, :description, :photo, :price, :category_id)

And new version of test:

  test "should create product" do
    sign_in users(:admin)
    image = fixture_file_upload "../../public/assets/products/1/original/" + @product.photo_file_name
    puts image.inspect
    assert_difference('Product.count') do
      post :create, product: { category_id: @product.category, description: @product.description, photo: image, price: @product.price, title: @product.title }
    end

    assert_redirected_to product_path(assigns(:product))
  end

We get rid of error but Failure persist, stdout:

# Running:

.......#<Rack::Test::UploadedFile:0x007f5a1894fdf8 @content_type=nil, @original_filename="ra_unisex_tshirt_x1000_fafafa-ca443f4786_front-c_235_200_225_294-bg_f8f8f8_(7).jpg", @tempfile=#<Tempfile:/tmp/ra_unisex_tshirt_x1000_fafafa-ca443f4786_front-c_235_200_225_294-bg_f8f8f8_(7).jpg20160515-31919-1j7oypa>>
F............

Finished in 0.800235s, 24.9926 runs/s, 37.4890 assertions/s.

  1) Failure:
ProductsControllerTest#test_should_create_product [/home/mertyildiran/Documents/SCOR/test/controllers/products_controller_test.rb:25]:
"Product.count" didn't change by 1.
Expected: 3
  Actual: 2

20 runs, 30 assertions, 1 failures, 0 errors, 0 skips

Validation Failure Cause:

<div id="error_explanation">
  <h2>2 errors prohibited this product from being saved:</h2>

  <ul>
    <li>Photo content type is invalid</li>
    <li>Photo is invalid</li>
  </ul>
</div>

Fixed with this image.content_type = @product.photo_content_type

Upvotes: 0

Views: 837

Answers (2)

max
max

Reputation: 101891

When you call post :create, product: ... in your test a validation is failing which causes the new view to be rendered.

The new view then causes an error by calling options_for_select(@categories) but @categories is not defined - in other words nil. options_for_select expects the argument to be an array and calls .map on nil.

Thats not the only problem - you are not nesting the input properly either.

<%= select_tag(:category_id, options_for_select(@categories), :prompt => "Select one!") %>

Would end up in params[:category_id] not params[:product][:category_id].

Instead what you want to do is use collection_select and bind it to the model:

<div class="field">
  <%= f.label :category_id %><br>
  <%= f.collection_select(:category_id, Category.all, :id, :name) %>
</div>

You should also ensure that you are whitelisting the correct param - and if you have a hard time keeping track of them make sure you test it as well!

def product_params
  params.require(:product).permit(:title, :description, :photo, :price, :category_id, :subcategory)
end

If you want to debug exactly what attribute causes the validation failure there are a few options:

  • assigns(:product) will give you the @product instance variable from your controller. assert_equals(@product.errors.full_messages, []) gives you a list. You have to call the controller action first!
  • You can do assert_equals(@response.body, '') to get a dump of the page HTML. You have to call the controller action first!
  • Keep a tab on the logs with $ tail -f logs/test.log

Upvotes: 1

Matouš Bor&#225;k
Matouš Bor&#225;k

Reputation: 15944

From the stack trace it appears that the validation failed when trying to save the @product and the controller wants to render the new template. But you don't have the @categories variable defined for this scenario, that is probably where the map call for nil is getting from.

So, probably you should do both of the following:

  • add the @categories array to the failed save part of your create and update actions in your controller so that the failed-save scenario works ok
  • fix the test code so that the validations do not fail when creating the product.

Upvotes: 0

Related Questions