Skip to content

Commit

Permalink
Merge pull request #241 from tommeier/fix-superclass-1-1-1
Browse files Browse the repository at this point in the history
Fix MySqlAdapter superclass bug via class_eval loading of superclasses
  • Loading branch information
bmabey committed Oct 3, 2013
2 parents 9be6942 + d5616b0 commit 1ce7c99
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 103 deletions.
55 changes: 19 additions & 36 deletions lib/database_cleaner/active_record/deletion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,41 @@
require "database_cleaner/generic/truncation"
require 'database_cleaner/active_record/base'
require 'database_cleaner/active_record/truncation'
# This file may seem to have duplication with that of truncation, but by keeping them separate
# we avoiding loading this code when it is not being used (which is the common case).

module ActiveRecord
module DatabaseCleaner
module ConnectionAdapters

class MysqlAdapter < MYSQL_ADAPTER_PARENT
def delete_table(table_name)
execute("DELETE FROM #{quote_table_name(table_name)};")
end
end

class Mysql2Adapter < MYSQL2_ADAPTER_PARENT
def delete_table(table_name)
execute("DELETE FROM #{quote_table_name(table_name)};")
end
end

class JdbcAdapter < AbstractAdapter
def delete_table(table_name)
execute("DELETE FROM #{quote_table_name(table_name)};")
end
end

class PostgreSQLAdapter < POSTGRES_ADAPTER_PARENT
module AbstractDeleteAdapter
def delete_table(table_name)
execute("DELETE FROM #{quote_table_name(table_name)};")
raise NotImplementedError
end
end

class SQLServerAdapter < AbstractAdapter
module GenericDeleteAdapter
def delete_table(table_name)
execute("DELETE FROM #{quote_table_name(table_name)};")
end
end

class OracleEnhancedAdapter < AbstractAdapter
def delete_table(table_name)
execute("DELETE FROM #{quote_table_name(table_name)}")
end
end

end
end

module ActiveRecord
module ConnectionAdapters
AbstractAdapter.class_eval { include DatabaseCleaner::ConnectionAdapters::AbstractDeleteAdapter }

JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(JdbcAdapter)
AbstractMysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(AbstractMysqlAdapter)
Mysql2Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(Mysql2Adapter)
SQLiteAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(SQLiteAdapter)
SQLite3Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(SQLite3Adapter)
PostgreSQLAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(PostgreSQLAdapter)
IBM_DBAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(IBM_DBAdapter)
SQLServerAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(SQLServerAdapter)
OracleEnhancedAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::GenericDeleteAdapter } if defined?(OracleEnhancedAdapter)
end
end

module DatabaseCleaner::ActiveRecord
class Deletion < Truncation

def clean
connection = connection_class.connection
connection.disable_referential_integrity do
Expand All @@ -60,8 +46,5 @@ def clean
end
end
end

end
end


96 changes: 30 additions & 66 deletions lib/database_cleaner/active_record/truncation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@

require 'active_record/connection_adapters/abstract_adapter'

begin
require 'active_record/connection_adapters/abstract_mysql_adapter'
rescue LoadError
#Load available connection adapters
%w( abstract_mysql_adapter postgresql_adapter sqlite3_adapter ).each do |known_adapter|
begin
require "active_record/connection_adapters/#{known_adapter}"
rescue LoadError
end
end

require "database_cleaner/generic/truncation"
require 'database_cleaner/active_record/base'

module DatabaseCleaner
module ActiveRecord
module ConnectionAdapters

module AbstractAdapter
# used to be called views but that can clash with gems like schema_plus
# this gem is not meant to be exposing such an extra interface any way
def database_cleaner_view_cache
@views ||= select_values("select table_name from information_schema.views where table_schema = '#{current_database}'") rescue []
end

def database_cleaner_table_cache
# the adapters don't do caching (#130) but we make the assumption that the list stays the same in tests
@database_cleaner_tables ||= tables
Expand All @@ -36,8 +38,7 @@ def truncate_tables(tables)
end
end

module MysqlAdapter

module AbstractMysqlAdapter
def truncate_table(table_name)
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
end
Expand All @@ -53,7 +54,6 @@ def pre_count_truncate_tables(tables, options = {:reset_ids => true})

private


def row_count(table)
# Patch for MysqlAdapter with ActiveRecord 3.2.7 later
# select_value("SELECT 1") #=> "1"
Expand All @@ -65,15 +65,15 @@ def row_count(table)
# but then the table is cleaned. In other words, this function tells us if the given table
# was ever inserted into.
def has_been_used?(table)
if row_count(table) > 0
if has_rows?(table)
true
else
# Patch for MysqlAdapter with ActiveRecord 3.2.7 later
# select_value("SELECT 1") #=> "1"
select_value(<<-SQL).to_i > 1 # returns nil if not present
SELECT Auto_increment
FROM information_schema.tables
WHERE table_name='#{table}';
SELECT Auto_increment
FROM information_schema.tables
WHERE table_name='#{table}';
SQL
end
end
Expand All @@ -83,14 +83,12 @@ def has_rows?(table)
end
end


