【问题标题】:Create FactoryBot User with has_one relationship创建具有 has_one 关系的 FactoryBot 用户
【发布时间】:2019-03-05 21:41:21
【问题描述】:

背景:

我正在尝试创建一个与 has_one/belongs_to 相关的 FactoryBot 对象

 User has_one Car

 Car has_one Style

 Style has an attribute {style_number:"1234"}

问题

我的控制器引用了用户,用户 has_one Car,Car has_one Style,我需要在 FactoryBot 中设置这些值。

如何创建一个同时拥有 Car 对象和 Style 对象的用户?

我阅读了文档https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md

但是,我不明白他们如何推荐这样做。想通了,我需要嵌套这三个对象,但对语法感到困惑。

控制器

before_action :authenticate_user!
before_action :set_steps
before_action :setup_wizard    

include Wicked::Wizard

def show
    @user = current_user
    @form_object = form_object_model_for_step(step).new(@user)
    render_wizard 
end

private

def set_steps
    if style_is_1234
        self.steps = car_steps.insert(1, :style_car)                
    else
        self.steps = car_steps
    end
end

def style_is_1234
    if params.dig(:form_object, :style_number)
        (params.dig(:form_object, :style_number) & ["1234"]).present?
    else
        (current_user.try(:car).try(:style).try(:style_number) & ["1234"]).present?
    end
end

def car_steps
    [:type,:wheel, :brand]
end

Rspec 测试

工厂:用户

FactoryBot.define do
  factory :user, class: User do
    first_name { "John" }
    last_name  { "Doe" }
    email { Faker::Internet.email }
    password { "somepassword" }
    password_confirmation { "some password"}
  end
end

方法之前

 before(:each) do
      @request.env["devise.mapping"] = Devise.mappings[:user]
      user = FactoryBot.create(:user)
      sign_in user

测试

用户需要登录并且 User.car.style.style_number 需要设置为“1234”
context "Requesting with second step CarStyle" do 
            it "should return success"  do
              get :show, params: { :id => 'car_style' }
              expect(response.status).to eq 200
            end
          end

目前此测试失败,因为 User.Car.Style.style_number 未设置为“1234”。

