2

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.

pawel7318
  • 3,383
  • 2
  • 28
  • 44
  • 1
    how about 'drop schema X cascade;' ?? that should drop the schema, then you could recreate a blank one. now that i read again the description, maybe schema means something different in rails? in postgres you can simply drop it and it will go away (if you know the name). – Greg May 19 '14 at 21:18

2 Answers2

5

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?

brad
  • 31,987
  • 28
  • 102
  • 155
  • a lot ! Thank you. I described final solution in another answer. (I decided to don't split feature specs that uses multi-tenancy as I it's crucial for many of them. For example I have some tests that checks if data are unique in User/tenant's scope) – pawel7318 May 20 '14 at 05:23
0

Another way to deal with truncation and multi tenant applications by now is creating the tenant and deleting it on each test. Like this:

On your rails_helper.rb:

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

  config.before(:each) do
    Apartment::Tenant.drop('test') rescue nil
    Company.create! name: "LittleCompany", subdomain: "test"
    DatabaseCleaner.start
    Apartment::Tenant.switch! "test"
  end

  config.after(:each) do
    Apartment::Tenant.reset
    DatabaseCleaner.clean
    Apartment::Tenant.drop('test')
  end
...

It's not very fast of course, but it's the only way i have found.

Osmond
  • 345
  • 4
  • 9