From 63147b21cefca764075af50f54834dc33059682d Mon Sep 17 00:00:00 2001 From: snusnu Date: Tue, 5 May 2009 17:06:02 +0200 Subject: [PATCH] basic support for transaction/truncation with datamapper --- examples/lib/datamapper.rb | 16 +++ features/cleaning.feature | 2 + lib/database_cleaner/configuration.rb | 18 +-- .../data_mapper/transaction.rb | 19 ++- .../data_mapper/truncation.rb | 131 ++++++++++++++++++ 5 files changed, 176 insertions(+), 10 deletions(-) create mode 100644 examples/lib/datamapper.rb create mode 100644 lib/database_cleaner/data_mapper/truncation.rb diff --git a/examples/lib/datamapper.rb b/examples/lib/datamapper.rb new file mode 100644 index 0000000..e9430bd --- /dev/null +++ b/examples/lib/datamapper.rb @@ -0,0 +1,16 @@ +require "dm-core" + +# only to please activerecord API used in database_cleaner/examples/features/step_definitions +# yes, i know that's lazy ... +require "dm-validations" +require "dm-aggregates" + +DataMapper.setup(:default, "sqlite3::memory:") + +class Widget + include DataMapper::Resource + property :id, Serial + property :name, String +end + +Widget.auto_migrate! \ No newline at end of file diff --git a/features/cleaning.feature b/features/cleaning.feature index 4d5b92c..418603a 100644 --- a/features/cleaning.feature +++ b/features/cleaning.feature @@ -14,3 +14,5 @@ Feature: database cleaning | ORM | Strategy | | ActiveRecord | transaction | | ActiveRecord | truncation | + | DataMapper | transaction | + | DataMapper | truncation | \ No newline at end of file diff --git a/lib/database_cleaner/configuration.rb b/lib/database_cleaner/configuration.rb index 6e1e136..8a8e668 100644 --- a/lib/database_cleaner/configuration.rb +++ b/lib/database_cleaner/configuration.rb @@ -12,7 +12,7 @@ module DatabaseCleaner module DataMapper def self.available_strategies - %w[] + %w[truncation transaction] end end @@ -69,14 +69,14 @@ module DatabaseCleaner def orm @orm ||=begin - if defined? ::ActiveRecord - 'active_record' - elsif defined? ::DataMapper - 'data_mapper' - else - raise NoORMDetected, "No known ORM was detected! Is ActiveRecord or DataMapper loaded?" - end - end + if defined? ::ActiveRecord + 'active_record' + elsif defined? ::DataMapper + 'data_mapper' + else + raise NoORMDetected, "No known ORM was detected! Is ActiveRecord or DataMapper loaded?" + end + end end diff --git a/lib/database_cleaner/data_mapper/transaction.rb b/lib/database_cleaner/data_mapper/transaction.rb index 29cfffd..12ee84c 100644 --- a/lib/database_cleaner/data_mapper/transaction.rb +++ b/lib/database_cleaner/data_mapper/transaction.rb @@ -1,6 +1,23 @@ module DatabaseCleaner::DataMapper class Transaction - end + def start(repo = :default) + repository(repo) do |r| + transaction = DataMapper::Transaction.new(r) + transaction.begin + r.adapter.push_transaction(transaction) + end + end + def clean(repo = :default) + repository(repo) do |r| + adapter = r.adapter + while adapter.current_transaction + adapter.current_transaction.rollback + adapter.pop_transaction + end + end + end + + end end diff --git a/lib/database_cleaner/data_mapper/truncation.rb b/lib/database_cleaner/data_mapper/truncation.rb new file mode 100644 index 0000000..9348e0d --- /dev/null +++ b/lib/database_cleaner/data_mapper/truncation.rb @@ -0,0 +1,131 @@ +module DataMapper + module Adapters + + class DataObjectsAdapter + + # make an equivalent to activerecord's Connection#tables method available + # dunno if there is a better/other way than this, or if this even works :) + def storage_names(repository = :default) + DataMapper::Resource.descendants.map { |model| model.storage_name(repository)} + end + + end + + class MysqlAdapter < DataObjectsAdapter + + def truncate_table(table_name) + execute("TRUNCATE TABLE #{quote_table_name(table_name)};") + end + + # copied from activerecord + def disable_referential_integrity + old = query("SELECT @@FOREIGN_KEY_CHECKS;") + begin + execute("SET FOREIGN_KEY_CHECKS = 0;") + yield + ensure + execute("SET FOREIGN_KEY_CHECKS = #{old};") + end + end + + end + + class Sqlite3Adapter < DataObjectsAdapter + + def truncate_table(table_name) + execute("DELETE FROM #{quote_table_name(table_name)};") + end + + # this is a no-op copied from activerecord + # i didn't find out if/how this is possible + # activerecord also doesn't do more here + def disable_referential_integrity + yield + end + + end + + + # FIXME + # this definitely won't work!!! + # i basically just copied activerecord code to get a rough idea what they do + # anyways, i don't have postgres available, so i won't be the one to write this. + # maybe the stub codes below gets some postgres/datamapper user going, though. + class PostgresAdapter < DataObjectsAdapter + + def truncate_table(table_name) + execute("TRUNCATE TABLE #{quote_table_name(table_name)};") + end + + # FIXME + # copied from activerecord + def supports_disable_referential_integrity? + version = query("SHOW server_version")[0][0].split('.') + (version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false + rescue + return false + end + + # FIXME + # copied unchanged from activerecord + def disable_referential_integrity(repository = :default) + if supports_disable_referential_integrity? then + execute(storage_names(repository).collect do |name| + "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" + end.join(";")) + end + yield + ensure + if supports_disable_referential_integrity? then + execute(storage_names(repository).collect do |name| + "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" + end.join(";")) + end + end + + end + + end + +end + + +module DatabaseCleaner::DataMapper + class Truncation + + def initialize(options={}) + if !options.empty? && !(options.keys - [:only, :except]).empty? + raise ArgumentError, "The only valid options are :only and :except. You specified #{options.keys.join(',')}." + end + if options.has_key?(:only) && options.has_key?(:except) + raise ArgumentError, "You may only specify either :only or :either. Doing both doesn't really make sense does it?" + end + + @only = options[:only] + @tables_to_exclude = (options[:except] || []) << 'migration_info' # dm-migrations calls it like so + end + + + def start(repository = :default) + # no-op + end + + def clean(repository = :default) + adapter = DataMapper.repository(repository).adapter + adapter.disable_referential_integrity do + tables_to_truncate.each do |table_name| + adapter.truncate_table table_name + end + end + end + + private + + # no idea if this works + def tables_to_truncate(repository = :default) + (@only || DataMapper.repository(repository).adapter.storage_names(repository)) - @tables_to_exclude + end + + end + +end