Merge branch 'master' of github.com:bmabey/database_cleaner

This commit is contained in:
Ben Mabey 2013-05-13 15:50:31 -06:00
commit ee5cd622d0
33 changed files with 802 additions and 79 deletions

View file

@ -26,6 +26,8 @@ group :development do
gem 'mysql', '~> 2.8.1' gem 'mysql', '~> 2.8.1'
gem 'mysql2' gem 'mysql2'
gem 'pg' gem 'pg'
gem 'sqlite3'
gem 'ohm', '~> 0.1.3'
gem 'guard-rspec' gem 'guard-rspec'
end end

View file

@ -135,6 +135,10 @@ GEM
multi_json (1.5.0) multi_json (1.5.0)
mysql (2.8.1) mysql (2.8.1)
mysql2 (0.3.11) mysql2 (0.3.11)
nest (1.1.2)
redis
ohm (0.1.5)
nest (~> 1.0)
origin (1.0.11) origin (1.0.11)
pg (0.14.1) pg (0.14.1)
plucky (0.5.2) plucky (0.5.2)
@ -160,6 +164,7 @@ GEM
rake (10.0.3) rake (10.0.3)
rdoc (3.12) rdoc (3.12)
json (~> 1.4) json (~> 1.4)
redis (3.0.4)
rest-client (1.6.7) rest-client (1.6.7)
mime-types (>= 1.16) mime-types (>= 1.16)
rspec (2.12.0) rspec (2.12.0)
@ -213,6 +218,7 @@ DEPENDENCIES
mongoid mongoid
mysql (~> 2.8.1) mysql (~> 2.8.1)
mysql2 mysql2
ohm (~> 0.1.3)
pg pg
rake rake
rspec-rails rspec-rails

View file

