Skip to main content

38 Multitenancy with the Apartment gem

Episode 47 · March 19, 2015

Learn how to separate your application data into different accounts or companies

Multitenancy


Transcripts

In this episode we're going to cover multitenancy in rails applications. If you're not familiar with multitenancy, it's basically a way to separate or sequester your records based upon the account that's currently being logged in or visible. That can come from a multitude of different ways, and my example here is Freshbooks, if you go to a subdomain freshbooks.com, you have an account there, and you set up this subdomain when you register. You're probably familiar with this, Basecamp used to do it, and a whole bunch of other sites used to do it, and this allows them to take a look at the subdomain, and then all of the records that are ever accessed through that subdomain are users, projects, you name it, through that specific account that's attached to the subdomain. We can do that using a gem called apartment, and in a future pro episode, we're going to do this from scratch so you can understand how this works. Now, apartment is a pretty great gem, it's pretty simple, and you have a simple installation process and you basically create these tenants. These tenants would be the subdomain that you want, so you'll replace tenant name with that, so you'll grab this information from the user when they register, and then automatically create a tenant accordingly. In apartment, it actually goes the extra step and creates a separate PostgreSQL schema, or if you're using sqlite, it will create a separate database for those records, which is really nifty. This separates data out into different databases effectively, within the same system. That's really cool and something that can be a little tricky to set up from scratch. Using this gem it's really helpful to do that. Let's just get started and take a look at what we can do. I'm going to grab the apartment gem from ruby gems, copy that to clipboard, so let's create a new rails application called "Project Management".

rails new project_management

cd project_management

paste into our Gemfile

gem 'apartment', '~> 1.0.0'

bundle install

rails g apartment:install

This will create an apartment config file for us. We can open that up in the config/initalizers/apartment.rb file, and we'll see here that there's a bunch of configuration stuff for us to use. Now, you want to take a look at this and set it up for your application, so this is going to be different for everything, in one case, by default, they use subdomains to split out the tenants, but this isn't actually wired up until we modify the application.rb to add in some middleware. Reading through some of these other ones though, you'll want to take a look at this, so excluded models is going to be important. Most of the time, you're going to want your user records to be global to your application, you won't have users that necessarily belong only to only a certain subdomain. If that's the case, you'll have to set that up differently, but in this case, we're going to set up users so that they're global and they're available on any subdomain. The list of tenant names here is also important, and apartment needs to be able to look all of those up easily and the recommendation here is to pull them out of the database, so imagine we have a user model that has a subdomain attribute on it, and so the user is the one that registers and gets their own subdomain, so you would say: User.pluck :subdomain attribute off of them, this would create that array of all of the subdomains that are available, and apartment knows which subdomains to respond to on the various requests that come in. I'm actually going to comment out these excluded models section real quick, and we'll go create the users for our application right now. Let's generate a scaffold for the user model, and it will just accept an email and a subdomain, we won't do any passwords or authentication around them because this is just our example application. You could use devise for that and it would work just the same way, solong as you add the subdomain attribute on there. We should be able to exclude the user model now, and then the only other thing we need to do to set up subdomains is to add our middleware into the application.rb file there. The line that you want to grab here is the config.middleware.use, and this is going to allow you to intercept those application requests and automatically switch the tenant that's active based on the subdomain, you can do the same thing with domains or the host as well, and then if you want to build something custom to do this, so maybe you want to include both domains and subdomains and have precedents there or something you would be able to do that as well. We're going to paste in just the subdomain one here and take a look at what we've got.

rake db:migrate

We'll see that apartment is going to give us some output here sating that there's no tenants and it can't migrate for no tenants, so what this is actually saying is that every time you run a migration, it keeps the schemas in sync for the various tenants in your application. So the database is separated based on those schemas, and your application has multiple effectively, databases. Your user records are separated out nicely, and they can run on the same system, which is cool. Now that we've created our user's database, and this one is a global one to our application, we've set this up such that it's excluded from being a tenant, it's the one that controls which tenants are available, and our apartment.rb file is going to do that. If we now create one of these in the browser, let's start the rails application, and then go create a user in the browser, we're going to see that nothing is going to work for us. If we do this normally, and create a user and a subdomain, this isn't going to create a tenant in the apartment gem, and the way to do that is to run the Aparment::Tenant.create('tenant_name') method, we need that to actually run after the user is created. I'm going to have

