Search code examples
ruby-on-railsrubycapybarafactory-botrspec-rails

Quadruple nested child elements in FactoryGirl


I'm trying to create dummy data with FactoryGirl.

User has many posts, post has many videos, video has many comments. Comment belong to video and user. Video belongs to post and user. Post belongs to user.

I would like to create at least 20 users, each with at least 10 posts, each post with at least 1 video, each video with at least 1 comment.

Thus far I have the following factories however I can't seem to get the videos or comments to work.

spec/factories/comments.rb

FactoryGirl.define do
  factory :comment do
    sequence(:body) { |n| "#{n}body" }
    video
    user
  end
end

spec/factories/posts.rb

FactoryGirl.define do
  factory :post do
    sequence(:title) { |n| "#{n}title" }
    date Date.today.strftime('%m/%d/%Y')
    body Faker::Lorem.paragraph(3)
    tags Faker::Lorem.words(4)
    user

    trait :with_videos do
      after(:build) do |post|
        create(:video, post: post)
      end
    end
  end
end

spec/factories/users.rb

FactoryGirl.define do
  factory :user do
    first_name  Faker::Name.first_name
    last_name  Faker::Name.last_name
    sequence(:username) { |n| "#{n}username" }
    sequence(:email) { |n| "#{n}[email protected]" }
    phone  Faker::PhoneNumber.phone_number
    password  Faker::Internet.password(6, 20)
    country  Faker::Address.country
    state  Faker::Address.state_abbr
    city  Faker::Address.city
    zip  Faker::Address.zip
    seeking_coach true
    accept_email true
    accept_phone true
  end
end

spec/factories/videos.rb

FactoryGirl.define do
  factory :video do
    sequence(:title) { |n| "#{n}title" }
    sequence(:url) { |n| "https://www.youtube.com/watch?v=tYm_#{n}2oCVdSM" }
    embed_id { "#{url}.split('=').last" }
    post
    user

    trait :with_comments do
      after(:build) do |video|
        create(:comment, video: video)
      end
    end
  end
end

Solution

  • I think in factories hook should be after(:create) instead of after(:build), e.g.:

    after(:create) do |video|
      create(:comment, video: video)
    end
    

    Here is all updated factories:

    spec/factories/users.rb

    FactoryGirl.define do
      factory :user do
        first_name  Faker::Name.first_name
        last_name  Faker::Name.last_name
        sequence(:username) { |n| "#{n}username" }
        sequence(:email) { |n| "#{n}[email protected]" }
        phone Faker::PhoneNumber.phone_number
        password Faker::Internet.password(6, 20)
        country Faker::Address.country
        state Faker::Address.state_abbr
        city Faker::Address.city
        zip Faker::Address.zip
        seeking_coach true
        accept_email true
        accept_phone true
    
        trait :with_10_posts do
          after(:create) do |user|
            create_list(:post, 10, :with_videos, user: user)
          end
        end
      end
    end
    

    spec/factories/posts.rb

    FactoryGirl.define do
      factory :post do
        sequence(:title) { |n| "#{n}title" }
        date Date.today.strftime('%m/%d/%Y')
        body Faker::Lorem.paragraph(3)
        tags Faker::Lorem.words(4)
        user
    
        trait :with_videos do
          after(:create) do |post|
            create(:video, :with_comments, post: post)
          end
        end
      end
    end
    

    spec/factories/videos.rb

    FactoryGirl.define do
      factory :video do
        sequence(:title) { |n| "#{n}title" }
        sequence(:url) { |n| "https://www.youtube.com/watch?v=tYm_#{n}2oCVdSM" }
        post
    
        trait :with_comments do
          after(:create) do |video|
            create(:comment, video: video)
          end
        end
      end
    end
    

    spec/factories/comments.rb

    FactoryGirl.define do
      factory :comment do
        sequence(:body) { |n| "#{n}body" }
        video
      end
    end
    

    And RSpec test to ensure that factories setup works:

    describe 'Factories' do
      it 'creates 20 users, each with at least 10 posts, each post with at least 1 video, each video with at least 1 comment' do
        FactoryGirl.create_list(:user, 20, :with_10_posts)
        expect(User.count).to eq(20)
        user = User.take
        expect(user.posts.count).to be >= 10
        post = user.posts.take
        expect(post.videos.count).to be >= 1
        video = post.videos.take
        expect(video.comments.count).to be >= 1
      end
    end
    

    Here is all the code - https://github.com/shhavel/stackoverflow_question_40136020