Skip to main content

Multitenancy with the Apartment gem Discussion

General • Asked by Chris Oliver

Gravatar

Hi, Chris video is not loading!


Gravatar

Gravatar

@ChrisOliver is self.matches? called automatically?

Gravatar

Yep! Rails knows to delegate that appropriately behind the scenes.


Gravatar

Thanks Chris. Is there any way to do this where it's URL based, rather than subdomain? For example myapp.com/company1/posts instead of company1.myapp.com/posts

Gravatar

I'm sure you could. You probably need to switch the tenants manually in this case. If you didn't need the company in the URL, you could also have it switch tenants in a before_action on every request based upon the user that is signed in and the tenant they are associated with.


Gravatar

Hey Chris, I've started adding this to an existing application. So far I've got four "accounts" at the global level that are already created and have users for each account. I've had these on one DB up until this point. I was able to add the subdomain attribute to the account and it's stored in the db. i ran rake db:migrate and got the following: see screen shot. I've checked in the rails console and it's storing the subdomain for each account but for some reason apartment isn't finding the tenant. Any ideas?

EDIT: It is working if I create a new school from scratch..

Gravatar

Are you able to go back and retroactively create those tenants for records that already exist?

Gravatar

I'm not sure how I would do that... I'll certainly need to figure that out for staging and production environments. on my development environment I ran rake db:reset and have been able to create subdomians with their separate databases. I'm still having issues:

I have users whose role == teacher (pundit gem). They are listed for the admin to create a classroom and select the appropriate teacher. The problem is that teachers (users) are showing up in both my test accounts on that list. So, i'm thinking the data isn't being rendered and/or stored in a separate db. What do you think?

Gravatar

I would think that you could loop through all the existing records you have and try doing Apartment::Tenant.create('tenant_name') on them, replacing tenant_name with the subdomain obviously.

It does sound like the teachers aren't sequestered appropriately yet. I don't quite know how to move records between the global tenant and a specific one, but that is probably what you need to do so they are stored separately.

Curious what you come up with to solve these problems. Keep me posted!

Gravatar

can i install apartment on ruby 1.8.7 with rails 2.3.0

Gravatar

I'm not sure that it does. The only gem version that looked compatible was the very first release of it. Probably isn't worth using.

Gravatar

i get this error when using rails 3.1.0
Gem::Dependency#version_requirements is deprecated and will be removed on or after August 2010. Use #requirement
/usr/local/lib/site_ruby/1.8/rubygems.rb:242:in `activate': can't activate rails (>= 3.0.10, runtime) for ["apartment-0.12.0"], already activated rails-2.3.5 for [] (Gem::LoadError)
from /usr/local/lib/site_ruby/1.8/rubygems.rb:258:in `activate'
from /usr/local/lib/site_ruby/1.8/rubygems.rb:257:in `each'
from /usr/local/lib/site_ruby/1.8/rubygems.rb:257:in `activate'
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:33:in `require'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:156:in `require'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:521:in `new_constants_in'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:156:in `require'
from /root/fedena-v2.3-bundle-linux/config/initializers/apartment.rb:7
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:145:in `load_without_new_constant_marking'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:145:in `load'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:521:in `new_constants_in'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:145:in `load'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/initializer.rb:622:in `load_application_initializers'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/initializer.rb:621:in `each'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/initializer.rb:621:in `load_application_initializers'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/initializer.rb:176:in `process'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/initializer.rb:113:in `send'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/initializer.rb:113:in `run'
from /root/fedena-v2.3-bundle-linux/config/environment.rb:6
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:29:in `gem_original_require'
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:29:in `require'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:156:in `require'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:521:in `new_constants_in'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:156:in `require'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/commands/server.rb:84
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:29:in `gem_original_require'
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:29:in `require'
from script/server:3
[email protected]:~/fedena-v2.3-bundle-linux# ruby script/server --port=3009
=> Booting WEBrick
=> Rails 2.3.5 application starting on http://0.0.0.0:3009
/usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/rails/gem_dependency.rb:119:Warning: Gem::Dependency#version_requirements is deprecated and will be removed on or after August 2010. Use #requirement
/usr/local/lib/site_ruby/1.8/rubygems.rb:242:in `activate': can't activate rails (>= 3.0.10, runtime) for ["apartment-0.12.0"], already activated rails-2.3.5 for [] (Gem::LoadError)
from /usr/local/lib/site_ruby/1.8/rubygems.rb:258:in `activate'
from /usr/local/lib/site_ruby/1.8/rubygems.rb:257:in `each'
from /usr/local/lib/site_ruby/1.8/rubygems.rb:257:in `activate'
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:33:in `require'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:156:in `require'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:521:in `new_constants_in'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:156:in `require'
from /root/fedena-v2.3-bundle-linux/config/initializers/apartment.rb:7
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:145:in `load_without_new_constant_marking'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:145:in `load'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:521:in `new_constants_in'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:145:in `load'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/initializer.rb:622:in `load_application_initializers'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/initializer.rb:621:in `each'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/initializer.rb:621:in `load_application_initializers'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/initializer.rb:176:in `process'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/initializer.rb:113:in `send'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/initializer.rb:113:in `run'
from /root/fedena-v2.3-bundle-linux/config/environment.rb:6
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:29:in `gem_original_require'
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:29:in `require'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:156:in `require'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:521:in `new_constants_in'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/dependencies.rb:156:in `require'
from /usr/lib/ruby/gems/1.8/gems/rails-2.3.5/lib/commands/server.rb:84
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:29:in `gem_original_require'
from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:29:in `require'
from script/server:3


