Anyone have any recommendations for creating a multi-tenant application that is straightforward to test and implement?
I have developed the application initially with a sort of top level “Tenant” class which has a lot of has_many relationships which is working okay. The frustrating part is the amount of joins you have to do from your models further down the line to keep your proper scope.
In looking at the popular multi-tenancy gems ( Category: Multitenancy - The Ruby Toolbox ), it appears like the gems either add a separate database/schema (e.g. Apartment) or add a tenant_id key in every table.
Does any one have experience or opinions on the best way to handle this?
I have a multi-tenant app in production and I use the default_scope method described by Ryan Bates in his Railscasts episode.
The real only issue I’ve had so far with this method is people yelling at me for using this method
I bought the book referenced above. It’s gone through a few iterations since I bought it, recommending different approaches through time. I was of some use while I was developing but the version I had spent a great deal of time building a forum gem to actually use the multi-tenant techniques with, which was not of much interest to me, and in fact confused the issue.
The main drawbacks I’ve found with the default_scope method are (1) in the Rail console you’re always tagging .unscoped onto everything, this can become a bit cumbersome, and (2) you need to ensure you have a tenant set up in your test_helper since most of your tests will need it. Apart from that it’s been fairly easy going.
The advantages are numerous - the tenant (and therefore default scope) is established as soon as a user signs in, then you don’t have to reference it again in any queries etc. The creation of new records is automatically scoped to the current tenant, very handy. Your models aren’t littered with a lot of conditional scopes for if a user is signed in or not. It’s secure because since there can only be a single tenant there’s no possibility of queries leaking across tenants.
I did look at using PostgreSQL schemas but I was concerned about performance, backups and how to query across schemas for records like User which need to be queried when there is no-one currently signed in.
I just finished a project with Apartment and it was an extremely simple setup. I have other apps which are multi-tenant apps and I have used the default_scope route, the id route and found Apartment to be simpler than all of them. It really let me focus on just developing the application like a single tenant app.
I can’t speak to performance in detail, but I use schemas in SQL Server and schemas adds no discernible overhead to the performance of the database. I imagine the only difference would be in ActiveRecord, but without knowing exactly how Apartment does it exactly I can image all it needs to do is prepend the schema name to the table name in the query.
Apartment also solves the issue of global tables like User. You define the global tables and when you query the table User it queries the global schema.
I am sure there are some downsides, but I wasn’t building the next big thing either and have not hit a single issue.
Thanks guys. I went ahead and picked up the book @andyw8 recommended and it seems like it could be useful. It does seem a bit strange in that it introduces Rails engines which seems to add some cognitive overhead that seems superfluous to the multi-tenant subject (though interesting in its own right). In skimming the book it seems as if he ends up recommending the apartment gem.
I really like the idea of apartment and being able to develop the app as if a single client. I am mainly concerned about performance issues like database caching, etc. I am using Postgres which seems ideally suited to the gem, so that’s a plus. To address @weavermedia’s concerns, the readme does mention how to use a “global” schema, and I think backups wouldn’t be too hard since the schemas are encompassed within the database being backed up.
However, when you create a tenant it says it runs migrations against the new schema instead of loading schema.rb or structure.sql, which seems a bit more error prone.
Though it seems like maybe if you started with apartment it wouldn’t be too hard to migrate to the other method if you run into performance issues.
I also wonder how well this would work with scheduled background jobs, and how difficult it would be to run conditional jobs based on tenant preferences.
I don’t know, I probably won’t feel comfortable with it until I do some extensive testing and will stick with the tenant_id method for now. Hopefully will revisit this at some point, but in the meantime @frank_west_iii I’d be grateful if you let us know if you run into anything using the gem.