Coder Social home page Coder Social logo

apartment's People

Contributors

actions-user avatar ahorek avatar apneadiving avatar bdq avatar bradrobertson avatar calmyournerves avatar cbeer avatar desheikh avatar eric88 avatar fphilipe avatar fsateler avatar jeremiahchurch avatar jonian avatar linki avatar lunks avatar maedhr avatar meganemura avatar michiomochi avatar mikecmpbll avatar mikekruckenberg avatar mnovelo avatar orafaelfragoso avatar radar avatar ramontayag avatar rebyn avatar rpbaltazar avatar scottatron avatar senjai avatar shterrett avatar virtualstaticvoid avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

apartment's Issues

enhanced db:create task breaks plugins compatibility

Steps to reproduce

application that uses hstore extension, e.g

Expected behavior

rails db:create
rails db:migrate

to not fail

Actual behavior

after running db:create an error is thrown regarding the hstore extension

System configuration

  • Database: (Tell us what database and its version you use.)

postgresql

  • Apartment version:

2.7.0

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: true
  • Rails (or ActiveRecord) version:

Rails 6

  • Ruby version:

Tenant.switch! raises exception on first call / Postgresql

Steps to reproduce

In rails production env: Apartment::Tenant.switch!('public')

Expected behavior

it changes current tenant to public.

Actual behavior

raises an exception when trying to switch to any tenant.

Caused by:
PG::ConnectionBad: FATAL:  database "public" does not exist
/opt/app-root/src/bundle/ruby/2.5.0/gems/pg-1.2.3/lib/pg.rb:58:in `initialize'
/opt/app-root/src/bundle/ruby/2.5.0/gems/pg-1.2.3/lib/pg.rb:58:in `new'
/opt/app-root/src/bundle/ruby/2.5.0/gems/pg-1.2.3/lib/pg.rb:58:in `connect'
/opt/app-root/src/bundle/ruby/2.5.0/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/postgresql_adapter.rb:46:in `postgresql_connection'
/opt/app-root/src/bundle/ruby/2.5.0/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:887:in `new_connection'
/opt/app-root/src/bundle/ruby/2.5.0/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:931:in `checkout_new_connection'
/opt/app-root/src/bundle/ruby/2.5.0/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:910:in `try_to_checkout_new_connection'
/opt/app-root/src/bundle/ruby/2.5.0/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:871:in `acquire_connection'
/opt/app-root/src/bundle/ruby/2.5.0/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:593:in `checkout'
/opt/app-root/src/bundle/ruby/2.5.0/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:437:in `connection'
/opt/app-root/src/bundle/ruby/2.5.0/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/abstract/connection_pool.rb:1119:in `retrieve_connection'
/opt/app-root/src/bundle/ruby/2.5.0/gems/activerecord-6.0.3.2/lib/active_record/connection_handling.rb:221:in `retrieve_connection'
/opt/app-root/src/bundle/ruby/2.5.0/gems/activerecord-6.0.3.2/lib/active_record/connection_handling.rb:189:in `connection'
/opt/app-root/src/bundle/ruby/2.5.0/gems/ros-apartment-2.7.1/lib/apartment/railtie.rb:39:in `connection'
/opt/app-root/src/bundle/ruby/2.5.0/gems/ros-apartment-2.7.1/lib/apartment/adapters/abstract_adapter.rb:184:in `connect_to_new'
/opt/app-root/src/bundle/ruby/2.5.0/gems/ros-apartment-2.7.1/lib/apartment/adapters/abstract_adapter.rb:76:in `block in switch!'
/opt/app-root/src/bundle/ruby/2.5.0/gems/activesupport-6.0.3.2/lib/active_support/callbacks.rb:101:in `run_callbacks'
/opt/app-root/src/bundle/ruby/2.5.0/gems/ros-apartment-2.7.1/lib/apartment/adapters/abstract_adapter.rb:75:in `switch!'
/opt/app-root/src/bundle/ruby/2.5.0/gems/ros-apartment-2.7.1/lib/apartment/adapters/abstract_adapter.rb:88:in `switch'
/opt/app-root/src/bundle/ruby/2.5.0/gems/ros-apartment-2.7.1/lib/apartment/adapters/abstract_adapter.rb:102:in `block in each'
/opt/app-root/src/bundle/ruby/2.5.0/gems/ros-apartment-2.7.1/lib/apartment/adapters/abstract_adapter.rb:101:in `each'
/opt/app-root/src/bundle/ruby/2.5.0/gems/ros-apartment-2.7.1/lib/apartment/adapters/abstract_adapter.rb:101:in `each'

System configuration

  • Database: Postgresql 9.5.14

  • pg gem: 1.2.3

  • OS: Linux / Docker

  • Apartment version: 2.7.1

  • Apartment config (in config/initializers/apartment.rb or so):

    • config.use_schemas = true
  • Rails (or ActiveRecord) version: 6.0.3.2

  • Ruby version: 2.5.5

db:create is failed

Steps to reproduce

./bin/rails db:create

Expected behavior

db:create is succeeded

Actual behavior

