Skip to main content

Mobomo webinars-now on demand! | learn more.

The issue of pre-loading needed data for a Rails application has always been somewhat confusing and difficult. A great post on Rail Spikes discusses the issue in-depth and offers a number of different solutions, but ultimately they all seem just a little short of the desired simplicity. By combining a few of the ideas and adding a few of my own, I have created a seeding system that I feel is very straightforward and easy to use.

Borrowing the basic premise of the db-populate plugin, Seed Fu is based around loading ruby scripts located in db/fixtures via a Rake task. What db-populate doesn’t offer is a clear syntax for describing the records to be seeded. That’s where Seed Fu comes in.

Usage

First, just create a new ruby script in db/fixtures (and create the directory itself, obviously). Any script that you drop in this folder will be automatically run when you execute your seeding rake task. Additionally, you can load environment-specific data by adding scripts in a folder of the same name (i.e. db/fixtures/development. In these scripts you can execute arbitrary Ruby code with the full Rails environment loaded; however, you should remember that this code will be executed every time the rake task is called and should not cause duplication or destroy anything that can’t be retrieved. The syntax for Seed Fu works as follows (with a User model as an example):

# db/fixtures/users.rb # put as many seeds as you like in  User.seed(:login, :email) do |s|   s.login = "admin"   s.email = "admin@adminnerson.com"   s.first_name = "Bob"   s.last_name = "Bobson" end  User.seed(:login, :email) do |s|   s.login = "michael"   s.email = "michael@abc.com"   s.first_name = "Michael"   s.last_name = "Bleigh" end

The seed method is available on any ActiveRecord. It takes as parameters the ‘constraints’ for that seeding; in other words, the fixed attributes that will not change in the record’s life. The constraints are used to find the record and update instead of creating it with the attributes provided if it already exists. This way your seeds can change without mucking with other live data on your server.

Once you have set up your fixtures, it’s simple to run them:

rake db:seed

Or if you want to run them for a targeted environment:

rake db:seed RAILS_ENV=production

Installation

In edge Rails or Rails 2.1 and beyond:

script/plugin install git://github.com/mbleigh/seed-fu.git

In previous versions of Rails:

git clone git://github.com/mbleigh/seed-fu.git vendor/plugins/seed-fu

I have some ideas for the expansion of this plugin (loading from CSV for larger datasets, etc.), so stay tuned! If you have ideas for additional features or encounter any problems, I have set up a Lighthouse project for your enjoyment.

Categories
Author

I’ve been working a lot with seed data in my applications lately, and it’s obviously a problem that can be pretty aggravating to deal with. I ultimately liked the db-populate approach the best with the addition of the ActiveRecord::Base.create_or_update method. My one problem with it is that it’s based entirely on fixed ids for the fixtures, which is a pain to deal with when loading up your attributes from arrays. Here’s an example of what I was doing:

i = 1  { "admin"     => ["Administrator", 1000],    "member"    => ["Member", 1],    "moderator" => ["Moderator", 100],   "disabled"  => ["Disabled User", -1] }.each_pair do |key, val|   Role.create_or_update(:id => i, :key => key, :name => val[0], :value => val[1])   i += 1 end

I really don’t like having to put the i in there to increment up. Not only is it messier code, but it would be dangerous if I wanted to move around the roles. I also don’t want to have to have an id explicitly in each entry since I really don’t care what the id is. So I thought I would hack up a better solution for this create_or_update scenario and this is what I came up with:

class << ActiveRecord::Base   def create_or_update(options = {})     self.create_or_update_by(:id, options)   end      def create_or_update_by(field, options = {})     find_value = options.delete(field)     record = find(:first, :conditions => {field => find_value}) || self.new     record.send field.to_s + "=", find_value     record.attributes = options     record.save!     record   end      def method_missing_with_create_or_update(method_name, *args)     if match = method_name.to_s.match(/create_or_update_by_([a-z0-9_]+)/)       field = match[1].to_sym       create_or_update_by(field,*args)     else       method_missing_without_create_or_update(method_name, *args)     end   end      alias_method_chain :method_missing, :create_or_update end

Basically, this allows me to call create_or_update with an arbitrary attribute as my “finder” by calling Model.create_or_update_by(:field, ...). To give it a little taste of syntactic sugar, I threw in a method missing to allow you to name a field in the method call itself. So now the code I wrote before can become this:

{ "admin"     => ["Administrator", 1000],    "member"    => ["Member", 1],    "moderator" => ["Moderator", 100],   "disabled"  => ["Disabled User", -1] }.each_pair do |key, val|   Role.create_or_update_by_key(:key => key, :name => val[0], :value => val[1]) end

This is much cleaner and prettier to look at, and also makes sure that it is keying off of the value that I really care about. This new create_or_update combined with db-populate creates the most powerful seed data solution I’ve yet come across.

Categories
Author
1
Subscribe to Seed Data