appending to rake db:seed in rails and running it without duplicating data
Asked Answered
S

9

26

Rake db:seed populates your db with default database values for an app right? So what if you already have a seed and you need to add to it(you add a new feature that requires the seed). In my experience, when I ran rake db:seed again, it added the existing content already so existing content became double.

What I need is to add some seeds and when ran, it should just add the newest ones, and ignore the existing seeds. How do I go about with this? (the dirty, noob way I usually do it is to truncate my whole db then run seed again, but that's not very smart to do in production, right?)

Sourdine answered 13/8, 2010 at 12:54 Comment(0)
S
15

I do something like this.... When I need to add a user

in seeds.rb:

if User.count == 0
  puts "Creating admin user"
  User.create(:role=>:admin, :username=>'blagh', :etc=>:etc)
end

You can get more interesting than that, but in this case, you could run it over again as needed.

Sapless answered 13/8, 2010 at 13:33 Comment(2)
hmmm so i could basically just add a count == 0 to any of the tables i need populated in my seed to ensure they won't duplicate right? thanks! I was also thinking of just doing a rake task for thatSourdine
You may have to find specific records before creating them as well. Check for their presence: e.g. User.create(:name => "Bob") unless User.find_by_name("Bob")Rousseau
M
38

A cleaner way to do this is by using find_or_create_by, as follows:

User.find_or_create_by_username_and_role(
  :username => "admin",
  :role => "admin",
  :email => "[email protected]")

Here are the possible outcomes:

  1. A record exists with username "admin" and role "admin". This record will NOT be updated with the new e-mail if it already exists, but it will also NOT be doubled.
  2. A record does not exist with username "admin" and role "admin". The above record will be created.
  3. Note that if only one of the username/role criteria are satisfied, it will create the above record. Use the right criteria to ensure you aren't duplicating something you want to remain unique.
Martica answered 31/12, 2010 at 12:52 Comment(3)
1. is wrong. Any properties there will NOT be updated. You will need to do a subsequent update if you want to update records. As the name indicates it will find or create, not find or update.Calling
@BillLeeper - you're absolutely right. Thanks for correcting my assumption - I just updated the answer.Martica
no problem, learned that one the hard way too as I am sure many have. Not sure why there is no standard find_create_or_update, but there are lots of examples out there with various approaches.Calling
S
15

I do something like this.... When I need to add a user

in seeds.rb:

if User.count == 0
  puts "Creating admin user"
  User.create(:role=>:admin, :username=>'blagh', :etc=>:etc)
end

You can get more interesting than that, but in this case, you could run it over again as needed.

Sapless answered 13/8, 2010 at 13:33 Comment(2)
hmmm so i could basically just add a count == 0 to any of the tables i need populated in my seed to ensure they won't duplicate right? thanks! I was also thinking of just doing a rake task for thatSourdine
You may have to find specific records before creating them as well. Check for their presence: e.g. User.create(:name => "Bob") unless User.find_by_name("Bob")Rousseau
C
8

Another option that might have a slight performance benefit:

# This example assumes that a role consists of just an id and a title.

roles = ['Admin', 'User', 'Other']
existing_roles = Role.all.map { |r| r.title }

roles.each do |role|
  unless existing_roles.include?(role)
    Role.create!(title: role)
  end
end

I think that doing it this way, you only have to do one db call to get an array of what exists, then you only need to call again if something isn't there and needs to be created.

Cockcrow answered 10/12, 2012 at 22:45 Comment(2)
existing_roles = Role.all.map { |r| r.title } can be written as Role.all.collect(&:title), or in Rails 3.2 just Role.pluck(:title)Sidelong
Also could do (roles - existing_roles).each { |role| Role.create! title: role }Carouse
A
1

Adding


from

departments = ["this", "that"]
departments.each{|d| Department.where(:name => d).first_or_create}

to

departments = ["this", "that", "there", "then"]
departments.each{|d| Department.where(:name => d).first_or_create}

this is a simple example,


Updating/rename


from

departments = ["this", "that", "there", "then"]
departments.each{|d| Department.where(:name => d).first_or_create}

to

departments = ["these", "those", "there", "then"]
new_names = [['these', 'this'],['those','that']]

new_names.each do |new| 
  Department.where(:name => new).group_by(&:name).each do |name, depts|
    depts.first.update_column :name, new[0] if new[1] == name # skips validation
    # depts[1..-1].each(&:destroy) if depts.size > 1 # paranoid mode
  end
end

departments.each{|d| Department.where(:name => d).first_or_create}

IMPORTANT: You need to update the elements of departments array else duplication will surely happen.

Work around: Add a validates_uniqueness_of validation or a validation of uniqueness comparing all necessary attributes BUT don't use methods skipping validations.

Arras answered 13/8, 2010 at 12:55 Comment(0)
E
1

My preference for this sort of thing is to create a custom rake task rather than use the seeds.rb file.

If you're trying to bulk create users I'd create a .csv files with the data then create a rake task called import_users and pass it the filename. Then loop through it to create the user records.

In lib/tasks/import_users.rake:

namespace :my_app do
  desc "Import Users from a .csv"
  task :import_users => :environment do
    # loop through records and create users
  end
end

Then run like so: rake bundle exec my_app:import_users path/to/.csv

If you need to run it in production: RAILS_ENV=production bundle exec rake my_app:import_users /path/to/.csv

Edge answered 6/9, 2012 at 3:24 Comment(0)
L
0

Another alternative is to use the #first_or_create.

categories = [
    [ "Category 1", "#e51c23" ],
    [ "Category 2", "#673ab7" ]
]

categories.each do |name, color|
  Category.where( name: name, color: color).first_or_create
end
Lamelli answered 21/3, 2022 at 12:41 Comment(0)
S
-1

A really hackable way would be to comment out the existing data, that's how i did it, and it worked fine for me

=begin

#Commented Out these lines since they where already seeded 
   PayType.create!(:name => "Net Banking")
   PayType.create!(:name => "Coupouns Pay")

=end
#New data to be used by seeds

PayType.create!(:name => "Check")
PayType.create!(:name => "Credit card")
PayType.create!(:name => "Purchase order")
PayType.create!(:name => "Cash on delivery")

Once done just remove the comments

Subaxillary answered 14/5, 2015 at 14:30 Comment(0)
P
-1

Another trivial alternative:

#categories => name, color 
categories = [
    [ "Category 1", "#e51c23" ],
    [ "Category 2", "#673ab7" ]
]

categories.each do |name, color|
  if ( Category.where(:name => name).present? == false )
    Category.create( name: name, color: color )
  end
end
Pantywaist answered 18/11, 2015 at 10:38 Comment(0)
N
-4

Just add User.delete_all and for all the models that you have included in your application at the beginning of your seed.rb file. There will not be any duplicate values for sure.

Naamann answered 15/11, 2011 at 13:30 Comment(2)
You are mistinterpreting the question. The OP clearly mentions that he wants to do this in production and that it is risky to truncate the DB. You answer is ignoring part of the question. I would advise you to delete it.Bolick
This has both performance issues and data risks - if any of the seed data was ever updated or if it was ever impacted by any other migrations, deleting everything and trying to reset will not necessarily restore the data you want.Martica

© 2022 - 2024 — McMap. All rights reserved.