Back to Blog

Get in Touch

ActiveRecord::Base.create_or_update on Steroids

By Michael Bleigh February 19, 2008 in rails, ActiveRecord, db-populate, metaprogramming, seed data

Missing

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.

Medium

Michael Bleigh

Michael has been with Intridea since 2007 and works to build Intridea's portfolio of products. With many years of experience working as both a designer and a developer, Michael specializes in helping to bridge the gap between the back-end development and the front-end design of a project. Michael is a prolific member of the Ruby on Rails community, having released popular open source libraries such as OmniAuth and spoken at conferences including RailsConf and RubyConf.

More posts by Michael Bleigh

Michael Bleigh

To the troubling idea isn't about what signal you're sending to your employee...

Michael Bleigh

Node.js has a pattern that I personally enjoy: if you require a directory, it...

Michael Bleigh

Last weekend I had the opportunity to speak at RubyConf 2012 about a topic th...