Gravatar

Wow This just great I was looking for something like this ill be your new member on gorails. :)

Gravatar

Thanks! :)


Gravatar

Can I do this with company. So when I create a company the company gets the subdomain. and do something like User.company.subdomain to look for the tenant ??? as my app is goin to have User model and Admin model. Admin will create the Companies and User for the companies

Gravatar

Yes you can! Just add that attribute to the company and you can use that.


Gravatar
Antonio Marcos Silva Junior

Thanks Chris. This is great information. Do you know if Apartment allows different databases to be hosted in different database servers? E.g. if I need to scale out, can I move a database to a new DB server and somehow tell Apartment that that DB/tenant is hosted in another server?

Gravatar

I would imagine so, but you'd probably want to ask on their Github issues to be sure. This is probably just more of a database configuration thing than anything with apartment or Rails.

Gravatar
Antonio Marcos Silva Junior

Thanks Chris. I've asked them and at the moment this is not a supported scnario, but they are actively looking at options:
https://github.com/influiti...


Gravatar

Thanks so much for this! I've got this up and running, but I don't know how to edit/create records in the rails console once the tenants and schemas are created.

With no tenants, you can do Post.create(name: "test post), but how do you find, edit, create posts by schema from within the rails console?

Gravatar

Thanks to Chris, the answer, for anyone else wondering, is simply to specify your tenant in the console by using Apartment::Tenant.switch!('tenant_name') first.

So, rails c, Apartment::Tenant.switch!('tenant_name'), then Post.first will be the first post for that tenant, etc.


Gravatar

Great info, thanks a lot for putting this up! One suggestion tho: when you create SubdomainConstraint and check if the subdomain is present _and_ not in the list... you don't have to check if it's present, because the list won't contain blank, nil, etc. :-)

Gravatar

Yes indeed! :) Mistakes in video are forever... ;)


Gravatar

Thanks Chris for the awesome video! Would Apartment allow you to do something like Heroku where you can create projects with their own subdomain such as mysite.herokuapp.com but then point your site name so that you can navigate to the page by the actual name such as mysite.com? What i would like to do is allow users to create their own sites and not necessarily subdomains or allow them to point their site names to their own subdomain. Any help or tips would be great.


Gravatar

