Rails Seeds Made Simple with Seed-Fu: A Step-by-Step Guide
Tags: ruby, rails, seed
Published: 2019-05-23 23:54:05

---
title: Rails Seeds Made Simple with Seed-Fu: A Step-by-Step Guide
published: true
description: 
tags: ruby, rails, seed
---

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`](https://github.com/mbleigh/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):

```ruby
# 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`.

```ruby
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](https://github.com/mbleigh/seed-fu#where-to-put-seed-files)). 
* `#{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.

```ruby
# db/fixtures/product_types.rb
ProductType.seed(:name,
  {name: 'fruit', description: 'Juicy'}
  {name: 'vegetable', description: 'Get your vitamins'}
  {name: 'snack', description: 'Cheat day!'})
```

```ruby
# 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](https://github.com/mbleigh/seed-fu#constraints) 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`:
```ruby
# db/seeds.rb
SeedFu.seed
```

You can also pass it two parameters ([docs](https://github.com/mbleigh/seed-fu#rake-task)):
* 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!