88

I have a rake task that populates some initial data in my rails app. For example, countries, states, mobile carriers, etc.

The way I have it set up now, is I have a bunch of create statements in files in /db/fixtures and a rake task that processes them. For example, one model I have is themes. I have a theme.rb file in /db/fixtures that looks like this:

Theme.delete_all
Theme.create(:id => 1, :name=>'Lite', :background_color=>'0xC7FFD5', :title_text_color=>'0x222222',
                      :component_theme_color=>'0x001277', :carrier_select_color=>'0x7683FF', :label_text_color=>'0x000000',
                      :join_upper_gradient=>'0x6FAEFF', :join_lower_gradient=>'0x000000', :join_text_color=>'0xFFFFFF',
                      :cancel_link_color=>'0x001277', :border_color=>'0x888888', :carrier_text_color=>'0x000000', :public => true)

Theme.create(:id => 2, :name=>'Metallic', :background_color=>'0x000000', :title_text_color=>'0x7299FF',
                      :component_theme_color=>'0xDBF2FF', :carrier_select_color=>'0x000000', :label_text_color=>'0xDBF2FF',
                      :join_upper_gradient=>'0x2B25FF', :join_lower_gradient=>'0xBEFFAC', :join_text_color=>'0x000000',
                      :cancel_link_color=>'0xFF7C12', :border_color=>'0x000000', :carrier_text_color=>'0x000000', :public => true)

Theme.create(:id => 3, :name=>'Blues', :background_color=>'0x0060EC', :title_text_color=>'0x000374',
                      :component_theme_color=>'0x000374', :carrier_select_color=>'0x4357FF', :label_text_color=>'0x000000',
                      :join_upper_gradient=>'0x4357FF', :join_lower_gradient=>'0xffffff', :join_text_color=>'0x000000',
                      :cancel_link_color=>'0xffffff', :border_color=>'0x666666', :carrier_text_color=>'0x000000', :public => true)
puts "Success: Theme data loaded"

The idea here is that I want to install some stock themes for users to start with. I have a problem with this method.

Setting the ID does not work. This means that if I decide to add a theme, let's call it 'Red', then I would simply like to add the theme statement to this fixture file and call the rake task to reseed the database. If I do that, because themes belong to other objects and their id's change upon this re-initialization, all links are broken.

My question is first of all, is this a good way to handle seeding a database? In a previous post, this was recommended to me.

If so, how can I hard code the IDs, and are there any downsides to that?

If not, what is the best way to seed the database?

I will truly appreciate long and thought out answers that incorporate best practices.

Nakilon
  • 34,866
  • 14
  • 107
  • 142
Tony
  • 18,776
  • 31
  • 129
  • 193

7 Answers7

118

Updating since these answers are slightly outdated (although some still apply).

Simple feature added in rails 2.3.4, db/seeds.rb

Provides a new rake task

rake db:seed

Good for populating common static records like states, countries, etc...

http://railscasts.com/episodes/179-seed-data

*Note that you can use fixtures if you had already created them to also populate with the db:seed task by putting the following in your seeds.rb file (from the railscast episode):

require 'active_record/fixtures'
Fixtures.create_fixtures("#{Rails.root}/test/fixtures", "operating_systems")

For Rails 3.x use 'ActiveRecord::Fixtures' instead of 'Fixtures' constant

require 'active_record/fixtures'
ActiveRecord::Fixtures.create_fixtures("#{Rails.root}/test/fixtures", "fixtures_file_name")
vincentp
  • 233
  • 4
  • 17
ajhit406
  • 1,405
  • 1
  • 14
  • 17
29

Usually there are 2 types of seed data required.

  • Basic data upon which the core of your application may rely. I call this the common seeds.
  • Environmental data, for example to develop the app it is useful to have a bunch of data in a known state that us can use for working on the app locally (the Factory Girl answer above covers this kind of data).

In my experience I was always coming across the need for these two types of data. So I put together a small gem that extends Rails' seeds and lets you add multiple common seed files under db/seeds/ and any environmental seed data under db/seeds/ENV for example db/seeds/development.

I have found this approach is enough to give my seed data some structure and gives me the power to setup my development or staging environment in a known state just by running:

rake db:setup

Fixtures are fragile and flakey to maintain, as are regular sql dumps.

Erowlin
  • 9,555
  • 4
  • 35
  • 63
james2m
  • 1,562
  • 12
  • 15
  • I like the terms "system data" and "runtime data" to describe things that the code relies on existing vs data from users. Sometimes the line between them is blurry. – Tim Abell Mar 13 '20 at 15:47
28

Using seeds.rb file or FactoryBot is great, but these are respectively great for fixed data structures and testing.

The seedbank gem might give you more control and modularity to your seeds. It inserts rake tasks and you can also define dependencies between your seeds. Your rake task list will have these additions (e.g.):