@ -5,7 +5,7 @@ Database Cleaner is a set of strategies for cleaning your database in Ruby.
The original use case was to ensure a clean state during tests. The original use case was to ensure a clean state during tests.
Each strategy is a small amount of code but is code that is usually needed in any ruby app that is testing with a database. Each strategy is a small amount of code but is code that is usually needed in any ruby app that is testing with a database.
ActiveRecord, DataMapper, Sequel, MongoMapper, Mongoid, and CouchPotato are supported. ActiveRecord, DataMapper, Sequel, MongoMapper, Mongoid, CouchPotato, Ohm and Redis are supported.
[![Build Status](https://secure.travis-ci.org/bmabey/database_cleaner.png)](http://travis-ci.org/bmabey/database_cleaner) [![Build Status](https://secure.travis-ci.org/bmabey/database_cleaner.png)](http://travis-ci.org/bmabey/database_cleaner)
@ -55,6 +55,18 @@ Here is an overview of the strategies supported for each library:
<td> Yes</td> <td> Yes</td>
<td> No</td> <td> No</td>
</tr> </tr>
<tr>
<td>Redis</td>
<td><b>Yes</b></td>
<td>No</td>
<td>No</td>
</tr>
<tr>
<td>Ohm</td>
<td><b>Yes</b></td>
<td>No</td>
<td>No</td>
</tr>
</tbody> </tbody>
</table> </table>
@ -72,6 +84,12 @@ Here is an overview of the strategies supported for each library:
<td> No</td> <td> No</td>
<td> No</td> <td> No</td>
</tr> </tr>
<tr>
<td> Moped</td>
<td> Yes</td>
<td> No</td>
<td> No</td>
</tr>
</tbody> </tbody>
</table> </table>
@ -88,11 +106,11 @@ For the SQL libraries the fastest option will be to use `:transaction` as transa
One common approach is to force all processes to use the same database connection ([common ActiveRecord hack](http://blog.plataformatec.com.br/2011/12/three-tips-to-improve-the-performance-of-your-test-suite/)) however this approach has been reported to result in non-deterministic failures. One common approach is to force all processes to use the same database connection ([common ActiveRecord hack](http://blog.plataformatec.com.br/2011/12/three-tips-to-improve-the-performance-of-your-test-suite/)) however this approach has been reported to result in non-deterministic failures.
Another approach is to have the transactions rolled back in the application's process and relax the isolation level of the database (so the tests can read the uncommited transactions). Another approach is to have the transactions rolled back in the application's process and relax the isolation level of the database (so the tests can read the uncommitted transactions).
An easier, but slower, solution is to use the `:truncation` or `:deletion` strategy. An easier, but slower, solution is to use the `:truncation` or `:deletion` strategy.
So what is fastest out of `:deletion` and `:truncation`? Well, it depends on your table structure and what percentage of tables you populate in an average test. The reasoning is out the the scope of this README but here is a [good SO answer on this topic for Postgres](http://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886). So what is fastest out of `:deletion` and `:truncation`? Well, it depends on your table structure and what percentage of tables you populate in an average test. The reasoning is out of the scope of this README but here is a [good SO answer on this topic for Postgres](http://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886).
Some people report much faster speeds with `:deletion` while others say `:truncation` is faster for them. The best approach therefore is it try all options on your test suite and see what is faster. Some people report much faster speeds with `:deletion` while others say `:truncation` is faster for them. The best approach therefore is it try all options on your test suite and see what is faster.
@ -123,6 +141,9 @@ DatabaseCleaner.strategy = :truncation, {:only => %w[widgets dogs some_other_tab
DatabaseCleaner.strategy = :truncation, {:except => %w[widgets]} DatabaseCleaner.strategy = :truncation, {:except => %w[widgets]}
``` ```
With Ohm and Redis, `:only` and `:except` take a list of strings to be
passed to [`keys`](http://redis.io/commands/keys)).
(I should point out the truncation strategy will never truncate your schema_migrations table.) (I should point out the truncation strategy will never truncate your schema_migrations table.)
Some strategies require that you call `DatabaseCleaner.start` before calling `clean` (for example the `:transaction` one needs to know to open up a transaction). So you would have: Some strategies require that you call `DatabaseCleaner.start` before calling `clean` (for example the `:transaction` one needs to know to open up a transaction). So you would have:
@ -276,6 +297,11 @@ Usage beyond that remains the same with `DatabaseCleaner.start` calling any setu
<td> <code>DatabaseCleaner[:mongoid]</code></td> <td> <code>DatabaseCleaner[:mongoid]</code></td>
<td> Multiple databases supported for Mongoid 3. Specify <code>DatabaseCleaner[:mongoid, {:connection =&gt; :db_name}]</code> </td> <td> Multiple databases supported for Mongoid 3. Specify <code>DatabaseCleaner[:mongoid, {:connection =&gt; :db_name}]</code> </td>
</tr> </tr>
<tr>
<td> Moped</td>
<td> <code>DatabaseCleaner[:moped]</code></td>
<td> It is necessary to configure database name with <code>DatabaseCleaner[:moped].db = db_name</code> otherwise name `default` will be used.</td>
</tr>
<tr> <tr>
<td> Couch Potato</td> <td> Couch Potato</td>
<td> <code>DatabaseCleaner[:couch_potato]</code></td> <td> <code>DatabaseCleaner[:couch_potato]</code></td>
@ -286,6 +312,16 @@ Usage beyond that remains the same with `DatabaseCleaner.start` calling any setu
<td> <code>DatabaseCleaner[:sequel]</code></td> <td> <code>DatabaseCleaner[:sequel]</code></td>
<td> Multiple databases supported; specify <code>Databasecleaner[:sequel, {:connection =&gt; Sequel.connect(uri)}]</code></td> <td> Multiple databases supported; specify <code>Databasecleaner[:sequel, {:connection =&gt; Sequel.connect(uri)}]</code></td>
</tr> </tr>
<tr>
<td>Redis</td>
<td><code>DatabaseCleaner[:redis]</code></td>
<td>Connection specified as Redis URI</td>
</tr>
<tr>
<td>Ohm</td>
<td><code>DatabaseCleaner[:ohm]</code></td>
<td>Connection specified as Redis URI</td>
</tr>
</tbody> </tbody>
</table> </table>

View file

@ -0,0 +1,8 @@
test:
url: 'redis://localhost:6379/0'
one:
url: 'redis://localhost:6379/1'
two:
url: 'redis://localhost:6379/2'

View file

@ -15,6 +15,10 @@ another_orm = ENV['ANOTHER_ORM']
strategy = ENV['STRATEGY'] strategy = ENV['STRATEGY']
multiple_db = ENV['MULTIPLE_DBS'] multiple_db = ENV['MULTIPLE_DBS']
config = YAML::load(File.open("#{File.dirname(__FILE__)}/../../config/redis.yml"))
ENV['REDIS_URL'] = config['test']['url']
ENV['REDIS_URL_ONE'] = config['one']['url']
ENV['REDIS_URL_TWO'] = config['two']['url']
if orm && strategy if orm && strategy
$:.unshift(File.dirname(__FILE__) + '/../../../lib') $:.unshift(File.dirname(__FILE__) + '/../../../lib')
@ -37,6 +41,9 @@ if orm && strategy
when :mongo_mapper when :mongo_mapper
DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_one'} ].strategy = strategy.to_sym DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_one'} ].strategy = strategy.to_sym
DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_two'} ].strategy = strategy.to_sym DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_two'} ].strategy = strategy.to_sym
when :redis, :ohm
DatabaseCleaner[ orm_sym, {:connection => ENV['REDIS_URL_ONE']} ].strategy = strategy.to_sym
DatabaseCleaner[ orm_sym, {:connection => ENV['REDIS_URL_TWO']} ].strategy = strategy.to_sym
when :active_record when :active_record
DatabaseCleaner[:active_record, {:model => ActiveRecordWidgetUsingDatabaseOne} ].strategy = strategy.to_sym DatabaseCleaner[:active_record, {:model => ActiveRecordWidgetUsingDatabaseOne} ].strategy = strategy.to_sym
DatabaseCleaner[:active_record, {:model => ActiveRecordWidgetUsingDatabaseTwo} ].strategy = strategy.to_sym DatabaseCleaner[:active_record, {:model => ActiveRecordWidgetUsingDatabaseTwo} ].strategy = strategy.to_sym
@ -53,5 +60,5 @@ if orm && strategy
end end
else else
raise "Run 'ORM=ActiveRecord|DataMapper|MongoMapper|CouchPotato [ANOTHER_ORM=...] [MULTIPLE_DBS=true] STRATEGY=transaction|truncation|default cucumber examples/features'" raise "Run 'ORM=ActiveRecord|DataMapper|MongoMapper|CouchPotato|Ohm|Redis [ANOTHER_ORM=...] [MULTIPLE_DBS=true] STRATEGY=transaction|truncation|default cucumber examples/features'"
end end

View file

@ -0,0 +1,43 @@
require 'ohm'
Ohm.connect :url => ENV['REDIS_URL']
class OhmWidget < Ohm::Model
attribute :name
def self.create!(attrs = {})
new({:name => 'some widget'}.merge(attrs)).save
end
def self.count
all.count
end
end
class OhmWidgetUsingDatabaseOne < Ohm::Model
connect :url => ENV['REDIS_URL_ONE']
attribute :name
def self.create!(attrs = {})
new({:name => 'a widget using database one'}.merge(attrs)).save
end
def self.count
all.count
end
end
class OhmWidgetUsingDatabaseTwo < Ohm::Model
connect :url => ENV['REDIS_URL_TWO']
attribute :name
def self.create!(attrs = {})
new({:name => 'a widget using database two'}.merge(attrs)).save
end
def self.count
all.count
end
end

View file

@ -0,0 +1,65 @@
require 'redis'
class RedisWidget
def self.redis
threaded ||= Redis.connect
end
def self.redis=(connection)
threaded = connection
end
def self.threaded
Thread.current[self.class.to_s] ||= {}
end
def initialize(options = {})
options = options.dup
@name = options[:name]
end
def connection
self.class.redis
end
def save
unless connection.get(self.class.to_s + ':id')
@id = 0
connection.set(self.class.to_s + ':id', @id)
end
@id = connection.incr(self.class.to_s + ':id')
connection.set(self.class.to_s + ':%d:name' % @id, @name)
end
def self.count
self.redis.keys(self.to_s + '*name').size
end
def self.create!
new(:name => 'some widget').save
end
end
class RedisWidgetUsingDatabaseOne < RedisWidget
def self.redis
threaded[self.class.to_s] ||= Redis.connect :url => ENV['REDIS_URL_ONE']
end
def self.create!
new(:name => 'a widget using database one').save
end
end
class RedisWidgetUsingDatabaseTwo < RedisWidget
def self.redis
threaded[self.class.to_s] ||= Redis.connect :url => ENV['REDIS_URL_TWO']
end
def self.create!
new(:name => 'a widget using database two').save
end
end

View file

@ -11,12 +11,14 @@ Feature: database cleaning
Then I should see all green Then I should see all green
Examples: Examples:
| ORM | Strategy | | ORM | Strategy |
| ActiveRecord | transaction | | ActiveRecord | transaction |
| ActiveRecord | truncation | | ActiveRecord | truncation |
| ActiveRecord | deletion | | ActiveRecord | deletion |
| DataMapper | transaction | | DataMapper | transaction |
| DataMapper | truncation | | DataMapper | truncation |
| MongoMapper | truncation | | MongoMapper | truncation |
| Mongoid | truncation | | Mongoid | truncation |
| CouchPotato | truncation | | CouchPotato | truncation |
| Redis | truncation |
| Ohm | truncation |

View file

@ -17,3 +17,5 @@ Feature: database cleaning
| MongoMapper | | MongoMapper |
| Mongoid | | Mongoid |
| CouchPotato | | CouchPotato |
| Redis |
| Ohm |

View file

@ -15,15 +15,34 @@ Feature: database cleaning using multiple ORMs
| ActiveRecord | MongoMapper | | ActiveRecord | MongoMapper |
| ActiveRecord | Mongoid | | ActiveRecord | Mongoid |
| ActiveRecord | CouchPotato | | ActiveRecord | CouchPotato |
| ActiveRecord | Ohm |
| ActiveRecord | Redis |
| DataMapper | ActiveRecord | | DataMapper | ActiveRecord |
| DataMapper | MongoMapper | | DataMapper | MongoMapper |
| DataMapper | Mongoid | | DataMapper | Mongoid |
| DataMapper | CouchPotato | | DataMapper | CouchPotato |
| DataMapper | Ohm |
| DataMapper | Redis |
| MongoMapper | ActiveRecord | | MongoMapper | ActiveRecord |
| MongoMapper | DataMapper | | MongoMapper | DataMapper |
| MongoMapper | Mongoid | | MongoMapper | Mongoid |
| MongoMapper | CouchPotato | | MongoMapper | CouchPotato |
| MongoMapper | Ohm |
| MongoMapper | Redis |
| CouchPotato | ActiveRecord | | CouchPotato | ActiveRecord |
| CouchPotato | DataMapper | | CouchPotato | DataMapper |
| CouchPotato | MongoMapper | | CouchPotato | MongoMapper |
| CouchPotato | Mongoid | | CouchPotato | Mongoid |
| CouchPotato | Ohm |
| CouchPotato | Redis |
| Ohm | ActiveRecord |
| Ohm | DataMapper |
| Ohm | MongoMapper |
| Ohm | Mongoid |
| Ohm | CouchPotato |
| Redis | ActiveRecord |
| Redis | DataMapper |
| Redis | MongoMapper |
| Redis | Mongoid |
| Redis | CouchPotato |
| Redis | Ohm |

View file

@ -1,10 +1,11 @@
orms_pattern = /(ActiveRecord|DataMapper|MongoMapper|Mongoid|CouchPotato|Redis|Ohm)/
Given /^I am using (ActiveRecord|DataMapper|MongoMapper|Mongoid|CouchPotato)$/ do |orm| Given /^I am using #{orms_pattern}$/ do |orm|
@feature_runner = FeatureRunner.new @feature_runner = FeatureRunner.new
@feature_runner.orm = orm @feature_runner.orm = orm
end end
Given /^I am using (ActiveRecord|DataMapper|MongoMapper|CouchPotato|Mongoid) and (ActiveRecord|DataMapper|MongoMapper|CouchPotato|Mongoid)$/ do |orm1,orm2| Given /^I am using #{orms_pattern} and #{orms_pattern}$/ do |orm1,orm2|
@feature_runner = FeatureRunner.new @feature_runner = FeatureRunner.new
@feature_runner.orm = orm1 @feature_runner.orm = orm1
@feature_runner.another_orm = orm2 @feature_runner.another_orm = orm2

View file

@ -0,0 +1,31 @@
Given /^I have setup database cleaner to clean multiple databases using ohm$/ do
#DatabaseCleaner
# require "#{File.dirname(__FILE__)}/../../../lib/ohm_models"
#
# DatabaseCleaner[:ohm, {:connection => ENV['REDIS_URL_ONE']} ].strategy = :truncation
# DatabaseCleaner[:ohm, {:connection => ENV['REDIS_URL_TWO']} ].strategy = :truncation
end
When /^I create a widget using ohm$/ do
OhmWidget.create!
end
Then /^I should see ([\d]+) widget using ohm$/ do |widget_count|
OhmWidget.count.should == widget_count.to_i
end
When /^I create a widget in one db using ohm$/ do
OhmWidgetUsingDatabaseOne.create!
end
When /^I create a widget in another db using ohm$/ do
OhmWidgetUsingDatabaseTwo.create!
end
Then /^I should see ([\d]+) widget in one db using ohm$/ do |widget_count|
OhmWidgetUsingDatabaseOne.count.should == widget_count.to_i
end
Then /^I should see ([\d]+) widget in another db using ohm$/ do |widget_count|
OhmWidgetUsingDatabaseTwo.count.should == widget_count.to_i
end

View file

@ -0,0 +1,31 @@
Given /^I have setup database cleaner to clean multiple databases using redis$/ do
#DatabaseCleaner
# require "#{File.dirname(__FILE__)}/../../../lib/redis_models"
#
# DatabaseCleaner[:redis, {:connection => ENV['REDIS_URL_ONE']} ].strategy = :truncation
# DatabaseCleaner[:redis, {:connection => ENV['REDIS_URL_TWO']} ].strategy = :truncation
end
When /^I create a widget using redis$/ do
RedisWidget.create!
end
Then /^I should see ([\d]+) widget using redis$/ do |widget_count|
RedisWidget.count.should == widget_count.to_i
end
When /^I create a widget in one db using redis$/ do
RedisWidgetUsingDatabaseOne.create!
end
When /^I create a widget in another db using redis$/ do
RedisWidgetUsingDatabaseTwo.create!
end
Then /^I should see ([\d]+) widget in one db using redis$/ do |widget_count|
RedisWidgetUsingDatabaseOne.count.should == widget_count.to_i
end
Then /^I should see ([\d]+) widget in another db using redis$/ do |widget_count|
RedisWidgetUsingDatabaseTwo.count.should == widget_count.to_i
end

View file

@ -38,10 +38,6 @@ module DatabaseCleaner
end end
end end
def create_connection_class
Class.new(::ActiveRecord::Base)
end
def connection_class def connection_class
@connection_class ||= if @db && !@db.is_a?(Symbol) @connection_class ||= if @db && !@db.is_a?(Symbol)
@db @db
@ -63,9 +59,7 @@ module DatabaseCleaner
end end
def establish_connection def establish_connection
strategy_class = create_connection_class ::ActiveRecord::Base.establish_connection(connection_hash)
strategy_class.send :establish_connection, connection_hash
strategy_class
end end
end end

View file

@ -7,6 +7,10 @@ module DatabaseCleaner::ActiveRecord
include ::DatabaseCleaner::Generic::Transaction include ::DatabaseCleaner::Generic::Transaction
def start def start
# Hack to make sure that the connection is properly setup for
# the clean code.
connection_class.connection.transaction{ }
if connection_maintains_transaction_count? if connection_maintains_transaction_count?
if connection_class.connection.respond_to?(:increment_open_transactions) if connection_class.connection.respond_to?(:increment_open_transactions)
connection_class.connection.increment_open_transactions connection_class.connection.increment_open_transactions

View file

@ -36,12 +36,22 @@ module DatabaseCleaner
def clean_with(*args) def clean_with(*args)
strategy = create_strategy(*args) strategy = create_strategy(*args)
set_strategy_db strategy, self.db
strategy.clean strategy.clean
strategy strategy
end end
alias clean_with! clean_with alias clean_with! clean_with
def set_strategy_db(strategy, desired_db)
if strategy.respond_to? :db=
strategy.db = desired_db
elsif desired_db != :default
raise ArgumentError, "You must provide a strategy object that supports non default databases when you specify a database"
end
end
def strategy=(args) def strategy=(args)
strategy, *strategy_args = args strategy, *strategy_args = args
if strategy.is_a?(Symbol) if strategy.is_a?(Symbol)
@ -52,7 +62,7 @@ module DatabaseCleaner
raise ArgumentError, "You must provide a strategy object, or a symbol for a known strategy along with initialization params." raise ArgumentError, "You must provide a strategy object, or a symbol for a known strategy along with initialization params."
end end
self.strategy_db = self.db set_strategy_db @strategy, self.db
@strategy @strategy
end end
@ -120,8 +130,14 @@ module DatabaseCleaner
:couch_potato :couch_potato
elsif defined? ::Sequel elsif defined? ::Sequel
:sequel :sequel
elsif defined? ::Moped
:moped
elsif defined? ::Ohm
:ohm
elsif defined? ::Redis
:redis
else else
raise NoORMDetected, "No known ORM was detected! Is ActiveRecord, DataMapper, Sequel, MongoMapper, Mongoid, or CouchPotato loaded?" raise NoORMDetected, "No known ORM was detected! Is ActiveRecord, DataMapper, Sequel, MongoMapper, Mongoid, Moped, or CouchPotato, Redis or Ohm loaded?"
end end
end end
end end
@ -130,7 +146,7 @@ module DatabaseCleaner
case orm case orm
when :active_record, :data_mapper, :sequel when :active_record, :data_mapper, :sequel
self.strategy = :transaction self.strategy = :transaction
when :mongo_mapper, :mongoid, :couch_potato when :mongo_mapper, :mongoid, :couch_potato, :moped, :ohm, :redis
self.strategy = :truncation self.strategy = :truncation
end end
end end

View file

@ -11,7 +11,7 @@ module DatabaseCleaner
# ghetto ordered hash.. maintains 1.8 compat and old API # ghetto ordered hash.. maintains 1.8 compat and old API
@connections ||= [] @connections ||= []
end end
def [](orm,opts = {}) def [](orm,opts = {})
raise NoORMDetected unless orm raise NoORMDetected unless orm
init_cleaners init_cleaners
@ -113,6 +113,10 @@ module DatabaseCleaner
DatabaseCleaner::CouchPotato DatabaseCleaner::CouchPotato
when :sequel when :sequel
DatabaseCleaner::Sequel DatabaseCleaner::Sequel
when :ohm
DatabaseCleaner::Ohm
when :redis
DatabaseCleaner::Redis
end end
end end
end end

View file

@ -1,7 +1,7 @@
require 'database_cleaner/mongoid/base' require 'database_cleaner/mongoid/base'
require 'database_cleaner/generic/truncation' require 'database_cleaner/generic/truncation'
require 'database_cleaner/mongo/truncation_mixin' require 'database_cleaner/mongo/truncation_mixin'
require 'database_cleaner/moped/truncation' require 'database_cleaner/moped/truncation_base'
require 'mongoid/version' require 'mongoid/version'
module DatabaseCleaner module DatabaseCleaner
@ -22,7 +22,7 @@ module DatabaseCleaner
else else
include ::DatabaseCleaner::Moped::Truncation include ::DatabaseCleaner::Moped::TruncationBase
private private

View file

@ -0,0 +1,35 @@
require 'database_cleaner/generic/base'
module DatabaseCleaner
module Moped
def self.available_strategies
%w[truncation]
end
module Base
include ::DatabaseCleaner::Generic::Base
def db=(desired_db)
@db = desired_db
end
def db
@db || :default
end
def host_port=(desired_host)
@host = desired_host
end
def host
@host || '127.0.0.1:27017'
end
private
def session
::Moped::Session.new([host], database: db)
end
end
end
end

View file

@ -1,29 +1,9 @@
require 'database_cleaner/moped/truncation_base'
module DatabaseCleaner module DatabaseCleaner
module Moped module Moped
module Truncation class Truncation
include ::DatabaseCleaner::Moped::TruncationBase
def clean
if @only
collections.each { |c| session[c].find.remove_all if @only.include?(c) }
else
collections.each { |c| session[c].find.remove_all unless @tables_to_exclude.include?(c) }
end
true
end
private
def collections
if db != :default
session.use(db)
end
session['system.namespaces'].find(:name => { '$not' => /system|\$/ }).to_a.map do |collection|
_, name = collection['name'].split('.', 2)
name
end
end
end end
end end
end end

View file

@ -0,0 +1,34 @@
require 'database_cleaner/moped/base'
require 'database_cleaner/generic/truncation'
module DatabaseCleaner
module Moped
module TruncationBase
include ::DatabaseCleaner::Moped::Base
include ::DatabaseCleaner::Generic::Truncation
def clean
if @only
collections.each { |c| session[c].find.remove_all if @only.include?(c) }
else
collections.each { |c| session[c].find.remove_all unless @tables_to_exclude.include?(c) }
end
true
end
private
def collections
if db != :default
session.use(db)
end
session['system.namespaces'].find(:name => { '$not' => /system|\$/ }).to_a.map do |collection|
_, name = collection['name'].split('.', 2)
name
end
end
end
end
end

View file

@ -0,0 +1,15 @@
require 'database_cleaner/redis/truncation'
module DatabaseCleaner
module Ohm
class Truncation < ::DatabaseCleaner::Redis::Truncation
private
def default_redis
::Ohm.redis
end
end
end
end

View file

@ -0,0 +1,31 @@
require 'database_cleaner/generic/base'
module DatabaseCleaner
module Redis
def self.available_strategies
%w{truncation}
end
module Base
include ::DatabaseCleaner::Generic::Base
def db=(desired_db)
@db = desired_db
end
def db
@db || :default
end
alias url db
private
def connection
@connection ||= url == :default ? ::Redis.connect : ::Redis.connect(:url => url)
end
end
end
end

View file

@ -0,0 +1,26 @@
require 'database_cleaner/redis/base'
require 'database_cleaner/generic/truncation'
module DatabaseCleaner
module Redis
class Truncation
include ::DatabaseCleaner::Redis::Base
include ::DatabaseCleaner::Generic::Truncation
def clean
if @only
@only.each do |term|
connection.keys(term).each { |k| connection.del k }
end
elsif @tables_to_exclude
keys_except = []
@tables_to_exclude.each { |term| keys_except += connection.keys(term) }
connection.keys.each { |k| connection.del(k) unless keys_except.include?(k) }
else
connection.flushdb
end
connection.quit unless url == :default
end
end
end
end

View file

@ -119,16 +119,6 @@ my_db:
end end
end end
describe "create_connection_class" do
it "should return a class" do
subject.create_connection_class.should be_a(Class)
end
it "should return a class extending ::ActiveRecord::Base" do
subject.create_connection_class.ancestors.should include(::ActiveRecord::Base)
end
end
describe "connection_class" do describe "connection_class" do
it { expect { subject.connection_class }.to_not raise_error } it { expect { subject.connection_class }.to_not raise_error }
it "should default to ActiveRecord::Base" do it "should default to ActiveRecord::Base" do
@ -158,16 +148,9 @@ my_db:
before { ::ActiveRecord::Base.stub!(:respond_to?).and_return(false) } before { ::ActiveRecord::Base.stub!(:respond_to?).and_return(false) }
before { subject.stub(:connection_hash).and_return(hash) } before { subject.stub(:connection_hash).and_return(hash) }
it "should create connection_class if it doesnt exist if connection_hash is set" do it "establish a connection using ActiveRecord::Base" do
subject.should_receive(:create_connection_class).and_return(mock('class').as_null_object) ::ActiveRecord::Base.should_receive(:establish_connection).with(hash)
subject.connection_class
end
it "should configure the class from create_connection_class if connection_hash is set" do
strategy_class = mock('strategy_class')
strategy_class.should_receive(:establish_connection).with(hash)
subject.should_receive(:create_connection_class).and_return(strategy_class)
subject.connection_class subject.connection_class
end end
end end

View file

@ -15,6 +15,7 @@ module DatabaseCleaner
[:begin_transaction, :begin_db_transaction].each do |begin_transaction_method| [:begin_transaction, :begin_db_transaction].each do |begin_transaction_method|
context "using #{begin_transaction_method}" do context "using #{begin_transaction_method}" do
before do before do
connection.stub(:transaction)
connection.stub(begin_transaction_method) connection.stub(begin_transaction_method)
connection.stub(:respond_to?).with(:begin_transaction).and_return(:begin_transaction == begin_transaction_method) connection.stub(:respond_to?).with(:begin_transaction).and_return(:begin_transaction == begin_transaction_method)
end end
@ -35,6 +36,7 @@ module DatabaseCleaner
connection.stub(:respond_to?).with(:increment_open_transactions).and_return(true) connection.stub(:respond_to?).with(:increment_open_transactions).and_return(true)
connection.stub(:increment_open_transactions) connection.stub(:increment_open_transactions)
connection.should_receive(begin_transaction_method) connection.should_receive(begin_transaction_method)
connection.should_receive(:transaction)
Transaction.new.start Transaction.new.start
end end
end end

View file

@ -18,6 +18,9 @@ module DatabaseCleaner
Temp_MO = ::Mongoid if defined?(::Mongoid) and not defined?(Temp_MO) Temp_MO = ::Mongoid if defined?(::Mongoid) and not defined?(Temp_MO)
Temp_CP = ::CouchPotato if defined?(::CouchPotato) and not defined?(Temp_CP) Temp_CP = ::CouchPotato if defined?(::CouchPotato) and not defined?(Temp_CP)
Temp_SQ = ::Sequel if defined?(::Sequel) and not defined?(Temp_SQ) Temp_SQ = ::Sequel if defined?(::Sequel) and not defined?(Temp_SQ)
Temp_MP = ::Moped if defined?(::Moped) and not defined?(Temp_MP)
Temp_RS = ::Redis if defined?(::Redis) and not defined?(Temp_RS)
Temp_OH = ::Ohm if defined?(::Ohm) and not defined?(Temp_OH)
end end
#Remove all ORM mocks and restore from cache #Remove all ORM mocks and restore from cache
@ -28,6 +31,9 @@ module DatabaseCleaner
Object.send(:remove_const, 'Mongoid') if defined?(::Mongoid) Object.send(:remove_const, 'Mongoid') if defined?(::Mongoid)
Object.send(:remove_const, 'CouchPotato') if defined?(::CouchPotato) Object.send(:remove_const, 'CouchPotato') if defined?(::CouchPotato)
Object.send(:remove_const, 'Sequel') if defined?(::Sequel) Object.send(:remove_const, 'Sequel') if defined?(::Sequel)
Object.send(:remove_const, 'Moped') if defined?(::Moped)
Object.send(:remove_const, 'Ohm') if defined?(::Ohm)
Object.send(:remove_const, 'Redis') if defined?(::Redis)
# Restore ORMs # Restore ORMs
@ -36,6 +42,9 @@ module DatabaseCleaner
::MongoMapper = Temp_MM if defined? Temp_MM ::MongoMapper = Temp_MM if defined? Temp_MM
::Mongoid = Temp_MO if defined? Temp_MO ::Mongoid = Temp_MO if defined? Temp_MO
::CouchPotato = Temp_CP if defined? Temp_CP ::CouchPotato = Temp_CP if defined? Temp_CP
::Moped = Temp_MP if defined? Temp_MP
::Ohm = Temp_OH if defined? Temp_OH
::Redis = Temp_RS if defined? Temp_RS
end end
#reset the orm mocks #reset the orm mocks
@ -46,8 +55,11 @@ module DatabaseCleaner
Object.send(:remove_const, 'Mongoid') if defined?(::Mongoid) Object.send(:remove_const, 'Mongoid') if defined?(::Mongoid)
Object.send(:remove_const, 'CouchPotato') if defined?(::CouchPotato) Object.send(:remove_const, 'CouchPotato') if defined?(::CouchPotato)
Object.send(:remove_const, 'Sequel') if defined?(::Sequel) Object.send(:remove_const, 'Sequel') if defined?(::Sequel)
Object.send(:remove_const, 'Moped') if defined?(::Moped)
Object.send(:remove_const, 'Ohm') if defined?(::Ohm)
Object.send(:remove_const, 'Redis') if defined?(::Redis)
end end
let(:cleaner) { DatabaseCleaner::Base.new :autodetect } let(:cleaner) { DatabaseCleaner::Base.new :autodetect }
it "should raise an error when no ORM is detected" do it "should raise an error when no ORM is detected" do
@ -61,6 +73,9 @@ module DatabaseCleaner
Object.const_set('Mongoid', 'Mongoid mock') Object.const_set('Mongoid', 'Mongoid mock')
Object.const_set('CouchPotato', 'Couching mock potatos') Object.const_set('CouchPotato', 'Couching mock potatos')
Object.const_set('Sequel', 'Sequel mock') Object.const_set('Sequel', 'Sequel mock')
Object.const_set('Moped', 'Moped mock')
Object.const_set('Ohm', 'Ohm mock')
Object.const_set('Redis', 'Redis mock')
cleaner.orm.should == :active_record cleaner.orm.should == :active_record
cleaner.should be_auto_detected cleaner.should be_auto_detected
@ -72,6 +87,9 @@ module DatabaseCleaner
Object.const_set('Mongoid', 'Mongoid mock') Object.const_set('Mongoid', 'Mongoid mock')
Object.const_set('CouchPotato', 'Couching mock potatos') Object.const_set('CouchPotato', 'Couching mock potatos')
Object.const_set('Sequel', 'Sequel mock') Object.const_set('Sequel', 'Sequel mock')
Object.const_set('Moped', 'Moped mock')
Object.const_set('Ohm', 'Ohm mock')
Object.const_set('Redis', 'Redis mock')
cleaner.orm.should == :data_mapper cleaner.orm.should == :data_mapper
cleaner.should be_auto_detected cleaner.should be_auto_detected
@ -82,6 +100,9 @@ module DatabaseCleaner
Object.const_set('Mongoid', 'Mongoid mock') Object.const_set('Mongoid', 'Mongoid mock')
Object.const_set('CouchPotato', 'Couching mock potatos') Object.const_set('CouchPotato', 'Couching mock potatos')
Object.const_set('Sequel', 'Sequel mock') Object.const_set('Sequel', 'Sequel mock')
Object.const_set('Moped', 'Moped mock')
Object.const_set('Ohm', 'Ohm mock')
Object.const_set('Redis', 'Redis mock')
cleaner.orm.should == :mongo_mapper cleaner.orm.should == :mongo_mapper
cleaner.should be_auto_detected cleaner.should be_auto_detected
@ -91,6 +112,9 @@ module DatabaseCleaner
Object.const_set('Mongoid', 'Mongoid mock') Object.const_set('Mongoid', 'Mongoid mock')
Object.const_set('CouchPotato', 'Couching mock potatos') Object.const_set('CouchPotato', 'Couching mock potatos')
Object.const_set('Sequel', 'Sequel mock') Object.const_set('Sequel', 'Sequel mock')
Object.const_set('Moped', 'Moped mock')
Object.const_set('Ohm', 'Ohm mock')
Object.const_set('Redis', 'Redis mock')
cleaner.orm.should == :mongoid cleaner.orm.should == :mongoid
cleaner.should be_auto_detected cleaner.should be_auto_detected
@ -99,17 +123,45 @@ module DatabaseCleaner
it "should detect CouchPotato fifth" do it "should detect CouchPotato fifth" do
Object.const_set('CouchPotato', 'Couching mock potatos') Object.const_set('CouchPotato', 'Couching mock potatos')
Object.const_set('Sequel', 'Sequel mock') Object.const_set('Sequel', 'Sequel mock')
Object.const_set('Moped', 'Moped mock')
Object.const_set('Ohm', 'Ohm mock')
Object.const_set('Redis', 'Redis mock')
cleaner.orm.should == :couch_potato cleaner.orm.should == :couch_potato
cleaner.should be_auto_detected cleaner.should be_auto_detected
end end
it "should detect Sequel last" do it "should detect Sequel sixth" do
Object.const_set('Sequel', 'Sequel mock') Object.const_set('Sequel', 'Sequel mock')
Object.const_set('Moped', 'Moped mock')
Object.const_set('Ohm', 'Ohm mock')
Object.const_set('Redis', 'Redis mock')
cleaner.orm.should == :sequel cleaner.orm.should == :sequel
cleaner.should be_auto_detected cleaner.should be_auto_detected
end end
it 'detects Ohm seventh' do
Object.const_set('Ohm', 'Ohm mock')
Object.const_set('Redis', 'Redis mock')
cleaner.orm.should == :ohm
cleaner.should be_auto_detected
end
it 'detects Redis last' do
Object.const_set('Redis', 'Redis mock')
cleaner.orm.should == :redis
cleaner.should be_auto_detected
end
it 'detects Moped seventh' do
Object.const_set('Moped', 'Moped mock')
cleaner.orm.should == :moped
cleaner.should be_auto_detected
end
end end
describe "orm_module" do describe "orm_module" do
@ -160,7 +212,7 @@ module DatabaseCleaner
cleaner = ::DatabaseCleaner::Base.new "mongoid" cleaner = ::DatabaseCleaner::Base.new "mongoid"
cleaner.orm.should == :mongoid cleaner.orm.should == :mongoid
end end
it "is autodetected if orm is not provided" do it "is autodetected if orm is not provided" do
cleaner = ::DatabaseCleaner::Base.new cleaner = ::DatabaseCleaner::Base.new
cleaner.should be_auto_detected cleaner.should be_auto_detected
@ -317,7 +369,7 @@ module DatabaseCleaner
it "should attempt to set strategy db" do it "should attempt to set strategy db" do
subject.stub(:db).and_return(:my_db) subject.stub(:db).and_return(:my_db)
subject.should_receive(:strategy_db=).with(:my_db) subject.should_receive(:set_strategy_db).with(mock_strategy, :my_db)
subject.strategy = mock_strategy subject.strategy = mock_strategy
end end
@ -330,8 +382,7 @@ module DatabaseCleaner
describe "strategy" do describe "strategy" do
subject { ::DatabaseCleaner::Base.new :a_orm } subject { ::DatabaseCleaner::Base.new :a_orm }
it "returns a null strategy when strategy no set and undetectable" do it "returns a null strategy when strategy is not set and undetectable" do
subject.instance_values["@strategy"] = nil
subject.strategy.should == DatabaseCleaner::NullStrategy subject.strategy.should == DatabaseCleaner::NullStrategy
end end
@ -487,6 +538,21 @@ module DatabaseCleaner
cleaner = DatabaseCleaner::Base.new(:couch_potato) cleaner = DatabaseCleaner::Base.new(:couch_potato)
cleaner.strategy.should be_instance_of DatabaseCleaner::CouchPotato::Truncation cleaner.strategy.should be_instance_of DatabaseCleaner::CouchPotato::Truncation
end end
it 'sets strategy to :truncation for Moped' do
cleaner = DatabaseCleaner::Base.new(:moped)
cleaner.strategy.should be_instance_of DatabaseCleaner::Moped::Truncation
end
it 'sets strategy to :truncation for Ohm' do
cleaner = DatabaseCleaner::Base.new(:ohm)
cleaner.strategy.should be_instance_of DatabaseCleaner::Ohm::Truncation
end
it 'sets strategy to :truncation for Redis' do
cleaner = DatabaseCleaner::Base.new(:redis)
cleaner.strategy.should be_instance_of DatabaseCleaner::Redis::Truncation
end
end end
end end

View file

@ -55,6 +55,20 @@ describe ::DatabaseCleaner do
cleaner.orm.should == :couch_potato cleaner.orm.should == :couch_potato
::DatabaseCleaner.connections.size.should == 1 ::DatabaseCleaner.connections.size.should == 1
end end
it "should accept :moped" do
cleaner = ::DatabaseCleaner[:moped]
cleaner.should be_a(::DatabaseCleaner::Base)
cleaner.orm.should == :moped
::DatabaseCleaner.connections.size.should == 1
end
it 'accepts :ohm' do
cleaner = ::DatabaseCleaner[:ohm]
cleaner.should be_a(::DatabaseCleaner::Base)
cleaner.orm.should == :ohm
::DatabaseCleaner.connections.size.should == 1
end
end end
it "should accept multiple orm's" do it "should accept multiple orm's" do
@ -119,7 +133,7 @@ describe ::DatabaseCleaner do
it "should give me a default (autodetection) databasecleaner by default" do it "should give me a default (autodetection) databasecleaner by default" do
cleaner = mock("cleaner").as_null_object cleaner = mock("cleaner").as_null_object
::DatabaseCleaner::Base.stub!(:new).and_return(cleaner) ::DatabaseCleaner::Base.stub!(:new).and_return(cleaner)
::DatabaseCleaner.connections.should == [cleaner] ::DatabaseCleaner.connections.should == [cleaner]
end end
end end

View file

@ -0,0 +1,26 @@
module MopedTest
class ThingBase
def self.collection
@db ||= 'database_cleaner_specs'
@session ||= ::Moped::Session.new(['127.0.0.1:27017'], database: @db)
@collection ||= @session[name]
end
def self.count
@collection.find.count
end
def initialize(attrs={})
@attrs = attrs
end
def save!
self.class.collection.insert(@attrs)
end
end
class Widget < ThingBase
end
class Gadget < ThingBase
end
end

View file

@ -0,0 +1,75 @@
require File.dirname(__FILE__) + '/../../spec_helper'
require 'moped'
require 'database_cleaner/moped/truncation'
require File.dirname(__FILE__) + '/moped_examples'
module DatabaseCleaner
module Moped
describe Truncation do
let(:args) {{}}
let(:truncation) { described_class.new(args).tap { |t| t.db=@db } }
#doing this in the file root breaks autospec, doing it before(:all) just fails the specs
before(:all) do
@test_db = 'database_cleaner_specs'
@session = ::Moped::Session.new(['127.0.0.1:27017'], database: @test_db)
end
before(:each) do
truncation.db = @test_db
end
after(:each) do
@session.drop
end
def ensure_counts(expected_counts)
# I had to add this sanity_check garbage because I was getting non-determinisc results from mongo at times..
# very odd and disconcerting...
expected_counts.each do |model_class, expected_count|
model_class.count.should equal(expected_count), "#{model_class} expected to have a count of #{expected_count} but was #{model_class.count}"
end
end
def create_widget(attrs={})
MopedTest::Widget.new({:name => 'some widget'}.merge(attrs)).save!
end
def create_gadget(attrs={})
MopedTest::Gadget.new({:name => 'some gadget'}.merge(attrs)).save!
end
it "truncates all collections by default" do
create_widget
create_gadget
ensure_counts(MopedTest::Widget => 1, MopedTest::Gadget => 1)
truncation.clean
ensure_counts(MopedTest::Widget => 0, MopedTest::Gadget => 0)
end
context "when collections are provided to the :only option" do
let(:args) {{:only => ['MopedTest::Widget']}}
it "only truncates the specified collections" do
create_widget
create_gadget
ensure_counts(MopedTest::Widget => 1, MopedTest::Gadget => 1)
truncation.clean
ensure_counts(MopedTest::Widget => 0, MopedTest::Gadget => 1)
end
end
context "when collections are provided to the :except option" do
let(:args) {{:except => ['MopedTest::Widget']}}
it "truncates all but the specified collections" do
create_widget
create_gadget
ensure_counts(MopedTest::Widget => 1, MopedTest::Gadget => 1)
truncation.clean
ensure_counts(MopedTest::Widget => 1, MopedTest::Gadget => 0)
end
end
end
end
end

View file

@ -0,0 +1,70 @@
require File.dirname(__FILE__) + '/../../spec_helper'
require 'ohm'
require 'database_cleaner/ohm/truncation'
module DatabaseCleaner
module Ohm
class Widget < ::Ohm::Model
attribute :name
end
class Gadget < ::Ohm::Model
attribute :name
end
describe Truncation do
before(:all) do
config = YAML::load(File.open("#{File.dirname(__FILE__)}/../../../examples/config/redis.yml"))
::Ohm.connect :url => config['test']['url']
@redis = ::Ohm.redis
end
before(:each) do
@redis.flushdb
end
it "should flush the database" do
Truncation.new.clean
end
def create_widget(attrs={})
Widget.new({:name => 'some widget'}.merge(attrs)).save
end
def create_gadget(attrs={})
Gadget.new({:name => 'some gadget'}.merge(attrs)).save
end
it "truncates all keys by default" do
create_widget
create_gadget
@redis.keys.size.should == 6
Truncation.new.clean
@redis.keys.size.should == 0
end
context "when keys are provided to the :only option" do
it "only truncates the specified keys" do
create_widget
create_gadget
@redis.keys.size.should == 6
Truncation.new(:only => ['*Widget*']).clean
@redis.keys.size.should == 3
@redis.get('DatabaseCleaner::Ohm::Gadget:id').should == '1'
end
end
context "when keys are provided to the :except option" do
it "truncates all but the specified keys" do
create_widget
create_gadget
@redis.keys.size.should == 6
Truncation.new(:except => ['*Widget*']).clean
@redis.keys.size.should == 3
@redis.get('DatabaseCleaner::Ohm::Widget:id').should == '1'
end
end
end
end
end

View file

@ -0,0 +1,32 @@
require 'spec_helper'
require 'database_cleaner/redis/base'
require 'database_cleaner/shared_strategy'
module DatabaseCleaner
describe Redis do
it { should respond_to(:available_strategies) }
end
module Redis
class ExampleStrategy
include ::DatabaseCleaner::Redis::Base
end
describe ExampleStrategy do
it_should_behave_like "a generic strategy"
it { should respond_to(:db) }
it { should respond_to(:db=) }
it "should store my describe db" do
url = 'redis://localhost:6379/2'
subject.db = 'redis://localhost:6379/2'
subject.db.should == url
end
it "should default to :default" do
subject.db.should == :default
end
end
end
end

View file

@ -0,0 +1,63 @@
require File.dirname(__FILE__) + '/../../spec_helper'
require 'redis'
require 'database_cleaner/redis/truncation'
module DatabaseCleaner
module Redis
describe Truncation do
before(:all) do
config = YAML::load(File.open("#{File.dirname(__FILE__)}/../../../examples/config/redis.yml"))
@redis = ::Redis.connect :url => config['test']['url']
end
before(:each) do
@redis.flushdb
end
it "should flush the database" do
Truncation.new.clean
end
def create_widget(attrs={})
@redis.set 'Widget', 1
end
def create_gadget(attrs={})
@redis.set 'Gadget', 1
end
it "truncates all keys by default" do
create_widget
create_gadget
@redis.keys.size.should == 2
Truncation.new.clean
@redis.keys.size.should == 0
end
context "when keys are provided to the :only option" do
it "only truncates the specified keys" do
create_widget
create_gadget
@redis.keys.size.should == 2
Truncation.new(:only => ['Widge*']).clean
@redis.keys.size.should == 1
@redis.get('Gadget').should == '1'
end
end
context "when keys are provided to the :except option" do
it "truncates all but the specified keys" do
create_widget
create_gadget
@redis.keys.size.should == 2
Truncation.new(:except => ['Widg*']).clean
@redis.keys.size.should == 1
@redis.get('Widget').should == '1'
end
end
end
end
end