Mysql2::Error: Unknown database 'public'
Couldn't create 'iroha_development' database. Please check your configuration.
rails aborted!
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown database 'public'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `_query'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `block in query'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:130:in `handle_interrupt'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:130:in `query'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:202:in `block (2 levels) in execute'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/dependencies/interlock.rb:47:in `permit_concurrent_loads'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:201:in `block in execute'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract_adapter.rb:718:in `block (2 levels) in log'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract_adapter.rb:717:in `block in log'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/notifications/instrumenter.rb:24:in `instrument'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract_adapter.rb:708:in `log'
/usr/local/bundle/gems/scout_apm-2.6.7/lib/scout_apm/instruments/active_record.rb:279:in `log'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:200:in `execute'
/usr/local/bundle/gems/active_record_mysql_xverify-0.1.1/lib/active_record_mysql_xverify/error_handler.rb:6:in `execute'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/mysql/database_statements.rb:41:in `execute'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/adapters/mysql2_adapter.rb:42:in `reset'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/adapters/mysql2_adapter.rb:36:in `initialize'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/adapters/mysql2_adapter.rb:9:in `new'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/adapters/mysql2_adapter.rb:9:in `mysql2_adapter'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/tenant.rb:56:in `adapter'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/tenant.rb:25:in `init_once'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/railtie.rb:39:in `block in connection'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/railtie.rb:38:in `tap'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/railtie.rb:38:in `connection'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/mysql_database_tasks.rb:8:in `connection'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/mysql_database_tasks.rb:16:in `create'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:126:in `create'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:185:in `block in create_current'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:479:in `block (2 levels) in each_current_configuration'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:476:in `each'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:476:in `block in each_current_configuration'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:475:in `each'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:475:in `each_current_configuration'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:184:in `create_current'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/railties/databases.rake:39:in `block (2 levels) in <main>'
/usr/local/bundle/gems/railties-6.0.2.2/lib/rails/commands/rake/rake_command.rb:23:in `block in perform'
/usr/local/bundle/gems/railties-6.0.2.2/lib/rails/commands/rake/rake_command.rb:20:in `perform'
/usr/local/bundle/gems/railties-6.0.2.2/lib/rails/command.rb:48:in `invoke'
/usr/local/bundle/gems/railties-6.0.2.2/lib/rails/commands.rb:18:in `<main>'
/usr/local/bundle/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
/usr/local/bundle/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/usr/local/bundle/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/dependencies.rb:325:in `block in require'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/dependencies.rb:291:in `load_dependency'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/dependencies.rb:325:in `require'
/app/bin/rails:12:in `<top (required)>'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `load'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `call'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/client.rb:30:in `run'
/usr/local/bundle/gems/spring-2.1.0/bin/spring:49:in `<top (required)>'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `load'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `<top (required)>'
/app/bin/spring:16:in `<top (required)>'
./bin/rails:5:in `load'
./bin/rails:5:in `<main>'

Caused by:
Mysql2::Error: Unknown database 'public'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `_query'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `block in query'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:130:in `handle_interrupt'
/usr/local/bundle/gems/mysql2-0.5.3/lib/mysql2/client.rb:130:in `query'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:202:in `block (2 levels) in execute'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/dependencies/interlock.rb:47:in `permit_concurrent_loads'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:201:in `block in execute'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract_adapter.rb:718:in `block (2 levels) in log'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract_adapter.rb:717:in `block in log'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/notifications/instrumenter.rb:24:in `instrument'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract_adapter.rb:708:in `log'
/usr/local/bundle/gems/scout_apm-2.6.7/lib/scout_apm/instruments/active_record.rb:279:in `log'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:200:in `execute'
/usr/local/bundle/gems/active_record_mysql_xverify-0.1.1/lib/active_record_mysql_xverify/error_handler.rb:6:in `execute'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/connection_adapters/mysql/database_statements.rb:41:in `execute'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/adapters/mysql2_adapter.rb:42:in `reset'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/adapters/mysql2_adapter.rb:36:in `initialize'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/adapters/mysql2_adapter.rb:9:in `new'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/adapters/mysql2_adapter.rb:9:in `mysql2_adapter'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/tenant.rb:56:in `adapter'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/tenant.rb:25:in `init_once'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/railtie.rb:39:in `block in connection'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/railtie.rb:38:in `tap'
/usr/local/bundle/gems/ros-apartment-2.6.1/lib/apartment/railtie.rb:38:in `connection'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/mysql_database_tasks.rb:8:in `connection'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/mysql_database_tasks.rb:16:in `create'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:126:in `create'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:185:in `block in create_current'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:479:in `block (2 levels) in each_current_configuration'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:476:in `each'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:476:in `block in each_current_configuration'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:475:in `each'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:475:in `each_current_configuration'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/tasks/database_tasks.rb:184:in `create_current'
/usr/local/bundle/gems/activerecord-6.0.2.2/lib/active_record/railties/databases.rake:39:in `block (2 levels) in <main>'
/usr/local/bundle/gems/railties-6.0.2.2/lib/rails/commands/rake/rake_command.rb:23:in `block in perform'
/usr/local/bundle/gems/railties-6.0.2.2/lib/rails/commands/rake/rake_command.rb:20:in `perform'
/usr/local/bundle/gems/railties-6.0.2.2/lib/rails/command.rb:48:in `invoke'
/usr/local/bundle/gems/railties-6.0.2.2/lib/rails/commands.rb:18:in `<main>'
/usr/local/bundle/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
/usr/local/bundle/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/usr/local/bundle/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
/usr/local/bundle/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/dependencies.rb:325:in `block in require'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/dependencies.rb:291:in `load_dependency'
/usr/local/bundle/gems/activesupport-6.0.2.2/lib/active_support/dependencies.rb:325:in `require'
/app/bin/rails:12:in `<top (required)>'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `load'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `call'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/client.rb:30:in `run'
/usr/local/bundle/gems/spring-2.1.0/bin/spring:49:in `<top (required)>'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `load'
/usr/local/bundle/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `<top (required)>'
/app/bin/spring:16:in `<top (required)>'
./bin/rails:5:in `load'
./bin/rails:5:in `<main>'
Tasks: TOP => db:create
(See full trace by running task with --trace)

System configuration

  • Database: (Tell us what database and its version you use.) MySQL 5.6.21

  • Apartment version: 2.6.1

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: true
  • Rails (or ActiveRecord) version: 6.0.2.2

  • Ruby version: 2.6.6

Check if tenant exists?

Steps to reproduce

How can I do something like:
Apartment::Tenant.exists('tenant_name')?

I wanna check if a tenant exists before creating it.

Expected behavior

Actual behavior

System configuration

  • Database: (Tell us what database and its version you use.)

  • Apartment version:

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: (true or false)
  • Rails (or ActiveRecord) version:

  • Ruby version:

not exclued model / tbl

I have a problem and I can't find anything in a place or on an

even adding models to exclude the new schemas are being created with the tables

config.excluded_models = ["Recruiter", "Account", "Access", "Admin", "Business", "Identity", "Lead", "Plan", "Transaction", "Subscription"]
config.tenant_names = -> {Account.pluck: tenant}

I have no idea why tables are being created, do you have any tips on where to look for an answer, or how to debug?

Rubocop cleanup

I believe that for the future of this project we should ensure code consistency. I propose we run perx-rubocop (which i also maintain)

Deprecate History.md

Versions change log is already maintained in the releases. Having an History.md is added work for something that can be achieved by looking at the github releases

Tenant creation in transaction makes some test cases fail

Steps to reproduce

Run the test suite with use_transactional_fixtures set to true. Creating a new tenant in a test will raise an exception because a transaction is already in place

Expected behavior

Should pass the tests and not raise exception

  • Database: (Tell us what database and its version you use.)

postgresql 12.4

  • Apartment version:

development branch

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: true
  • Rails (or ActiveRecord) version:

6.0.3.4

  • Ruby version:

2.7.1

configure story branch

Steps to reproduce

Expected behavior

Actual behavior

System configuration

  • Database: (Tell us what database and its version you use.)

  • Apartment version:

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: (true or false)
  • Rails (or ActiveRecord) version:

  • Ruby version:

Rails 6.1 compatibility

Steps to reproduce

  • Use gem 'rails', '~> 6.1.rc1' and gem 'ros-apartment', require: 'apartment' in the same application

Expected behavior

It lets you install the bundle.

Actual behavior

It fails due to the current strict requirement of a Rails version < 6.1

Proposed Change

Specs run fine when relaxing the Rails version requirement to < 6.2 in the gemspec. Would love to open a PR with this change if that is ok with current release standards. Cheers!

rails 6.1.alpha support

Steps to reproduce

I'm trying to use apartment with rails 6.1.alpha (rails-master) tests are failing and I don't understand the issue yet.

The error is:

ActiveRecord::StatementInvalid:
PG::UndefinedTable: ERROR:  relation "schema_migrations" does not exist
LINE 1: SELECT "schema_migrations"."version" FROM "schema_migrations...

When I try to debug the application the only difference I see between rails-6.0 and rails-master
is during migrations

With Rails-6.0 it executes:

