From e2b5086d47c20fb12d934c832a4ff0ee7ba5c093 Mon Sep 17 00:00:00 2001 From: stanislaw Date: Sun, 8 Jul 2012 16:23:37 +0300 Subject: [PATCH] Fast truncation strategy for ActiveRecord with mysql, mysql2 or pg --- .../active_record/truncation.rb | 136 +++++++++++++++++- lib/database_cleaner/generic/truncation.rb | 5 +- .../active_record/truncation/mysql2_spec.rb | 40 ++++++ .../active_record/truncation/mysql_spec.rb | 40 ++++++ .../truncation/postgresql_spec.rb | 103 +++++++++++++ .../active_record/truncation/shared_mysql.rb | 63 ++++++++ .../active_record/truncation_spec.rb | 107 ++++++++------ .../generic/truncation_spec.rb | 15 ++ spec/support/active_record/mysql2_setup.rb | 41 ++++++ spec/support/active_record/mysql_setup.rb | 41 ++++++ .../support/active_record/postgresql_setup.rb | 45 ++++++ 11 files changed, 588 insertions(+), 48 deletions(-) create mode 100644 spec/database_cleaner/active_record/truncation/mysql2_spec.rb create mode 100644 spec/database_cleaner/active_record/truncation/mysql_spec.rb create mode 100644 spec/database_cleaner/active_record/truncation/postgresql_spec.rb create mode 100644 spec/database_cleaner/active_record/truncation/shared_mysql.rb create mode 100644 spec/support/active_record/mysql2_setup.rb create mode 100644 spec/support/active_record/mysql_setup.rb create mode 100644 spec/support/active_record/postgresql_setup.rb diff --git a/lib/database_cleaner/active_record/truncation.rb b/lib/database_cleaner/active_record/truncation.rb index aa057cd..07eef84 100755 --- a/lib/database_cleaner/active_record/truncation.rb +++ b/lib/database_cleaner/active_record/truncation.rb @@ -52,12 +52,86 @@ module ActiveRecord def truncate_table(table_name) execute("TRUNCATE TABLE #{quote_table_name(table_name)};") end + + def fast_truncate_tables *tables_and_opts + opts = tables_and_opts.last.is_a?(::Hash) ? tables_and_opts.pop : {} + reset_ids = opts[:reset_ids] != false + + _tables = tables_and_opts + + _tables.each do |table_name| + if reset_ids + truncate_table_with_id_reset(table_name) + else + truncate_table_no_id_reset(table_name) + end + end + end + + def truncate_table_with_id_reset(table_name) + rows_exist = execute("SELECT EXISTS(SELECT 1 FROM #{quote_table_name(table_name)} LIMIT 1)").fetch_row.first.to_i + + if rows_exist == 0 + auto_inc = execute(<<-AUTO_INCREMENT + SELECT Auto_increment + FROM information_schema.tables + WHERE table_name='#{table_name}'; + AUTO_INCREMENT + ) + + truncate_table(table_name) if auto_inc.fetch_row.first.to_i > 1 + else + truncate_table(table_name) + end + end + + def truncate_table_no_id_reset(table_name) + rows_exist = execute("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table_name)} LIMIT 1)").fetch_row.first.to_i + truncate_table(table_name) if rows_exist > 0 + end end class Mysql2Adapter < MYSQL2_ADAPTER_PARENT def truncate_table(table_name) execute("TRUNCATE TABLE #{quote_table_name(table_name)};") end + + def fast_truncate_tables *tables_and_opts + opts = tables_and_opts.last.is_a?(::Hash) ? tables_and_opts.pop : {} + reset_ids = opts[:reset_ids] != false + + _tables = tables_and_opts + + _tables.each do |table_name| + if reset_ids + truncate_table_with_id_reset(table_name) + else + truncate_table_no_id_reset(table_name) + end + end + end + + def truncate_table_with_id_reset(table_name) + rows_exist = execute("SELECT EXISTS(SELECT 1 FROM #{quote_table_name(table_name)} LIMIT 1)").first.first.to_i + + if rows_exist == 0 + auto_inc = execute(<<-AUTO_INCREMENT + SELECT Auto_increment + FROM information_schema.tables + WHERE table_name='#{table_name}'; + AUTO_INCREMENT + ) + + truncate_table(table_name) if auto_inc.first.first.to_i > 1 + else + truncate_table(table_name) + end + end + + def truncate_table_no_id_reset(table_name) + rows_exist = execute("SELECT EXISTS(SELECT 1 FROM #{quote_table_name(table_name)} LIMIT 1)").first.first + truncate_table(table_name) if rows_exist == 1 + end end class IBM_DBAdapter < AbstractAdapter @@ -101,12 +175,60 @@ module ActiveRecord def truncate_table(table_name) truncate_tables([table_name]) end - + def truncate_tables(table_names) return if table_names.nil? || table_names.empty? execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} #{restart_identity} #{cascade};") end + def fast_truncate_tables(*tables_and_opts) + opts = tables_and_opts.last.is_a?(::Hash) ? tables_and_opts.pop : {} + reset_ids = opts[:reset_ids] != false + + _tables = tables_and_opts + + if reset_ids + truncate_tables_with_id_reset(_tables) + else + truncate_tables_no_id_reset(_tables) + end + end + + def truncate_tables_with_id_reset(_tables) + tables_to_truncate = [] + + _tables.each do |table| + begin + table_curr_value = execute(<<-CURR_VAL + SELECT currval('#{table}_id_seq'); + CURR_VAL + ).first['currval'].to_i + rescue ActiveRecord::StatementInvalid + table_curr_value = nil + end + + if table_curr_value && table_curr_value > 0 + tables_to_truncate << table + end + end + + truncate_tables(tables_to_truncate) if tables_to_truncate.any? + end + + def truncate_tables_no_id_reset(_tables) + tables_to_truncate = [] + + _tables.each do |table| + rows_exist = execute(<<-TR + SELECT true FROM #{table} LIMIT 1; + TR + ) + + tables_to_truncate << table if rows_exist.any? + end + + truncate_tables(tables_to_truncate) if tables_to_truncate.any? + end end class SQLServerAdapter < AbstractAdapter @@ -137,7 +259,11 @@ module DatabaseCleaner::ActiveRecord def clean connection = connection_klass.connection connection.disable_referential_integrity do - connection.truncate_tables(tables_to_truncate(connection)) + if connection.respond_to?(:fast_truncate_tables) + connection.fast_truncate_tables(tables_to_truncate(connection), {:reset_ids => reset_ids?}) + else + connection.truncate_tables(tables_to_truncate(connection)) + end end end @@ -152,8 +278,8 @@ module DatabaseCleaner::ActiveRecord 'schema_migrations' end + def reset_ids? + @reset_ids != false + end end end - - - diff --git a/lib/database_cleaner/generic/truncation.rb b/lib/database_cleaner/generic/truncation.rb index 51d1616..abfadef 100644 --- a/lib/database_cleaner/generic/truncation.rb +++ b/lib/database_cleaner/generic/truncation.rb @@ -2,8 +2,8 @@ module DatabaseCleaner module Generic module Truncation def initialize(opts={}) - if !opts.empty? && !(opts.keys - [:only, :except]).empty? - raise ArgumentError, "The only valid options are :only and :except. You specified #{opts.keys.join(',')}." + if !opts.empty? && !(opts.keys - [:only, :except, :reset_ids]).empty? + raise ArgumentError, "The only valid options are :only, :except, or :reset_ids. You specified #{opts.keys.join(',')}." end if opts.has_key?(:only) && opts.has_key?(:except) raise ArgumentError, "You may only specify either :only or :except. Doing both doesn't really make sense does it?" @@ -12,6 +12,7 @@ module DatabaseCleaner @only = opts[:only] @tables_to_exclude = (opts[:except] || []).dup @tables_to_exclude << migration_storage_name if migration_storage_name + @reset_ids = opts[:reset_ids] end def start diff --git a/spec/database_cleaner/active_record/truncation/mysql2_spec.rb b/spec/database_cleaner/active_record/truncation/mysql2_spec.rb new file mode 100644 index 0000000..57fb040 --- /dev/null +++ b/spec/database_cleaner/active_record/truncation/mysql2_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' +require 'active_record' +require 'support/active_record/mysql2_setup' +require 'database_cleaner/active_record/truncation' +require 'database_cleaner/active_record/truncation/shared_mysql' + +module ActiveRecord + module ConnectionAdapters + describe do + before(:all) { active_record_mysql2_setup } + + let(:adapter) { Mysql2Adapter } + let(:connection) { active_record_mysql2_connection } + + describe "#truncate_table" do + it "should truncate the table" do + 2.times { User.create } + + connection.truncate_table('users') + User.count.should == 0 + end + + it "should reset AUTO_INCREMENT index of table" do + 2.times { User.create } + User.delete_all + + connection.truncate_table('users') + + User.create.id.should == 1 + end + end + + it_behaves_like "Fast truncation" do + let(:adapter) { Mysql2Adapter } + let(:connection) { active_record_mysql2_connection } + end + end + end +end + diff --git a/spec/database_cleaner/active_record/truncation/mysql_spec.rb b/spec/database_cleaner/active_record/truncation/mysql_spec.rb new file mode 100644 index 0000000..35d46b4 --- /dev/null +++ b/spec/database_cleaner/active_record/truncation/mysql_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' +require 'active_record' +require 'support/active_record/mysql_setup' +require 'database_cleaner/active_record/truncation' +require 'database_cleaner/active_record/truncation/shared_mysql' + +module ActiveRecord + module ConnectionAdapters + describe do + before(:all) { active_record_mysql_setup } + + let(:adapter) { MysqlAdapter } + let(:connection) { active_record_mysql_connection } + + describe "#truncate_table" do + it "should truncate the table" do + 2.times { User.create } + + connection.truncate_table('users') + User.count.should == 0 + end + + it "should reset AUTO_INCREMENT index of table" do + 2.times { User.create } + User.delete_all + + connection.truncate_table('users') + + User.create.id.should == 1 + end + end + + it_behaves_like "Fast truncation" do + let(:adapter) { MysqlAdapter } + let(:connection) { active_record_mysql_connection } + end + end + end +end + diff --git a/spec/database_cleaner/active_record/truncation/postgresql_spec.rb b/spec/database_cleaner/active_record/truncation/postgresql_spec.rb new file mode 100644 index 0000000..5b81f45 --- /dev/null +++ b/spec/database_cleaner/active_record/truncation/postgresql_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' +require 'active_record' +require 'support/active_record/postgresql_setup' +require 'database_cleaner/active_record/truncation' + +module ActiveRecord + module ConnectionAdapters + describe do + before(:all) { active_record_pg_setup } + + let(:adapter) { PostgreSQLAdapter } + + let(:connection) do + active_record_pg_connection + end + + before(:each) do + connection.truncate_tables connection.tables + end + + describe "#truncate_table" do + it "should truncate the table" do + 2.times { User.create } + + connection.truncate_table('users') + User.count.should == 0 + end + + it "should reset AUTO_INCREMENT index of table" do + 2.times { User.create } + User.delete_all + + connection.truncate_table('users') + + User.create.id.should == 1 + end + end + + describe "#truncate_tables_with_id_reset" do + it "responds" do + adapter.instance_methods.should include('truncate_tables_with_id_reset') + end + + it "should truncate the table" do + 2.times { User.create } + + connection.truncate_tables_with_id_reset('users') + User.count.should == 0 + end + + it "should reset AUTO_INCREMENT index of table" do + 2.times { User.create } + User.delete_all + + connection.truncate_tables_with_id_reset('users') + + User.create.id.should == 1 + end + end + + describe "#truncate_tables_no_id_reset" do + it "responds" do + adapter.instance_methods.map(&:to_s).should include('truncate_tables_no_id_reset') + end + + it "should truncate the table" do + 2.times { User.create } + + connection.truncate_tables_no_id_reset('users') + User.count.should == 0 + end + + it "should not reset AUTO_INCREMENT index of table" do + 2.times { User.create } + User.delete_all + + connection.truncate_tables_no_id_reset('users') + + User.create.id.should == 3 + end + end + + describe "#fast_truncate_tables" do + it "responds" do + adapter.instance_methods.should include('fast_truncate_tables') + end + + it 'should call #truncate_tables_with_id_reset on each table if :reset_ids option true was given' do + connection.should_receive(:truncate_tables_with_id_reset).exactly(connection.tables.size).times + + connection.fast_truncate_tables(connection.tables) + end + + it 'should call #truncate_tables_with_id_reset on each table if :reset_ids option false was given' do + connection.should_receive(:truncate_tables_no_id_reset).exactly(connection.tables.size).times + + connection.fast_truncate_tables(connection.tables, :reset_ids => false) + end + end + end + end +end + diff --git a/spec/database_cleaner/active_record/truncation/shared_mysql.rb b/spec/database_cleaner/active_record/truncation/shared_mysql.rb new file mode 100644 index 0000000..8fc5a93 --- /dev/null +++ b/spec/database_cleaner/active_record/truncation/shared_mysql.rb @@ -0,0 +1,63 @@ +shared_examples_for "Fast truncation" do + describe "#truncate_table_with_id_reset" do + it "responds" do + adapter.instance_methods.should include('truncate_table_with_id_reset') + end + + it "should truncate the table" do + 2.times { User.create } + + connection.truncate_table_with_id_reset('users') + User.count.should == 0 + end + + it "should reset AUTO_INCREMENT index of table" do + 2.times { User.create } + User.delete_all + + connection.truncate_table_with_id_reset('users') + + User.create.id.should == 1 + end + end + + describe "#truncate_table_no_id_reset" do + it "responds" do + adapter.instance_methods.map(&:to_s).should include('truncate_table_no_id_reset') + end + + it "should truncate the table" do + 2.times { User.create } + + connection.truncate_table_no_id_reset('users') + User.count.should == 0 + end + + it "should not reset AUTO_INCREMENT index of table" do + 2.times { User.create } + User.delete_all + + connection.truncate_table_no_id_reset('users') + + User.create.id.should == 3 + end + end + + describe "#fast_truncate_tables" do + it "responds" do + adapter.instance_methods.should include('fast_truncate_tables') + end + + it 'should call #truncate_table_with_id_reset on each table if :reset_ids option true was given' do + connection.should_receive(:truncate_table_with_id_reset).exactly(connection.tables.size).times + + connection.fast_truncate_tables(connection.tables) + end + + it 'should call #truncate_table_with_id_reset on each table if :reset_ids option false was given' do + connection.should_receive(:truncate_table_no_id_reset).exactly(connection.tables.size).times + + connection.fast_truncate_tables(connection.tables, :reset_ids => false) + end + end +end diff --git a/spec/database_cleaner/active_record/truncation_spec.rb b/spec/database_cleaner/active_record/truncation_spec.rb index a7e2f6a..273fef2 100644 --- a/spec/database_cleaner/active_record/truncation_spec.rb +++ b/spec/database_cleaner/active_record/truncation_spec.rb @@ -1,16 +1,15 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'active_record' -require 'database_cleaner/active_record/truncation' +require 'database_cleaner/active_record/truncation' module ActiveRecord module ConnectionAdapters [MysqlAdapter, Mysql2Adapter, SQLite3Adapter, JdbcAdapter, PostgreSQLAdapter, IBM_DBAdapter].each do |adapter| describe adapter, "#truncate_table" do it "responds" do - adapter.new("foo").should respond_to(:truncate_table) + adapter.instance_methods.should include('truncate_table') end - it "should truncate the table" end end end @@ -22,55 +21,81 @@ module DatabaseCleaner describe Truncation do let(:connection) { mock('connection') } - before(:each) do connection.stub!(:disable_referential_integrity).and_yield connection.stub!(:database_cleaner_view_cache).and_return([]) ::ActiveRecord::Base.stub!(:connection).and_return(connection) end - it "should truncate all tables except for schema_migrations" do - connection.stub!(:database_cleaner_table_cache).and_return(%w[schema_migrations widgets dogs]) - - connection.should_receive(:truncate_tables).with(['widgets', 'dogs']) - Truncation.new.clean + describe '#clean' do + it "should truncate all tables except for schema_migrations" do + connection.stub!(:database_cleaner_table_cache).and_return(%w[schema_migrations widgets dogs]) + + connection.should_receive(:truncate_tables).with(['widgets', 'dogs']) + Truncation.new.clean + end + + it "should only truncate the tables specified in the :only option when provided" do + connection.stub!(:database_cleaner_table_cache).and_return(%w[schema_migrations widgets dogs]) + + connection.should_receive(:truncate_tables).with(['widgets']) + + Truncation.new(:only => ['widgets']).clean + end + + it "should not truncate the tables specified in the :except option" do + connection.stub!(:database_cleaner_table_cache).and_return(%w[schema_migrations widgets dogs]) + + connection.should_receive(:truncate_tables).with(['dogs']) + + Truncation.new(:except => ['widgets']).clean + end + + it "should raise an error when :only and :except options are used" do + running { + Truncation.new(:except => ['widgets'], :only => ['widgets']) + }.should raise_error(ArgumentError) + end + + it "should raise an error when invalid options are provided" do + running { Truncation.new(:foo => 'bar') }.should raise_error(ArgumentError) + end + + it "should not truncate views" do + connection.stub!(:database_cleaner_table_cache).and_return(%w[widgets dogs]) + connection.stub!(:database_cleaner_view_cache).and_return(["widgets"]) + + connection.should_receive(:truncate_tables).with(['dogs']) + + Truncation.new.clean + end + + describe "relying on #fast_truncate_tables if connection allows it" do + it "should rely on #fast_truncate_tables if connection allows it" do + connection.stub!(:database_cleaner_table_cache).and_return(%w[widgets dogs]) + connection.stub!(:database_cleaner_view_cache).and_return(["widgets"]) + + connection.should_receive(:fast_truncate_tables).with(['dogs'], :reset_ids => true) + + Truncation.new.clean + end + end end - it "should only truncate the tables specified in the :only option when provided" do - connection.stub!(:database_cleaner_table_cache).and_return(%w[schema_migrations widgets dogs]) + describe '#reset_ids?' do + subject { Truncation.new } + its(:reset_ids?) { should == true } - connection.should_receive(:truncate_tables).with(['widgets']) + it 'should return true if @reset_id is set and non false or nil' do + subject.instance_variable_set(:"@reset_ids", 'Something') + subject.send(:reset_ids?).should == true + end - Truncation.new(:only => ['widgets']).clean + it 'should return false if @reset_id is set to false' do + subject.instance_variable_set(:"@reset_ids", false) + subject.send(:reset_ids?).should == false + end end - - it "should not truncate the tables specified in the :except option" do - connection.stub!(:database_cleaner_table_cache).and_return(%w[schema_migrations widgets dogs]) - - connection.should_receive(:truncate_tables).with(['dogs']) - - Truncation.new(:except => ['widgets']).clean - end - - it "should raise an error when :only and :except options are used" do - running { - Truncation.new(:except => ['widgets'], :only => ['widgets']) - }.should raise_error(ArgumentError) - end - - it "should raise an error when invalid options are provided" do - running { Truncation.new(:foo => 'bar') }.should raise_error(ArgumentError) - end - - it "should not truncate views" do - connection.stub!(:database_cleaner_table_cache).and_return(%w[widgets dogs]) - connection.stub!(:database_cleaner_view_cache).and_return(["widgets"]) - - connection.should_receive(:truncate_tables).with(['dogs']) - - Truncation.new.clean - end - end end end diff --git a/spec/database_cleaner/generic/truncation_spec.rb b/spec/database_cleaner/generic/truncation_spec.rb index 8b56703..b00451e 100644 --- a/spec/database_cleaner/generic/truncation_spec.rb +++ b/spec/database_cleaner/generic/truncation_spec.rb @@ -13,6 +13,10 @@ module ::DatabaseCleaner def except @tables_to_exclude end + + def reset_ids? + !!@reset_ids + end end class MigrationExample < TruncationExample @@ -44,6 +48,7 @@ module ::DatabaseCleaner it { expect{ TruncationExample.new( { :except => "something",:only => "something else" } ) }.to raise_error(ArgumentError) } it { expect{ TruncationExample.new( { :only => "something" } ) }.to_not raise_error(ArgumentError) } it { expect{ TruncationExample.new( { :except => "something" } ) }.to_not raise_error(ArgumentError) } + it { expect{ TruncationExample.new( { :reset_ids => "something" } ) }.to_not raise_error(ArgumentError) } context "" do subject { TruncationExample.new( { :only => ["something"] } ) } @@ -57,6 +62,16 @@ module ::DatabaseCleaner its(:except) { should include("something") } end + context "" do + subject { TruncationExample.new( { :reset_ids => ["something"] } ) } + its(:reset_ids?) { should == true } + end + + context "" do + subject { TruncationExample.new( { :reset_ids => nil } ) } + its(:reset_ids?) { should == false } + end + context "" do subject { MigrationExample.new } its(:only) { should == nil } diff --git a/spec/support/active_record/mysql2_setup.rb b/spec/support/active_record/mysql2_setup.rb new file mode 100644 index 0000000..b94d7f4 --- /dev/null +++ b/spec/support/active_record/mysql2_setup.rb @@ -0,0 +1,41 @@ +module MySQL2Helper + puts "Active Record #{ActiveRecord::VERSION::STRING}, mysql2" + + # ActiveRecord::Base.logger = Logger.new(STDERR) + + @@mysql2_db_spec = { + :adapter => 'mysql2', + :host => 'localhost', + :username => 'root', + :password => '', + :encoding => 'utf8' + } + + @@db = {:database => 'database_cleaner_test'} + + def active_record_mysql2_setup + ActiveRecord::Base.establish_connection(@@mysql2_db_spec) + + ActiveRecord::Base.connection.drop_database @@db[:database] rescue nil + ActiveRecord::Base.connection.create_database @@db[:database] + + ActiveRecord::Base.establish_connection(@@mysql2_db_spec.merge(@@db)) + + ActiveRecord::Schema.define do + create_table :users, :force => true do |t| + t.integer :name + end + end + end + + def active_record_mysql2_connection + ActiveRecord::Base.connection + end + + class ::User < ActiveRecord::Base + end +end + +RSpec.configure do |c| + c.include MySQL2Helper +end diff --git a/spec/support/active_record/mysql_setup.rb b/spec/support/active_record/mysql_setup.rb new file mode 100644 index 0000000..8f43762 --- /dev/null +++ b/spec/support/active_record/mysql_setup.rb @@ -0,0 +1,41 @@ +module MySQLHelper + puts "Active Record #{ActiveRecord::VERSION::STRING}, mysql" + + # ActiveRecord::Base.logger = Logger.new(STDERR) + + @@mysql_db_spec = { + :adapter => 'mysql', + :host => 'localhost', + :username => 'root', + :password => '', + :encoding => 'utf8' + } + + @@db = {:database => 'database_cleaner_test'} + + def active_record_mysql_setup + ActiveRecord::Base.establish_connection(@@mysql_db_spec) + + ActiveRecord::Base.connection.drop_database @@db[:database] rescue nil + ActiveRecord::Base.connection.create_database @@db[:database] + + ActiveRecord::Base.establish_connection(@@mysql_db_spec.merge(@@db)) + + ActiveRecord::Schema.define do + create_table :users, :force => true do |t| + t.integer :name + end + end + end + + def active_record_mysql_connection + ActiveRecord::Base.connection + end + + class ::User < ActiveRecord::Base + end +end + +RSpec.configure do |c| + c.include MySQLHelper +end diff --git a/spec/support/active_record/postgresql_setup.rb b/spec/support/active_record/postgresql_setup.rb new file mode 100644 index 0000000..3fc9f24 --- /dev/null +++ b/spec/support/active_record/postgresql_setup.rb @@ -0,0 +1,45 @@ +module PostgreSQLHelper + puts "Active Record #{ActiveRecord::VERSION::STRING}, pg" + + # ActiveRecord::Base.logger = Logger.new(STDERR) + + # createdb database_cleaner_test -E UTF8 -T template0 + + @@pg_db_spec = { + :adapter => 'postgresql', + :database => 'postgres', + :host => '127.0.0.1', + :username => 'postgres', + :password => '', + :encoding => 'unicode', + :template => 'template0' + } + + @@db = {:database => 'database_cleaner_test'} + + # ActiveRecord::Base.establish_connection(@@pg_db_spec) + + # ActiveRecord::Base.connection.drop_database db[:database] rescue nil + # ActiveRecord::Base.connection.create_database db[:database] + + def active_record_pg_setup + ActiveRecord::Base.establish_connection(@@pg_db_spec.merge(@@db)) + + ActiveRecord::Schema.define do + create_table :users, :force => true do |t| + t.integer :name + end + end + end + + def active_record_pg_connection + ActiveRecord::Base.connection + end + + class ::User < ActiveRecord::Base + end +end + +RSpec.configure do |c| + c.include PostgreSQLHelper +end