app/models/user.rb

class User < ActiveRecord::Base 
  after_create :create_tenant

  private

  def create_tenant
    Aparment::Tenant.create('tenant_name')
  end
end

Now when we create users, apartment will know to create a tenant based on the subdomain, and we only need to do this once it's created, that will automatically happen, and the other thing they have basically here is Aparment::Tenant.switch('tenant_name). This is called automatically because we've added the middleware in the middle. This middleware is going to look up the subdomain on the user, and then it will go and switch automatically for us, so this is the only line that we need to actually add to our application to handle this, so that's pretty cool.

If we do "[email protected]" and we make a "onemonth" subdomain, we can create this user and that's all that happened. There's nothing else that's going on. The user got created, the tenant got created with aparment, and that's simply it. Now, in development, you can't use the localhost to reference subdomains and you can't use IP addresses either, so if someone registered the lvh.me domain and this actually points to 127.0.0.1. This is actually just going to work like localhost, but because it's a real domain, it allows us to test locally with subdomains, so you want to use lvh.me instead, and when you request that, you'll get the application just as you expected, but now it allows you to do subdomains at the beginning, so you could type in subdomain at the beginning, but you're going to get a "tenant not found" error. That's of course, because the subdomain named "subdomain" is not listed in the apartment config here.

aparment.rb

config.tenant_names = lambda { User.pluck :subdomain }

is called everytime that a subdomain is requested, and it looks to see if it's in this array, and User.pluck :subdomain is just going to give us onemonth right now, so if we type in onemonth here, we'll see that the application actually responds, and we have working subdomains in our app. Now, if we were to add another user, so if we do "[email protected]" and we add the "Gorails" subdomain, then I still show up on the onemonth lvh.me. If we go to the main, we show up, and then also if we go to gorails.lvh.me, it's the exact same output, and that is because these users are not scoped, they are excluded from the multitenancy of the application, so to test out the actual multitenancy here, let's generate a scaffold for project, rails g scaffold Project title, and you're going to notice here that what I did is I just generated a project without any associations, so I didn't add a user id to the project, this is because apartment is going to know which tenant we're in, and it's going to automatically scope things for us, because it's going to insert it into the separate schema, and that is going to allow us to build our application in a way that is modular like that without having to worry about it. As you can see, now when we run rake db:migrate, the existing tenants also get the migrations applied to them, so onemonth and gorails both got projects, and so did the main application. This allows your migrations to run across all these tenants, and the more tenants that you create, the more of these that will run but everyone will stay in sync that way.

Now we can go to the project section of the gorails subdomain and you'll see there's no projects, but let's create one called "screencasts" and let's create another one called "forum", and if we switch our subdomain, we should see that these projects have only stayed in the gorails section, so if we go over to onemonth subdomain, there's no projects there, and that's because they're looking in a separate database, and if you look in our rails logs, you'll see that the projects output from the queries are no different, the setup and connection to the database happen behind the scenes with apartment through that middleware that we're using, and the project.load knows to query these databases separately based upon the subdomain. That's really all we have to deal with in our application on a general level, so you've already got all of your records separated out automatically, and that means you can start building things independently for each subdomain. The one thing that you have to be careful with is that your main domain handles things appropriately. Now, it's wise not to ever use domains without www for many reasons. Some DNS related, but in multitenant applications, it's important to have www in there so that you get things that work correctly.

In this situation here, we're on the onemonth subdomain, I added a project called onemonth rails, but if we go to the normal lvh mean without a subdomain, this is going to load up the first tenant that's available, and that's something that you probably don't want to do. You don't actually want that to be the homepage of your application, so the important thing to do is to redirect the non subdomain one domain to something like www.domain. When you're in production, you want to use the www subdomain especially in multitenant applications, but we need to be able to ignore that in apartment, we don't want users to be able to register the www subdomain and then take over our site. That would be bad. Here we can add this into our initalizers section, and we can add that excluded subdomain. They recommend putting it inside the apartment folder in subdomain exclusions, but I'm just going to paste it here at the bottom of our config/initalizer/aparment.rb file. I just noticed that it adds the middleware in here for a subdomain, so we don't actually need to follow the direction that the README said of putting it inside your application.rb because it's already handled for us here.

