Sergio Rodriguez
Sergio Rodriguez

Reputation: 145

Rails: How to create a Factory using FactoryGirl for a belongs_to + has_many relationship with both ends validating the presence of each other?

I'm learning about associations and I'm having trouble testing my associations using RSpec, shoulda-matchers and FactoryGirl.

I have a Game model who's instances must belong_to a manufacturer, so I'm using a presence validation.

Manufactures can have more than one game associated to them so there is a has_many games relationship from the manufacturer's point of view. Also, I want to ensure that a manufacturer is only created if it is associated with a game (through validation) (I don't want to track any manufacturer that does not have any games associated with it).

Here is the code:

class Game < ActiveRecord::Base
    belongs_to :manufacturer
    validates :name, presence: {message: 'The game name is required'}
    validates :description, presence:  {message: 'A short game description is required'}
    validates :manufacturer, presence:  true
 end

class Manufacturer < ActiveRecord::Base
    has_many :games
    validates :name, presence: {message: 'The manufacturer name is required and must be unique'}
    validates :name, uniqueness: {message: 'A manufacturer by this name already exits.'}, if: "name.present?"
    validates :games, presence: { message: 'Every manufacturer must be associated with at least one game.' }
end

So as you can see both validate each other, so there will be no games without manufacturer and no manufacturers without games.

I'm having a hard time trying to create a Factory using FactoryGirl to create a valid manufacturer instance for testing.

How can I create the manufacturer factory and the games factory in order to have a valid Factory where I can test the associations using shoulda-matchers? (the DB is properly set up with the columns for foreign keys and ids)

EDIT

I have tried the following without luck...

TRY 1

FactoryGirl.define do
    factory :game do
        name "Space Invaders"
        description "Space Invaders was one of the early blockbuster shooting games."
        release_date '1978'
        association :manufacturer, strategy: :build
      end
    end


FactoryGirl.define do
      factory :manufacturer do
        name 'Atari'
        games {create_list(:game, 1)}
      end
 end

Error:

/Users/mymac/.rvm/gems/ruby-2.1.5@sr-arcade-nomad/bin/ruby_executable_hooks:15: stack level too deep (SystemStackError)

TRY 2

FactoryGirl.define do
  factory :game do
    name 'Space Invaders'
    description "Space Invaders was one of the early blockbuster shooting games."
    manufacturer 'Atari'
  end
end

FactoryGirl.define do
  factory :manufacturer do
    name 'Atari'
    games {create_list(:game, 1)}
  end
end

Error:

.../gems/activerecord-4.1.1/lib/active_record/associations/association.rb:216:in `raise_on_type_mismatch!': Manufacturer(#70244044131760) expected, got String(#70244001467180) (ActiveRecord::AssociationTypeMismatch)

Upvotes: 1

Views: 846

Answers (2)

Ivo Dancet
Ivo Dancet

Reputation: 191

I'm looking for a solution with which I can easily override the games association. If that's no concern in this case, you can use an after build hook:

factory :manufacturer do
   …
   after(:build) do |m|
      m.games << build(:game, manufacturer: m)
   end
end

The problem with overriding is "kind of solved" if you put that after(:build) in a trait, but then build(:manufacturer) is no longer valid. So still no ideal but nevertheless a working factory.

Then again, this situation with circular presence validation is not ideal to start with. The only way in Rails to do this (the Rails way) is with a nested form. You will then create nested objects with params like { name: … , games_attributes: {1 => {<attributes for game 1}, …}. You probably should build the factory like with games_attributes too then. Other cases are just not possible:

game = Game.new …
manufacturer = Manufacturer.new …

Now save them (nested) with those validations in place …, one will have to go first …

Upvotes: 0

IcyBright
IcyBright

Reputation: 664

Try the following code:

factory :game do
  name
  description
  manufactor
end

factory :manufacturer do
  name
  games { create_list(:game, 1) }
end

Upvotes: 0

Related Questions