Search code examples
ruby-on-railsrubyfactory-bot

FactoryBot - How can I pass a transient array?


I currently have a FactoryBot trait set up as follows:

trait :with_role do
  transient do
    role { nil }
  end

  after(:create) do |staff_member, factory|
    staff_member.staff_roles << StaffRole.fetch(factory.role) if factory.role
  end
end

However, this trait only allows the factory to be passed a single role. I'm refactoring a series of tests wherein mutliple roles need to be assigned. This is a M-N relationship supported both by the DB and ORM via a junction table, and clearly functions as expected outside of FactoryBot because the original test implementation passes. My approach is as follows:

# Inside the factory
trait :with_roles do
  transient do
    roles { [] }
  end

  after(:create) do |staff_member, factory|
    roles.each { |role| staff_member.staff_roles << StaffRole.fetch(factory.send(role)) }
  end
end
# Invocation
staff_member = FactoryBot.create(:staff_member, :with_roles, roles: [
  :role_1,
  :role_2,
  :role_3
], some_other_attribute: 1)

The problem is that when I try to invoke in this way, I get the following error:

NameError: undefined local variable or method `roles' for #<FactoryBot::SyntaxRunner:...>

I get the exact same error if I initialise roles as nil instead of an empty array. The previous implementation works fine, and I've followed it as closely as possible. Why is roles undefined despite being defined as a transient variable? Am I missing something about how FactoryBot works, and trying to do something impossible? Or am I just appproaching this wrong?


Solution

  • You should call a transient attribute on a factory:

    after(:create) do |staff_member, factory|
      factory.roles.each { |role| staff_member.staff_roles << StaffRole.fetch(factory.send(role)) }
    end