module IBM_DBAdapter
def truncate_table(table_name)
execute("TRUNCATE #{quote_table_name(table_name)} IMMEDIATE")
end
end


module SQLiteAdapter
def delete_table(table_name)
execute("DELETE FROM #{quote_table_name(table_name)};")
Expand Down Expand Up @@ -122,6 +120,12 @@ def truncate_table(table_name)
end
end

module OracleEnhancedAdapter
def truncate_table(table_name)
execute("TRUNCATE TABLE #{quote_table_name(table_name)}")
end
end

module PostgreSQLAdapter
def db_version
@db_version ||= postgresql_version
Expand Down Expand Up @@ -164,66 +168,26 @@ def has_rows?(table)
select_value("SELECT true FROM #{table} LIMIT 1;")
end
end

module OracleEnhancedAdapter
def truncate_table(table_name)
execute("TRUNCATE TABLE #{quote_table_name(table_name)}")
end
end

end
end

#TODO: Remove monkeypatching and decorate the connection instead!

module ActiveRecord
module ConnectionAdapters
# Activerecord-jdbc-adapter defines class dependencies a bit differently - if it is present, confirm to ArJdbc hierarchy to avoid 'superclass mismatch' errors.
USE_ARJDBC_WORKAROUND = defined?(ArJdbc)
# ActiveRecord 3.1+ support
MYSQL_ABSTRACT_ADAPTER = defined?(AbstractMysqlAdapter) ? AbstractMysqlAdapter : AbstractAdapter

AbstractAdapter.send(:include, ::DatabaseCleaner::ActiveRecord::AbstractAdapter)

if USE_ARJDBC_WORKAROUND
MYSQL_ADAPTER_PARENT = JdbcAdapter
else
MYSQL_ADAPTER_PARENT = MYSQL_ABSTRACT_ADAPTER
class SQLiteAdapter < AbstractAdapter; end
end
MYSQL2_ADAPTER_PARENT = MYSQL_ABSTRACT_ADAPTER

if defined?(SQLite3Adapter) && SQLite3Adapter.superclass == ActiveRecord::ConnectionAdapters::AbstractAdapter
SQLITE_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractAdapter
else
SQLITE_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : SQLiteAdapter
end
POSTGRES_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractAdapter

MYSQL_ADAPTER_PARENT.class_eval { include ::DatabaseCleaner::ActiveRecord::MysqlAdapter }
MYSQL2_ADAPTER_PARENT.class_eval { include ::DatabaseCleaner::ActiveRecord::MysqlAdapter }
SQLITE_ADAPTER_PARENT.class_eval { include ::DatabaseCleaner::ActiveRecord::SQLiteAdapter }
POSTGRES_ADAPTER_PARENT.class_eval { include ::DatabaseCleaner::ActiveRecord::PostgreSQLAdapter }

class IBM_DBAdapter < AbstractAdapter
include ::DatabaseCleaner::ActiveRecord::IBM_DBAdapter
end

class JdbcAdapter < AbstractAdapter
include ::DatabaseCleaner::ActiveRecord::TruncateOrDelete
end

class SQLServerAdapter < AbstractAdapter
include ::DatabaseCleaner::ActiveRecord::TruncateOrDelete
end

class OracleEnhancedAdapter < AbstractAdapter
include ::DatabaseCleaner::ActiveRecord::OracleEnhancedAdapter
end
#Apply adapter decoraters where applicable (adapter should be loaded)
AbstractAdapter.class_eval { include DatabaseCleaner::ConnectionAdapters::AbstractAdapter }

JdbcAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::TruncateOrDelete } if defined?(JdbcAdapter)
AbstractMysqlAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(AbstractMysqlAdapter)
Mysql2Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::AbstractMysqlAdapter } if defined?(Mysql2Adapter)
SQLiteAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLiteAdapter)
SQLite3Adapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::SQLiteAdapter } if defined?(SQLite3Adapter)
PostgreSQLAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::PostgreSQLAdapter } if defined?(PostgreSQLAdapter)
IBM_DBAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::IBM_DBAdapter } if defined?(IBM_DBAdapter)
SQLServerAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::TruncateOrDelete } if defined?(SQLServerAdapter)
OracleEnhancedAdapter.class_eval { include ::DatabaseCleaner::ConnectionAdapters::OracleEnhancedAdapter } if defined?(OracleEnhancedAdapter)
end
end


module DatabaseCleaner::ActiveRecord
class Truncation
include ::DatabaseCleaner::ActiveRecord::Base
Expand Down
3 changes: 2 additions & 1 deletion spec/database_cleaner/active_record/truncation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

module ActiveRecord
module ConnectionAdapters
[MysqlAdapter, Mysql2Adapter, SQLite3Adapter, JdbcAdapter, PostgreSQLAdapter, IBM_DBAdapter].each do |adapter|
#JdbcAdapter IBM_DBAdapter
[ MysqlAdapter, Mysql2Adapter, SQLite3Adapter, PostgreSQLAdapter ].each do |adapter|
describe adapter, "#truncate_table" do
it "responds" do
adapter.instance_methods.should include(:truncate_table)
Expand Down

0 comments on commit 1ce7c99

Please sign in to comment.