database_cleaner 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +41 -0
- data/LICENSE +20 -0
- data/README.textile +118 -0
- data/Rakefile +46 -0
- data/VERSION.yml +4 -0
- data/cucumber.yml +1 -0
- data/examples/features/example.feature +11 -0
- data/examples/features/step_definitions/example_steps.rb +8 -0
- data/examples/features/support/env.rb +23 -0
- data/examples/lib/activerecord.rb +12 -0
- data/examples/lib/datamapper.rb +16 -0
- data/features/cleaning.feature +18 -0
- data/features/step_definitions/database_cleaner_steps.rb +25 -0
- data/features/support/env.rb +7 -0
- data/lib/database_cleaner.rb +3 -0
- data/lib/database_cleaner/active_record/transaction.rb +26 -0
- data/lib/database_cleaner/active_record/truncation.rb +69 -0
- data/lib/database_cleaner/configuration.rb +94 -0
- data/lib/database_cleaner/cucumber.rb +8 -0
- data/lib/database_cleaner/data_mapper/transaction.rb +23 -0
- data/lib/database_cleaner/data_mapper/truncation.rb +142 -0
- data/lib/database_cleaner/truncation_base.rb +41 -0
- data/spec/database_cleaner/active_record/truncation_spec.rb +66 -0
- data/spec/database_cleaner/configuration_spec.rb +101 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +12 -0
- metadata +87 -0
data/History.txt
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
0.2.x (In Git)
|
2
|
+
|
3
|
+
== 0.2.3 2009-05-30
|
4
|
+
|
5
|
+
=== New features
|
6
|
+
* Support for SQL Server truncation (Adam Meehan)
|
7
|
+
|
8
|
+
== 0.2.2 2009-05-08
|
9
|
+
=== Bufixes
|
10
|
+
* Added proper gemspec description and summary. (Ben Mabey, thanks to Martin Gamsjaeger)
|
11
|
+
|
12
|
+
=== New features
|
13
|
+
|
14
|
+
== 0.2.1 2009-05-08
|
15
|
+
=== Bufixes
|
16
|
+
* Removed extraneous TruncationBase class definition. (Ben Mabey)
|
17
|
+
|
18
|
+
== 0.2.0 2009-05-08 - The Datamapper Release
|
19
|
+
|
20
|
+
=== New features
|
21
|
+
* DataMapper strategies (Martin Gamsjaeger)
|
22
|
+
* Transaction
|
23
|
+
* Truncation - working SQLite3, MySQL adapters. Experimental Postgres adapter (not tested).
|
24
|
+
|
25
|
+
== 0.1.3 2009-04-30
|
26
|
+
|
27
|
+
=== New features
|
28
|
+
* PostgresSQLAdapter for AR to support the truncation strategy. (Alberto Perdomo)
|
29
|
+
=== Bufixes
|
30
|
+
* Added missing quotes around table names in truncation calls. (Michael MacDonald)
|
31
|
+
|
32
|
+
== 0.1.2 2009-03-05
|
33
|
+
=== New features
|
34
|
+
* JDBC Adapter to enable AR truncation strategy to work. (Kamal Fariz Mahyuddin)
|
35
|
+
|
36
|
+
== 0.1.1 2009-03-04 - Initial Release (Ben Mabey)
|
37
|
+
* Basic infrastructure
|
38
|
+
* Features, RSpec code examples
|
39
|
+
* ActiveRecord strategies
|
40
|
+
* Truncation - with MySQL, and SQLite3 adapters.
|
41
|
+
* Transaction - wrap your modifications and roll them back.
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Ben Mabey
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
h1. Database Cleaner
|
2
|
+
|
3
|
+
Database Cleaner is a set of strategies for cleaning your database in Ruby.
|
4
|
+
The original use case was to ensure a clean state during tests. Each strategy
|
5
|
+
is a small amount of code but is code that is usually needed in any ruby app
|
6
|
+
that is testing with a database.
|
7
|
+
|
8
|
+
Both ActiveRecord and DataMapper are supported.
|
9
|
+
|
10
|
+
h2. How to use
|
11
|
+
|
12
|
+
<pre>
|
13
|
+
require 'database_cleaner'
|
14
|
+
DatabaseCleaner.strategy = :truncation
|
15
|
+
|
16
|
+
# then, whenever you need to clean the DB
|
17
|
+
DatabaseCleaner.clean
|
18
|
+
</pre>
|
19
|
+
|
20
|
+
With the :truncation strategy you can also pass in options, for example:
|
21
|
+
<pre>
|
22
|
+
DatabaseCleaner.strategy = :truncation, {:only => %w[widgets dogs some_other_table]}
|
23
|
+
</pre>
|
24
|
+
|
25
|
+
<pre>
|
26
|
+
DatabaseCleaner.strategy = :truncation, {:except => %w[widgets]}
|
27
|
+
</pre>
|
28
|
+
|
29
|
+
(I should point out the truncation strategy will never truncate your schema_migrations table.)
|
30
|
+
|
31
|
+
|
32
|
+
Some strategies require that you call DatabaseCleaner.start before calling clean
|
33
|
+
(for example the :transaction one needs to know to open up a transaction). So
|
34
|
+
you would have:
|
35
|
+
|
36
|
+
<pre>
|
37
|
+
require 'database_cleaner'
|
38
|
+
DatabaseCleaner.strategy = :transaction
|
39
|
+
|
40
|
+
DatabaseCleaner.start # usually this is called in setup of a test
|
41
|
+
dirty_the_db
|
42
|
+
DatabaseCleaner.clean # cleanup of the test
|
43
|
+
</pre>
|
44
|
+
|
45
|
+
At times you may want to do a single clean with one strategy. For example, you may want
|
46
|
+
to start the process by truncating all the tables, but then use the faster transaction
|
47
|
+
strategy the remaining time. To accomplish this you can say:
|
48
|
+
|
49
|
+
<pre>
|
50
|
+
require 'database_cleaner'
|
51
|
+
DatabaseCleaner.clean_with :truncation
|
52
|
+
DatabaseCleaner.strategy = :transaction
|
53
|
+
# then make the DatabaseCleaner.start and DatabaseCleaner.clean calls appropriately
|
54
|
+
</pre>
|
55
|
+
|
56
|
+
Example usage with RSpec:
|
57
|
+
|
58
|
+
<pre>
|
59
|
+
Spec::Runner.configure do |config|
|
60
|
+
|
61
|
+
config.before(:suite) do
|
62
|
+
DatabaseCleaner.strategy = :transaction
|
63
|
+
DatabaseCleaner.clean_with(:truncation)
|
64
|
+
end
|
65
|
+
|
66
|
+
config.before(:each) do
|
67
|
+
DatabaseCleaner.start
|
68
|
+
end
|
69
|
+
|
70
|
+
config.after(:each) do
|
71
|
+
DatabaseCleaner.clean
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
</pre>
|
76
|
+
|
77
|
+
For use in Cucumber please see the section below.
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
h2. Why?
|
82
|
+
|
83
|
+
One of my motivations for writing this library was to have an easy way to
|
84
|
+
turn on what Rails calls "transactional_fixtures" in my non-rails
|
85
|
+
ActiveRecord projects. For example, Cucumber ships with a Rails world that
|
86
|
+
will wrap each scenario in a transaction. This is great, but what if you are
|
87
|
+
using ActiveRecord in a non-rails project? You used to have to copy-and-paste
|
88
|
+
the needed code, but with DatabaseCleaner you can now say:
|
89
|
+
|
90
|
+
<pre>
|
91
|
+
#env.rb
|
92
|
+
require 'database_cleaner'
|
93
|
+
require 'database_cleaner/cucumber'
|
94
|
+
DatabaseCleaner.strategy = :transaction
|
95
|
+
</pre>
|
96
|
+
|
97
|
+
Now lets say you are running your features and it requires that another process be
|
98
|
+
involved (i.e. Selenium running against your app's server.) You can simply change
|
99
|
+
your strategy type:
|
100
|
+
|
101
|
+
<pre>
|
102
|
+
#env.rb
|
103
|
+
require 'database_cleaner'
|
104
|
+
require 'database_cleaner/cucumber'
|
105
|
+
DatabaseCleaner.strategy = :truncation
|
106
|
+
</pre>
|
107
|
+
|
108
|
+
You can have the best of both worlds and use the best one for the job:
|
109
|
+
<pre>
|
110
|
+
#env.rb
|
111
|
+
require 'database_cleaner'
|
112
|
+
require 'database_cleaner/cucumber'
|
113
|
+
DatabaseCleaner.strategy = (ENV['SELENIUM'] == 'true') ? :truncation : :transaction
|
114
|
+
</pre>
|
115
|
+
|
116
|
+
h2. COPYRIGHT
|
117
|
+
|
118
|
+
Copyright (c) 2009 Ben Mabey. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |s|
|
6
|
+
s.name = "database_cleaner"
|
7
|
+
s.summary = %Q{Strategies for cleaning databases. Can be used to ensure a clean state for testing.}
|
8
|
+
s.email = "ben@benmabey.com"
|
9
|
+
s.homepage = "http://github.com/bmabey/database_cleaner"
|
10
|
+
s.description = "Strategies for cleaning databases. Can be used to ensure a clean state for testing."
|
11
|
+
s.files = FileList["[A-Z]*.*", "{examples,lib,features,spec}/**/*", "Rakefile", "cucumber.yml"]
|
12
|
+
s.authors = ["Ben Mabey"]
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'rake/rdoctask'
|
19
|
+
Rake::RDocTask.new do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'rdoc'
|
21
|
+
rdoc.title = 'database_cleaner'
|
22
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
23
|
+
rdoc.rdoc_files.include('README*')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'spec/rake/spectask'
|
28
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
29
|
+
t.libs << 'lib' << 'spec'
|
30
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
31
|
+
end
|
32
|
+
|
33
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
34
|
+
t.libs << 'lib' << 'spec'
|
35
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
36
|
+
t.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
begin
|
40
|
+
require 'cucumber/rake/task'
|
41
|
+
Cucumber::Rake::Task.new(:features)
|
42
|
+
rescue LoadError
|
43
|
+
puts "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
|
44
|
+
end
|
45
|
+
|
46
|
+
task :default => [:spec, :features]
|
data/VERSION.yml
ADDED
data/cucumber.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
default: features
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Feature: example
|
2
|
+
In order to test DataBase Cleaner
|
3
|
+
Here are some scenarios that rely on the DB being clean!
|
4
|
+
|
5
|
+
Scenario: dirty the db
|
6
|
+
When I create a widget
|
7
|
+
Then I should see 1 widget
|
8
|
+
|
9
|
+
Scenario: assume a clean db
|
10
|
+
When I create a widget
|
11
|
+
Then I should see 1 widget
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec/expectations'
|
3
|
+
|
4
|
+
orm = ENV['ORM']
|
5
|
+
strategy = ENV['STRATEGY']
|
6
|
+
|
7
|
+
if orm && strategy
|
8
|
+
|
9
|
+
begin
|
10
|
+
require "#{File.dirname(__FILE__)}/../../lib/#{orm}"
|
11
|
+
rescue LoadError
|
12
|
+
raise "You don't have the #{orm} ORM installed"
|
13
|
+
end
|
14
|
+
|
15
|
+
$:.unshift(File.dirname(__FILE__) + '/../../../lib')
|
16
|
+
require 'database_cleaner'
|
17
|
+
require 'database_cleaner/cucumber'
|
18
|
+
|
19
|
+
DatabaseCleaner.strategy = strategy.to_sym
|
20
|
+
|
21
|
+
else
|
22
|
+
raise "Run 'ORM=activerecord|datamapper STRATEGY=transaction|truncation cucumber examples/features'"
|
23
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'activerecord'
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
|
4
|
+
|
5
|
+
ActiveRecord::Schema.define(:version => 1) do
|
6
|
+
create_table :widgets do |t|
|
7
|
+
t.string :name
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Widget < ActiveRecord::Base
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "dm-core"
|
2
|
+
|
3
|
+
# only to please activerecord API used in database_cleaner/examples/features/step_definitions
|
4
|
+
# yes, i know that's lazy ...
|
5
|
+
require "dm-validations"
|
6
|
+
require "dm-aggregates"
|
7
|
+
|
8
|
+
DataMapper.setup(:default, "sqlite3::memory:")
|
9
|
+
|
10
|
+
class Widget
|
11
|
+
include DataMapper::Resource
|
12
|
+
property :id, Serial
|
13
|
+
property :name, String
|
14
|
+
end
|
15
|
+
|
16
|
+
Widget.auto_migrate!
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Feature: database cleaning
|
2
|
+
In order to ease example and feature writing
|
3
|
+
As a developer
|
4
|
+
I want to have my database in a clean state
|
5
|
+
|
6
|
+
Scenario Outline: ruby app
|
7
|
+
Given I am using <ORM>
|
8
|
+
And the <Strategy> cleaning strategy
|
9
|
+
|
10
|
+
When I run my scenarios that rely on a clean database
|
11
|
+
Then I should see all green
|
12
|
+
|
13
|
+
Examples:
|
14
|
+
| ORM | Strategy |
|
15
|
+
| ActiveRecord | transaction |
|
16
|
+
| ActiveRecord | truncation |
|
17
|
+
| DataMapper | transaction |
|
18
|
+
| DataMapper | truncation |
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Given /^I am using (ActiveRecord|DataMapper)$/ do |orm|
|
2
|
+
@orm = orm
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^the (.+) cleaning strategy$/ do |strategy|
|
6
|
+
@strategy = strategy
|
7
|
+
end
|
8
|
+
|
9
|
+
When "I run my scenarios that rely on a clean database" do
|
10
|
+
full_dir ||= File.expand_path(File.dirname(__FILE__) + "/../../examples/")
|
11
|
+
Dir.chdir(full_dir) do
|
12
|
+
ENV['ORM'] = @orm.downcase
|
13
|
+
ENV['STRATEGY'] = @strategy
|
14
|
+
@out = `cucumber features`
|
15
|
+
@status = $?.exitstatus
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Then "I should see all green" do
|
20
|
+
unless @status == 0
|
21
|
+
raise "Expected to see all green but we saw FAIL! Output:\n#{@out}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DatabaseCleaner::ActiveRecord
|
2
|
+
class Transaction
|
3
|
+
|
4
|
+
def start
|
5
|
+
if ActiveRecord::Base.connection.respond_to?(:increment_open_transactions)
|
6
|
+
ActiveRecord::Base.connection.increment_open_transactions
|
7
|
+
else
|
8
|
+
ActiveRecord::Base.__send__(:increment_open_transactions)
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveRecord::Base.connection.begin_db_transaction
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def clean
|
16
|
+
ActiveRecord::Base.connection.rollback_db_transaction
|
17
|
+
|
18
|
+
if ActiveRecord::Base.connection.respond_to?(:decrement_open_transactions)
|
19
|
+
ActiveRecord::Base.connection.decrement_open_transactions
|
20
|
+
else
|
21
|
+
ActiveRecord::Base.__send__(:decrement_open_transactions)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "database_cleaner/truncation_base"
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
|
6
|
+
class MysqlAdapter
|
7
|
+
def truncate_table(table_name)
|
8
|
+
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class SQLite3Adapter
|
13
|
+
def truncate_table(table_name)
|
14
|
+
execute("DELETE FROM #{quote_table_name(table_name)};")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class JdbcAdapter
|
19
|
+
def truncate_table(table_name)
|
20
|
+
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class PostgreSQLAdapter
|
25
|
+
def truncate_table(table_name)
|
26
|
+
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class SQLServerAdapter
|
31
|
+
def truncate_table(table_name)
|
32
|
+
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
module DatabaseCleaner::ActiveRecord
|
41
|
+
class Truncation < ::DatabaseCleaner::TruncationBase
|
42
|
+
|
43
|
+
def clean
|
44
|
+
connection.disable_referential_integrity do
|
45
|
+
tables_to_truncate.each do |table_name|
|
46
|
+
connection.truncate_table table_name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def tables_to_truncate
|
54
|
+
(@only || connection.tables) - @tables_to_exclude
|
55
|
+
end
|
56
|
+
|
57
|
+
def connection
|
58
|
+
::ActiveRecord::Base.connection
|
59
|
+
end
|
60
|
+
|
61
|
+
# overwritten
|
62
|
+
def migration_storage_name
|
63
|
+
'schema_migrations'
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module DatabaseCleaner
|
2
|
+
|
3
|
+
class NoStrategySetError < StandardError; end
|
4
|
+
class NoORMDetected < StandardError; end
|
5
|
+
class UnknownStrategySpecified < ArgumentError; end
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
def self.available_strategies
|
9
|
+
%w[truncation transaction]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module DataMapper
|
14
|
+
def self.available_strategies
|
15
|
+
%w[truncation transaction]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
|
21
|
+
def create_strategy(*args)
|
22
|
+
strategy, *strategy_args = args
|
23
|
+
orm_strategy(strategy).new(*strategy_args)
|
24
|
+
end
|
25
|
+
|
26
|
+
def clean_with(*args)
|
27
|
+
strategy = create_strategy(*args)
|
28
|
+
strategy.clean
|
29
|
+
strategy
|
30
|
+
end
|
31
|
+
|
32
|
+
def strategy=(args)
|
33
|
+
strategy, *strategy_args = args
|
34
|
+
if strategy.is_a?(Symbol)
|
35
|
+
@strategy = create_strategy(*args)
|
36
|
+
elsif strategy_args.empty?
|
37
|
+
@strategy = strategy
|
38
|
+
else
|
39
|
+
raise ArgumentError, "You must provide a strategy object, or a symbol for a know strategy along with initialization params."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def orm=(orm_string)
|
44
|
+
@orm = orm_string
|
45
|
+
end
|
46
|
+
|
47
|
+
def start
|
48
|
+
strategy.start
|
49
|
+
end
|
50
|
+
|
51
|
+
def clean
|
52
|
+
strategy.clean
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def strategy
|
58
|
+
return @strategy if @strategy
|
59
|
+
raise NoStrategySetError, "Please set a strategy with DatabaseCleaner.strategy=."
|
60
|
+
end
|
61
|
+
|
62
|
+
def orm_strategy(strategy)
|
63
|
+
require "database_cleaner/#{orm}/#{strategy}"
|
64
|
+
orm_module.const_get(strategy.to_s.capitalize)
|
65
|
+
rescue LoadError => e
|
66
|
+
raise UnknownStrategySpecified, "The '#{strategy}' strategy does not exist for the #{orm} ORM! Available strategies: #{orm_module.available_strategies.join(', ')}"
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def orm
|
71
|
+
@orm ||=begin
|
72
|
+
if defined? ::ActiveRecord
|
73
|
+
'active_record'
|
74
|
+
elsif defined? ::DataMapper
|
75
|
+
'data_mapper'
|
76
|
+
else
|
77
|
+
raise NoORMDetected, "No known ORM was detected! Is ActiveRecord or DataMapper loaded?"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def orm_module
|
84
|
+
case orm
|
85
|
+
when 'active_record'
|
86
|
+
DatabaseCleaner::ActiveRecord
|
87
|
+
when 'data_mapper'
|
88
|
+
DatabaseCleaner::DataMapper
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DatabaseCleaner::DataMapper
|
2
|
+
class Transaction
|
3
|
+
|
4
|
+
def start(repo = :default)
|
5
|
+
DataMapper.repository(repo) do |r|
|
6
|
+
transaction = DataMapper::Transaction.new(r)
|
7
|
+
transaction.begin
|
8
|
+
r.adapter.push_transaction(transaction)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def clean(repo = :default)
|
13
|
+
DataMapper.repository(repo) do |r|
|
14
|
+
adapter = r.adapter
|
15
|
+
while adapter.current_transaction
|
16
|
+
adapter.current_transaction.rollback
|
17
|
+
adapter.pop_transaction
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require "database_cleaner/truncation_base"
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
module Adapters
|
5
|
+
|
6
|
+
class DataObjectsAdapter
|
7
|
+
|
8
|
+
def storage_names(repository = :default)
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
class MysqlAdapter < DataObjectsAdapter
|
15
|
+
|
16
|
+
# taken from http://github.com/godfat/dm-mapping/tree/master
|
17
|
+
def storage_names(repository = :default)
|
18
|
+
query 'SHOW TABLES'
|
19
|
+
end
|
20
|
+
|
21
|
+
def truncate_table(table_name)
|
22
|
+
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
23
|
+
end
|
24
|
+
|
25
|
+
# copied from activerecord
|
26
|
+
def disable_referential_integrity
|
27
|
+
old = query("SELECT @@FOREIGN_KEY_CHECKS;")
|
28
|
+
begin
|
29
|
+
execute("SET FOREIGN_KEY_CHECKS = 0;")
|
30
|
+
yield
|
31
|
+
ensure
|
32
|
+
execute("SET FOREIGN_KEY_CHECKS = #{old};")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
class Sqlite3Adapter < DataObjectsAdapter
|
39
|
+
|
40
|
+
# taken from http://github.com/godfat/dm-mapping/tree/master
|
41
|
+
def storage_names(repository = :default)
|
42
|
+
# activerecord-2.1.0/lib/active_record/connection_adapters/sqlite_adapter.rb: 177
|
43
|
+
sql = <<-SQL.compress_lines
|
44
|
+
SELECT name
|
45
|
+
FROM sqlite_master
|
46
|
+
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
|
47
|
+
SQL
|
48
|
+
# activerecord-2.1.0/lib/active_record/connection_adapters/sqlite_adapter.rb: 181
|
49
|
+
query sql
|
50
|
+
end
|
51
|
+
|
52
|
+
def truncate_table(table_name)
|
53
|
+
execute("DELETE FROM #{quote_table_name(table_name)};")
|
54
|
+
end
|
55
|
+
|
56
|
+
# this is a no-op copied from activerecord
|
57
|
+
# i didn't find out if/how this is possible
|
58
|
+
# activerecord also doesn't do more here
|
59
|
+
def disable_referential_integrity
|
60
|
+
yield
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# FIXME
|
67
|
+
# i don't know if this works
|
68
|
+
# i basically just copied activerecord code to get a rough idea what they do.
|
69
|
+
# i don't have postgres available, so i won't be the one to write this.
|
70
|
+
# maybe codes below gets some postgres/datamapper user going, though.
|
71
|
+
class PostgresAdapter < DataObjectsAdapter
|
72
|
+
|
73
|
+
# taken from http://github.com/godfat/dm-mapping/tree/master
|
74
|
+
def storage_names(repository = :default)
|
75
|
+
sql = <<-SQL.compress_lines
|
76
|
+
SELECT table_name FROM "information_schema"."tables"
|
77
|
+
WHERE table_schema = current_schema()
|
78
|
+
SQL
|
79
|
+
query(sql)
|
80
|
+
end
|
81
|
+
|
82
|
+
def truncate_table(table_name)
|
83
|
+
execute("TRUNCATE TABLE #{quote_table_name(table_name)};")
|
84
|
+
end
|
85
|
+
|
86
|
+
# FIXME
|
87
|
+
# copied from activerecord
|
88
|
+
def supports_disable_referential_integrity?
|
89
|
+
version = query("SHOW server_version")[0][0].split('.')
|
90
|
+
(version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false
|
91
|
+
rescue
|
92
|
+
return false
|
93
|
+
end
|
94
|
+
|
95
|
+
# FIXME
|
96
|
+
# copied unchanged from activerecord
|
97
|
+
def disable_referential_integrity(repository = :default)
|
98
|
+
if supports_disable_referential_integrity? then
|
99
|
+
execute(storage_names(repository).collect do |name|
|
100
|
+
"ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL"
|
101
|
+
end.join(";"))
|
102
|
+
end
|
103
|
+
yield
|
104
|
+
ensure
|
105
|
+
if supports_disable_referential_integrity? then
|
106
|
+
execute(storage_names(repository).collect do |name|
|
107
|
+
"ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL"
|
108
|
+
end.join(";"))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
module DatabaseCleaner::DataMapper
|
119
|
+
class Truncation < ::DatabaseCleaner::TruncationBase
|
120
|
+
|
121
|
+
def clean(repository = :default)
|
122
|
+
adapter = DataMapper.repository(repository).adapter
|
123
|
+
adapter.disable_referential_integrity do
|
124
|
+
tables_to_truncate.each do |table_name|
|
125
|
+
adapter.truncate_table table_name
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def tables_to_truncate(repository = :default)
|
133
|
+
(@only || DataMapper.repository(repository).adapter.storage_names(repository)) - @tables_to_exclude
|
134
|
+
end
|
135
|
+
|
136
|
+
# overwritten
|
137
|
+
def migration_storage_name
|
138
|
+
'migration_info'
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module DatabaseCleaner
|
2
|
+
class TruncationBase
|
3
|
+
|
4
|
+
def initialize(options = {})
|
5
|
+
if !options.empty? && !(options.keys - [:only, :except]).empty?
|
6
|
+
raise ArgumentError, "The only valid options are :only and :except. You specified #{options.keys.join(',')}."
|
7
|
+
end
|
8
|
+
if options.has_key?(:only) && options.has_key?(:except)
|
9
|
+
raise ArgumentError, "You may only specify either :only or :either. Doing both doesn't really make sense does it?"
|
10
|
+
end
|
11
|
+
|
12
|
+
@only = options[:only]
|
13
|
+
@tables_to_exclude = (options[:except] || [])
|
14
|
+
if migration_storage = migration_storage_name
|
15
|
+
@tables_to_exclude << migration_storage
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
# no-op
|
21
|
+
end
|
22
|
+
|
23
|
+
def clean
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def tables_to_truncate
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
|
34
|
+
# overwrite in subclasses
|
35
|
+
# default implementation given because migration storage need not be present
|
36
|
+
def migration_storage_name
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
require 'database_cleaner/active_record/truncation'
|
3
|
+
require 'active_record'
|
4
|
+
module ActiveRecord
|
5
|
+
module ConnectionAdapters
|
6
|
+
[MysqlAdapter, SQLite3Adapter, JdbcAdapter, PostgreSQLAdapter].each do |adapter|
|
7
|
+
describe adapter, "#truncate_table" do
|
8
|
+
it "should truncate the table"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module DatabaseCleaner
|
15
|
+
module ActiveRecord
|
16
|
+
|
17
|
+
describe Truncation do
|
18
|
+
before(:each) do
|
19
|
+
@connection = mock('connection')
|
20
|
+
@connection.stub!(:disable_referential_integrity).and_yield
|
21
|
+
::ActiveRecord::Base.stub!(:connection).and_return(@connection)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should truncate all tables except for schema_migrations" do
|
25
|
+
@connection.stub!(:tables).and_return(%w[schema_migrations widgets dogs])
|
26
|
+
|
27
|
+
@connection.should_receive(:truncate_table).with('widgets')
|
28
|
+
@connection.should_receive(:truncate_table).with('dogs')
|
29
|
+
@connection.should_not_receive(:truncate_table).with('schema_migrations')
|
30
|
+
|
31
|
+
Truncation.new.clean
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should only truncate the tables specified in the :only option when provided" do
|
35
|
+
@connection.stub!(:tables).and_return(%w[schema_migrations widgets dogs])
|
36
|
+
|
37
|
+
@connection.should_receive(:truncate_table).with('widgets')
|
38
|
+
@connection.should_not_receive(:truncate_table).with('dogs')
|
39
|
+
|
40
|
+
Truncation.new(:only => ['widgets']).clean
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should not truncate the tables specified in the :except option" do
|
44
|
+
@connection.stub!(:tables).and_return(%w[schema_migrations widgets dogs])
|
45
|
+
|
46
|
+
@connection.should_receive(:truncate_table).with('dogs')
|
47
|
+
@connection.should_not_receive(:truncate_table).with('widgets')
|
48
|
+
|
49
|
+
Truncation.new(:except => ['widgets']).clean
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should raise an error when :only and :except options are used" do
|
53
|
+
running {
|
54
|
+
Truncation.new(:except => ['widgets'], :only => ['widgets'])
|
55
|
+
}.should raise_error(ArgumentError)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should raise an error when invalid options are provided" do
|
59
|
+
running { Truncation.new(:foo => 'bar') }.should raise_error(ArgumentError)
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require 'database_cleaner/active_record/transaction'
|
3
|
+
require 'database_cleaner/data_mapper/transaction'
|
4
|
+
|
5
|
+
describe DatabaseCleaner do
|
6
|
+
|
7
|
+
# These examples muck around with the constants for autodetection so we need to clean up....
|
8
|
+
before(:all) do
|
9
|
+
TempAR = ActiveRecord unless defined?(TempAR)
|
10
|
+
# need to add one for each ORM that we load in the spec helper...
|
11
|
+
end
|
12
|
+
after(:all) do
|
13
|
+
Object.send(:remove_const, 'ActiveRecord') if defined?(::ActiveRecord) #want to make sure we have the real one...
|
14
|
+
ActiveRecord = TempAR
|
15
|
+
end
|
16
|
+
|
17
|
+
before(:each) do
|
18
|
+
DatabaseCleaner::ActiveRecord::Transaction.stub!(:new).and_return(@strategy = mock('strategy'))
|
19
|
+
Object.const_set('ActiveRecord', "just mocking out the constant here...") unless defined?(::ActiveRecord)
|
20
|
+
DatabaseCleaner.strategy = nil
|
21
|
+
DatabaseCleaner.orm = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".create_strategy" do
|
25
|
+
it "should initialize and return the appropirate strategy" do
|
26
|
+
DatabaseCleaner::ActiveRecord::Transaction.should_receive(:new).with('options' => 'hash')
|
27
|
+
result = DatabaseCleaner.create_strategy(:transaction, {'options' => 'hash'})
|
28
|
+
|
29
|
+
result.should == @strategy
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe ".clean_with" do
|
34
|
+
it "should initialize the appropirate strategy and clean with it" do
|
35
|
+
DatabaseCleaner::ActiveRecord::Transaction.should_receive(:new).with('options' => 'hash')
|
36
|
+
@strategy.should_receive(:clean)
|
37
|
+
|
38
|
+
DatabaseCleaner.clean_with(:transaction, 'options' => 'hash')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe ".strategy=" do
|
43
|
+
it "should initialize the appropirate strategy based on the ORM adapter detected" do
|
44
|
+
DatabaseCleaner::ActiveRecord::Transaction.should_receive(:new).with('options' => 'hash')
|
45
|
+
DatabaseCleaner.strategy = :transaction, {'options' => 'hash'}
|
46
|
+
|
47
|
+
Object.send(:remove_const, 'ActiveRecord')
|
48
|
+
Object.const_set('DataMapper', "just mocking out the constant here...")
|
49
|
+
DatabaseCleaner.orm = nil
|
50
|
+
|
51
|
+
DatabaseCleaner::DataMapper::Transaction.should_receive(:new).with(no_args)
|
52
|
+
DatabaseCleaner.strategy = :transaction
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should raise an error when no ORM is detected" do
|
56
|
+
Object.send(:remove_const, 'ActiveRecord') if defined?(::ActiveRecord)
|
57
|
+
Object.send(:remove_const, 'DataMapper') if defined?(::DataMapper)
|
58
|
+
|
59
|
+
running { DatabaseCleaner.strategy = :transaction }.should raise_error(DatabaseCleaner::NoORMDetected)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should use the strategy version of the ORM specified with #orm=" do
|
63
|
+
DatabaseCleaner.orm = 'data_mapper'
|
64
|
+
DatabaseCleaner::DataMapper::Transaction.should_receive(:new)
|
65
|
+
|
66
|
+
DatabaseCleaner.strategy = :transaction
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should raise an error when multiple args is passed in and the first is not a symbol" do
|
70
|
+
running { DatabaseCleaner.strategy=Object.new, {:foo => 'bar'} }.should raise_error(ArgumentError)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should raise an error when the specified strategy is not found" do
|
74
|
+
running { DatabaseCleaner.strategy = :foo }.should raise_error(DatabaseCleaner::UnknownStrategySpecified)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should allow any object to be set as the strategy" do
|
78
|
+
mock_strategy = mock('strategy')
|
79
|
+
running { DatabaseCleaner.strategy = mock_strategy }.should_not raise_error
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
%w[start clean].each do |strategy_method|
|
86
|
+
describe ".#{strategy_method}" do
|
87
|
+
it "should be delgated to the strategy set with strategy=" do
|
88
|
+
DatabaseCleaner.strategy = :transaction
|
89
|
+
|
90
|
+
@strategy.should_receive(strategy_method)
|
91
|
+
|
92
|
+
DatabaseCleaner.send(strategy_method)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should raise en error when no strategy has been set" do
|
96
|
+
running { DatabaseCleaner.send(strategy_method) }.should raise_error(DatabaseCleaner::NoStrategySetError)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: database_cleaner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Mabey
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-05-30 00:00:00 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Strategies for cleaning databases. Can be used to ensure a clean state for testing.
|
17
|
+
email: ben@benmabey.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.textile
|
25
|
+
files:
|
26
|
+
- History.txt
|
27
|
+
- README.textile
|
28
|
+
- Rakefile
|
29
|
+
- VERSION.yml
|
30
|
+
- cucumber.yml
|
31
|
+
- examples/features/example.feature
|
32
|
+
- examples/features/step_definitions/example_steps.rb
|
33
|
+
- examples/features/support/env.rb
|
34
|
+
- examples/lib/activerecord.rb
|
35
|
+
- examples/lib/datamapper.rb
|
36
|
+
- features/cleaning.feature
|
37
|
+
- features/step_definitions/database_cleaner_steps.rb
|
38
|
+
- features/support/env.rb
|
39
|
+
- lib/database_cleaner.rb
|
40
|
+
- lib/database_cleaner/active_record/transaction.rb
|
41
|
+
- lib/database_cleaner/active_record/truncation.rb
|
42
|
+
- lib/database_cleaner/configuration.rb
|
43
|
+
- lib/database_cleaner/cucumber.rb
|
44
|
+
- lib/database_cleaner/data_mapper/transaction.rb
|
45
|
+
- lib/database_cleaner/data_mapper/truncation.rb
|
46
|
+
- lib/database_cleaner/truncation_base.rb
|
47
|
+
- spec/database_cleaner/active_record/truncation_spec.rb
|
48
|
+
- spec/database_cleaner/configuration_spec.rb
|
49
|
+
- spec/spec.opts
|
50
|
+
- spec/spec_helper.rb
|
51
|
+
- LICENSE
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/bmabey/database_cleaner
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --charset=UTF-8
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.3.3
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Strategies for cleaning databases. Can be used to ensure a clean state for testing.
|
80
|
+
test_files:
|
81
|
+
- spec/spec_helper.rb
|
82
|
+
- spec/database_cleaner/configuration_spec.rb
|
83
|
+
- spec/database_cleaner/active_record/truncation_spec.rb
|
84
|
+
- examples/lib/datamapper.rb
|
85
|
+
- examples/lib/activerecord.rb
|
86
|
+
- examples/features/step_definitions/example_steps.rb
|
87
|
+
- examples/features/support/env.rb
|