The Seed Controversy
Published 10th April 2016 at 07:39pm UTC (Last Updated 22nd March 2020 at 08:59pm UTC)
In the Rails community there is some controversy over where to put seed data. Best practice advises that these are placed in the db/seeds.rb file within a Rails application, which is great if all you want to do is run
rake db:seed once throughout the entire life of your application or are ok with deleting everything in your database should you ever have to run the
rake db:seed command again.
So far, so terrible.
Many Rubyists have opted to place seed data in their migration files so as to avoid this issue. This is however, not considered to be 100% safe and is fraught with its own problems. Yes, you could just create a migration file, with a down as well as an up method...
OR OR OR,
you could create a deseed environment in the db/seeds.rb file. It's an interesting compromise between the two sides of the debate - essentially add some variant of an if/else statement like so:
if ENV['deseed'] #Insert "down" method that reverses the #changes made to the database else #The "up" method that seeds the data you want end
With a block like that added to the db/seeds.rb file, you can seed data the old fashioned way...
..and you can also remove seed data with the following command:
rake db:seed deseed=true
Awesome, right? So how exactly would we reverse a
create action or even a
find_by_or_create action? You might think that simply using the
destroy method would be the way to do it. However, this won't work if your database already contains duplicates of the rows you'd like to delete as the
find method is an alias for the
detect method which will essentially stop looking once it's found the first row that matches the condition in the block.
Now that's kind of rubbish.
Instead, try using the
where method followed by
if ENV["deseed"] User.where(name: "Jimmy", instrument: "vocals", admin: true).destroy_all User.where(name: "Lindsey", instrument: "bass", admin: false).destroy_all else User.find_or_create_by(name: "Jimmy", instrument: "vocals", admin: true) User.find_or_create_by(name: "Lindsey", instrument: "bass", admin: false) end
Note if using
where you may want to avoid matching float values as this may produce unexpected results in the underlying SQL. Make sure any floating point digits that you intend to match are stored in the database as a
#example migration file class CreateItems < ActiveRecord::Migration def change t.string :name t.decimal :price, precision: 10, scale: 2 end end
#Example create method for the seed file Item.find_or_create_by(name: "quill", price: BigDeimal.new("5.99"))
Values stored in this way will appear as integers in the Rails Console but the SQL database you use should store the exact values correctly. The
where method will work correctly in the underlying SQL.
Gediminas Zubovicius for inadvertently giving me the idea for this blog at the London Ruby Hack Night
Cort3z's SO answer
Ryan Bigg's answer regarding the
find method in another Stack Overflow question. I honestly didn't know that
find did that so this was quite helpful.
I hope that was informative for at least some of you. This is the first proper blog post I've written on programming so let me know what you think of it in the comments section below!