Ruby on rails 在(:create)之后设置的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

很抱歉标题含糊不清,这个问题有很多可移动的部分,所以我想只有在看到我的代码后才能清楚。我相当肯定我知道这里发生了什么,我正在寻找关于如何以不同方式进行的反馈:

我有一个用户模型,它通过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的使用看起来相当优雅,所以我真的希望它能像这样工作


谢谢你阅读我的长问题!非常感谢您的任何见解

您的问题与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