DEV Community

Risa Fujii
Risa Fujii

Posted on

Rails Seeds Made Simple with Seed-Fu: A Step-by-Step Guide

I was seeding some default data in a Rails app, but finding it increasingly difficult to maintain the data without generating duplicates or failing to delete some unnecessary records.
Then I learned about the seed-fu gem, which helped me avoid such pitfalls and make the syntax look clean.

In this post, I'll describe the issue I had with my original seeds, and walk you through how to use seed-fu. If you're using lots of find_or_intialize_by / find_or_create_by in your seeds, or finding it difficult to maintain your data integrity, this might be for you.

The Problem

Let me give you a rough idea of what I was doing.
I wanted to seed data for product_types and products. Each product belongs to a product_type. My seeds.rb looked like this (just longer and not as silly):

# db/seeds.rb
# Seed product types
  {name: 'fruit', description: 'Juicy'}
  {name: 'vegetable', description: 'Get your vitamins'}
  {name: 'snack', description: 'Cheat day!'}
].each do |attributes|
  ProductType.find_or_initialize_by(name: attributes[:name]).update!(attributes)

# Seed products
  {product_type: ProductType.find_by(name: 'fruit'), name: 'apple'},
  {product_type: ProductType.find_by(name: 'fruit'), name: 'ooorange'},
  {product_type: ProductType.find_by(name: 'vegetable'), name: 'tomato'},
  {product_type: ProductType.find_by(name: 'snack'), name: 'chocolate'}
].each do |attributes|
Enter fullscreen mode Exit fullscreen mode

I'm using find_or_initialize_by for product_types in order to overwrite the record if I'm just updating a description. For example, if I changed the description of fruit from 'Juicy' to 'Scrumptious' and ran rails db:seed, Rails would update the description for the existing fruit record, instead of creating a new one. So far, so good.

But then, I noticed an issue with products. One product_type can have multiple products, which means I can't tell Rails to overwrite the name if there's an existing record with the same product_type.
Notice how orange is misspelled? If you corrected the spelling and reloaded the seeds, the product called ooorange would remain while another one called orange would be created. In order to clean this up, you would have to manually go into the console and delete the unnecessary product, or reset your DB - both unideal.

Note: I later realised that you could designate the id for each record and update your records with the id as the key, without using this gem at all. But I'll focus on the solution using seed-fu in this post.

Manage your seeds with seed-fu

1. Installation

Add the gem to your Gemfile and run bundle.

gem 'seed-fu'
Enter fullscreen mode Exit fullscreen mode

2. Create your seed folder

Choose where to put your files from the two options below; seed-fu will know to read from them. Or you can set a custom path if you like (check the docs).

  • #{Rails.root}/db/fixtures
  • #{Rails.root}/db/fixtures/#{Rails.env}

3. Create your seed data files

Now for the actual data - this is what our seeds look like if rewritten seed-fu-style.

# db/fixtures/product_types.rb
  {name: 'fruit', description: 'Juicy'}
  {name: 'vegetable', description: 'Get your vitamins'}
  {name: 'snack', description: 'Cheat day!'})
Enter fullscreen mode Exit fullscreen mode
# db/fixtures/product_types.rb
  {id: 1, product_type: ProductType.find_by(name: 'fruit'), name: 'apple'},
  {id: 2, product_type: ProductType.find_by(name: 'fruit'), name: 'ooorange'},
  {id: 3, product_type: ProductType.find_by(name: 'vegetable'), name: 'tomato'},
  {id: 4, product_type: ProductType.find_by(name: 'snack'), name: 'chocolate'})
Enter fullscreen mode Exit fullscreen mode

Let me briefly explain what's happening.

For product_types, we don't want multiple records with the same name being created, so we use :name as the key (or "constraint"). If a product_type with that name already exists, the description would be updated, instead of a whole new product_type being created.

As for products, we designated the id for each one. We use this :id as the constraint, and create/update accordingly. So if we corrected the spelling from ooorange to orange and reloaded the seeds, the name for the product whose id is 2 would be updated. Like this, we can avoid the pitfalls of leaving incorrect records or accidentally creating ones.

The docs have more on using these constraints. I personally think it's much more reader-friendly than the original seeds.

4. Load your seed-fu data automatically

If you want to load your seed-fu data manually from the command line, you can do so with rails db:seed_fu. But it would be more convenient if the data could be automatically loaded in situations where you'd normally expect your seeds to be loaded (like rails db:reset).

This couldn't be simpler. Just add this line to your seeds.rb:

# db/seeds.rb
Enter fullscreen mode Exit fullscreen mode

You can also pass it two parameters (docs):

  • fixture path: in case you're not using the default path
  • filter: in case you only want to load certain files

Hope this has been helpful. Thanks for reading!

Top comments (1)

andrewbrown profile image
Andrew Brown πŸ‡¨πŸ‡¦

The codebase could use help having better seed data.
I started this but ran out of time.

🌱More Seed Data #2356

omenking avatar
omenking commented on Apr 10, 2019

Is your feature request related to a problem? Please describe.

Working on this ticket I don't have sufficient seed data.

Describe the solution you'd like

  • I am going to bring in more tags
  • I am going to generate some fake organizations if this does not already exist in the seed data
  • I am going to create a new user outside the random users.
  • For this new user, I am going to randomly generate data so they have more in their dashboard.
  • I am going to add an endpoint that will only work in development for an easy way to log in as this user

Describe alternatives you've considered Nada.

Additional context Nada.

Open Source Contributions is really good for career progression.