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.