rails-on-services / apartment Goto Github PK
View Code? Open in Web Editor NEWDatabase multi-tenancy for Rack (and Rails) applications
Database multi-tenancy for Rack (and Rails) applications
Upgrade ros-apartment 2.7.2 (was 2.6.1)
CHANGELOG.m documents breaking changes
You also should not rename historical entries for the old term:
NoMethodError: undefined method `default_schema=' for Apartment:Module
Did you mean? default_tenant=
application that uses hstore extension, e.g
rails db:create
rails db:migrate
to not fail
after running db:create an error is thrown regarding the hstore extension
postgresql
2.7.0
Apartment config (in config/initializers/apartment.rb
or so):
use_schemas
: true
Rails (or ActiveRecord) version:
Rails 6
In rails production env: Apartment::Tenant.switch!('public')
it changes current tenant to public.
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'
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):
Rails (or ActiveRecord) version: 6.0.3.2
Ruby version: 2.5.5
./bin/rails db:create
db:create is succeeded
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)
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
How can I do something like:
Apartment::Tenant.exists('tenant_name')?
I wanna check if a tenant exists before creating it.
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:
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?
I believe that for the future of this project we should ensure code consistency. I propose we run perx-rubocop (which i also maintain)
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
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
Should pass the tests and not raise exception
postgresql 12.4
development branch
Apartment config (in config/initializers/apartment.rb
or so):
use_schemas
: true
Rails (or ActiveRecord) version:
6.0.3.4
2.7.1
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:
gem 'rails', '~> 6.1.rc1'
and gem 'ros-apartment', require: 'apartment'
in the same applicationIt lets you install the bundle.
It fails due to the current strict requirement of a Rails version < 6.1
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!
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
Linking a few issues still open in the original apartment that I think might be worth to look at in the immediate:
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.
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.
db/schema.rb is leaved almost empty.
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
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'
Create Tenant named "demo"
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'
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
6.0
2.7.1
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
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
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
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
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)
rails db:rollback
Rolling back latest migration
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!
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
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
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?
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.
Excluded models get queried against the public
shcema:
SELECT "tentants".* from "public"."tenants"
Excluded models get queried without schema.
SELECT "tentants".* from "tenants"
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
: trueRails (or ActiveRecord) version: 6.0
Ruby version: 2.6
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).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?
As with PostgreSQL DDL is transaction safe, the schema creation should be wrapped with a transaction.
Database: PostgreSQL 10
Apartment version: 2.6.1
Apartment config (in config/initializers/apartment.rb
or so):
use_schemas
: truepersistent_schemas
: %w[shared_extensions]Rails (or ActiveRecord) version: 6.0.2.1
Ruby version: 2.7.0
excluded_models
No error
/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)
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.
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
The global main namespace should not be polluted.
The global main namespace is polluted.
I think those methods should be extracted to a module, so that they don't pollute the global namespace.
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!
execute 'CREATE EXTENSION pgcrypto WITH SCHEMA pg_catalog;'
enable_extension "uuid-ossp"
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
ENV
configuration (not database.yml
(ex: .env.development
)DATABASE_URL=postgres://##:password@localhost:5432/###
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"
rails db:drop db:create db:migrate
rails r db/demo.rb
rails db:drop db:create db:migrate
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?
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>
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
rails db:drop db:create db:migrate db:seed
(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
(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.)
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
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:
Hi @rpbaltazar, thank you for maintaining and improving apartment. I found an issue with the after
switch callback when passing nil
to switch
method.
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"
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.
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
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!
Seeding testtenant1 tenant
One of the following schema(s) is invalid: "testtenant1" "public", "shared_extensions"
PostgresSQL
2.6.0
Apartment config (in config/initializers/apartment.rb
or so):
use_schemas
: truepersistent_schemas
: %w[shared_extensions]Rails (or ActiveRecord) version:
6.0.2.1
2.7.0
User::Account.find(10)
Cached statement to not break
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'
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
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.
https://github.com/rails-on-services/apartment/blob/v2.6.1/HISTORY.md
Entries for all releases
Last entry is for 2.3.0.
If the file is not updated, it should be removed or a header added that it isn't maintained.
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.
No errors
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.
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
)Rails (or ActiveRecord) version:
Rails 6.0.2.1
ruby 2.5.5p157 (2019-03-15 revision 67260) [x86_64-darwin18]
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
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?
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
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:
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?
Just create a new Rails 6.0.3 application with postgres database and add the ros-apartment, require: 'apartment'
and run bundle install
That give me the rails g apartment:install task
Don't have the rails g apartment:install task but has the others like apartment:create /migrate, etc
Ruby 2.7.1
Rails 6.0.3
Postgres
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.
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.
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.
Currently there is no way to support this unless we monkeypatch the postgresql adapter in order to achieve it.
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
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)
A tenant is switched in Active Job.
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'
config/initializers/apartment.rb
or so):
use_schemas
: true
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?
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.