CREATE TABLE "users" ("id" bigserial primary key, "name" character varying, "birthdate" timestamp, "sex" character varying)
CREATE TABLE "schema_migrations" ("version" character varying NOT NULL PRIMARY KEY)
SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC

with Rails-6.1.alpha (master) it executes:

CREATE TABLE "users" ("id" bigserial primary key, "name" character varying, "birthdate" timestamp, "sex" character varying)
SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC

That would justify the error, but I don't understand why it doesn't create the schema_migrations database

  • use_schemas: true

db/schema.rb not update on migration

Steps to reproduce

If you don't use the public schema a a tentant, which for our sould be a best practice. Then after running migrations, rails will dump the schema, and is left practically empty. We believe that rails is using the default public schema for the dump, and that is the reason. But were unable to correctly pinpoint how to fix it. If anyone can prove gidance we are willing to contribute a PR for this.

Expected behavior

When not using public schema, aparment should use other tenant for generating the schema. Anyways all tenants should have the same schema so using any other should give no problem.

Actual behavior

db/schema.rb is leaved almost empty.

System configuration

  • Database: postgres

  • Apartment version: 2.7.2

  • Apartment config (in config/initializers/apartment.rb or so):

require 'apartment/elevators/generic'

Apartment.configure do |config|
  config.tenant_names = ['alpha_generales']
  config.persistent_schemas = ['public']
end

Rails.application.config.middleware.use Apartment::Elevators::Custom

  • use_schemas: (true or false)

  • Rails (or ActiveRecord) version: 6.0.3.4

  • Ruby version: ruby-2.7.0

ActiveRecord::StatementInvalid: PG::UndefinedObject: ERROR: operator class "gin_trgm_ops" does not exist for access method "gin"

Steps to reproduce

Rails 6 
ruby '~> 2.7.1'

Rails console

Apartment::Tenant.create("demo")
--OUTPUT--
[8] pry(main)> Apartment::Tenant.create("demo")
   (6.4ms)  CREATE SCHEMA "demo"
  SQL (0.3ms)  CREATE EXTENSION IF NOT EXISTS "pg_trgm"
  SQL (0.3ms)  CREATE EXTENSION IF NOT EXISTS "plpgsql"
   (0.2ms)  DROP TABLE IF EXISTS "active_storage_attachments" CASCADE
   (4.3ms)  CREATE TABLE "active_storage_attachments" ("id" bigserial primary key, "name" character varying NOT NULL, "record_type" character varying NOT NULL, "record_id" bigint NOT NULL, "blob_id" bigint NOT NULL, "created_at" timestamp NOT NULL)
   (1.4ms)  CREATE  INDEX  "index_active_storage_attachments_on_blob_id" ON "active_storage_attachments"  ("blob_id")
   (1.7ms)  CREATE UNIQUE INDEX  "index_active_storage_attachments_uniqueness" ON "active_storage_attachments"  ("record_type", "record_id", "name", "blob_id")
   (0.2ms)  DROP TABLE IF EXISTS "active_storage_blobs" CASCADE
   (3.1ms)  CREATE TABLE "active_storage_blobs" ("id" bigserial primary key, "key" character varying NOT NULL, "filename" character varying NOT NULL, "content_type" character varying, "metadata" text, "byte_size" bigint NOT NULL, "checksum" character varying NOT NULL, "created_at" timestamp NOT NULL)
   (1.1ms)  CREATE UNIQUE INDEX  "index_active_storage_blobs_on_key" ON "active_storage_blobs"  ("key")
   (0.2ms)  DROP TABLE IF EXISTS "admin_users" CASCADE
   (3.4ms)  CREATE TABLE "admin_users" ("id" serial NOT NULL PRIMARY KEY, "email" character varying DEFAULT '' NOT NULL, "encrypted_password" character varying DEFAULT '' NOT NULL, "reset_password_token" character varying, "reset_password_sent_at" timestamp, "remember_created_at" timestamp, "sign_in_count" integer DEFAULT 0 NOT NULL, "current_sign_in_at" timestamp, "last_sign_in_at" timestamp, "current_sign_in_ip" inet, "last_sign_in_ip" inet, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL)
   (1.1ms)  CREATE UNIQUE INDEX  "index_admin_users_on_email" ON "admin_users"  ("email")
   (1.0ms)  CREATE UNIQUE INDEX  "index_admin_users_on_reset_password_token" ON "admin_users"  ("reset_password_token")
   (0.2ms)  DROP TABLE IF EXISTS "delayed_jobs" CASCADE
   (3.1ms)  CREATE TABLE "delayed_jobs" ("id" serial NOT NULL PRIMARY KEY, "priority" integer DEFAULT 0 NOT NULL, "attempts" integer DEFAULT 0 NOT NULL, "handler" text NOT NULL, "last_error" text, "run_at" timestamp, "locked_at" timestamp, "failed_at" timestamp, "locked_by" character varying, "queue" character varying, "created_at" timestamp, "updated_at" timestamp)
   (1.4ms)  CREATE  INDEX  "delayed_jobs_priority" ON "delayed_jobs"  ("priority", "run_at")
   (0.2ms)  DROP TABLE IF EXISTS "exception_hunter_error_groups" CASCADE
   (3.2ms)  CREATE TABLE "exception_hunter_error_groups" ("id" bigserial primary key, "error_class_name" character varying NOT NULL, "message" character varying, "status" integer DEFAULT 0, "tags" text[] DEFAULT '{}', "created_at" timestamp(6) NOT NULL, "updated_at" timestamp(6) NOT NULL)
   (0.7ms)  CREATE  INDEX  "index_exception_hunter_error_groups_on_message" ON "exception_hunter_error_groups" USING gin ("message" gin_trgm_ops)
ActiveRecord::StatementInvalid: PG::UndefinedObject: ERROR:  operator class "gin_trgm_ops" does not exist for access method "gin"

from /Users/xxxxx/.rvm/gems/ruby-2.7.1/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/postgresql/database_statements.rb:92:in `async_exec'
Caused by PG::UndefinedObject: ERROR:  operator class "gin_trgm_ops" does not exist for access method "gin"

from /Users/xxxxx/.rvm/gems/ruby-2.7.1/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/postgresql/database_statements.rb:92:in `async_exec'

Expected behavior

Create Tenant named "demo"

Actual behavior


ActiveRecord::StatementInvalid: PG::UndefinedObject: ERROR:  operator class "gin_trgm_ops" does not exist for access method "gin"

from /Users/xxxxx/.rvm/gems/ruby-2.7.1/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/postgresql/database_statements.rb:92:in `async_exec'
Caused by PG::UndefinedObject: ERROR:  operator class "gin_trgm_ops" does not exist for access method "gin"

from /Users/xxxxx/.rvm/gems/ruby-2.7.1/gems/activerecord-6.0.3.2/lib/active_record/connection_adapters/postgresql/database_statements.rb:92:in `async_exec'

System configuration

  • Database: (Tell us what database and its version you use.)
    Postgres 12
    OSX

  • Apartment version:
    gem 'ros-apartment', require: 'apartment'

  • Apartment config (in config/initializers/apartment.rb or so):

