Ruby on rails 在(:create)之后设置的FactoryGirl属性在被引用之前不会持续存在?
很抱歉标题含糊不清,这个问题有很多可移动的部分,所以我想只有在看到我的代码后才能清楚。我相当肯定我知道这里发生了什么,我正在寻找关于如何以不同方式进行的反馈: 我有一个用户模型,它通过ActiveRecord回调设置uuid attr(这实际上是一个“SetsUuid”问题,但效果是这样的): 这是我的用户工厂(我尝试了几种不同的方式设置uuid,但我的问题(我将在下面讨论)总是一样的。我认为这种方式(带有特征)是最优雅的,所以这就是我在这里提出的): 最后,问题是: 只有在规范中route.call()之前引用user.uuid时,这才有效 与中一样,如果我只是在route.call()之前添加一行“user.uuid”,那么一切都会按预期进行 如果我没有这一行,规范将失败,因为用户的uuid实际上没有通过工厂中trait中的after(:create)回调得到更新,因此user.find_by_uuid!控制器中的行找不到用户 我只是想抢先发表另一条评论:我不是在问如何重新编写这个规范,使它能够像我所希望的那样工作。我已经知道很多方法可以做到这一点(最简单、最明显的是手动更新规范中的user.uuid,而完全忘记在工厂中设置uuid)。我在这里要问的是,为什么factorygirl会这样做? 我知道它与惰性属性有关(如果我有一行代码来计算user.uuid,它会神奇地工作,这一点很明显),但为什么呢?还有,更好的是:有没有什么方法可以让我在这里做我想做的事情(在工厂设置uuid),让一切都按照我的意愿工作?我认为rspec/factorygirl的使用看起来相当优雅,所以我真的希望它能像这样工作Ruby on rails 在(:create)之后设置的FactoryGirl属性在被引用之前不会持续存在?,ruby-on-rails,ruby,rspec,factory-bot,Ruby On Rails,Ruby,Rspec,Factory Bot,很抱歉标题含糊不清,这个问题有很多可移动的部分,所以我想只有在看到我的代码后才能清楚。我相当肯定我知道这里发生了什么,我正在寻找关于如何以不同方式进行的反馈: 我有一个用户模型,它通过ActiveRecord回调设置uuid attr(这实际上是一个“SetsUuid”问题,但效果是这样的): 这是我的用户工厂(我尝试了几种不同的方式设置uuid,但我的问题(我将在下面讨论)总是一样的。我认为这种方式(带有特征)是最优雅的,所以这就是我在这里提出的): 最后,问题是: 只有在规范中route.c
谢谢你阅读我的长问题!非常感谢您的任何见解您的问题与FactoryGirl的关系不大,而与
让
被懒散地评估有关
:
使用let定义已记忆的辅助对象方法。该值将跨多个服务器缓存
同一示例中的多个调用,但不跨示例调用
请注意,let是惰性计算的:直到第一次才进行计算
调用它定义的方法。你可以用let!强制该方法的
在每个示例之前调用
由于您的测试不会调用
用户
对象,直到达到预期为止,因此不会创建任何内容。要强制rspec加载对象,可以使用let
您应该在初始化之后使用,而不是在\u验证之前使用
回调。这样,回调甚至在调用.valid?
之前就被激发
这里不是工厂女孩,是rspec的let
-如果你叫let代码>它强制创建/保存该对象。@Anthony哇,谢谢!这确实解决了我的问题。你能把你的回复贴出来作为真实的答复吗?这样我就可以接受了?我没有考虑let()中的东西在被引用之前是如何被调用的。因此,我在错误的印象中操作create()总是会立即创建对象,尽管它位于let()中。因此,我的代码无法使用let()的原因是,直到route.call()之后才创建用户对象,此时控制器操作已经运行,这意味着按uuid查找!总是找不到任何东西。谢谢你的评论。我一直想解释一下生成重复uuid的可能性很小,我真的很喜欢你用begin-end编写代码的方式,而我甚至不知道语法是有效的,tbqh。
class User < ActiveRecord::Base
before_validation :set_uuid, on: :create
validates :uuid, presence: true, uniqueness: true
private
def set_uuid
self.uuid = SecureRandom.uuid
end
end
class FoosController < ApplicationController
def add_user
@foo.users << User.find_by_uuid!(@params[:user_id])
render json: {
status: 'awesome controller great job'
}
end
end
RSpec.describe FoosController, type: :controller do
describe '#add_user' do
it_behaves_like 'has @foo' do
it_behaves_like 'has @params', {user_id: 'user-uuid'} do
context 'user with uuid exists' do
let(:user) { create(:user_with_uuid, uuid: params[:user_id]) } # params is set by the 'has @params' shared_context
it 'adds user with uuid to @foo' do
route.call() # route is defined by a previous let that I truncated from this example code
expect(foo.users).to include(user) # foo is set by the 'has @foo' shared_context
end
end
end
end
end
end
FactoryGirl.define do
factory :user do
email { |n| "user-#{n}@example.com" }
first_name 'john'
last_name 'naglick'
phone '718-555-1234'
trait :with_uuid do
after(:create) do |user, eval|
user.update!(uuid: eval.uuid)
end
end
factory :user_with_uuid, traits: [:with_uuid]
end
end
class User < ActiveRecord::Base
before_initialization :set_uuid!, on: :create, if: :set_uuid?
validates :uuid, presence: true, uniqueness: true
private
def set_uuid!
# we should also check that the UUID
# does not actually previously exist in the DB
begin
self.uuid = SecureRandom.uuid
end while User.where(uuid: self.uuid).any?
end
def set_uuid?
self.uuid.nil?
end
end
let!(:user) { create(:user) }
it 'adds user with uuid to @foo' do
post :add_user, user_id: user.uuid, { baz: 3 }
end