basic support for transaction/truncation with datamapper

This commit is contained in:
snusnu 2009-05-05 17:06:02 +02:00
parent af6237d95f
commit 63147b21ce
5 changed files with 176 additions and 10 deletions

View File

@ -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!

View File

@ -14,3 +14,5 @@ Feature: database cleaning
| ORM | Strategy | | ORM | Strategy |
| ActiveRecord | transaction | | ActiveRecord | transaction |
| ActiveRecord | truncation | | ActiveRecord | truncation |
| DataMapper | transaction |
| DataMapper | truncation |

View File

@ -12,7 +12,7 @@ module DatabaseCleaner
module DataMapper module DataMapper
def self.available_strategies def self.available_strategies
%w[] %w[truncation transaction]
end end
end end
@ -69,14 +69,14 @@ module DatabaseCleaner
def orm def orm
@orm ||=begin @orm ||=begin
if defined? ::ActiveRecord if defined? ::ActiveRecord
'active_record' 'active_record'
elsif defined? ::DataMapper elsif defined? ::DataMapper
'data_mapper' 'data_mapper'
else else
raise NoORMDetected, "No known ORM was detected! Is ActiveRecord or DataMapper loaded?" raise NoORMDetected, "No known ORM was detected! Is ActiveRecord or DataMapper loaded?"
end end
end end
end end

View File

@ -1,6 +1,23 @@
module DatabaseCleaner::DataMapper module DatabaseCleaner::DataMapper
class Transaction 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 end

View File

@ -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