require 'apartment/elevators/subdomain'
# require 'apartment/elevators/first_subdomain'
# require 'apartment/elevators/host'

#
# Apartment Configuration
#
Apartment.configure do |config|

  # Add any models that you do not want to be multi-tenanted, but remain in the global (public) namespace.
  # A typical example would be a Customer or Tenant model that stores each Tenant's information.
  #
  config.excluded_models = %w{ Landlord }

 
  config.tenant_names = lambda { User.pluck :subdomain }




end


Rails.application.config.middleware.use Apartment::Elevators::Subdomain
# Rails.application.config.middleware.use Apartment::Elevators::FirstSubdomain
# Rails.application.config.middleware.use Apartment::Elevators::Host

  • use_schemas: (true or false)
use_schemas: false
  • Rails (or ActiveRecord) version:
6.0
  • Ruby version:
2.7.1

[Postgresql users] Help testing development branch in your environment

Hi have made some significant changes on the development branch in order to get the queries to be executed in the right tenant even when using read/write replica in rails 6.

Our product codebase has all its tests passing with this new version and I'm currently running it on our staging environment.

If you're using ros-apartment, or intend to, could you possibly test the development branch against your test suite?

This only applies to projects running postgresql with schemas. Although if you're not using schemas and would like to test the development branch as well, that would be reassuring that I have not broken your projects.

Thank you

When manually switching the connection it resets the search path

Steps to reproduce

Apartment::Tenant.switch!('existing_tenant')
puts Apartment::Tenant.current # prints 'existing_tenant'
ActiveRecord::Base.connected_to(role: :writing) do
  puts Apartment::Tenant.current # prints 'existing_tenant'
  puts Apartment.connection.schema_search_path # does not print 'existing_tenant'
end

Expected behavior

Apartment::Tenant.switch!('existing_tenant')
puts Apartment::Tenant.current # prints 'existing_tenant'
ActiveRecord::Base.connected_to(role: :writing) do
  puts Apartment::Tenant.current # prints 'existing_tenant'
  puts Apartment.connection.schema_search_path # prints 'existing_tenant'
end

Actual behavior

System configuration

  • Database: Postgresql 12.1

  • Apartment version: 2.3.0

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: true
  • Rails (or ActiveRecord) version: 6.0.2.1

  • Ruby version: 2.6.5

Improve changelog automatic generation

Currently, changelog is being committed to development as it is the main development branch. This causes some issues because it won't be updated on releases properly.
If we publish directly to master, without updating development first, there will be no changelog tracked in the development branch which is shown by default when people come into the repository.

Update the changelog action to push the changelog to the proper branch(es)

db:rollback uses second latest migration for tenants

Steps to reproduce

rails db:rollback

Expected behavior

Rolling back latest migration

Actual behavior

Tenants try to roll back with n - 1.

== 20200528194507 Latest: reverting =============================
== 20200528194507 Latest: reverted (0.0526s) ====================

Rolling back testtenant1 tenant
== 20200526235656  SecondLatest: reverting ======================
-- failing_statement
rails aborted!

System configuration

  • Database: (Tell us what database and its version you use.) PostgreSQL 10.0

  • Apartment version: 2.6.0

  • Apartment config (in config/initializers/apartment.rb or so):

    • tenant_names: -> { 'a', 'b', 'dev' }
    • use_schemas: true
    • default_schema: dev
    • persistent_schemas: %w[shared_extensions]
  • Rails (or ActiveRecord) version: 6.0.3

  • Ruby version: 2.7.0

Custom Console deprecation warning

warning: setting prompt with help of `Pry.config.prompt = [proc {}, proc {}]` is deprecated. Use Pry::Prompt API instead
  • Rails (or ActiveRecord) version: 6.0.2.2

  • Ruby version: 2.7.1

Improve the Issue template to request for more useful information

Currently the issue template seems to be insufficient for having a reproducible bug. The last few opened bugs were not reproducible and I ended up by asking for people who were opening the tickets to recreate a sample app where the bug is evident.

Is this where we should be headed with the tickets? Can we somehow collect enough info to reproduce the bug and help fixing?

Delayed Initialization is not thread-safe

Steps to reproduce

I haven't been able to reproduce at will. Has happened in production though. Under some circumstances (high load during boot), excluded models are not correctly processed, and queries are made to the wrong schema. I believe #39 introduced the problem.

My current theory is that multiple threads are trying to init apartment at the same time, one reads @already_initialized as true, but process_excluded_models has not been called yet.

Expected behavior

Excluded models get queried against the public shcema:

SELECT "tentants".* from "public"."tenants"

Actual behavior

Excluded models get queried without schema.

SELECT "tentants".* from "tenants"

System configuration

This problems occurs on production, where puma is configured with multiple threads (5 max). This problem appears only intermittently, but always under high load.

  • Database: Postgres 11

  • Apartment version: 2.7.1

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: true
  • Rails (or ActiveRecord) version: 6.0

  • Ruby version: 2.6

Possible solutions

  1. Revert #39 and friends. This would reintroduce the problem that it is impossible to use any ruby code from the app without a working database connection (ie, during CI).
  2. Somehow protect the initialization with a lock.
  3. Add yet another hook that inits apartment before threads are started.
  4. Deprecate excluded_models, in favor of a new method, acts_as_apartment or something like that, that does the model exclusion (I have not tried if this works).
  5. Some other option?
  6. Variation of 1, instead of revert, reintroduce the old behavior, behind an option (activated by default). This way, advanced users can disable it (and we can document this pitfall).

Locally, we have worked around the issue with 3:

on_worker_boot do
  ActiveRecord::Base.connection_pool.with_connection do
    Apartment::Tenant.init_once
  end
end

Thoughts?

Use a transaction for Apartment::Tenant.create

Steps to reproduce

  1. Have tenant creating fail in the middle (by missing functions for example)

Expected behavior

  1. Nothing gets created in the schema

Actual behavior

  1. You get leftover objects

As with PostgreSQL DDL is transaction safe, the schema creation should be wrapped with a transaction.

System configuration

  • Database: PostgreSQL 10

  • Apartment version: 2.6.1

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: true
    • persistent_schemas: %w[shared_extensions]
  • Rails (or ActiveRecord) version: 6.0.2.1

  • Ruby version: 2.7.0

Undefined method devise with 2.6.1

Steps to reproduce

  • Setup a project with devise and ros-appartment 2.6.1
  • Add devise to a user model
  • Add the model to the excluded_models

Expected behaviour

No error

Actual behaviour

/Users/gaetan/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activerecord-6.0.3.1/lib/active_record/dynamic_matchers.rb:22:in `method_missing': undefined method `devise' for #<Class:0x00007ffe6480f240> (NoMethodError)

System configuration

  • Database: (Tell us what database and its version you use.) Postgres 12

  • Apartment version: 2.6.1

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: true
  • Rails (or ActiveRecord) version: Rails 6.0.3.1

  • Ruby version: ruby 2.6.5p114

Downgrading to 2.6.0 fixes the issue.

Rake tasks define methods on main

Steps to reproduce

Open rails console (with pry):

> Rails.application.load_tasks
> show-source each_tenant

