Search code examples
ruby-on-railspostgresqlrspecdevisedatabase-cleaner

database cleaning for postgres schemas


I have gem devise and gem apartment which I'm using to create separate schemas for each devise's user account.

Apartment's doc and advice in that issue suggest to use Rack middleware to switch between tenants. In that case it's not possible (as far as I know) as I have it user depended rather than request depended.

All works just great except my RSpec tests. The problem is that after every test database is not clean properly (it doesn't remove schema for new created user). All tests pass if I run a small set of them but if I run to many than Faker::Internet.first_name generates usernames that already was taken (which is not valid).

So this is how I did it:

app/controllers/application_controller.rb

def scope_tenant
  Apartment::Database.switch(current_user.username)
end

app/controllers/albums_controller.rb (album model belong_to :user)

class AlbumsController < ApplicationController
  before_action :authenticate_user! # devise magic
  before_action :scope_tenant

app/model/user.rb

after_create :create_schema

private

  def create_schema    
    Apartment::Database.create(self.username)
  end  

This is what I've added to my specs:

spec/factories/user.rb

FactoryGirl.define do
  factory :user do
    username { Faker::Name.first_name }
    email { Faker::Internet.email("#{username}") }
    password "login_as will not use it anyway"
  end
end

spec/support/auth_helpers.rb

Warden.test_mode!

def login_and_switch_schema(user)
 login_as(user)
 Apartment::Database.switch(user.username)    # for some reason `login_as()` didn't do that by itself
end

spec/features/albums_spec.rb

feature "Album Pages" do

  given(:user) { create(:user) }
  given(:album) { create(:album) }

  around :each do
    login_and_switch_schema user
  end

  scenario...

As I have some tests with js: true than I have that:

spec/support/database_cleaner.rb

RSpec.configure do |config|

  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, js: true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

end

Current commit for all sources are available at my github here.

So.. the main question is: how to clean database created schemas for each user after test ? I'll appreciate any other comment as well. Thank you in advance for help.


Solution

  • This isn't specific to Apartment in any way, it's more related to how DatabaseCleaner cleans your db. When using transactions, any schemas created within that transaction will be rolled back as well. Unfortunately however, you need to truncate for feature specs as transactions don't work (don't try the shared connection 'solution', it causes random failures due to a race condition). So, given that, for your feature specs, you need a way of ensuring any schemas created are deleted, since truncation only truncates tables and will NOT clean up schemas.

    I'd suggest isolating your feature specs that test the multi-tenant behaviour specifically, to ensure it works the way you want it to, and manually clean up any created schemas in those specs. Then for the rest of the feature specs, assume you're testing within one tenant, or in your case one User.

    We do this in our test suite, where a new Company model creates a new tenant. So we test that behaviour, for multiple tenants, then for the rest of our features we operate within 1 Company, so we don't have to worry about cleanup anymore and we can just truncate the tables within that 1 tenant. Truncate will always truncate the tables in the current tenant unless you have excluded_models.

    Does that help?