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)
end
# 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|
Product.find_or_create_by(attributes)
end
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'
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
ProductType.seed(:name,
{name: 'fruit', description: 'Juicy'}
{name: 'vegetable', description: 'Get your vitamins'}
{name: 'snack', description: 'Cheat day!'})
# db/fixtures/product_types.rb
Product.seed(:id,
{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'})
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
SeedFu.seed
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)
The DEV.to codebase could use help having better seed data.
I started this but ran out of time.
π±More Seed Data #2356
Is your feature request related to a problem? Please describe.
Working on this ticket I don't have sufficient seed data. github.com/thepracticaldev/dev.to/...
Describe the solution you'd like
Describe alternatives you've considered Nada.
Additional context Nada.
Open Source Contributions is really good for career progression.