From: /home/felipe/src/satelabs/buk/gems/ruby/2.5.0/bundler/gems/apartment-9665917fde57/lib/tasks/apartment.rake @ line 122:
Owner: Object
Visibility: private
Number of lines: 5

def each_tenant(&block)
  Parallel.each(tenants, in_threads: Apartment.parallel_migration_threads) do |tenant|
    block.call(tenant)
  end
end

Expected behavior

The global main namespace should not be polluted.

Actual behavior

The global main namespace is polluted.

System configuration

  • Apartment version: git

Solution

I think those methods should be extracted to a module, so that they don't pollute the global namespace.

Add tenant info to console boot?

Loving the new custom console!

What would you think about adding a short message on console boot so that folks that aren't familiar with the methods would be introduced to them?

I'm using the below as a quick way to show new devs to an apartment project the console commands without them needing to dig around or ask about them. And thought it might be something that could be incorporated in the gem.

# .pryrc
class WelcomeClass
  def self.tenant_info
    ActiveRecord::Base.logger.level = 1
    puts "Available Tenants: #{tenant_list}\n"
    print "Use `st 'tenant'` to switch tenants & `tenant_list` to see list\n"
    ActiveRecord::Base.logger.level = 1
  end
end

# run the code at `bin/rails console`
Pry.config.exec_string = WelcomeClass.tenant_info
Loading development environment (Rails 6.0.2.2)
Available Tenants: ["public", "ffb", "other"]
Use `st 'tenant'` to switch tenants & `tenant_list` to see list
[1] [development][public] pry(main)>

If you're open to the idea I'd be happy to put together a PR.

Thanks for your great work in picking up the maintenance on this gem!

Error Dropping Tenant

Steps to reproduce

  1. Postgres Extension Migration:
    execute 'CREATE EXTENSION pgcrypto WITH SCHEMA pg_catalog;'
    enable_extension "uuid-ossp"
  1. Account Model(.rb):
Account(id: uuid, name: string, account_number: string, subdomain: string, created_at: datetime, updated_at: datetime)
class Account < ApplicationRecord
  after_create :create_tenant
  private
    def create_tenant
      Apartment::Tenant.create(subdomain)
    end
