Skip to content

Seeding a Database in Ruby on Rails

ALT

David Morales

Seed
Photo by Jeremy Bishop on Unsplash

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 sampleCode 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:dateCode 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
endCode 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

endCode 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 -TCode language: Shell Session (shell)

You can even filter by namespace, such as db:

rails -T dbCode 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_genresCode 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
endCode language: Ruby (ruby)

It’s now listed in the Rails commands list:

$ rails -T movies
rake movies:seed_genres  # Seeds genresCode 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_seedCode 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'
endCode 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.

Video version

Written by

David Morales

Computer Engineer

Teaching my software engineering knowledge in a creative way.

Would you like to get the 10 Ninja Keys of the Developer?

An exclusive PDF for Newsletter subscribers.

You will also be notified of future articles and affordable courses (in the pipeline) that will turn you into a ninja developer.

View privacy information

Comments (1)

Leave a reply

Please fill in all fields.

Your email address will not be published.

View privacy information