Thanks Chris, great material.
Could you give me adivce?
I'm creating application for dietician as a project on my studies. There will be a database with about 15 tables(like products, meals, tags, diets etc.). I'm going to create users with accounts. Each user should have access only to his tables (products which he added etc.). Do you think apartment gem is best approach for this problem(I don't want to use subdomains)? Or maybe could you recommend me another aproach? I wonder about add user_id column to every table, but it would generate so many records in database. What do you think?

Gravatar

You can just associate all the records with the user_id. The only time you need Apartment is if the data should be kept private in their own databases for security reasons. You should be fine associating them to the user and making sure you don't load the other users's records.

Gravatar

It is useful information, thanks.
So when you wrote "You can just associate all the records with the user_id" you mean I should add user_id to all tables in my database? Or is another way?

Gravatar

Yep that's it!


Gravatar

Anyone got this working with Active Admin?


Gravatar

Does it work only on production mode? Because a have created a host on my hosts file but when i create the tenant and go for it, no server respond. (I'm using puma.)

Gravatar

It works everywhere. You might need to make sure you have Puma running on the correct IP (I think in this case you'd want 0.0.0.0) and that you're accessing the host with the correct port as well.


Gravatar

Chris, i have a important question. Can it work with subdomains of a subdomain? How would be that?

Gravatar

Yep, I'm sure it can. You may have to build a custom handler for it based on their Subdomain one, but definitely could work. Check their docs / Github issues for more on it as I haven't done that before so I don't know the details of what you'll need.


Gravatar

Thanks Chris, is it possible to transfer a record from one tenant to another while using apartment/multitenancy? how wd you go about that. Thanks. Gret Article.

Gravatar

That's a good question. Aside from a brute force approach, I'm not sure. What I would probably do is just query for the record, make a copy of the object with .dup, switch the tenant to the new one, save the duplicate in the new tenant, verify it exists, switch back to the old tenant and delete the old record (if necessary).

Gravatar

Thats very much. I will consider that approach.


Gravatar

Hi Chris, great videos. I have a question , is it possible to use this gem without subdomain. So every tenant will have same domain but inside the logic there is scheme segregation. I already tried to config in domain many times but looks not works. Do you have an idea / sample ? Any help or tips would be great

Gravatar

Yeah, you can use anything you want to switch the tenant. I'd ask this question on the Github repo though. They're the ones that know it really in-depth and can get you the best answer!


Gravatar

I was able to create a multi-tenant app based on this tutorial. But now any user can login from any subdomain and the data will be shown based on subdomain e.g. if 'user1' login from user2.example.com it will login fine but it will show records of 'user2'. Ideally, user1 should not be allowed to login from any subdomain other than user1.example.com and if it is logged in it should be redirected to correct subdomain url and never be allowed to see other users record.

Gravatar

You'll want to modify your authentication code to lookup the current tenant and then scope the User lookup query to only ones for that domain that way only users of that tenant can access that tenant.

Gravatar

i have the same request ! Any help about this (I used acts_as_tenant gem before move to apartment) ? . I use Devise for authentication :
--Devise session controller
class SessionsController < Devise::SessionsController
prepend_before_action :set_tenant

def set_tenant
Apartment::Tenant.switch!(request.subdomain)
end
end

-- Application controller
class ApplicationController < ActionController::Base
before_action :authenticate_user!
prepend_before_filter :current_tenant
def current_tenant
@current_tenant ||= Tenant.find_by(subdomain: request.subdomain)
end

def welcome
#not work
if user_signed_in?
redirect_to dashboard_path
else
redirect_to sign_in_path
end
end
end

Gravatar
check this out 

https://github.com/plataformatec/devise/wiki/How-to:-Scope-login-to-subdomain

Gravatar

Chris - how does this work if the rails app is just an API?

Gravatar

Nothing different. An API just doesn't have HTML, JS, or CSS to serve up, but everything else is the same. Any multi-tenancy is on the database level, not the UI level.


Gravatar

Hi Chris , I am using rolify and I want to exclude roles , how to do it ? I did the following

config.excluded_models = %w{ User Role}

But I want to exclude users_roles table too, How can I do this ?


Gravatar

Okay, so I have Apartment working wonderfully in an app. The issue that I'm having is that Google Apps / Mail isn't able to send or receive. I'm guessing the app is intercepting traffic meant for "mail.appblah.com". I've been unable to find a recommendation as to how this should be handled, ideas?

Gravatar
Jeremy Christopher Bray

Hi joe, how did you go? Are you using devise for authentication? if so this might help

config.action_mailer.delivery_method = :smtp
config.action_mailer.default_url_options = { :host => "localhost:3000" }
config.action_mailer.smtp_settings = { :address => "localhost", :port => 1025 }
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = false

ActionMailer::Base.smtp_settings = {
:address => 'smtp.gmail.com',
:domain => 'mail.google.com',
:port => 587,
:user_name => '[email protected]',
:password => 'your_password',
:authentication => :plain,
:enable_starttls_auto => true
}

Gravatar

Thanks for the response, @jeremychristopherbray:disqus. The issue isn't with the app sending messages out through action_mailer, it's that I'm unable to send and receive email for all business related email addresses. I use Google Apps and the account is essentially useless right now as I believe that traffic is not being routed to the Mail Subdomain / MX Records.

To the above point, I'm more a Jr. Dev than anything, so if I misunderstood your response, my apology.


Gravatar
Hello Chris Oliver,
Its a great video. I need some help regarding the same so I have the scenario with user and company. User has one company and that company has many orientations. And there are only 6 orientations possible. So i need to generate 6 sub domains with specific company. So can you please give me some brief in this scenario. 

Gravatar
Hi @Chris Oliver,
it's a great video as usual, but i have one question which is how to handle one-to-many or many-to-many relations between the general aka public schema and the other schemes ? 




Login or create an account to join the conversation.