3
votes

I am going to build an app using Rails. It uses multi-tenancy using the Apartment gem and PostgreSQL. The app will have users, each of which have an account. This implies that each user has it's own PostgresSQL schema; the users table is in the default schema.

Each user has his own list of customers in his own schema. A customer with the same email (essentially the same customer) can appear in multiple schemas. I want a customer to be able to log in and see all the users he's associated with. I can't put the customers table in the default/public schema because it's related to other tables that are not in the default schema.

What I thought I would do is create a link table between customers and users in the public schema. This table would contain the email of the customer and the id of the user. My issue with that is that I don't understand how well this would work with Rails. What I would like to achieve is something like customer.users.

So the question is: How should I approach this problem?

2
You seem to be saying that some users are tenants, and some users are customers. (Since customers can log in, they're users in some sense of that word.) And you want customers to be able to query across all tenants, because the mapping between users and customers is stored in tenant-specific schemas. But isn't that exactly the kind of thing a multi-tenant architecture is supposed to prevent?Mike Sherrill 'Cat Recall'
@MikeSherrill'Catcall' Thanks for your answer. I took a couple days to think, and I realised that in my case, multi-tenancy has more disadvantages than advantages. I will build my Rails app without multi-tenancy.Robert Audi

2 Answers

1
votes

I suggest to differ between your users (who log in, not part of a tenant), and the customers (which are kept separately, and located in each tenant). The users table (possibly accompanied by other tables) can hold the information for the assignment from user to schema/customer etc. I would not even use foreign keys to link the user table with the tables in the tenant, just to keep them really separate.

In short, the user table serves to authenticate and to authorize only.

Update: The question describes a multi-tenancy approach using separate database schemas for the individual tenants. In this setup up, I would not link the users with the customers by database foreign keys, and I would not query them together. Just authenticate against the users, and get the assigned tenant(s). After that switch to the tenant.

If you really want to query both items (users and customers) in one run, I would not use separate schemas: One schema, create a tenant table, and put a foreign key into all other tables (customers etc.). In this scenario you could even do without a separate user table, and you could query the (single) customer table.

Update 2: To answer your query question:

You can query for schemas in PostgreSql's meta data:

select schemaname from pg_tables where tablename = 'Customer'

Which gives you all schemas with a customer table. Using that information you can dynamically build a union select:

select name from schema1.customer
union all
select name from schema2.customer
union all

[...repeat for all schemas...]

to query all tables across schemas. You could use group by to eliminate duplicates.

0
votes

I created this lib to help us solve this issue

https://github.com/Dandush03/pg_rls

Currently the most famous implementation are Apartment Gem (from Influitive), ActiveRecordMultiTenant (from Citus) and the rails 6.1 way, DB sharding

there are many issues with the Apartment and rails 6.1 approach when dealing with a huge amount of schemas/databases mainly when you most run a scale migration or when you have to change default values on a table, this is because you would need to run this migration on each tenant, and that very cost-efficient. and Citus's approach gets expensive in the long run. 

thankfully PostgreSQL came with a great solution on pg V-9 but it had some performance issue that was solved on pg V-10. This approach allows you to keep specific tables behind a 'special id' which can be later on partitioned with pg new tools

my approach is mainly focused on PG and how they recommend you to implement RLS, and most of my queries are executed with SQL statements which help us a bit when dealing with performance issues when running the migration. I tried to mix the best of rails with the best of pg functions.

what is even better on my approach is that when you start running your special pg search function, there will be no downfall because all data is secure and under the same DB. also you will gain the ability to log as a superuser and get your statistics.

I can keep going but I think I make my point clear, if you'd like I encourage you to check out the gem (prob still some bugs to handle like right now it only handle setting the Tenant from subdomain) but it does make my life easier on my ongoing projects. and if I get some more supports (like) I would keep on maintaining it and upgrading it to be a more generic tool