Reputation: 41
I'm trying to build a multi tenant app, where a user can have their own website, and within that websites, visitors can create accounts and login etc.
I'll be using Postgresql schemas for this app.
Now a user can create a basic website, add content etc. But I want visitors to be able to register for an account in those websites.
For example, a user create the website site.app.com He can login to site.app.com/admin to manage his website. Now I want visitors to be able to create an account in site.app.com (of course the account will be stored and that website's schema), login and logout etc.
This is getting confused and if someone has ever done something like this please help me achieve this.
Thanks
Upvotes: 0
Views: 1486
Reputation: 76784
I've done something like it before.
I need to explain it...
Multi Tenancy
in the standard sense denotes the use of multiple environments for a single application. As explained:
Software Multitenancy refers to a software architecture in which a single instance of a software runs on a server and serves multiple tenants. A tenant is a group of users who share a common access with specific privileges to the software instance. With a multitenant architecture, a software application is designed to provide every tenant a dedicated share of the instance including its data, configuration, user management, tenant individual functionality and non-functional properties. Multitenancy contrasts with multi-instance architectures, where separate software instances operate on behalf of different tenants.
In short, this means that a truly multi tenant application should be able to serve a number of different users with the same resources. In terms of Rails, this is pretty simple because it's all built on HTTP - a stateless protocol.
Real "cloud" applications (which we're yet to see) would have to be stateful - much like a native application harnessing the computing power of a datacenter. A good example would be one of those RPG games which keep all your data stored on their server.
--
Although Rails is naturally multi-tenant, its database is not.
How to create a multi-tenant database with shared table structures?
In my head - and maybe I'm "wrong" - a real multi tenant app should allocate a separate data-base for each user, sharing a central repository of data such as "accounts" etc:
-- database
-- accounts
-- invoices
-- users
-- 1
-- pictures
-- users
-- etc
-- 2
-- pictures
-- users
-- etc
This might seem complicated, but if you treat it properly, it will give you the ability to store the data for each user securely etc.
In regards your application, it means that you're going to be storing all your data in a single db, linked to a single app.
This means that your user authentication structure can be created with sessions
and roles
:
Simple implementation of the models:
#app/models/account.rb
class Account < ActiveRecord::Base
# This is for the "site" -- will create the subdomain etc
has_many :memberships
has_many :users, through: :memberships
end
#app/models/membership.rb
class Membership < ActiveRecord::Base
belongs_to :account
belongs_to :user
belongs_to :role
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :memberships
has_many :accounts, thorugh: :memberships
end
#app/models/role.rb
class Role < ActiveRecord::Base
has_many :memberships
end
This will allow you to create accounts, assign users to those accounts, and store different information in the membership model (such as role etc).
--
This will give you the primary functionality of a multi-tenant application (the ability to have "accounts", customize the environment for those accounts, and then have members for that specific account).
There are various other things you can do, but the main after that is the sessions (so a user can only log in to a single subdomain):
Share session (cookies) between subdomains in Rails?
This is handled by default; if you wanted to "share" sessions between subdomains, you'd have to add tld_length: 2
to your session_store.rb
--
To handle the subdomains, just set it up in the routes (like how Ryan Bates did it with his Railscast):
#config/routes.rb
scope constraints: AccountManager do
#This is from an app we're working on..... put what you want here.
#Users
devise_for :users, path: "", controllers: { sessions: "auth/sessions" }, path_names: { sign_in: "login", password: "forgot", confirmation: "confirm", unlock: "unblock", sign_up: "", invitation: "add", accept: "", registration: "register", sign_out: "logout" }
#Authentication
authenticate :user do
root "transactions#index", as: :authenticated
end
#Core
authenticated :user do
resource :settings, controller: :users, only: [:show, :update], constraints: { format: :js } # User profile & settings (doubles up as options editor)
resources :transactions, path: "", only: [:index, :update, :destroy], constraints: { format: :js } do # CRUD transactions
scope format: true do #-> for constraints
get :new, on: :new, constraints: { format: :js }
end
match "search(/:query)", action: :search, as: :search, on: :collection, via: [:get, :post]
end
end
end
#lib/account_manager.rb
module AccountManager
# Refs
# https://viget.com/extend/using-routing-constraints-to-root-your-app
# https://stackoverflow.com/questions/5192175/how-to-set-in-a-middleware-a-variable-accessible-in-all-my-application
def initializer(router)
@router = router
end
def self.matches?(request)
Account.exists? request.subdomain
end
end
This will allow you to sign in etc (you'll have to modify Devise to accept only members
of the account).
Finally, to get the "admin" section working, you'll be best creating an "/admin" path in your subdomain, allowing only admin roles access:
#config/routes.rb
scope constraint: AccountManager do
namespace :admin do #-> account.app.com/admin
# stuff here
end
end
This would allow you to use basic authorization in the admin controllers:
#app/controllers/admin/application_controller.rb
class Admin::ApplicationController < ActionController::Base
before_action :is_admin?
private
def is_admin?
redirect_to root_path, notice: "Admin Only" unless current_user.admin? #-> has to check membership if is admin
end
end
You could also make a separate admin interface on app.com/admins or something; where you could only let admins sign in etc.
Upvotes: 2
Reputation: 6095
There are several ways you can do this. If you are using schemas and switching the scope of the app when someone visits a particular page or is logged into a specific context, then that should be all you need to do.
Most people will accomplish this by just adding a model above the users (like Account), then when you create the account when someone freshly signs up to your site and then associate the users with the account_id as a foreign key. After that you scope the activity in your app to the proper account and everything else works the same. Here is how you do that with devise. Here is an example of how you can do this in devise in the RegistrationsController.
http://www.austinstory.com/category/programming/rubyonrails/devise/
Upvotes: 0