README.md in octoshark-0.2.1 vs README.md in octoshark-0.2.2

- old
+ new

@@ -161,11 +161,11 @@ * Some rake tasks like `rake db:test:prepare` ## Cleaning test databases -When using persistent connections, you can use tools like [DatabaseCleaner](https://github.com/DatabaseCleaner/database_cleaner) or [DatabaseRewinder](https://github.com/amatsuda/database_rewinder) to clean test databases. Here's an example of RSpec config for `DatabaseCleaner`: +For `Octoshark::ConnectionPoolsManager`, we can use [DatabaseCleaner](https://github.com/DatabaseCleaner/database_cleaner) and RSpec like: ```ruby config.before(:suite) do setup_database_cleaner DatabaseCleaner.clean_with(:truncation) @@ -175,11 +175,10 @@ setup_database_cleaner DatabaseCleaner.start end config.after(:each) do - setup_database_cleaner DatabaseCleaner.clean_with(:transaction) end def setup_database_cleaner DatabaseCleaner[:active_record, {connection: ActiveRecord::Base.connection_pool}] @@ -189,52 +188,96 @@ end end end ``` -When using non-persistent connections where transaction rollback as a cleaning strategy will not work, we can use a custom solution inspired by `DatabaseRewinder`. It also works with dynamic databases created on the fly in the test suite. +For `Octoshark::ConnectionManager` where connections and databases are dynamically created and cannot be configured in the test setup, we can write a custom database cleaner inspired by [DatabaseRewinder](https://github.com/amatsuda/database_rewinder). The example below is for a multi-tenant test setup with a main (core) database and a tenant database for the `CURRENT_USER` in the test suite. `CustomDatabaseCleaner.clean_all` cleans all core database tables before test suite and `CustomDatabaseCleaner.clean` cleans used tables in both core and tenant databases after each test. + ```ruby -module DatabaseCleaner - module InsertRecorder - def execute(sql, *) - DatabaseCleaner.record_inserted_table(self, sql) - super +module CustomDatabaseCleaner + INSERT_REGEX = /\AINSERT(?:\s+IGNORE)?\s+INTO\s+(?:\.*[`"]?(?<table>[^.\s`"]+)[`"]?)*/i + + @@tables_with_inserts = [] + + class << self + def record_inserted_table(connection, sql) + match = sql.match(INSERT_REGEX) + + if match && match[:table] && tables_with_inserts.exclude?(match[:table]) + tables_with_inserts << match[:table] + end end - def exec_query(sql, *) - DatabaseCleaner.record_inserted_table(self, sql) - super + def clean_all + with_core_db_connection do |connection| + clean_tables(connection) + end + + reset_tables_with_inserts end - end - @@tables = [] + def clean + with_core_db_connection do |connection| + clean_tables(connection, { 'users' => [CURRENT_USER.id] }) + end - def self.tables - @@tables - end + CURRENT_USER.with_tenant do |connection| + clean_tables(connection) + end - def self.record_inserted_table(connection, sql) - match = sql.match(/\AINSERT(?:\s+IGNORE)?\s+INTO\s+(?:\.*[`"]?([^.\s`"]+)[`"]?)*/i) - tables << match[1] if match && !tables.include?(match[1]) - end + reset_tables_with_inserts + end - def self.clean - CoreDB.with_connection do |connection| - ( - connection.tables.reject { |t| t == ActiveRecord::Migrator.schema_migrations_table_name } & tables - ).each do |table| + private + def with_core_db_connection(&block) + CoreDBManager.with_connection(ActiveRecord::Base.configurations[Rails.env].symbolize_keys, &block) + end + + def clean_tables(connection, keep_data = {}) + tables_to_clean = connection.tables.reject { |t| t == ActiveRecord::Migrator.schema_migrations_table_name } + tables_to_clean = tables_to_clean & tables_with_inserts if tables_with_inserts.present? + + tables_to_clean.each do |table| connection.disable_referential_integrity do - connection.execute "DELETE FROM #{connection.quote_table_name(table)};" + table_name = connection.quote_table_name(table) + keep_ids = keep_data[table] + + if keep_ids + connection.execute("DELETE FROM #{table_name} WHERE id NOT IN (#{keep_ids.join(',')});") + else + connection.execute("DELETE FROM #{table_name};") + end end end end - @@tables = [] + + def reset_tables_with_inserts + @@tables_with_inserts = [] + end + + def tables_with_inserts + @@tables_with_inserts + end end end +module CustomDatabaseCleaner + module InsertRecorder + def execute(sql, *) + CustomDatabaseCleaner.record_inserted_table(self, sql) + super + end + + def exec_query(sql, *) + CustomDatabaseCleaner.record_inserted_table(self, sql) + super + end + end +end + require 'active_record/connection_adapters/abstract_mysql_adapter' -ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.send(:prepend, DatabaseCleaner::InsertRecorder) +ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.send(:prepend, CustomDatabaseCleaner::InsertRecorder) ``` ## Development Setup