has_many :通过创建 child after_save --> ActionView::Template::Error

has_many :through creating child after_save --> ActionView::Template::Error

我有三个模型:列表、食物和数量。 List 和 Food 通过 Quantity via has_many :through 关联。 model association是按我的要求做的,但是我测试的时候出错了

test_valid_list_creation_information#ListsCreateTest (1434538267.92s)
ActionView::Template::Error:         ActionView::Template::Error: Couldn't find Food with 'id'=14
app/views/lists/show.html.erb:11:in `block in _app_views_lists_show_html_erb__3286583530286700438_40342200'
app/views/lists/show.html.erb:10:in `_app_views_lists_show_html_erb__3286583530286700438_40342200'
test/integration/lists_create_test.rb:17:in `block (2 levels) in <class:ListsCreateTest>'
test/integration/lists_create_test.rb:16:in `block in <class:ListsCreateTest>'
app/views/lists/show.html.erb:11:in `block in _app_views_lists_show_html_erb__3286583530286700438_40342200'
app/views/lists/show.html.erb:10:in `_app_views_lists_show_html_erb__3286583530286700438_40342200'
test/integration/lists_create_test.rb:17:in `block (2 levels) in <class:ListsCreateTest>'
test/integration/lists_create_test.rb:16:in `block in <class:ListsCreateTest>'

我的目标是在每次创建列表时创建一个新的数量(与该列表关联)。每个数量都有数量 food_id 和 list_id.

错误中,数字14("Food with 'id'=14)是从1到[=57=之间随机选择一个数字生成的。Food.count等于食物的数量objects 在 test/fixtures/foods.yml 中,所以食物肯定是可以识别的,至少当我 运行 Food.count 时是这样。那么为什么 'id'=14 的食物不能识别存在吗?

我认为列表控制器、装置或集成测试有问题。导致测试失败的任何原因似乎都不会影响性能(控制台和 server/user 界面中的一切正常),但我正在尝试理解 TDD 并编写好的测试,因此我将不胜感激任何指导。

列出型号:

class List < ActiveRecord::Base
  has_many :quantities
  has_many :foods, :through => :quantities
  validates :days, presence: true
  validates :name, uniqueness: { case_sensitive: false }

  after_save do   
    Quantity.create(food_id: rand(Food.count), list_id: self.id, amount: rand(6)) 
  end
end

夹具数量:

one:
  food: grape
  list: weekend
  amount: 1
two:
  food: banana
  list: weekend
  amount: 1

注意:Quantities 夹具先前组织如下...

one:
  food_id: 1
  list_id: 1
  amount: 1

...好像没什么区别

lists_create 集成测试:

require 'test_helper'
class ListsCreateTest < ActionDispatch::IntegrationTest
  test "invalid list creation information" do
    get addlist_path
    assert_no_difference 'List.count' do
      post lists_path, list: { days:  "a",
                               name: "a" * 141 }
    end
    assert_template 'lists/new'
  end

  test "valid list creation information" do
    get addlist_path
    assert_difference 'List.count', 1 do
      post_via_redirect lists_path, list: {
                                            days: 2,
                                            name: "example list"
                                          }
      end
      assert_template 'lists/show'
  end
end

和app/views/lists/show.html.erb错误中引用:

<% provide(:title, @list.name) %>
<div class="row"><aside class="col-md-4"><section class="user_info">
      <h1> <%= @list.name %></h1>
      <p><%= @list.days %> day(s)</p><p>
       <% Quantity.where(:list_id => @list.id).each do |f| %>
       <%= "#{f.amount} #{Food.find(f.food_id).name}" %>
       <% end %>
      </p></section></aside></div><%= link_to "edit the properties of this list", edit_list_path %>

感谢您的任何建议或参考。如果您需要其他您认为相关的代码或信息,请告诉我。我希望使用固定装置而不是其他方法(例如 FactoryGirl)来完成这一切,即使这意味着需要一些额外的代码。

Rails 4.2.3,Cloud9。开发数据库 = SQLite3,生产数据库 = postgres heroku。

除了在 after_save 回调中创建一个随机值非常奇怪(我认为你正在做练习,但无论如何最好从一开始就使用良好的做法),你永远不应该使用 rand(Model.count) 获取示例记录。主要有两个问题:

  1. rand(upper_bound) 方法 returns 零和 upper_bound 参数之间的数字,但不能保证零是第一个创建的 ID。我正在使用 PostgreSQL,第一个模型的 ID 为 1。您可以指定一个范围 (rand(1..upper_bound)),但无论如何您都是在赌当前数据库的工作方式。
  2. 您假设所有记录在任何给定时间都按顺序存在,但这并不总是正确的。如果你删除了一条记录并且它的 id 是随机选择的,你会得到一个错误。该库还可以使用任何策略来创建固定装置,因此最好不要对它的工作原理做任何假设。

如果你真的需要随机选择一条记录,我建议简单地使用数组的sample方法:Food.all.sample。它很慢,但它有效。如果您需要优化,还有其他选择。

现在,我真的建议不惜一切代价避免随机值,只在必要时使用它们。很难测试,也很难跟踪错误。另外,我会避免在回调中创建关系,它会迅速发展成无法管理的混乱。

我发布了一个答案,因为在实施建议后,我的错误消失了,我想我对发生的事情有了更好的理解。

以前,我在使用关系创建列表时在列表模型中创建了数量。该关系现在位于控制器中,而不是模型中。

列出没有关系的模型:

class List < ActiveRecord::Base
  has_many :quantities
  has_many :foods, :through => :quantities
  validates :days, presence: true
  validates :name, uniqueness: { case_sensitive: false }
end

数量夹具和 lists_create 集成测试未更改。

之前这个 show.html.erb 包含一个查询。现在,它只有在列表控制器中定义的@quantities。查询在控制器中,而不是视图中。

app/views/lists/show.html.erb:

<% provide(:title, @list.name) %>
<div class="row"><aside class="col-md-4"><section class="user_info">
  <h1> <%= @list.name %></h1>
  <p><%= @list.days %> day(s)</p>
  <p><%= @quantities %></p>
  </section></aside></div><%= link_to "edit the properties of this list", edit_list_path %>

在 show 方法中使用查询的列表控制器(过滤具有适当 list_id 的数量)和 create 方法中的关系(在创建列表时创建新数量)。

class ListsController < ApplicationController

  def show
    @list = List.find(params[:id])
    @quantities = []
    Quantity.where(:list_id => @list.id).each do |f|
        @quantities.push("#{f.amount} #{Food.find(f.food_id).name}")
    end
  end
# ...
  def create
  @list = List.new(list_params) 
  if @list.save
    flash[:success] = "A list has been created!"
    @a = Food.all.sample.id
    @b = Food.all.sample.id

    Quantity.create(food_id: @a, list_id: @list.id, amount: rand(6)) 
    if (@a != @b)
      Quantity.create(food_id: @b, list_id: @list.id, amount: rand(6))
    end
    redirect_to @list
    else
      render 'new'
    end
  end
# ...
end

如果我没理解错的话,我是在误用模型和视图,并且不恰当地将 rand 与 Food.count 一起使用。

如果您认为我遗漏了任何内容或者您可以推荐任何内容来改进我的代码,请告诉我。感谢@mrodrigues、@jonathan 和@vamsi 的帮助!