influitive / apartment

Database multi-tenancy for Rack (and Rails) applications
2.66k stars 464 forks source link

rails 6.1.alpha support #638

Open jean-francois-labbe opened 4 years ago

jean-francois-labbe commented 4 years ago

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

During tests 2 dbs are created, the first one is perfectly created, but the second one is missing the tables schema_migrations and ar_internal_metadata.

After some debugging here is what I found.

This is the sql log of the first database creation. It is here just to ease comparison between the two dbs creation.

CREATE SCHEMA "db1"
CREATE EXTENSION IF NOT EXISTS "plpgsql"
DROP TABLE IF EXISTS "books" CASCADE
CREATE TABLE "books" ("id" bigserial primary key, "name" character varying, "pages" integer, "published" timestamp)
DROP TABLE IF EXISTS "companies" CASCADE
CREATE TABLE "companies" ("id" bigserial primary key, "dummy" boolean, "database" character varying)
DROP TABLE IF EXISTS "delayed_jobs" CASCADE
CREATE TABLE "delayed_jobs" ("id" bigserial primary key, "priority" integer DEFAULT 0, "attempts" integer DEFAULT 0, "handler" text, "last_error" text, "run_at" timestamp, "locked_at" timestamp, "failed_at" timestamp, "locked_by" character varying, "created_at" timestamp, "updated_at" timestamp, "queue" character varying)
CREATE  INDEX  "delayed_jobs_priority" ON "delayed_jobs"  ("priority", "run_at")
DROP TABLE IF EXISTS "public_tokens" CASCADE
CREATE TABLE "public_tokens" ("id" serial NOT NULL PRIMARY KEY, "token" character varying, "user_id" integer)
DROP TABLE IF EXISTS "users" CASCADE
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
INSERT INTO "schema_migrations" (version) VALUES (20180415260934)
INSERT INTO "schema_migrations" (version) VALUES
(20110613152810),
(20111202022214);

CREATE TABLE "ar_internal_metadata" ("key" character varying NOT NULL PRIMARY KEY, "value" character varying, "created_at" timestamp(6) NOT NULL, "updated_at" timestamp(6) NOT NULL)
SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2  [["key", "environment"], ["LIMIT", 1]]
BEGIN
INSERT INTO "ar_internal_metadata" ("key", "value", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "key"  [["key", "environment"], ["value", "test"], ["created_at", "2020-02-12 21:09:41.525485"], ["updated_at", "2020-02-12 21:09:41.525485"]]
COMMIT
BEGIN
INSERT INTO "companies" ("database") VALUES ($1) RETURNING "id"  [["database", "db1"]]
COMMIT

Second db is where troubles are.


CREATE SCHEMA "db2"
CREATE EXTENSION IF NOT EXISTS "plpgsql"
DROP TABLE IF EXISTS "books" CASCADE
CREATE TABLE "books" ("id" bigserial primary key, "name" character varying, "pages" integer, "published" timestamp)
DROP TABLE IF EXISTS "companies" CASCADE
CREATE TABLE "companies" ("id" bigserial primary key, "dummy" boolean, "database" character varying)
DROP TABLE IF EXISTS "delayed_jobs" CASCADE
CREATE TABLE "delayed_jobs" ("id" bigserial primary key, "priority" integer DEFAULT 0, "attempts" integer DEFAULT 0, "handler" text, "last_error" text, "run_at" timestamp, "locked_at" timestamp, "failed_at" timestamp, "locked_by" character varying, "created_at" timestamp, "updated_at" timestamp, "queue" character varying)
CREATE  INDEX  "delayed_jobs_priority" ON "delayed_jobs"  ("priority", "run_at")
DROP TABLE IF EXISTS "public_tokens" CASCADE
CREATE TABLE "public_tokens" ("id" serial NOT NULL PRIMARY KEY, "token" character varying, "user_id" integer)
DROP TABLE IF EXISTS "users" CASCADE
CREATE TABLE "users" ("id" bigserial primary key, "name" character varying, "birthdate" timestamp, "sex" character varying)

[22, 31] in rails-39d3c9bcacb4/activerecord/lib/active_record/schema_migration.rb
   24: 
   25:       def create_table
   26:         byebug
=> 27:         unless table_exists?
   28:           version_options = connection.internal_string_options_for_primary_key
   29: 
   30:           connection.create_table(table_name, id: false) do |t|
   31:             t.string :version, **version_options
(byebug) ActiveRecord::Base.connection.schema_search_path
"\"db2\""
(byebug) table_exists?
true
(byebug) table_name
"schema_migrations"
(byebug) ActiveRecord::Base.connection.tables
["books", "companies", "delayed_jobs", "public_tokens", "users"]
(byebug) ActiveRecord::Base.connection.data_source_exists?(table_name)
false
(byebug) ActiveRecord::Base.connection.schema_cache.data_source_exists?(table_name)
true

(byebug) continue

SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
DROP SCHEMA "db1" CASCADE
DROP SCHEMA "db2" CASCADE
DELETE FROM "companies"
    clears the ActiveRecord::QueryCache after switching databases (FAILED - 1)

Failures:

  1) query caching when use_schemas = true clears the ActiveRecord::QueryCache after switching databases
     Failure/Error:
       ActiveRecord::Schema.define(version: 2018_04_15_260934) do

         # These are extensions that must be enabled in order to support this database
         enable_extension "plpgsql"

         create_table "books", force: :cascade do |t|
           t.string "name"
           t.integer "pages"
           t.datetime "published"
         end

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

create_table is defined in schema_migration.rb

table_exists? implementation is in model_schema.rb

def table_exists?
  connection.schema_cache.data_source_exists?(table_name)
end

In rails 6.0 schema_migration.rb had a method table_exists? defined in it, the implementation was:

def table_exists?
  connection.table_exists?(table_name)
end

It was removed in this commit https://github.com/rails/rails/commit/c5ecc338e863e771adcbcd87d5c23ae5e7ca7894#diff-a15c218db8b9dd9f84a75d9a3894db56

cristianrosu commented 4 years ago

How are you able to use it with Rails 6? I installed the gem but then the application crashed and I can't even run the apartment generator

jean-francois-labbe commented 4 years ago

@cristianrosu this repo is no more maintained. You need to use this one now.

Use gem 'ros-apartment', require: 'apartment' in your Gemfile and it should be working

Ps: If you're working on a new project, you may think twice before use this gem, it may not be the best solution, if you have a lot of tenants, as the creators themselves explain here.

I use it with Postgresql schemas and few tenants, it's working great.

cristianrosu commented 4 years ago

@jean-francois-labbe I was not aware of that fork, thanks a lot, looks promising!

I won't have many tenants but it's important to separate data per account. It will work good for now. Thanks!

jjb commented 1 year ago

Above link is dead, working link is here: https://medium.com/infinite-monkeys/our-multi-tenancy-journey-with-postgres-schemas-and-apartment-6ecda151a21f