Seeding a Database in Ruby on Rails

Ruby on Rails has excellent tools for seeding a database, and thanks to the work of the community, many gems make this task easier.
Apart from seeding a database, we have helpful tools to check the database and better organize the data seeds.
Do you want to see this article in video format?
Creating a sample application
Let’s start by creating a new application.
rails new sample
cd sample
Code language: Shell Session (shell)
Creating a model
Next, generate a new model. If you are curious, by typing rails generate
(or the rails g
shortcut), you will see all available generators.
rails g model Movie title director storyline:text watched_on:date
Code language: Shell Session (shell)
Here you are setting the title
and director
as strings (default type if not specified), storyline
as text, and watched_on
as date (when setting dates, not datetimes, the convention is to append on
to the name).
Rails will generate a migration for you adapted to the default database, which is SQLite. Migrations are saved in db/migrate
. Let’s see how it looks like!
class CreateMovies < ActiveRecord::Migration[6.1]
def change
create_table :movies do |t|
t.string :title
t.string :director
t.text :storyline
t.date :watched_on
t.timestamps
end
end
end
Code language: Ruby (ruby)
As you can see, Rails adds the version you are using in square brackets at the end of the parent class.
The timestamps statement will generate the created_at
and updated_at
fields automatically. Very handy.
Let’s run it.
$ rails db:migrate
== 20210311174407 CreateMovies: migrating =====================================
-- create_table(:movies)
-> 0.0020s
== 20210311174407 CreateMovies: migrated (0.0021s) ============================
Code language: Shell Session (shell)
So now Rails has actually created the table. Just in case you did something wrong, you can always rollback:
$ rails db:rollback
== 20210311174407 CreateMovies: reverting =====================================
-- drop_table(:movies)
-> 0.0019s
== 20210311174407 CreateMovies: reverted (0.0064s) ============================
Code language: Shell Session (shell)
This command accepts an optional step
parameter to go back as many migrations as needed. For example, if you want to undo 2 migrations, you can do it like this: rails db:rollback STEP=2
Let’s see how the schema in db/schema.rb
looks like after running the migration:
ActiveRecord::Schema.define(version: 2021_03_11_174407) do
create_table "movies", force: :cascade do |t|
t.string "title"
t.string "director"
t.text "storyline"
t.date "watched_on"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
end
Code language: Ruby (ruby)
Cool! This file will contain the entire database schema as we run more migrations.
Rails commands
How can you know about the available Rails commands? By using the -T
parameter you can see a list:
rails -T
Code language: Shell Session (shell)
You can even filter by namespace, such as db
:
rails -T db
Code language: Shell Session (shell)
Creating some seeds
Let’s get to the interesting part of this article. Open the db/seeds.rb
file, and paste this:
Movie.destroy_all
Movie.create!([{
title: "Soul",
director: "Pete Docter",
storyline: "After landing the gig of a lifetime, a New York jazz pianist suddenly finds himself trapped in a strange land between Earth and the afterlife.",
watched_on: 1.week.ago
},
{
title: "The Lord of the Rings: The Fellowship of the Ring",
director: "Peter Jackson",
storyline: "The Fellowship of the Ring embark on a journey to destroy the One Ring and end Sauron's reign over Middle-earth. A young Hobbit known as Frodo has been thrown on an amazing adventure, when he is appointed the job of destroying the One Ring, which was created by the Dark Lord Sauron.",
watched_on: 2.years.ago
},
{
title: "Terminator 2",
director: "James Cameron",
storyline: "Terminator 2 follows Sarah Connor and her ten-year-old son John as they are pursued by a new, more advanced Terminator: the liquid metal, shapeshifting T-1000, sent back in time to kill John Connor and prevent him from becoming the leader of the human resistance.",
watched_on: 3.years.ago
}])
p "Created #{Movie.count} movies"
Code language: Ruby (ruby)
First, you destroy all movies to have a clean state and add three movies passing an array to the create
method. The seeds file uses Rails ActiveSupport, to use those handy Ruby X.ago
statements to define dates.
In the end, there’s some feedback about the total movies created. Let’s run it!
$ rails db:seed
"Created 3 movies"
Code language: Shell Session (shell)
You can execute this command as many times as you need. Existing records are deleted thanks to the first line containing the destroy
statement.
To check them, you can use rails runner
:
$ rails runner 'p Movie.pluck :title'
["Soul", "The Lord of the Rings: The Fellowship of the Ring", "Terminator 2"]
Code language: Shell Session (shell)
Using a custom Rails task to seed actual data
All your seeds are considered development data, not actual data for production use. So, don’t seed in production the way you just did! Mainly because the first step deletes all movies!
To seed actual data, it is best to create a custom Rails task. Let’s generate one to add genres.
First generate the model and then migrate the database. Finally create the task.
rails g model Genre name
rails db:migrate
rails g task movies seed_genres
Code language: Shell Session (shell)
This command creates a movies rake file in the lib/tasks
directory containing the seed_genres
task.
Copy the code below and paste it into lib/tasks/movies.rake
:
namespace :movies do
desc "Seeds genres"
task seed_genres: :environment do
Genre.create!([{
name: "Action"
},
{
name: "Sci-Fi"
},
{
name: "Adventure"
}])
p "Created #{Genre.count} genres"
end
end
Code language: Ruby (ruby)
It’s now listed in the Rails commands list:
$ rails -T movies
rake movies:seed_genres # Seeds genres
Code language: Shell Session (shell)
Time to run it!
$ rails movies:seed_genres
"Created 3 genres"
Code language: Shell Session (shell)
Loading seeds using the console
The console is handy for playing with your data. Let’s open it:
$ rails c
Loading development environment (Rails 6.1.3)
Code language: Shell Session (shell)
Did you know that you can load and access your seeds from the inside? Try this:
Rails.application.load_seed
Code language: Ruby (ruby)
Playing with data using the console sandbox
Sometimes you will need to run destructive commands on real data in your development or production environment without making the changes permanent. It’s kind of like a safe mode where you can do whatever you want and then go to a previous state.
This mode is called sandbox, and you can access it with the command rails c --sandbox
This technique is handy for debugging a real database, such as when a user says they are trying to update their profile name and see a weird error. You could reproduce that error directly using sandbox mode without affecting the actual data.
Loading more seeds using Faker
If you need, for example, 100 movies, you can replace your app/db/seeds.rb
file with this:
Movie.destroy_all
100.times do |index|
Movie.create!(title: "Title #{index}",
director: "Director #{index}",
storyline: "Storyline #{index}",
watched_on: index.days.ago)
end
p "Created #{Movie.count} movies"
Code language: Ruby (ruby)
Now run rails db:seed
:
$ rails db:seed
"Created 100 movies"
Code language: Shell Session (shell)
But the result doesn’t look realistic at all:
$ rails runner 'p Movie.select(:title, :director, :storyline).last'
#<Movie id: nil, title: "Title 99", director: "Director 99", storyline: "Storyline 99">
Code language: Shell Session (shell)
Time to use Faker, a gem that generates random values. Add it into the development group in your Gemfile
:
group :development, :test do
# ...
gem 'faker'
end
Code language: Ruby (ruby)
Run bundle install
and replace app/db/seeds.rb
with this:
Movie.destroy_all
100.times do |index|
Movie.create!(title: Faker::Lorem.sentence(word_count: 3, supplemental: false, random_words_to_add: 0).chop,
director: Faker::Name.name,
storyline: Faker::Lorem.paragraph,
watched_on: Faker::Time.between(from: 4.months.ago, to: 1.week.ago))
end
p "Created #{Movie.count} movies"
Code language: Ruby (ruby)
Check it out again:
$ rails db:seed
"Created 100 movies"
$ rails runner 'p Movie.select(:title, :director, :storyline, :watched_on).last'
#<Movie id: nil, title: "Toy Story 2", director: "Michael Dickinson", storyline: "Modi esse et at eum deserunt harum qui itaque reru...", watched_on: "2020-11-18">
Code language: Shell Session (shell)
Much better!
Conclusion
Seeding the database while developing the application is essential, as it will give you the feeling of working with actual data.
Also, knowing the tools available for working with seeds is more convenient and productive, so it is worth investing time in learning them.