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.
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:
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?
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.
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.
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.
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!
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.
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.