Wednesday, May 28, 2008

Loading Rails Fixtures Without Deleting Existing Records

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.create_fixtures method and remove the line which calls Fixtures#delete_existing_fixtures. But we all know that copy/paste have its problem. Monkey-patching Fixtures#delete_existing_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

0 comments: