Skip to content

Allow overriding of FactoryBot.automatically_define_enum_traits on a per-factory basis #1597

Open
@mikespokefire

Description

@mikespokefire

Problem this feature will solve

Having FactoryBot.automatically_define_enum_traits default to true is useful when using ActiveRecord::Enum to get up and running with factories quickly. However this is an all-or-nothing option for an entire codebase, with no way to override this on a per-factory basis where we need to customise, or deviate, from the automatic enum trait behaviour.

This is problematic when given a large codebase, with lots of ActiveRecord::Enum usage, as disabling this globally would mean re-working a lot of factories that already work perfectly fine, they would need to be updated to use traits_for_enum instead.

It would be great if we could allow a single factory to stop the automatic definition of enum's from happening, or vice versa if FactoryBot.automatically_define_enum_traits was false and a factory wanted to automatically define them.

Desired solution

If there was an additional option when defining a factory, it would allow us to opt-in, or opt-out of the global automatic enum trait definition behaviour. Something like the following when defining a factory:

FactoryBot.define do
  factory :task, automatically_define_enum_traits: false do
    # Normal FactoryBot scoping, only with no automatically defined enum traits just for this factory.
  end
end

Alternatives considered

Additional context

We have a monolithic codebase, with lots of models and lots of uses of ActiveRecord::Enum across various models. However 1 model has a complex Single Table Inheritance model that uses ActiveRecord::Enum. One of the types of this model doesn't work with all values of the enum. Which in turn doesn't work well with the global default of automatically defining enum traits. I've added a contrived example below as a demonstration, for the sake of a worked example, let's call this Task.

A Task has an ActiveRecord::Enum attribute of confirmed_by that uses the prefix option. On top of the prefixing, when the enums value is :user, it doesn't require extra metadata. When the enums value is :organisation, a validation kicks in to ensure extra metadata is populated to be considered valid.

FactoryBot doesn't seem to take prefixing into account when creating the enum's, it just uses the enums values. However, even if prefixing was supported, we don't refer to this within the tests as :confirmed_by_organisation, we refer to them as :org_confirmed. If FactoryBot supported an enum's :prefix and :suffix options, it would be helpful here, however we want to define our own set of traits that make more sense from this models perspective.

It would be great to have an additional config option, or something similar, when defining a complex factory. E.g. given the following contrived example model:

class Task
  enum :confirmed_by, [:user, :organisation], prefix: true
  validates :additional_metadata_for_organisation, presence: true, if: :confirmed_by_organisation?
end

If we could disable enum traits just for this factory, we could have full control over the defined traits. E.g.

FactoryBot.define do
  factory :task, automatically_define_enum_traits: false do
    confirmed_by { :user }
    
    trait :org_confirmed do
      confirmed_by { :organisation }
      organisation_metadata { { some_data: "with a value" } }
    end
  end
end

Without this option, the above factory would have been automatically defined with the following traits, of which the :organisation trait wouldn't pass linting, as it doesn't pass validation. This is fine in itself, however to fix it, we would need to redefine the :organisation trait to add in the extra attributes, when what we really want to do here is completely customise the traits. E.g.

FactoryBot.define do
  factory :task, automatically_define_enum_traits: false do
    trait :user do  # Ideally we don't want/need this trait
      confirmed_by { :user }
    end
    
    trait :organisation do  # Ideally we would name this trait something else if we could opt-out of automatically_define_enum_traits
      confirmed_by { :organisation }
      organisation_metadata { { some_data: "with a value" } }
    end
  end
end

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions