An adaptation of seed-fu, to make seeds.rb a bit nicer

Posted by Floyd Price on January 12, 2010

The db:seed rake task in Rails is a nice addition and we use it a lot. The trouble is, you end up writing a lot of redundant code to check if records exist and stuff like that. Not very DRY, not very Rails.

Seed-Fu is a nice plugin that works very well, but it replaces the new db:seed rake task, and this solution is far more light-weight. You add another file to your db folder, seeds-helper.rb, and just require it at the top of seeds.rb.

Here’s the contents of seeds-helper.rb:

class SeedsHelper
  def self.seed(model_class, *keys, &block)
    s = SeedsHelper.new(model_class)
    s.parse_keys(*keys)
    yield s
    s.plant
  end

  def initialize(model_class)
    @model_class = model_class
    @keys = []
    @data = {}
  end

  def parse_keys(*keys)
    keys = [:id] if keys.empty?
    keys.each do |key|
      raise "'#{key}' is not defined in #{@model_class}" unless @model_class.column_names.include?(key.to_s)
      @keys << key.to_sym
    end
  end

  def plant
    r = find_record
    @data.each do |key, value|
      r.send("#{key}=", value)
    end
    raise "Error Seeding: #{r.inspect}" unless r.save(false)
    puts r.inspect
    return r
  end

  def find_record
    results = @model_class.find(:all, :conditions => conditions_hash)
    if results.any?
      return results.first
    else
      return @model_class.new
    end
  end

  def conditions_hash
    @keys.inject({}) {|a, c| a[c] = @data[c]; a }
  end

  def method_missing(method_name, *args)
    if args.size == 1 and (match = method_name.to_s.match(/(.*)=$/))
      @data[match[1].to_sym] = args[0]
    else
      super
    end
  end
end

class ActiveRecord::Base
  def self.seed(*keys, &block)
    SeedsHelper.seed(self, *keys, &block)
  end
end

You can now use the seed-fu magic in seeds.rb:

require 'db/seeds_helper.rb'

Account.seed(:username) do |t|
  t.username = "adrian.oconnor"
  t.name = "Adrian O'Connor"
end

The active record model object is extended with a seed method. This takes a list of symbols that act as the key — if the values passed in match the keys in an existing record, it’ll replace it, otherwise it’ll create a new record. Pretty clever stuff.

If you like this, you should definitely check the full seed-fu plugin. You can find it here: http://github.com/mbleigh/seed-fu. I just like having a single additional file that is only used by seeds.rb.

Tags:

Leave a comment