试用 1 (https://github.com/thoughtbot/factory_bot_rails/issues/232)

FactoryBot.define do
      factory :user, class: User do
        first_name { "John" }
        last_name  { "Doe" }
        email { Faker::Internet.email }
        password { "somepassword" }
        password_confirmation { "some password"}
        car
      end
    end

FactoryBot.define do
      factory :car, class: Car do
        make { "Holden" }
        model  { "UTE" }
      end
    end


FactoryBot.define do
      factory :style, class: Style do
        color { "blue" }
        for_car

        trait :for_car do
          association(:styable, factory: :car)
        end
      end
    end

路径 1 出错

系统堆栈错误: 堆栈层太深

步道 2

我试过srng的推荐

编辑:对于多态关联尝试;

FactoryBot.define do
  factory :car, class: Car do
    make { "Holden" }
    model  { "UTE" }
    association :stylable, factory: :style
  end
end

得到错误:

ActiveRecord::RecordInvalid:验证失败:样式必须存在

我认为这是 Rails 5 的问题。 https://github.com/rails/rails/issues/24518

但是,我想通过添加 optional:true 来保留我的代码。有什么办法吗?

步道 3

FactoryBot.define do
   factory :car, class: Car do
     make { "Holden" }
     model  { "UTE" }
     after(:create) do |car|
        create(:style, stylable: car)
     end
   end
 end

尝试了 Srng 的第二个建议,虽然它对他有用,但我得到了一个稍微不同的错误:

ActiveRecord::RecordInvalid: 验证失败:用户必须存在

【问题讨论】:

    标签: ruby-on-rails rspec factory-bot ruby-on-rails-5.2


    【解决方案1】:

    为了创建依赖工厂,您必须为每个模型创建一个工厂,然后只需将依赖 Model 名称添加到您的工厂,即。

        FactoryBot.define do
          factory :user, class: User do
            first_name { "John" }
            last_name  { "Doe" }
            email { Faker::Internet.email }
            password { "somepassword" }
            password_confirmation { "some password"}
            car
          end
        end
    
    FactoryBot.define do
          factory :car, class: Car do
            make { "Holden" }
            model  { "UTE" }
            style
          end
        end
    
    
    FactoryBot.define do
          factory :style, class: Style do
            color { "blue" }
          end
        end
    

    编辑: 相关代码;

    # Factories
    FactoryBot.define do
          factory :user, class: User do
            first_name { "John" }
            last_name  { "Doe" }
            email { Faker::Internet.email }
            password { "somepassword" }
            password_confirmation { "some password"}
            after(:create) do |user|
              user.car ||= create(:car, :user => user)
            end
          end
        end
    
      factory :style, class: Style do
        style_number { "Blue" }
      end
    
      factory :car, class: Car do
        name { "Holden" }
        trait :style do
          association :stylable, factory: :style
        end
    end
    
    #models
    class Car < ApplicationRecord
      has_one :style, as: :styleable
    end
    
    class Style < ApplicationRecord
      belongs_to :styleable, polymorphic: true
      belongs_to :car
    end
    
    # Migrations - The belongs_to is the only important one
    class CreateStyles < ActiveRecord::Migration[5.2]
      def change
        create_table :styles do |t|
          t.string :style_number
          t.belongs_to :stylable, polymorphic: true
          t.timestamps
        end
      end
    end
    
    class CreateCars < ActiveRecord::Migration[5.2]
      def change
        create_table :cars do |t|
          t.string :name
          t.timestamps
        end
      end
    end
    

    【讨论】:

    • 当我尝试上述方法时,ActiveRecord::RecordInvalid:,验证失败:必须存在 Stylable,注意:Style 与 Car 具有多态关联。样式有 stylable_type 和 Stylable_id。有什么特别需要我做的吗?
    • 尝试了您的示例并得到相同的错误:ActiveRecord::RecordInvalid: Validation failed: Stylable must exist
    • 查看更改后的答案,我已经在本地对其进行了测试,并且没有出现错误,因此正在创建它。我不确定你有多少私有方法。
    • 你用的是什么版本的rails?
    • 嗯,好吧,我删除了我编写的所有示例,当我重新编写它们时,它突然坏了……现在我把它拿回来了,但有一个不同的解决方案。我将发布所有相关代码。我没有包括用户,因为多态 Style -> Car 关系会带来麻烦。
    【解决方案2】:

    还有另一种方法可以使用工厂中的瞬态块来实现。 希望下面的sn-p可以帮助你以新的方式探索。

    注意:这未经测试。

    ## To Create a user in test case
    # create(:user) # defaults to 1234 style number
    # create(:user, car_style_number: 5678)
    
    DEFAULT_STYLE_NUMBER = 1234
    
    FactoryBot.define do
      factory :user do
        transient do
          car_style_number { DEFAULT_STYLE_NUMBER }
        end
    
        first_name { "John" }
        last_name  { "Doe" }
        email { Faker::Internet.email }
    
        after(:create) do |user, evaluator|
          user.car = create(:car, car_style_number: evaluator.car_style_number, user: user)
        end
      end
    end
    
    FactoryBot.define do
      factory :car do
        transient do
          car_style_number { DEFAULT_STYLE_NUMBER }
        end
    
        make { "Holden" }
        model  { "UTE" }
        after(:create) do |car, evaluator|
          car.style = create(:style, style_number: evaluator.car_style_number, car: car)
        end
      end
    end
    
    FactoryBot.define do
      factory :style do
        style_number { DEFAULT_STYLE_NUMBER }
      end
    end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-30
      • 2021-09-24
      • 1970-01-01
      • 2021-12-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多