We've moved discussions to Discord

Advice on Creating Multitenant App with Jumpstart

Ryan Chin
I want to create a multitenant app that allows users to sign-up for a paid account which they can invite other users to use without paying.  Something like how Slack works.

  • tenant is associated with paid plans (as opposed to User)
  • uses subdomain to view the tenant
  • the tenant site is available for everyone to see (but only Users scoped can make changes to it)

I was looking at the https://gorails.com/episodes/multitenancy-with-apartment tutorial, and I was wondering if Apartment would be overkill.

It seems like Teams can be leveraged, but I was wondering:
  • what's the best way to stop a Team from being created when a User joins via an invite to a team
  • what's the best way to associate billing to a Team instead of a User
  • is there an easy way to scope created objects to only a Team

I'm curious what approach others would use for implementing this using Jumpstart.  I do like how Teams already allows people to remove and add other Users (and creates a grouping).
Dan Weaver
I'm interested in this too. I have a medium-sized Rails app in production that does multi-tenancy with Ryan Bates's old `default_scope` method.

It would be easy to add this method to the existing Jumpstart template but I can already hear the screams from developers saying `default_scope` is a terrible idea. That being said, I've had few to zero issues with it in the past.

Seems like using Teams for multi-tenancy might work. Maybe the payment issue would be most difficult.
Chris Oliver
CitusData which is a  Postgres-focused company strongly recommends using database scoping for scalability. Effectively this is the `default_scope` method.

Apartment creates a separate schema which supposedly causes a lot of issues for performance as you get a lot of tenants. It's definitely the "safest" solution though because the data literally cannot be accessed by another tenant.

From what I was reading, the acts_as_tenant approach seems to be the best option if you are worried about scalability.

Billing in Jumpstart is already associated to teams, not users, so that's solved for you. 
Acts As Tenant should take care of the scoping for your models, but you can also just use `current_team` in your controllers to make sure resources are only available to them.
Chris Oliver
We're adding a scoped scaffold generator that will let you scope it to the `current_team` by default, so that is automatically handled for you safely out of the gate.
Ryan Chin
Thanks Chris.

Is there an easy way to avoid creating a team for a new user when they are joining as the result of being invited to a team (and their intent is just to join an existing team)?
Brandon B.
Bump on Ryan's question from 6/19/2019 @ 12:14pm <follow if this allows me, haha>
Chris Oliver
Ryan Chin , every user gets a personal team for several reasons, but if you don't want to show it, you can hide the link to them and adjust the code so it always picks the team they were joined to instead of their personal team.
Brandon B.
Chris Oliver - I never thought of this angle, and I like it: if not team_admin(role - can be added or modified), then do not show personal team and default to team they were joined to, if team_admin, show personal team as default. 
I want to know How could I get the role of the current team. Any help ? 
 

Chris Oliver
Added some helper methods for current_team_member and current_roles to Jumpstart Pro. You can find the docs here: https://jumpstartrails.com/docs/teams
Ryan Chin
Thanks  Chris Oliver

Do you think that some of the other multi-tenancy features mentioned in https://jumpstartrails.com/discussions/151 will all on the near(er) term road map?  I think the most important ones for me would be: subomains, and a way to scope to a team (acts_as_tenant).  I also need to associate subscription plans based on team users (more users, higher plan), but I can probably do that by creating a job to check team user count, and making a status attribute for the team (i.e. too many users for the current plan, change status == 'suspended' or something like that). 
Chris Oliver
Ryan Chin you can already scope by the team, just use current_team in your controllers.

For choosing the team by domain or subdomain, we'll update the code so that current_team looks at the domain/subdomain to find the team first, rather than the session.

If you modify the TeamMembersController, you can have it adjust the subscriptions when team members are added and removed. That way you don't have to run a job or anything and you can do it as team members are modified (and warn them if they add another users, they're plan must be upgraded, etc).
Ryan Chin
Thanks  Chris Oliver !

I'm working on a multi-tenancy SaaS app that charges monthly per user.  I want to do two things, but I don't have a good approach, so I'm hoping you might have some ideas.

What I want to do is similar to the pricing of Jira.

(1) Users subscribe to a tier of a plan - the tier is based on the number of features and/or Team users (e.g. under 10 users, you can be on a free tier)

(2) I want to implement pricing that charges per Team user monthly based on the tier (i.e. $0/user - free tier, $5/user - premium tier)

(3) I want to be able to discount the price on a Team basis through my admin UI - either by providing a discount per user (i.e. -20%), or by being able to overwrite the price manually

Thanks,
Ryan
Chris Oliver
Sounds fairly complex. I know you can do the pricing tiers in Stripe based upon quantity. That would proably satisfy #1 and #2. There's no code in Jumpstart Pro to reflect that, so you'd need to implement that to display the correct price if you wanted to show that.



Braintree doesn't have a feature like this as far as I know, so you'd need to cancel and re-subscribe them to the correct plan if the quantity change crosses tiers if you wanted to use Braintree.

Discounting the price I would try and keep as simple as possible and use Coupons in Stripe for a flat 20% off, etc.
Notifications
You’re not receiving notifications from this thread.