Search code examples
ruby-on-railsrubypostgresqlfactory-bot

How to create valid factories with FactoryBot when different columns need to correspond with one another


I have a model called Event. It looks like this:

class Event < ApplicationRecord
  belongs_to :eventable, polymorphic: true
  belongs_to :user, inverse_of: :events

  validates :event_category, presence: true

  enum event_category: {
    add_to_library: 0,
    change_completion_status: 1,
    favorite_game: 2,
    new_user: 3,
    following: 4
  }
end

I was running into errors where users were able to create Events where eventable didn't match the corresponding type (e.g. GamePurchase represents a game in a user's library and is what eventable is supposed to be when the category is add_to_library), so I added a Postgres check constraint that checks to make sure the event_category and eventable_type match. The problem is that my event factories really don't like this constraint and tries to create records where the event_category and eventable_type don't match. Is there a way to fix this?

My factories for Event look like this:

FactoryBot.define do
  factory :game_purchase_library_event, class: 'Event', aliases: [:event] do
    user
    association(:eventable, factory: :game_purchase)
    event_category { :add_to_library }
  end

  factory :game_purchase_completion_event, class: 'Event' do
    user
    association(:eventable, factory: :game_purchase)
    event_category { :change_completion_status }
    differences do
      # Pick two random values
      { completion_status: GamePurchase.completion_statuses.values.sample(2) }
    end
  end

  factory :favorite_game_event, class: 'Event' do
    user
    association(:eventable, factory: :favorite_game)
    event_category { :favorite_game }
  end

  factory :new_user_event, class: 'Event' do
    user
    association(:eventable, factory: :user)
    event_category { :new_user }
  end

  factory :following_event, class: 'Event' do
    user
    association(:eventable, factory: :relationship)
    event_category { :following }
  end
end

I'm getting factory_bot lint failures like this:

FactoryBot::InvalidFactoryError: The following factories are invalid:

* game_purchase_library_event+favorite_game - PG::CheckViolation: ERROR:  new row for relation "events" violates check constraint "event_category_type_check"
DETAIL:  Failing row contains (f9fb682b-8ae6-4d79-b91a-d17c58b4d02c, 29157, 4, 2021-02-28 01:05:27.591942, 2021-02-28 01:05:27.591942, 2, null, GamePurchase).
 (ActiveRecord::StatementInvalid)
* game_purchase_library_event+new_user - PG::CheckViolation: ERROR:  new row for relation "events" violates check constraint "event_category_type_check"
DETAIL:  Failing row contains (a8de4c5c-cdc2-4bdb-9a8f-a09cf1813dac, 13791, 5, 2021-02-28 01:05:27.650202, 2021-02-28 01:05:27.650202, 3, null, GamePurchase).
 (ActiveRecord::StatementInvalid)
* game_purchase_library_event+following - PG::CheckViolation: ERROR:  new row for relation "events" violates check constraint "event_category_type_check"
DETAIL:  Failing row contains (96d11b13-3cdd-44e2-80e5-e4a070ef8ac8, 25781, 6, 2021-02-28 01:05:27.705169, 2021-02-28 01:05:27.705169, 4, null, GamePurchase).
 (ActiveRecord::StatementInvalid)

Solution

  • Turns out this was being caused by the automatic generation of enum traits, which is why there were those incompatible traits that were causing the linter to fail. The feature is described here, you can disable it with FactoryBot.automatically_define_enum_traits = false (I created a factory_bot.rb file in my initializers directory and disabled it there).