Slide 1

Slide 1 text

Anatomy of a gem: DatabaseCleaner

Slide 2

Slide 2 text

Ernesto Tagwerker Founder, Ombu Labs @etagwerker

Slide 3

Slide 3 text

DatabaseCleaner: Strategies for cleaning databases

Slide 4

Slide 4 text

https://github.com/ DatabaseCleaner/ database_cleaner

Slide 5

Slide 5 text

# Gemfile group :test do gem 'database_cleaner' end # spec_helper.rb require 'database_cleaner' RSpec.configure do |config| config.before(:suite) do DatabaseCleaner.strategy = :truncation DatabaseCleaner.clean_with(:truncation) end config.around(:each) do |example| DatabaseCleaner.cleaning do example.run end end end Setup

Slide 6

Slide 6 text

RSpec.configure do |config| config.before(:suite) do DatabaseCleaner.strategy = :truncation, { except: %w(states countries) } DatabaseCleaner.clean_with(:truncation) end config.around(:each) do |example| DatabaseCleaner.cleaning do example.run end end end Setup with exceptions

Slide 7

Slide 7 text

RSpec.configure do |config| config.before(:suite) do DatabaseCleaner[:active_record].strategy = :transaction DatabaseCleaner[:mongo_mapper].strategy = :truncation DatabaseCleaner[:active_record].clean_with(:transaction) DatabaseCleaner[:mongo_mapper].clean_with(:truncation) end config.around(:each) do |example| DatabaseCleaner.cleaning do example.run end end end Setup with multiple ORMs

Slide 8

Slide 8 text

Strategies: • Truncation • Transaction • Deletion

Slide 9

Slide 9 text

ORM: • ActiveRecord • Sequel • Ohm • … and six more

Slide 10

Slide 10 text

It works with: • Cucumber • RSpec • Minitest

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Complexity

Slide 13

Slide 13 text

Too many combinations: Drivers + ORMs + Database + Strategies

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

# configuration.rb module DatabaseCleaner class NoORMDetected < StandardError; end class UnknownStrategySpecified < ArgumentError; end class << self def [](orm, opts = {}) raise NoORMDetected unless orm init_cleaners # TODO: deprecate # this method conflates creation with lookup. if @cleaners.has_key? [orm, opts] @cleaners[[orm, opts]] else add_cleaner(orm, opts) end end Configuration.[]

Slide 16

Slide 16 text

module DatabaseCleaner class << self def clean_with(*args) connections.each { |connection| connection.clean_with(*args) } end def connections # double yuck.. can't wait to deprecate this whole class... unless defined?(@cleaners) && @cleaners autodetected = ::DatabaseCleaner::Base.new add_cleaner(autodetected.orm) end @connections end Configuration.connections

Slide 17

Slide 17 text

module DatabaseCleaner class Base def autodetect_orm if defined? ::ActiveRecord :active_record elsif defined? ::DataMapper :data_mapper ...… elsif defined? ::Moped :moped elsif defined? ::Ohm :ohm elsif defined? ::Redis :redis elsif defined? ::Neo4j :neo4j end end Base#autodetect_orm

Slide 18

Slide 18 text

module DatabaseCleaner class Base def start strategy.start end def clean strategy.clean end alias clean! clean def cleaning(&block) strategy.cleaning(&block) end DatabaseCleaner::Base (start, clean, cleaning)

Slide 19

Slide 19 text

module ::DatabaseCleaner module Generic module Base def cleaning(&block) begin start yield ensure clean end end DatabaseCleaner::Generic::Base

Slide 20

Slide 20 text

module Truncation OPTS = [:only, :except, :pre_count, :reset_ids, :cache_tables] def initialize(opts={}) if !opts.empty? && !(opts.keys - OPTS).empty? msg = <<-EOS The only valid options are #{OPTS.join(', ')}. You specified #{opts.keys. join(',')}. EOS raise ArgumentError, msg end if opts.has_key?(:only) && opts.has_key?(:except) raise ArgumentError, "You may only specify either :only or :except." end @only = opts[:only] @tables_to_exclude = Array((opts[:except] || []).dup).flatten @tables_to_exclude += migration_storage_names @pre_count = opts[:pre_count] @reset_ids = opts[:reset_ids] @cache_tables = opts.has_key?(:cache_tables) ? !!opts[:cache_tables] : true end DatabaseCleaner::Generic::Truncation

Slide 21

Slide 21 text

module DatabaseCleaner module Generic module Truncation # ... def start # included for compatibility reasons, # do nothing if you don't need to end def clean raise NotImplementedError end Generic Public Interface

Slide 22

Slide 22 text

require "database_cleaner/generic/truncation" require “database_cleaner/active_record/base" module DatabaseCleaner module ConnectionAdapters module AbstractAdapter def truncate_table(table_name) raise NotImplementedError end def truncate_tables(tables) tables.each do |table_name| self.truncate_table(table_name) end end end DatabaseCleaner::ActiveRecord::Truncation

Slide 23

Slide 23 text

module DatabaseCleaner module ConnectionAdapters # ... module AbstractMysqlAdapter def truncate_table(table_name) execute("TRUNCATE TABLE #{quote_table_name(table_name)};") end # ... module IBM_DBAdapter def truncate_table(table_name) execute("TRUNCATE #{quote_table_name(table_name)} IMMEDIATE") end end # ... module PostgreSQLAdapter def truncate_tables(table_names) return if table_names.nil? || table_names.empty? execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}. join(', ')} #{restart_identity} #{cascade};") end DatabaseCleaner::ActiveRecord::Truncation

Slide 24

Slide 24 text

Roadmap (v2.0)

Slide 25

Slide 25 text

• Split DatabaseCleaner into smaller gems • Change setup interface • Reduce complexity of configuration.rb & base.rb • Release v1.99 with deprecation warnings • Make it easier for people to write their own strategies • Look for more maintainers

Slide 26

Slide 26 text

# Gemfile group :test do gem ‘database_cleaner-active_record’ end # spec_helper.rb require ‘database_cleaner/active_record’ RSpec.configure do |config| config.before(:suite) do DatabaseCleaner.strategy = :truncation DatabaseCleaner.clean_with(:truncation) end config.around(:each) do |example| DatabaseCleaner.cleaning do example.run end end end Setup for ActiveRecord (v2.0)

Slide 27

Slide 27 text

# running the build bundle exec rspec spec/ # you will get this message database_cleaner 2 will no longer automatically detect your ORM. You must explicitly require the right path: # spec_helper.rb require ‘database_cleaner/active_record’ # Gemfile group :test do gem ‘database_cleaner-active_record’ end If you wish to upgrade to database_cleaner 2.0+, you must change this in your project. Deprecation Warnings (v1.99)

Slide 28

Slide 28 text

DatabaseCleaner v2.0 https://github.com/ DatabaseCleaner/ database_cleaner/tree/2-0

Slide 29

Slide 29 text

DatabaseCleaner- e.g. https://github.com/ DatabaseCleaner/ database_cleaner-active_record

Slide 30

Slide 30 text

Thank you!

Slide 31

Slide 31 text

Questions?