Factory Bot HABTM nil value in join table

Viewed 580

I am getting a (frustrating!) error when running rspec (with Factory Bot) tests for my Rails app. I'm seeing the following error:

ActiveRecord::NotNullViolation:
        Mysql2::Error: Field 'group_id' doesn't have a default value: INSERT INTO `groups_users` VALUES ()

It is intermittent - different tests will throw the error on different runs - which might suggest a race condition?

My models are configured as:

User habtm Groups
Groups habtm Users

Staff members MUST belong to one group - with a validation of:

validates :groups, presence: { message: 'Staff must be in at least one group' }, if: :staff?

Users

factory :a_staff_member, class: User do
    first_name { Faker::Name.first_name }
    last_name { Faker::Name.last_name }
    username { "#{first_name}_#{last_name}".downcase.gsub(/[^a-z]/i, '') }
    email { "#{first_name}.#{last_name}@example.ac.uk".downcase }
    user_type { :staff }
    groups {|s| [s.association(:group)]}
end

Groups

FactoryBot.define do
  factory :group do
    sequence(:title) { |n| "Group Number #{n}" }
    sequence(:short_title) { |n| "GRP#{n}" }
    sequence(:url_slug) { |n| "grp#{n}" }
  end
end

The common factor in all of the failing tests is the User creation factory (and the Groups creation given a User MUST belong to a group) - so this is where I have been focussing my efforts.

Given that the error is on the groups_users table I'm guessing that it's trying to save an entry to the join table before the group exists in the groups table?

Based on other SO posts and trawling the Internet, I've tried various ways of setting the group:

1) groups {|s| [s.association(:group)]}

2) after(:build) {|user| user.groups = [create(:group)]}

3) groups {[FactoryBot.create(:group)]}

The users are just instantiated via

let!(:staff_user) { FactoryBot.create(:a_staff_member) }

in the individual specs - so nothing exciting there.

However, no mater how I create the groups in the factories it doesn't make any difference - and I've run out of ideas - any pointers or suggestions would be gratefully received!

3 Answers

I think trying to change the equality with concatenation might work.

I'd add after(:create) { |user| user.groups << create(:group) }

Moving to after(:create) is the main thing here as the problem you are facing also seems to be trying to add a join table record for a record that is not created yet.

Managed to track down the issue. After putting some comprehensive debug messages around the point in the factory that the group was created and assigned to the user, it became clear that the error message relating to nil values in the join table between these was, in fact, a red herring and being caused outside of the factory.

I believe the issue was caused by the user session/log in not being cleaned up between tests from Capybara.

Adding:

config.after :each do
  Warden.test_reset!
end

to /spec/spec_helper.rb appears to have resolved the issue. Hopefully the hair pulled out during this investigation will grow back!

I think that it might work if you change like this.

 from
 after(:build) {|user| user.groups = [create(:group)]}

 to
 after(:create) {|user| user.groups = [create(:group)]}

Once a user id is created by running "create", then "users.groups" will be acceptable.

Related