mgh
mgh

Reputation: 1021

undefined method `map' for nil:NilClass when use paperclip in rails

I get a wonderful error in a rails project. I have a controller => prdocuts_controller.rb. I use options_from_collection_for_select when I want create a new product. Every things are ok when I create a new product, and I can do this, but when I add file_field to upload a picture to product, I get below error:

NoMethodError in Products#create
undefined method `map' for nil:NilClass

and when I remove file.field, every thing are ok again and new product is save to database.

products_controller.rb

class ProductsController < ApplicationController
  before_action :set_product, only: [:show, :edit, :update, :destroy]
  layout "product"
  # GET /products
  # GET /products.json
  def index
    @categories = Category.all
    @products = Product.all
  end

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

  # GET /products/new
  def new
    @categories = Category.all
    @product = Product.new
  end

  # GET /products/1/edit
  def edit
    @categories = Category.all
    @product = Product.all
  end

  # POST /products
  # POST /products.json
  def create
    @product = Product.new(product_params)
    @product.responsibility = current_user.responsibility
    @product.date_time = Time.now

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

  # PATCH/PUT /products/1
  # PATCH/PUT /products/1.json
  def update
    respond_to do |format|
      if @product.update(product_params)
        format.html { redirect_to @product, notice: 'Product was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @product.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /products/1
  # DELETE /products/1.json
  def destroy
    @product.destroy
    respond_to do |format|
      format.html { redirect_to products_url }
      format.json { head :no_content }
    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(:category_id, :name, :code, :date_time, :describe, :picture)
    end
end

product.rb

class Product < ActiveRecord::Base
  has_attached_file :picture, :styles => { :medium => "300x300>", :thumb => "100x100>" }, :default_url => "/images/missing.png"
  belongs_to :category
end

views/products/new.html.erb

<%= form_for @product, :html => {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 |msg| %>
                <li><%= msg %></li>
            <% end %>
          </ul>
        </div>
    <% end %>
      <div class="control-group">
        <label class="control-label">Product Name</label>
        <%= f.text_field :name, :class => "m-wrap span8", :placeholder => "enter product name" %>
      </div>
      <div class="control-group">
        <label class="control-label">Product Code</label>
        <%= f.text_field :code, :class => "m-wrap span8", :placeholder => "enter product code" %>
      </div>

      <div class="control-group">
        <label class="control-label" >Category</label>
        <div class="controls">
          <div class="select2-wrapper">
            <select name="product[category_id]" class="select2_category m-wrap span8">
              <%= options_from_collection_for_select(@categories,:id, :name,@product.category_id) %>
            </select>
          </div>
        </div>
      </div>

      <div class="control-group">
        <label class="control-label">Product Code</label>
        <%= f.text_area :describe, :class => "m-wrap span8", :placeholder => "enter describe" %>
      </div>
      <br/>
      <%= f.file_field :picture %>
      <br/>
      <div class="action">
        <%= f.submit "Save change", :class => "btn green" %>
      </div>
<% end %>

log of system:

User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 17 ORDER BY "users"."id" ASC LIMIT 1
Command :: identify -format '%wx%h,%[exif:orientation]' "C:/Users/MGH~1.119/AppData/Local/Temp/6f87f43a62513173b9edfea7b58ee2d020140504-660-1luu1b5.jpg[0]" 2>NUL
[paperclip] An error was received while processing: #<Paperclip::Errors::NotIdentifiedByImageMagickError: Paperclip::Errors::NotIdentifiedByImageMagickError>
Command :: identify -format '%wx%h,%[exif:orientation]' "C:/Users/MGH~1.119/AppData/Local/Temp/6f87f43a62513173b9edfea7b58ee2d020140504-660-1luu1b5.jpg[0]" 2>NUL
[paperclip] An error was received while processing: #<Paperclip::Errors::NotIdentifiedByImageMagickError: Paperclip::Errors::NotIdentifiedByImageMagickError>
  Responsibility Load (3.0ms)  SELECT "responsibilities".* FROM "responsibilities" WHERE "responsibilities"."user_id" = ? ORDER BY "responsibilities"."id" ASC LIMIT 1  [["user_id", 17]]
   (0.0ms)  begin transaction
   (0.0ms)  rollback transaction
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
  Rendered products/_form.html.erb (12.0ms)
  Rendered products/new.html.erb within layouts/product (13.0ms)
Completed 500 Internal Server Error in 132ms

ActionView::Template::Error (undefined method `map' for nil:NilClass):
    27:         <div class="controls">
    28:           <div class="select2-wrapper">
    29:             <select name="product[category_id]" class="select2_category m-wrap span8">
    30:               <%= options_from_collection_for_select(@categories,:id, :name,@product.category_id) %>
    31:             </select>
    32:           </div>
    33:         </div>

I use 'paperclip', '~> 3.0' in this project. Any one can help me to find the problem?

Upvotes: 0

Views: 839

Answers (2)

user740584
user740584

Reputation:

The cause of the error is @categories is nil. Your product cannot save because of the ImageMagick errors so when you go back to show the view @categories is not defined.

You need to include

@categories = Category.all

in your create method in the case where the save fails. Alternatively consider a before_filter in the controller so you can avoid having to repeat code in all your actions.

Why you are getting ImageMagick errors in the first place is another matter, but this is the cause of your map error. Paperclip will throw a NotIdentifiedByImageMagickError error where ImageMagick cannot parse the file - normally because it is not a valid image. You can test this out at the command line.

Upvotes: 1

tompave
tompave

Reputation: 12412

#map could be called internally when processing this line from new.html.erb:

<%= options_from_collection_for_select(@categories,:id, :name,@product.category_id) %>

I'd guess that it's called on @categories.

Here is the problem:
When the user requests the /new page, the new action is processed and @categories is normally initialized.
However, when you render 'new' in create, Rails will render that template without going through the action in the controller, thus @categories will be nil.

Try with:

def new
  @categories = Category.all
  @product = Product.new
end


def create
  @product = Product.new(product_params)
  @product.responsibility = current_user.responsibility
  @product.date_time = Time.now

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

Upvotes: 0

Related Questions