[英]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.