Add Redis & Ohm support

This contains the work of @hbpoison, updated to work with the current
version of DatabaseCleaner.

https://github.com/bmabey/database_cleaner/pull/87/files
This commit is contained in:
James Conroy-Finn 2013-05-11 14:16:01 +01:00
parent ce1d162989
commit b70f09a3ed
23 changed files with 559 additions and 20 deletions

View file

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

View file

@ -135,6 +135,10 @@ GEM
multi_json (1.5.0)
mysql (2.8.1)
mysql2 (0.3.11)
nest (1.1.2)
redis
ohm (0.1.5)
nest (~> 1.0)
origin (1.0.11)
pg (0.14.1)
plucky (0.5.2)
@ -160,6 +164,7 @@ GEM
rake (10.0.3)
rdoc (3.12)
json (~> 1.4)
redis (3.0.4)
rest-client (1.6.7)
mime-types (>= 1.16)
rspec (2.12.0)
@ -213,6 +218,7 @@ DEPENDENCIES
mongoid
mysql (~> 2.8.1)
mysql2
ohm (~> 0.1.3)
pg
rake
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.
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)
@ -55,6 +55,18 @@ Here is an overview of the strategies supported for each library:
<td> Yes</td>
<td> No</td>
</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>
</table>
@ -129,6 +141,9 @@ DatabaseCleaner.strategy = :truncation, {:only => %w[widgets dogs some_other_tab
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.)
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:
@ -297,6 +312,16 @@ Usage beyond that remains the same with `DatabaseCleaner.start` calling any setu
<td> <code>DatabaseCleaner[:sequel]</code></td>
<td> Multiple databases supported; specify <code>Databasecleaner[:sequel, {:connection =&gt; Sequel.connect(uri)}]</code></td>
</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>
</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']
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
$:.unshift(File.dirname(__FILE__) + '/../../../lib')
@ -37,6 +41,9 @@ if orm && strategy
when :mongo_mapper
DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_one'} ].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
DatabaseCleaner[:active_record, {:model => ActiveRecordWidgetUsingDatabaseOne} ].strategy = strategy.to_sym
DatabaseCleaner[:active_record, {:model => ActiveRecordWidgetUsingDatabaseTwo} ].strategy = strategy.to_sym
@ -53,5 +60,5 @@ if orm && strategy
end
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

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

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

View file

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

View file

@ -15,15 +15,34 @@ Feature: database cleaning using multiple ORMs
| ActiveRecord | MongoMapper |
| ActiveRecord | Mongoid |
| ActiveRecord | CouchPotato |
| ActiveRecord | Ohm |
| ActiveRecord | Redis |
| DataMapper | ActiveRecord |
| DataMapper | MongoMapper |
| DataMapper | Mongoid |
| DataMapper | CouchPotato |
| DataMapper | Ohm |
| DataMapper | Redis |
| MongoMapper | ActiveRecord |
| MongoMapper | DataMapper |
| MongoMapper | Mongoid |
| MongoMapper | CouchPotato |
| MongoMapper | Ohm |
| MongoMapper | Redis |
| CouchPotato | ActiveRecord |
| CouchPotato | DataMapper |
| CouchPotato | MongoMapper |
| 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.orm = orm
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.orm = orm1
@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

@ -132,8 +132,12 @@ module DatabaseCleaner
:sequel
elsif defined? ::Moped
:moped
elsif defined? ::Ohm
:ohm
elsif defined? ::Redis
:redis
else
raise NoORMDetected, "No known ORM was detected! Is ActiveRecord, DataMapper, Sequel, MongoMapper, Mongoid, Moped, 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
@ -142,7 +146,7 @@ module DatabaseCleaner
case orm
when :active_record, :data_mapper, :sequel
self.strategy = :transaction
when :mongo_mapper, :mongoid, :couch_potato, :moped
when :mongo_mapper, :mongoid, :couch_potato, :moped, :ohm, :redis
self.strategy = :truncation
end
end

View file

@ -113,6 +113,10 @@ module DatabaseCleaner
DatabaseCleaner::CouchPotato
when :sequel
DatabaseCleaner::Sequel
when :ohm
DatabaseCleaner::Ohm
when :redis
DatabaseCleaner::Redis
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

@ -19,6 +19,8 @@ module DatabaseCleaner
Temp_CP = ::CouchPotato if defined?(::CouchPotato) and not defined?(Temp_CP)
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
#Remove all ORM mocks and restore from cache
@ -30,6 +32,8 @@ module DatabaseCleaner
Object.send(:remove_const, 'CouchPotato') if defined?(::CouchPotato)
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
@ -39,6 +43,8 @@ module DatabaseCleaner
::Mongoid = Temp_MO if defined? Temp_MO
::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
#reset the orm mocks
@ -50,6 +56,8 @@ module DatabaseCleaner
Object.send(:remove_const, 'CouchPotato') if defined?(::CouchPotato)
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
let(:cleaner) { DatabaseCleaner::Base.new :autodetect }
@ -66,6 +74,8 @@ module DatabaseCleaner
Object.const_set('CouchPotato', 'Couching mock potatos')
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.should be_auto_detected
@ -78,6 +88,8 @@ module DatabaseCleaner
Object.const_set('CouchPotato', 'Couching mock potatos')
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.should be_auto_detected
@ -89,6 +101,8 @@ module DatabaseCleaner
Object.const_set('CouchPotato', 'Couching mock potatos')
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.should be_auto_detected
@ -99,6 +113,8 @@ module DatabaseCleaner
Object.const_set('CouchPotato', 'Couching mock potatos')
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.should be_auto_detected
@ -108,6 +124,8 @@ module DatabaseCleaner
Object.const_set('CouchPotato', 'Couching mock potatos')
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.should be_auto_detected
@ -116,12 +134,29 @@ module DatabaseCleaner
it "should detect Sequel sixth" do
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.should be_auto_detected
end
it "should detect Moped seventh" do
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
@ -508,6 +543,16 @@ module DatabaseCleaner
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

View file

@ -62,6 +62,13 @@ describe ::DatabaseCleaner do
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
it "should accept multiple orm's" do

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