简体   繁体   English

如何验证和测试Rails 4中关联对象的串联创建?

[英]How do I validate and test the tandem creation of associated objects in Rails 4?

In my app, when a User is initialized, I want them to build 5 items. 在我的应用中,初始化用户后,我希望他们构建5个项目。 I've seen tests that assert there are, for example, expect(@user.items.count).to eq(5) . 我见过断言存在例如expect(@user.items.count).to eq(5) ..的测试。 However, I've been trying to validate the length of items and test the validation itself, not the number of objects associated with a user. 但是,我一直在尝试验证项目的长度并测试验证本身,而不是与用户关联的对象数。 Is this even possible? 这有可能吗? If so, what's the best way of going about this? 如果是这样,最好的方法是什么?

Here is the relevant code I have so far. 这是我到目前为止的相关代码。

class User < ActiveRecord::Base
  ITEMS_ALLOWED = 5
  has_many :items

  validates :items, length: {is: ITEMS_ALLOWED}
  after_initialize :init_items

  def init_items
    ITEMS_ALLOWED.times { items.build }
  end
...

My relevant test, using RSpec, Faker and FactoryGirl 我的相关测试,使用RSpec,Faker和FactoryGirl

describe User do
  before :each do
    @user = build(:user, username: "bob")
    @user.save
  end

  it "is invalid with more than 5 items" do
    user3 = build(:user)
    user3.save
    expect(user3.items.create(name: "test")).not_to be_valid
  end
end

Currently the test tries to validate the item that's created. 当前,测试将尝试验证创建的项目。 I tried to move the validation to the Item class instead, but I'm receiving the error, undefined method items for nil on the line that tries to call user.items.count . 我尝试将验证移至Item类,但是在尝试调用user.items.count的行上,我收到了错误的nil未定义方法项。

class Item < ActiveRecord::Base
  belongs_to :user

  validates :number_of_items, length: {is: 5}

  def number_of_items
    errors.add("User must have exactly 5 items.") unless user.items.count == 5
  end
end

================ Update: Failure Message when there are no validations in the Item class. ================更新:失败消息,在Item类中没有验证时。

Failures:

  1) User initialization is invalid with more than 5 items
     Failure/Error: expect(user3.items.create(name: "test")).not_to be_valid
       expected #<Item id: 16, name: "test", user_id: 3, photo: nil, created_at: "2014-01-14 00:24:11", updated_at: "2014-01-14 00:24:11", photo_file_name: nil, photo_content_type: nil, photo_file_size: nil, photo_updated_at: nil, description: nil> not to be valid

When you create your User instance, the init_items is being called and the Item instances are being created. 创建User实例时,将init_items并创建Item实例。 However, the user's id is not defined at that point, so the user_id value of the created items is nil . 但是,此时尚未定义用户的id,因此创建的项的user_id值为nil This in turn results in the table's user method returning nil in your number_of_items validation. 依次导致表的user方法在number_of_items验证中返回nil

When you remove the Item validations, then you're RSpec example will fail because you're doing a validation on an Item (ie the result of user3.items.create ) rather than validating the resulting User . 当删除Item验证时,RSpec示例将失败,因为您正在对Item进行验证(即user3.items.create的结果),而不是验证结果User Instead, you can do something like this: 相反,您可以执行以下操作:

user3.items.create(name: "test")
expect(user3).to_not be_valid

I'd avoid using after_initialize . 我会避免使用after_initialize It is called whenever an object is instantiated, even after merely calling User.find . 每当实例化对象时都会调用它,即使仅调用User.find之后User.find If you must use it, add a test for new_record? 如果必须使用它,请为new_record?添加测试new_record? so that the items are only added for new User 's. 以便仅为新User添加项目。

An alternative approach is to write a builder method to use instead of User.new. 一种替代方法是编写一个生成器方法来代替User.new使用。

class User < ActiveRecord::Baae
  ITEMS_ALLOWED = 5
  has_many :items

  validates :items, length { is: ITEMS_ALLOWED }

  def self.build_with_items
    new.tap do |user|
      user.init_items
    end
  end

  def init_items
    ITEMS_ALLOWED.times { items.build }
  end
end

describe User do
  context "when built without items" do
    let(:user) { User.new }

    it "is invalid" do
      expect(user.items.size).to eq 0
      expect(user.valid?).to be_false
    end
  end

  context "when built with items" do
    let(:user) { User.build_with_items }

    it "is valid" do
      expect(user.items.size).to eq 5
      expect(user.valid?).to be_true
    end
  end
end

This allows you to separate the item initialization from the user initialization, in case you end up wanting to have a User without items. 这可以让你的项目从初始化用户初始化分开,在你最终希望有一个情况下User没有项目。 In my experience, this works out better than requiring all newed up objects to be built the same way. 以我的经验,这比要求以相同方式构建所有新对象要好。 The tradeoff is that you now need to use User.build_with_items in the new action in the controller. 折衷方案是您现在需要在控制器的new操作中使用User.build_with_items

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM