arrow_backward Back to blog

ActiveRecord::Base.create_or_update on Steroids

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}) ||     record.send field.to_s + "=", find_value     record.attributes = options!     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.


New Project Request