end
  1. Create database using ENV configuration (not database.yml (ex: .env.development)
DATABASE_URL=postgres://##:password@localhost:5432/###
  1. Create db/demo.rb file with content:
schemas = Account.all.pluck(:subdomain)
schemas.each do |schema|
  Apartment::Tenant.drop(schema)
end
Account.delete_all
load "db/seeds.rb"
  1. < Create models to go below account (ex: Users) >
  2. Create database: rails db:drop db:create db:migrate
  3. Seed Data: rails r db/demo.rb
  4. Reset Database: rails db:drop db:create db:migrate

Expected behavior

I expect the error segment to not appear -- not sure if it's necessarily a bad thing as things appear to be correct in the end and I can seed the database after this with the same Account information and the schemas are created.

Appears to be an issue handling UUID's on the tenants?

Actual behavior

Error (after step 7):

Dropping ["be6c9a5c-66e7-4bc7-8db1-e98b34d7b40b", "First", "73a67d3cf158e61a0b1a", "first", Wed, 06 May 2020 18:20:55 UTC +00:00, Wed, 06 May 2020 18:20:55 UTC +00:00] tenant
Error while dropping tenant ["be6c9a5c-66e7-4bc7-8db1-e98b34d7b40b", "First", "73a67d3cf158e61a0b1a", "first", Wed, 06 May 2020 18:20:55 UTC +00:00, Wed, 06 May 2020 18:20:55 UTC +00:00]: PG::SyntaxError: ERROR:  syntax error at or near "be6c9a5c"
LINE 1: DROP SCHEMA "["be6c9a5c-66e7-4bc7-8db1-e98b34d7b40b", "First...
                       ^
Dropping ["073f33e6-8a21-4e71-9f3c-479ce71f224d", "Second", "019772af66a637c83ba1", "sec", Wed, 06 May 2020 18:20:59 UTC +00:00, Wed, 06 May 2020 18:20:59 UTC +00:00] tenant
Error while dropping tenant ["073f33e6-8a21-4e71-9f3c-479ce71f224d", "Second", "019772af66a637c83ba1", "sec", Wed, 06 May 2020 18:20:59 UTC +00:00, Wed, 06 May 2020 18:20:59 UTC +00:00]: PG::SyntaxError: ERROR:  syntax error at or near "073"
LINE 1: DROP SCHEMA "["073f33e6-8a21-4e71-9f3c-479ce71f224d", "Second...

                       ^
Dropped database 'dev'
Created database 'dev'
... 
<rebuilds database>

System configuration

  • Database: (Tell us what database and its version you use.)
    pg-0.21.0

  • Apartment version:
    ros-apartment-2.5.0

  • Apartment config (in config/initializers/apartment.rb or so):

require 'apartment/elevators/subdomain'
Apartment.configure do |config|
  config.excluded_models = %w{ Account }
  config.tenant_names = lambda { Account.pluck(&:subdomain) }
  config.use_schemas = true 
  config.persistent_schemas = ['shared_extensions']
  config.pg_excluded_names = ["uuid_generate_v4"]
end

Rails.application.config.middleware.use Apartment::Elevators::Subdomain
Apartment::Elevators::Subdomain.excluded_subdomains = ['www', 'admin']
  • Rails (or ActiveRecord) version:
    6.0.1

  • Ruby version:
    2.6.3

error running db:seed task

Steps to reproduce

rails db:drop db:create db:migrate db:seed

Expected behavior

(truncated)
[WARNING] - The list of tenants to migrate appears to be empty. This could mean a few things:

      1. You may not have created any, in which case you can ignore this message
      2. You've run `apartment:migrate` directly without loading the Rails environment
        * `apartment:migrate` is now deprecated. Tenants will automatically be migrated with `db:migrate`

    Note that your tenants currently haven't been migrated. You'll need to run `db:migrate` to rectify this.

Seeding myapp_development_mygroup tenant

Actual behavior

(truncated)
[WARNING] - The list of tenants to migrate appears to be empty. This could mean a few things:

      1. You may not have created any, in which case you can ignore this message
      2. You've run `apartment:migrate` directly without loading the Rails environment
        * `apartment:migrate` is now deprecated. Tenants will automatically be migrated with `db:migrate`

    Note that your tenants currently haven't been migrated. You'll need to run `db:migrate` to rectify this.

Creating myapp_development_mygroup tenant
Error while creating tenant myapp_development_mygroup: Mysql2::Error: Can't create database 'myapp_development_mygroup'; database exists
Seeding myapp_development_mygroup tenant

(Seems like the application tries to create a database before running the seed command.)

System configuration

  • Database: Mysql 8.0.20

  • Apartment version: 2.7.1

  • Apartment config (in config/initializers/apartment.rb or so): #config.use_schemas = true (line is commented)

  • Rails (or ActiveRecord) version: 6.0.3.2

  • Ruby version: 2.7.1

I'm getting an error while running db:seed. It looks like the application tries to create a database before running the seed command. Everything runs ok at version 2.7.0

Due to this commit:
b6af077
apartment.zip

Update travis config to only run PR's instead of running all commits

Every time we commit something in any branch, we spawn 36 jobs in travis. This is potentially very time consuming (and annoying because there are always jobs running when you want to know the actual status of things)

I think we should update the travis config to run only branches:

  • master
  • development
  • open PRs

After switch callback not working with nil argument

Hi @rpbaltazar, thank you for maintaining and improving apartment. I found an issue with the after switch callback when passing nil to switch method.

Steps to reproduce

Apartment::Adapters::AbstractAdapter.set_callback :switch, :before do
  puts("Before tenant switch from: #{current}")
end

Apartment::Adapters::AbstractAdapter.set_callback :switch, :after do
  puts("After tenant switch to: #{current}")
end

Apartment::Tenant.switch!(Apartment.default_tenant)
# Before tenant switch from: public
# After tenant switch to: public
# => nil

Apartment::Tenant.switch!
# Before tenant switch from: public
# => "public"

Proposed fixes

module Apartment
  module Adapters
    class AbstractAdapter
      # Original method
      def switch!(tenant = nil)
        run_callbacks :switch do
          return reset if tenant.nil?

          connect_to_new(tenant).tap do
            Apartment.connection.clear_query_cache
          end
        end
      end

      # Proposal 1
      def switch!(tenant = nil)
        run_callbacks :switch do
          if tenant.nil?
            reset
          else
            connect_to_new(tenant).tap do
              Apartment.connection.clear_query_cache
            end
          end
        end
      end

      # Proposal 2
      def switch!(tenant = nil)
        run_callbacks :switch do
          connect_to_new(tenant || default_tenant).tap do
            Apartment.connection.clear_query_cache
          end
        end
      end
    end
  end
end

If you want, I can make a PR with the solution you prefer.

How to exclude all models from engine?

I tried adding this to the apartment initializer:

engine_models = myEngine.constants.map {|c| myEngine.const_get(c) }.select do |const|
  const.class == Class && const < ActiveRecord::Base
end

config.excluded_models = %w[User] + engine_models

This works perfectly when run in the console, however engine_models is empty when its being executed as part of initialisation. Does anyone have a good idea?

This is in Rails 6.0.2.1

[ActiveJob::DeserializationError] solution: require "apartment"

I'm relaying this message on behalf of a colleague who upgraded us to Rails 6.

We run a rails app with Apartment and upgraded to Rails 6.
We use sidekiq.

We were running into a ActiveJob::DeserializationError due to name conflicts.

To get around it:

Gemfile:

gem 'ros-apartment', require: 'apartment'
gem 'ros-apartment-sidekiq', require: 'apartment/sidekiq'

Apparently this error came up on the sidekiq side.

It would be nice if the Readme was updated for others moving forward.

Thanks for your work!

rake db:setup tries to seed non existing tenant

Steps to reproduce

  1. Setup config.tenant_names to a static array
  2. bundle exec rake db:setup

Expected behavior

  1. create the tenants
  2. seed

Actual behavior

  1. Seeding gives a warning and does nothing
Seeding testtenant1 tenant
One of the following schema(s) is invalid: "testtenant1" "public", "shared_extensions"

System configuration

  • Database: (Tell us what database and its version you use.)

PostgresSQL

  • Apartment version:

2.6.0

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: true
    • persistent_schemas: %w[shared_extensions]
  • Rails (or ActiveRecord) version:

6.0.2.1

  • Ruby version:

2.7.0

Cached statement breaks in find

Steps to reproduce

User::Account.find(10)

Expected behavior

Cached statement to not break

Actual behavior

TypeError:
        no implicit conversion of String into Array
      # /usr/local/bundle/bundler/gems/apartment-ff12543ee1b7/lib/apartment/model.rb:15:in `cached_find_by_statement'

System configuration

  • Database: Postgresql

  • Apartment version: development branch

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: true
  • Rails (or ActiveRecord) version: 6.0.2.1

  • Ruby version: 2.6.x

Should I move from acts_as_tenant to Apartment?

Hi folks, I've been using acts_as_tenant for a while, it uses column scoping for tenancy. I have around 350 customers and as the tables are getting bigger, I'm starting to face performance issues.

When I first saw this gem and saw the possibility to apply tenancy by different schemas I became very interested.

Digging further into the issues, I discovered this fork and this post (https://influitive.io/our-multi-tenancy-journey-with-postgres-schemas-and-apartment-6ecda151a21f) and now I'm worried if it's gonna fulfill my needs.

I see that it has the possibility to apply tenancy by schemas and databases, what are you guys using? what is the best option?

I have around 100 tables and 350 customers/accounts, what's your use case? is it working well?

Thanks.

Error creating tenant with uuid column

Steps to reproduce

Run
Apartment::Tenant.create("nfl")
in console

[nfl] SQL (0.3ms) CREATE EXTENSION IF NOT EXISTS "pgcrypto"  []
...
[nfl]  (6.3ms) CREATE TABLE "squirrels" ("id" uuid DEFAULT gen_random_uuid() NOT NULL PRIMARY KEY, ...

Traceback (most recent call last):
        1: from (irb):2
ActiveRecord::StatementInvalid (PG::UndefinedFunction: ERROR:  function gen_random_uuid() does not exist)
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.

Expected behavior

No errors

Actual behavior

ActiveRecord::StatementInvalid (PG::UndefinedFunction: ERROR: function gen_random_uuid() does not exist)
HINT: No function matches the given name and argument types. You might need to add explicit type casts.

System configuration

  • Database: (Tell us what database and its version you use.)

Postgresql server 11.6

  • Apartment version:

    ros-apartment (2.7.1)

  • Apartment config (in config/initializers/apartment.rb or so):

    config.pg_excluded_names = %w[uuid_generate_v4 gen_random_uuid]
    config.tenant_names = -> { DomainSettings.pluck(:name) }

    • use_schemas: (true or false)
      default = true
  • Rails (or ActiveRecord) version:

Rails 6.0.2.1

  • Ruby version:

ruby 2.5.5p157 (2019-03-15 revision 67260) [x86_64-darwin18]

Investigate possibility of not modifying instance variables for resetting sequence names

reset_sequence_names method implementation for postgresql adapter removes the variable sequence_name if it exists. this has the following note:

# sequence_name contains the schema, so it must be reset after switch
# There is `reset_sequence_name`, but that method actually goes to the database
# to find out the new name. Therefore, we do this hack to only unset the name,
# and it will be dynamically found the next time it is needed

but as the name indicates it, it feels hackish and seems to have caused some random issues as described in #81

This logic should be revisited and improved if possible

Detach from original repo?

Hey!

The gem is great, it would be nice to detach the gem from the upstream influitive/apartment repo, so the search works on this repo. This can be done by contacting Github support. What do you think?

Support GUID locator/generator

The main idea behind this is that we have a guid generator that builds an id with tenant information as well as record type and id.
Once we have such generator, we could also have the locator that knows how to switch to the correct tenant and finds the record.

This might be particularly useful if apartment consumers want to have a unique id reference to a model

Steps to reproduce

Expected behavior

Actual behavior

System configuration

  • Database: (Tell us what database and its version you use.)

  • Apartment version:

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: (true or false)
  • Rails (or ActiveRecord) version:

  • Ruby version:

NameError: instance variable @sequence_name not defined

After upgrading to ros-apartment from apartment, I get the above error at times:

[GEM_ROOT]/gems/ros-apartment-2.6.1/lib/apartment/adapters/postgresql_adapter.rb:124 :in `remove_instance_variable`
 [GEM_ROOT]/gems/ros-apartment-2.6.1/lib/apartment/adapters/postgresql_adapter.rb:124 :in `block in reset_sequence_names`
 [GEM_ROOT]/gems/ros-apartment-2.6.1/lib/apartment/adapters/postgresql_adapter.rb:123 :in `each`
 [GEM_ROOT]/gems/ros-apartment-2.6.1/lib/apartment/adapters/postgresql_adapter.rb:123 :in `reset_sequence_names`
 [GEM_ROOT]/gems/ros-apartment-2.6.1/lib/apartment/adapters/postgresql_adapter.rb:40 :in `reset`
 [GEM_ROOT]/gems/ros-apartment-2.6.1/lib/apartment/adapters/postgresql_adapter.rb:30 :in `initialize`
 [GEM_ROOT]/gems/ros-apartment-2.6.1/lib/apartment/adapters/postgresql_adapter.rb:11 :in `new`
 [GEM_ROOT]/gems/ros-apartment-2.6.1/lib/apartment/adapters/postgresql_adapter.rb:11 :in `postgresql_adapter`
 [GEM_ROOT]/gems/ros-apartment-2.6.1/lib/apartment/tenant.rb:56 :in `adapter`
/home/app_au/.rbenv/versions/2.6.5/lib/ruby/2.6.0/forwardable.rb:224 :in `switch!`
 [PROJECT_ROOT]/lib/country_manager.rb:188 :in `country=`
 [PROJECT_ROOT]/config/initializers/sidekiq.rb:55 :in `set_country_from_job`
 [PROJECT_ROOT]/config/initializers/sidekiq.rb:27 :in `call`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/middleware/chain.rb:140 :in `block in invoke`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/middleware/chain.rb:140 :in `block in invoke`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/middleware/chain.rb:143 :in `invoke`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/processor.rb:163 :in `block in process`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/processor.rb:136 :in `block (6 levels) in dispatch`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/job_retry.rb:111 :in `local`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/processor.rb:135 :in `block (5 levels) in dispatch`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/rails.rb:43 :in `block in call`
 [GEM_ROOT]/gems/activesupport-5.2.4.2/lib/active_support/execution_wrapper.rb:87 :in `wrap`
 [GEM_ROOT]/gems/activesupport-5.2.4.2/lib/active_support/reloader.rb:73 :in `block in wrap`
 [GEM_ROOT]/gems/activesupport-5.2.4.2/lib/active_support/execution_wrapper.rb:87 :in `wrap`
 [GEM_ROOT]/gems/activesupport-5.2.4.2/lib/active_support/reloader.rb:72 :in `wrap`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/rails.rb:42 :in `call`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/processor.rb:131 :in `block (4 levels) in dispatch`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/processor.rb:257 :in `stats`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/processor.rb:126 :in `block (3 levels) in dispatch`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/job_logger.rb:13 :in `call`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/processor.rb:125 :in `block (2 levels) in dispatch`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/job_retry.rb:78 :in `global`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/processor.rb:124 :in `block in dispatch`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/logger.rb:10 :in `with`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/job_logger.rb:33 :in `prepare`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/processor.rb:123 :in `dispatch`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/processor.rb:162 :in `process`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/processor.rb:78 :in `process_one`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/processor.rb:68 :in `run`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/util.rb:15 :in `watchdog`
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/util.rb:24 :in `block in safe_thread`More (36 lines)
Nested Exceptions
NameError: instance variable @sequence_name not defined
 [GEM_ROOT]/gems/ros-apartment-2.6.1/lib/apartment/adapters/postgresql_adapter.rb:124 :in `remove_instance_variable`
 Sidekiq::JobRetry::Handled: Sidekiq::JobRetry::Handled
 [GEM_ROOT]/gems/sidekiq-6.0.7/lib/sidekiq/job_retry.rb:99 :in `rescue in global`
 NameError: instance variable @sequence_name not defined
 [GEM_ROOT]/gems/ros-apartment-2.6.1/lib/apartment/adapters/postgresql_adapter.rb:124 :in `remove_instance_variable`

Rails 5.2 and latest ros-apartment from rubygems.

Any idea what it could be?

Could not find generator 'apartment:install'.

Steps to reproduce

Just create a new Rails 6.0.3 application with postgres database and add the ros-apartment, require: 'apartment' and run bundle install

Expected behavior

That give me the rails g apartment:install task

Actual behavior

Don't have the rails g apartment:install task but has the others like apartment:create /migrate, etc

System configuration

Ruby 2.7.1
Rails 6.0.3
Postgres

Add console info about tenants and fast switches

This is an enhancement proposal based on our use case. It is open for discussion and if we don't think its valuable for the users (or it bloats the project with unnecessary things) it can be released separately.

When we run the console in a project with apartment, generally we need to switch across tenants or execute some tasks within a specific tenant. We found it useful to have that info printed in the prompt. E.g

$ be rails c
$pry[public]> User.count
0
$pry[public]> st tenant_one
$[tenant_one]> User.count
10
$[tenant_one]> st
public
tenant_one
tenant_two
$[tenant_one]> st public
$pry[public]>

These are console commands that we have as part of our codebase, but could probably be adjusted to work out of the box.

Support configuration for skip checking of schema existence before switching

Steps to reproduce

Using postgresql, when running Apartment::Tenant.switch, before switching it check for the schema existence. In some cases, when there is a fair amount of load in the database, this can have an impact since it is yet another query being done.

Expected behavior

I think we should support a configuration that allows the apartment users to skip the schema existence and let them handle the errors on their end however they want.

Actual behavior

Currently there is no way to support this unless we monkeypatch the postgresql adapter in order to achieve it.

System configuration

  • Database: (Tell us what database and its version you use.)

postgresql - 12.0

  • Apartment version: 2.3.0

  • Apartment config (in config/initializers/apartment.rb or so):

    • use_schemas: true
  • Rails (or ActiveRecord) version: 6.0.2.1

  • Ruby version: 2.6

Tenant is not switched in Active Job

Steps to reproduce

I need to run a job like the one below for each tenant.

class ReviewNotificationJob < ApplicationJob
  queue_as :default

  def perform(review)
    Rails.logger.debug "Thank you your review - #{review.comment}"
  end
end

Therefore, config/initializers/active_job.rb is as follows.

class ActiveJob::Base
  class << self
    def execute(job_data)
      Apartment::Tenant.switch(job_data['tenant']) do
        super
      end
    end
  end

  def serialize
    super.merge('tenant' => Apartment::Tenant.current)
  end
end

I created a sample app to report this issue.
If you execute the following with this app, it will be reproduced.

company = Company.create(name: 'Company 1', tenant_name: 'company1')
Apartment::Tenant.create(company.tenant_name)
Apartment::Tenant.switch!(company.tenant_name)
review = Review.create(comment: 'test')
ReviewNotificationJob.perform_later(review)

Expected behavior

A tenant is switched in Active Job.

Actual behavior

The following is output to the log/development.log, and it seems that a tenant is not switched.

[ActiveJob] Enqueued ReviewNotificationJob (Job ID: 721907ae-98b4-4380-bc5c-9751259b4fa7) to Resque(default) with arguments: #<GlobalID:0x000055a164a78d10 @uri=#<URI::GID gid://app/Review/1>>
     (0.9ms) SET NAMES utf8mb4,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483  []
  ↳ config/initializers/active_job.rb:4:in `execute'
     (0.5ms) use `app_development`  []
  ↳ config/initializers/active_job.rb:4:in `execute'
     (0.5ms) use `app_development`  []
  ↳ config/initializers/active_job.rb:4:in `execute'
     (0.3ms) use `company1`  []
  ↳ config/initializers/active_job.rb:4:in `execute'
     (0.3ms) use `app_development`  []
  ↳ config/initializers/active_job.rb:5:in `block in execute'
    Review Load (0.5ms) SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`id` = 1 LIMIT 1  []
  ↳ config/initializers/active_job.rb:5:in `block in execute'
     (0.3ms) use `app_development`  []
  ↳ config/initializers/active_job.rb:4:in `execute'

System configuration

  • Database: MySQL 5.6.21
  • Apartment version: 2.7.2
  • Apartment config (in config/initializers/apartment.rb or so):
    • use_schemas: true
  • Rails (or ActiveRecord) version: 6.0.3.2
  • Ruby version: 2.6.6

Apartment.configure throws NoMethodError

Hi, thank you for keeping this project alive!

I installed your fork the normal way and it throws a NoMethodError when Apartment.configure is being called in an initializer.

Stacktrace:

/Users/niklashanft/Development/Ruby/faqtory/config/initializers/apartment.rb:14:in `<main>': undefined method `configure' for Apartment:Module (NoMethodError)
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in `load'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:54:in `load'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:319:in `block in load'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:291:in `load_dependency'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:319:in `load'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.1/lib/rails/engine.rb:667:in `block in load_config_initializer'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/activesupport-6.0.2.1/lib/active_support/notifications.rb:182:in `instrument'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.1/lib/rails/engine.rb:666:in `load_config_initializer'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.1/lib/rails/engine.rb:624:in `block (2 levels) in <class:Engine>'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.1/lib/rails/engine.rb:623:in `each'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.1/lib/rails/engine.rb:623:in `block in <class:Engine>'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.1/lib/rails/initializable.rb:32:in `instance_exec'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.1/lib/rails/initializable.rb:32:in `run'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.1/lib/rails/initializable.rb:61:in `block in run_initializers'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:228:in `block in tsort_each'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:350:in `block (2 levels) in each_strongly_connected_component'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:422:in `block (2 levels) in each_strongly_connected_component_from'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:431:in `each_strongly_connected_component_from'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:421:in `block in each_strongly_connected_component_from'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.1/lib/rails/initializable.rb:50:in `each'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.1/lib/rails/initializable.rb:50:in `tsort_each_child'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:415:in `call'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:415:in `each_strongly_connected_component_from'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:349:in `block in each_strongly_connected_component'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:347:in `each'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:347:in `call'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:347:in `each_strongly_connected_component'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:226:in `tsort_each'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/2.6.0/tsort.rb:205:in `tsort_each'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.1/lib/rails/initializable.rb:60:in `run_initializers'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.1/lib/rails/application.rb:363:in `initialize!'
	from /Users/niklashanft/Development/Ruby/faqtory/config/environment.rb:5:in `<main>'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/zeitwerk-2.2.2/lib/zeitwerk/kernel.rb:23:in `require'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:325:in `block in require'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:291:in `load_dependency'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/activesupport-6.0.2.1/lib/active_support/dependencies.rb:325:in `require'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/spring-2.1.0/lib/spring/application.rb:106:in `preload'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/spring-2.1.0/lib/spring/application.rb:157:in `serve'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/spring-2.1.0/lib/spring/application.rb:145:in `block in run'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/spring-2.1.0/lib/spring/application.rb:139:in `loop'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/spring-2.1.0/lib/spring/application.rb:139:in `run'
	from /Users/niklashanft/.rvm/gems/ruby-2.6.5/gems/spring-2.1.0/lib/spring/application/boot.rb:19:in `<top (required)>'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
	from /Users/niklashanft/.rvm/rubies/ruby-2.6.5/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'

Ideas?

I'd like to add a Wiki...

Apologies, I couldn't see a contact email so I've posted an issue.

Is it possible that you could turn on the Wiki functionality for this gem?

I've spent a lot of time consolidating information creating a pattern that uses a series of callbacks on a Tenant model that synchronises subdomain/domain CRUD actions in the database with a Heroku instance via Heroku's Platform API.

I think this could be useful for a lot of people using this gem. For now, here's a snapshot of what I've done...

class Organisation < ApplicationRecord

  # version control
  has_paper_trail

  # validations
  validates :name, length: { in: 3..100 }, presence: true
  validates :subdomain, length: { in: 3..20 }, presence: true, uniqueness: true

  validates { subdomain_available_on_heroku } unless Rails.env.development?

  after_create { create_tenant }
  after_destroy { remove_tenant }
  after_save  { rename_tenant }

  private

    def subdomain_available_on_heroku
      # get list of domains from Heroku
      domain_list = heroku.domain.list(app_id)

      # check if domains include the subdomain and abort if so
      if domain_list.include?(self.subdomain)
        errors.add(:subdomain, 'something has gone wrong - the subdomain is available in the database but not available on Heroku')
        throw(:abort)
      end
    end

    def create_tenant
      Apartment::Tenant.create(self.subdomain)

      # create the subdomain on heroku
      unless Rails.env.development?
        heroku.domain.create(ENV['HEROKU_APP_ID'], "#{self.subdomain}.#{ENV['DOMAIN_NAME']}")
      end
    end

    def remove_tenant
      Apartment::Tenant.drop(self.subdomain)

      # cleanup the subdomain on heroku
      unless Rails.env.development?
        heroku.domain.delete(ENV['HEROKU_APP_ID'], "#{self.subdomain}.#{ENV['DOMAIN_NAME']}")
      end
    end

    def rename_tenant
      if self.paper_trail.previous_version.present? && self.paper_trail.previous_version.subdomain != self.subdomain
        ActiveRecord::Base.connection.execute("ALTER SCHEMA \"#{self.paper_trail.previous_version.subdomain}\" RENAME TO \"#{self.subdomain}\";")

        # cleanup the subdomain on heroku
        unless Rails.env.development?
          heroku.domain.delete(ENV['HEROKU_APP_ID'], "#{self.paper_trail.previous_version.subdomain}.#{ENV['DOMAIN_NAME']}")
          heroku.domain.create(ENV['HEROKU_APP_ID'], "#{self.subdomain}.#{ENV['DOMAIN_NAME']}")
        end
      end
    end
end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.