rake db:seed                    # Load the seed data from db/seeds.rb, db/seeds/*.seeds.rb and db/seeds/ENVIRONMENT/*.seeds.rb. ENVIRONMENT is the current environment in Rails.env.
rake db:seed:bar                # Load the seed data from db/seeds/bar.seeds.rb
rake db:seed:common             # Load the seed data from db/seeds.rb and db/seeds/*.seeds.rb.
rake db:seed:development        # Load the seed data from db/seeds.rb, db/seeds/*.seeds.rb and db/seeds/development/*.seeds.rb.
rake db:seed:development:users  # Load the seed data from db/seeds/development/users.seeds.rb
rake db:seed:foo                # Load the seed data from db/seeds/foo.seeds.rb
rake db:seed:original           # Load the seed data from db/seeds.rb
Mirv - Matt
  • 553
  • 7
  • 22
Yuri
  • 17,422
  • 2
  • 23
  • 25
27

factory_bot sounds like it will do what you are trying to achieve. You can define all the common attributes in the default definition and then override them at creation time. You can also pass an id to the factory:

Factory.define :theme do |t|
  t.background_color '0x000000'
  t.title_text_color '0x000000',
  t.component_theme_color '0x000000'
  t.carrier_select_color '0x000000'
  t.label_text_color '0x000000',
  t.join_upper_gradient '0x000000'
  t.join_lower_gradient '0x000000'
  t.join_text_color '0x000000',
  t.cancel_link_color '0x000000'
  t.border_color '0x000000'
  t.carrier_text_color '0x000000'
  t.public true
end

Factory(:theme, :id => 1, :name => "Lite", :background_color => '0xC7FFD5')
Factory(:theme, :id => 2, :name => "Metallic", :background_color => '0xC7FFD5')
Factory(:theme, :id => 3, :name => "Blues", :background_color => '0x0060EC')

When used with faker it can populate a database really quickly with associations without having to mess about with Fixtures (yuck).

I have code like this in a rake task.

100.times do
    Factory(:company, :address => Factory(:address), :employees => [Factory(:employee)])
end
Mirv - Matt
  • 553
  • 7
  • 22
Hates_
  • 66,613
  • 6
  • 32
  • 37
  • 12
    FactoryGirl is actually meant for testing in lieu of fixtures, but it can be used to load stuff into production as well. Use a rake task that has db:migrate as a prerequisite to load all the default data. You may need to make the rake task intelligent enough that it doesn't create copies of existing data. – Bob Aman Apr 24 '09 at 19:05
  • 2
    Using FactoryGirl for seed is not recommended, [check this post](https://robots.thoughtbot.com/factory_girl-for-seed-data). – blackbiron Jun 19 '17 at 16:31
1

Rails has a built in way to seed data as explained here.

Another way would be to use a gem for more advanced or easy seeding such as: seedbank.

The main advantage of this gem and the reason I use it is that it has advanced capabilities such as data loading dependencies and per environment seed data.

Adding an up to date answer as this answer was first on google.

SimonW
  • 6,175
  • 4
  • 33
  • 39
-3

The best way is to use fixtures.

Note: Keep in mind that fixtures do direct inserts and don't use your model so if you have callbacks that populate data you will need to find a workaround.

Ben Aubin
  • 5,542
  • 2
  • 34
  • 54
p01nd3xt3r
  • 821
  • 1
  • 9
  • 19
-5

Add it in database migrations, that way everyone gets it as they update. Handle all of your logic in the ruby/rails code, so you never have to mess with explicit ID settings.

Matt Rogish
  • 24,435
  • 11
  • 76
  • 92
  • if i need to change the initial data, things can get messy when using migrations. your second comment does not really make sense. links via foreign keys will be destroyed – Tony Apr 17 '09 at 20:15
  • c = Category.create( stuff ) p = Post.create( stuff ) p.category = c No need to explictly set IDs. If you change the initial data, you just create a new migration. Pretty easy. – Matt Rogish Apr 17 '09 at 20:32
  • that is assuming the associations can be made at the time the object is created. here is an example where i believe your logic fails...correct me if i am wrong. i seed the DB with template themes. user id=1 creates template id=2 and with theme id=4. at this point, a record is in the db as follows: template: id=2, user_id=1, theme_id=4. now if i re-init the db, theme id=4 is now theme id=10...and then the user's template is incorrectly themed – Tony Apr 17 '09 at 20:55
  • well, it depends on what you mean by "re-init" -- if you start from zero, Rails handles all of the associations automagically. If you're hard coding ID values (bad!!!), then yes it will blow up. – Matt Rogish Apr 17 '09 at 21:26
  • ok i am starting to see your point, but i have to run this scenario by you. i seed the database with a country look up table. USA => country id=1. then a user creates a restaurant which exists in the USA. the restaurant database row has country_id = 1. this is very common, right? i decide later that i want to add more countries...if i wipe the db clean and repopulate the country lookup table, now the restaurants country is no longer accurate unless the id is the same. how do i handle this? – Tony Apr 17 '09 at 21:46
  • Yes. This would be a problem. It is a great example though, of why you don't 'use' primary keys in lookups. E.g. always do Country.find(:conditions => ['country_name' = ?], 'USA'. Then use the ID found. The issue about associated tables having 'old' id's? Yes that is data integrity and if you decide to delete an existing record that is a foreign key elsewhere then yes you will need to handle it. In live production system these sort of changes frequently include a 'data migration' to deal with this and other issues. – Michael Durrant Sep 27 '11 at 18:40