views:

2547

answers:

7

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.

Thank you!

A: 

Instead of using explicit creates, use YAML files. With a simple syntax, you can fill in all the values of an object. Actually, if you know anything about rails testing, that's the standard way to seed the test database. Check out these pages:
http://railspikes.com/2008/2/1/loading-seed-data http://quotedprintable.com/2007/11/16/seed-data-in-rails

YenTheFirst
i know about rails testing. i am not testing my app, i am seeding my database for development and production. furthermore, the article you sent me concludes by supporting my method of seeding the database.
Tony
A: 

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.

see http://www.trawlr.com/items/3997706-loading-seed-data for more info

p01nd3xt3r
this is the same article as posted above, and also concludes that fixtures are not the best way. did you bother to read the article you posted?
Tony
A: 

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
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
c = Category.create( stuff )p = Post.create( stuff )p.category = cNo need to explictly set IDs. If you change the initial data, you just create a new migration. Pretty easy.
Matt Rogish
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
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
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
+5  A: 

factory_girl 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
Hates_
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
A: 

The best way is to use fixtures ...

Because in your case setting the ids explicitly is the most important part

I agree with the articles you didnt like that not validating the data is somehow bad but you could get around this with loading the same fixtures in a test and checking them all for validity.

What I do: I use the bootstrap.rake task that you can get from e.g. technoweenie with separate fixtures that reside in db/bootstrap

The big thing for you is that id: 1 does work, therefor you don't corrupt your database if the table only contains data created by yourself, if users also write to the theme table I am not completly sure that the fixtures will not clear the table before inserting

nasmorn
+2  A: 

seed_fu is handy for this sort of thing as well. In your cars.rb file found in the /db/fixtures/development/ directory say you wanted seed data for a Car for each Manufacturer and Model (assuming these all have the right associations in the models)

(1..10).each do |x|
  manufacturer = Manufacturer.find(x)
  (1..10).each do |y|
    model = Model.find(y)
    Car.create({ :manufacturer_id => x, :model_id => y, :name => "#{manufacturer.name}-#{model.name} #{y}" })
  end
end
revgum
+10  A: 

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")
ajhit406