This will allow us to exclude the subdomain, and now we can refresh this page after restarting our rails application, so refreshing this page of course we get an empty list of projects, and that means that this is going to store these projects in the main rails database. This would happen just like you're used to. Anytime you're in a subdomain, like gorails, these will be saved in the gorails database schema. They will be separated out, but you'll want to be sure to remember that the www and the other excluded subdomains that you have are going to still have these database tables in them, and these routes will still be available. We can add database or routing constraints to make the projects resource unavailable from the homepage so that they're only available inside of our tenants, that's an optional thing to do, but I'll show you how to do that right now.

Now in this application so far we've only got two resources for our database models, but we're probably going to add a lot more as we make this a full application. I'm going to create a constraints block and we'll use that here to wrap any of those routes that are going to be specifically for subdomains, so our customers will have different routes than our homepage and our main application, so if you sign up, there will be a certain amount of things you can do that you can only do there as part of signup like our marketing stuff, things like that, and then inside the subdomains, there will be a whole section that only applies to subdomains and using the application as an actual customer. We'll set these up in two different blocks here, one is going to be unconstrained, that will be the user section there, and then the constraint on subdomain will be here. I'm going to paste in this REGEX, and this is basically going to negatively match the www subdomain, so it's going to look for any subdomain that isn't www and it will match that saying that the projects resources routes are available if it's not www. Saving this we can go back to Chrome and we can refresh the www /projects and we'll get a routing error now because it no longer matches a route, but if we go to gorails.lvh.me, we'll be able to access that again. On your marketing site, you probably don't need the projects to be available, you probably want to display your landing page, your sign up process, things like that, so having a section here for subdomain only resources is kind of useful and allows you to separate your applications a little bit that way.

Another important refactoring here is that once you start to match more than just www, you're going to run into a slew of problems because this REGEX is going to be unmanageable pretty quickly, so we're going to delete that and we're going to add a class called SubdomainConstraint, and I'm just going to create it right here, but we need to create a method:

routes.rb

class SubdomainConstraint
  def self.matches?(request)
  subdomain = %w { www admin }
    request.subdomain.present && subdomains.include?(request.subdomain)
  end
end

This will be a way to refactor that and put the separate logic out somewhere else. Refreshing our rails app we'll be able to see the project still works on gorails, if we go to www, we get the routing error as we should expect. This subdomain constraint probably will be more useful to have something like the list of subdomains that you want to exclude.

This allows us to reserve some subdomains as well that don't match these projects and things like that, so you want to make sure that this is the same list as the one that you are using in apartment, but you can share that with a constant variable across your application or something like that as well.

The last thing that I want to show you is that if we open up the config/initalizers/session_store.rb file, this is where the cookies are defined in your rails application, so your session is saved to a cookie that's encrypted, and this sets the key so it's the name of the cookie. The important thing here though is that your browser is going to save the cookie by the full subdomain, and that is going to mean that you log in to gorails.lvh.me, and you won't be allowed into onemonth.lvh.me or www, and you probably want to change that so that logging in once applies to all these subdomains. The way to do that is to set the domain to the top level domain, and if you put it to lvh.me without subdomains, then this will set the cookie and the browser accepts that, you couldn't set this for someone else's domain, and your browser will know that ok, you have subdomains will allow you to set a cookie on the global domain or the subdomain specifically, so you get one or the other and you're able to share this session accross the subdomains by setting the domain here. In development you want to set it to lvh.me and in production you want it to match your production domain that you're running on, of course.

That was the rough introduction to the complicated world that is multitenant applications, there's a lot to worry about, you have to understand lots of different small things about browsers, DNS, subdomains, things like that, but all in all the apartment gem does a fantastic job to build your basic multitenant rails application, so in the next pro episode, I'm going to take a look at how to build this on a basic level from scratch and how you can implement this without database separation if that's something you're not too worried about

Discussion


Gravatar

Hi, Chris video is not loading!


Gravatar

Great job Chris.


Gravatar
Kohl Kohlbrenner on

@ChrisOliver is self.matches? called automatically?

Gravatar

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


Gravatar
Jonathan Denney on

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
malak ebrahem on

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
malak ebrahem on

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 on

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 on

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
Dave Aronson on

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
g2g0nzalez on

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
Jordano Moscoso on

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
Jordano Moscoso on

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
Ratna Vanapalli on

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 on

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. 

Login or create an account to join the conversation.