Leo's Technical Blog

Loading Rails Fixtures Without Deleting Existing Records

Introduction

user

Leo Soto


ruby, rails

Loading Rails Fixtures Without Deleting Existing Records

Posted by Leo Soto on .
Featured

ruby, rails

Loading Rails Fixtures Without Deleting Existing Records

Posted by Leo Soto on .

This blog post by choonkeat explain how to overcome the ruby limitation which forbids the use of the "class [name-of-the-class-to-monkeypatch]" statement inside methods.

Why would you need this?

I just used it to define a db:fixtures:insert task, which differs from db:fixtures:load on not removing existing records. I could have copied the Fixtures.createfixtures method and remove the line which calls Fixtures#deleteexistingfixtures. But we all know that copy/paste have its problem. Monkey-patching Fixtures#deleteexisting_fixtures to do nothing seemed like a less ugly solution, except that it could have unforeseen consequences. So I wanted to limit the effect of the monkey patching to last as little as possible. Here is the result:

  
    # Mimics rails Fixtures.create_fixtures, without deleting existing records
    # from the database.
    #
    # The implementation monkey-patches Fixtures.delete_existing_fixtures
    # *temporarily*, restoring the original behaviours before exiting
    def self.insert_fixtures(fixtures_directory, table_names, class_names = {})
      ::Fixtures.module_eval do
        alias_method :original_delete_existing_fixtures,
                     :delete_existing_fixtures
        def delete_existing_fixtures
        end
      end
      ::Fixtures.create_fixtures(fixtures_directory, table_names, class_names)
      ::Fixtures.module_eval do
        alias_method :delete_existing_fixtures,
                     :original_delete_existing_fixtures
        remove_method :original_delete_existing_fixtures
      end

[By the way, looks like this monkey-patch -> call -> undo monkey-patch may be end being common idiom which could be better encapsulated on another method. Perhaps someone already did it...]

[Update: Here is what seems an elegant way to do it]

Finally, Here is the task:

  
namespace :db do  
  namespace :fixtures do
    desc "Inserts fixtures into the current environment's database,
*without* deleting existing records (as db:fixtures:load does).
Insert specific fixtures using FIXTURES=x,y"

    task :insert => :environment do
      ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
      (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'test', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
        Utilities::Fixtures.insert_fixtures('test/fixtures', File.basename(fixture_file, '.*'))
      end
    end
  end
end