Everyday Rails

Persist existing data when setting up a Rails development environment

By Aaron Sumner, February 28, 2021. File under: .
I stumbled upon this problem and solution while working on my Docker containers for Rails development environments series, but it applies to any Rails development setup.

Does your Rails app take advantage of the bin/setup script to ease onboarding new developers to your project? If it doesn’t, it’s often worth the effort to automate the process as much as possible. The default version provided by rails new is a great starting point. Doing so can save valuable ramp-up time for future developers, and identify potential holes in the setup process in the meantime. And as I’ve been learning, it’s super-handy for container-based development environments in Visual Studio Code.

But there’s a problem lurking!

The sample bin/setup provided by Rails includes a step for setting up new development and test databases, using the db:setup rake task. This task actually performs a few distinct tasks:

  • Create the databases (development and test)
  • Build out the schema on those databases, as defined in db/schema.rb (or .sql)
  • Seed the development database with data in db/seeds.rb, if provided

One of these steps, though, is not idempotent, and will wipe out any development data in place on subsequent runs!

Building the schema from the definition file is faster than migrations, and avoids the potential problem of deleted migration files. But, by design, it drops existing tables and recreates them based on the latest definitions provided in the schema file. The fact that documentation for the db:reset task explicitly states that the database gets wiped out, yet db:setup’s documentation makes no such mention, makes this even more surprising and confusing!

Anyway, this likely isn’t an issue when running bin/setup (or rails db:setup) as a one-off process when setting up a traditional development environment the first time, but what about in a Docker container-based development environment, where rebuilding from scratch may occur much more frequently?

Keep your development data!

I found a solution: Add a rake task that checks for the existence of a development database by checking to see if Active Record can establish a connection to it. Then, determine whether to run db:setup based on the results.

Here’s the task, courtesy of penguincoder on Stack Overflow. I put it in lib/tasks/db.rake.

namespace :db do
  desc "Checks to see if the database exists"
  task :exists do
    begin
      Rake::Task["environment"].invoke
      ActiveRecord::Base.connection
    rescue
      exit 1
    else
      exit 0
    end
  end
end

Then, update bin/setup with a little extra bash in the system! call that does the actual database setup: Check to see if a database already exists, and if it does, just run migrations to bring it up-to-speed with the current schema. If not, do a full setup, including rebuilding the database from the app’s current schema definition.

puts "\n== Preparing database =="
system! 'bin/rails db:exists && bin/rails db:migrate || bin/rails db:setup'

I like this approach for a couple of reasons. First, making it a rake task means I can reuse it in other workflows, and even extract to a gem. Second, it keeps bin/setup close to its original spirit—just a lightweight Ruby script that performs lower-level requirements to prepare a development environment. As long as Ruby is installed, it should be able to do its work.

I also tested an interactive approach—prompting the developer for whether or not to reset the database—but that doesn’t work with setting bin/setup as the postCreateCommand in a VS Code devcontainer.json file. It may be possible to have it both ways by incorporating a third party CLI application gem, but again, I wanted to keep bin/setup simple.

Summary

As I continue to build and refine container-based development environments in the name of programmer happiness, I’m leaning hard on making Ruby and Rails do what they’re good at, and using Docker for the rest. I’m happy that this solution allows me to continue with that approach.

Whether you’re building a Docker container-based development environment for your application, or beefing up the onboarding experience in other ways, I hope this approach is useful. Thanks for reading!

What do you think? Follow along on on Twitter or Facebook to let me know what you think and catch my latest posts. Better yet, subscribe to my newsletter for updates from Everyday Rails, book picks, and other thoughts and ideas that didn't quite fit here.

Black lives matter.

I stand with the Black community against systemic racism, police violence and brutality, intolerance, and hate in the United States and worldwide. We must all demand better from our leaders, and ourselves. Stop tolerating intolerance.

While you're here, please consider making a donation to Black Girls CODE, who do great, important work to provide opportunity to underprivileged girls interested in tech, or any organization working toward equity and safety for all, not just the privileged. Thank you.

Test with confidence!

If you liked my series on practical advice for adding reliable tests to your Rails apps, check out the expanded ebook version. Lots of additional, exclusive content and a complete sample Rails application.

Newsletter

Ruby on Rails news and tips, and other ideas and surprises from Aaron at Everyday Rails. Delivered to your inbox on no particular set schedule.