3

I would like to use FactoryBot to return trait randomly like that:

FactoryBot.define do
  factory :user do

    [:active, inactive].sample

    trait :active do
      active { true }
      ...
    end
    trait :inactive do
      active { false }
      ...
    end
  end
end

To do that:

(1..5).map{ |e| FactoryBot.build(:user) }.map(&:active?)
=> [true, false, false, true, false]

Actually is like that:

FactoryBot.define do
  factory :user do
    active                    { [true, false].Sample }
    name                      { "name-#{SecureRandom.uuid}" }
    birthday                  { active == true ? rand(18..99).years.ago - rand(0..365).days.ago : nil }
    preferred_contact_method  { active == true ? %w(phone email).sample : nil }
    activated_at              { active == true ? rand(1..200).days.ago : nil }
    contact_phone_number      { preferred_contact_method == "phone" ? "+33XXXXXXXXX" : nil }
    contact_email             { preferred_contact_method == "email" ? "toto@tati.com" : nil }
  end
end

Is it possible to do that?

Hugo Barthelemy
  • 129
  • 1
  • 11

2 Answers2

0

Working on an eventual answer, I came across the fact that a trait execute only once the block it is given. I'll share the sample of code I was on for the time being, although if you test it, you will only have active users or inactive users, but not both.


You could export the logic of your traits in two lambdas and then choose one by random:

trait_active = lambda do |context|
    context.active { true }
    #...
end
trait_inactive = lambda do |context|
    context.active { false }
    # ...
end

FactoryBot.define do
    factory :user do
        trait :active do
            trait_active.call(self)
        end
        trait :inactive do
            trait_inactive.call(self)
        end
        trait :schrodinger do
            [trait_active, trait_inactive].sample.call(self)
        end
    end
end

The context attribute in the lambda is quite important here, you can see more about that in this answer.

Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
0

That is quite an old question, but just had the same need as OP and I think I found the solution, alas it is not pretty:

FactoryBot.define do
  factory :user do
    active { nil }

    initialize_with do
      if active.nil?
        build :user, %i[active inactive].sample, attributes
      else
        User.new(attributes)
      end
    end
    
    trait :active do
      active { true }
      ...
    end
    trait :inactive do
      active { false }
      ...
    end
  end
end

FactoryBot.create_list(:user, 5).map(&:active) #=> random array of booleans
FactoryBot.create_list(:user, 5, :active).all?(&:active?) #=> true
BroiSatse
  • 44,031
  • 8
  • 61
  • 86