Database Seeds

Bullet Train introduces a new, slightly different expectation for Rails seed data: It should be possible to run rake db:seed multiple times without creating duplicate data.

The Rails Default

This is different than the Rails default, as evidenced by the Rails example which uses Product.create:

5.times do |i|
  Product.create(name: "Product ##{i}", description: "A product.")
end

Bullet Train Example

In Bullet Train applications, you would implement that same db/seeds.rb logic like so:

5.times do |i|
  Product.find_or_create_by(name: "Product ##{i}") do |product|
    # this only happens if on a `create`.
    production.description = "A product."
  end
end

Why?

We do this so Bullet Train applications can re-use the logic in db/seeds.rb for three purposes:

  1. Set up new local development environments.
  2. Ensure the test suite has the same configuration for features whose configuration is backed by Active Record (e.g. outgoing webhooks).
  3. Ensure any updates to the baseline configuration that have been tested both locally and in CI are the exact same updates being executed in production upon deploy.

This makes db/seeds.rb a single source of truth for this sort of baseline data, instead of having this concern spread and sometimes duplicated across db/seeds.rb, db/migrations/*, and test/fixtures.

Seeds for Different Environments

In some cases, you may have core seed data like roles that needs to exist in every environment, but you also have development data to populate in your non-production environments. Bullet Train makes this easy by supporting per-environment seed files in the db/seeds folder like db/seeds/test.rb and db/seeds/development.rb.

Then in db/seeds.rb, you can load all of the shared core seed data at the beginning of db/seeds.rb and then load the environment-specific seeds only when you've specified one of those environments.

load "#{Rails.root}/db/seeds/development.rb" if Rails.env.development?
load "#{Rails.root}/db/seeds/test.rb" if Rails.env.test?

Feedback

We're always very hesitant to stray from Rails defaults, so it must be said that our commitment to this approach isn't set in stone. It's worked very well for us in a number of applications, so we've standardized on it, but the approach is certainly open to discussion.