未定义的方法“映射”错误应该创建模型测试

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 %>

我的测试在 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

我 运行 我的测试 rake test:functionals。其他测试都没问题,只有这个测试有问题

app/views/products/_form.html.erb:中甚至没有任何名为map的方法。所以我不知道这个错误的原因。

如果您想查看整个项目:https://github.com/mertyildiran/SCOR

变化:

根据回答:

我在表格中进行了此更改:<%= f.collection_select(:category_id, Category.all, :id, :name) %>

在产品控制器中:params.require(:product).permit(:title, :description, :photo, :price, :category_id)

以及新版测试:

  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

我们摆脱了错误,但失败仍然存在,标准输出:

# 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

验证失败原因:

<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>

已修复 image.content_type = @product.photo_content_type

从堆栈跟踪来看,似乎在尝试保存 @product 时验证失败,并且控制器想要呈现 new 模板。但是您没有为这种情况定义 @categories 变量,这可能是对 nilmap 调用的来源。

因此,您可能应该执行以下两项操作:

  • @categories 数组添加到控制器中 createupdate 操作的失败保存部分,以便失败保存场景正常工作
  • 修复测试代码,以便在创建产品时验证不会失败。

当您在测试中调用 post :create, product: ... 时,验证失败导致呈现新视图。

new 视图然后通过调用 options_for_select(@categories) 导致错误,但 @categories 未定义 - 换句话说 niloptions_for_select 期望参数是一个数组并在 nil.

上调用 .map

这不是唯一的问题 - 您也没有正确嵌套输入。

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

最终会出现在 params[:category_id] 而不是 params[:product][:category_id]

您要做的是使用 collection_select 并将其绑定到模型:

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

您还应确保将正确的参数列入白名单 - 如果您很难跟踪它们,请确保也对其进行测试!

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

如果您想准确调试导致验证失败的属性,有几个选项:

  • assigns(:product) 将为您提供控制器中的 @product 实例变量。 assert_equals(@product.errors.full_messages, []) 给你一个列表。您必须先调用控制器操作!
  • 您可以执行 assert_equals(@response.body, '') 来获取页面 HTML 的转储。您必须先调用控制器操作!
  • 使用 $ tail -f logs/test.log
  • 在日志上保留一个标签