4

I have a rails 4.2 multi-tenant app using the Apartment gem which has been awesome.

Each company has their own subdomain. I'm using a custom "elevator" which looks at the full request host to determine which "Tenant" should be loaded.

When I create a new company I have an after_create hook to create the new tenant with the proper request host.

This always seems to require a restart of the server both in development and production otherwise I get a Tenant Not Found error.

It's using sqlite in development and postgres in production.

Do I really have to restart the server each time I create a new tenant? Is there an automated way to do this? Maybe just reloading the initializer will work, but I'm not sure how to do that/if that's possible?

I have been messing around with this for a month and haven't been able to find a solution that works. Please help!

initializers/apartment.rb

require 'apartment/elevators/host_hash'

config.tenant_names = lambda { Company.pluck :request_host }

Rails.application.config.middleware.use 'Apartment::Elevators::HostHash', Company.full_hosts_hash

initializers/host_hash.rb

require 'apartment/elevators/generic'

module Apartment
    module Elevators
    class HostHash < Generic
      def initialize(app, hash = {}, processor = nil)
        super app, processor
        @hash = hash
      end

      def parse_tenant_name(request)
        if request.host.split('.').first == "www"
            nil
        else
            raise TenantNotFound,
              "Cannot find tenant for host #{request.host}" unless @hash.has_key?(request.host)
            @hash[request.host]
        end
      end
    end
    end
end 

Company Model

after_create :create_tenant


def self.full_hosts_hash
    Company.all.inject(Hash.new) do |hash, company|
      hash[company.request_host] = company.request_host
      hash
    end
end

private

    def create_tenant
        Apartment::Tenant.create(request_host)
    end

What ended up working

I changed the elevator configuration to get away from the HostHash one that's in the apartment gem and used a completely custom one. Mostly based off of an issue on the apartment gem github: https://github.com/influitive/apartment/issues/280

initializers/apartment.rb

Rails.application.config.middleware.use 'BaseSite::BaseElevator'

app/middleware/base_site.rb

require 'apartment/elevators/generic'

module BaseSite
    class BaseElevator < Apartment::Elevators::Generic
        def parse_tenant_name(request)
          company = Company.find_by_request_host(request.host)

          return company.request_host unless company.nil?

          fail StandardError, "No website found at #{request.host} not found"
        end
    end
end
Steve Q
  • 395
  • 5
  • 28
  • 1
    I don't have an answer to your specific question, but go ahead and start running Postgres in your dev environment. It may not solve this problem (but it might!) but its best practice for your dev env. to mimic your prod env. SQLite is great for prototyping, but once you app moves to prod you should go ahead a get dev in line with prod. – Mark Locklear Apr 15 '16 at 12:22
  • 1
    agreed. I should definitely do that. It's definitely on my agenda to do over the next week. This was a side project turned production app. – Steve Q Apr 15 '16 at 15:50
  • Would be helpful to see Elevator code. As Lumbee pointed out, you should go ahead and change your development database to PostgreSQL. This is especially true when using the Apartment gem. When you create a new Tenant, Apartment sets up a new schema for that Tenant in the database and in SQLite it literally creates a new database for records pertaining to the new Tenant. This can be a pain to get working manually, which is the beauty of Apartment. **Chris Oliver at GoRails** has an great example that simplifies the concept here: https://gorails.com/episodes/multitenancy-with-apartment – B. Bulpett Apr 16 '16 at 16:31
  • 1
    Also, how is your middleware set up? Can you show your *config/initializers/apartment.rb* please? It would help to see how exactly you're generating subdomains. For instance, the above example at GoRails lets the user pick a subdomain and plucks that to be used as nae of tenant. Also a directive to use `Apartment::Elevators::Subdomain` which will allow you to intercept request and send the user to whatever subdomain, etc. – B. Bulpett Apr 16 '16 at 16:50
  • @B.Bulpett sorry for the slow response. Was away for the weekend. I followed the gorails episode when I first set this up. I added in the code so you could see that. One difference from the Gorails episode is that I use a custom elevator called host_hash (gotten from the aparment gem readme and a little help to customize it in a github issue there.) I'm thinking right now the problem is that these things are in initializers which are only loaded once so I have restart the server every time I create a new tenant. But I'm not sure if that's right and if I am right how to fix it. Any ideas? – Steve Q Apr 17 '16 at 23:29
  • You can add your initializer code to `lib` and auto load lib as explained here : http://www.hemju.com/index.php/2010/09/rails-3-quicktip-autoload-lib-directory-including-all-subdirectories/ – Muhammad Ali Apr 18 '16 at 11:12

1 Answers1

1

I think the problem could be that your host_hash.rb lives in the initializers directory. Shouldn't it be in a folder called "middleware"?, as per the Apartment gem ReadME you referenced in your comment. In that example they used app/middleware/my_custom_elevator.rb. Perhaps yours might look like app/middleware/host_hash.rb?

Right now the file is in initializers, so it's loading from there. But your apartment.rb references it by Rails.application.config.middleware.use. Just a hunch but in addition to loading it initially, it may be looking for it in a nonexistent middleware folder. I'd go ahead and create app/middleware, put the file in there instead, and see what happens. Not sure but you might need to alter require paths too.

Let us know if that helps.

B. Bulpett
  • 814
  • 12
  • 27
  • 1
    Thanks! I got it working. Added how exactly I did it to the question. I'm going to accept your answer because it started me down the path that led to success. Basically changing how I found the correct Tenant (outside of an initializer) and your recommendation to move it to the app/middleware directory is what fixed it. – Steve Q Apr 19 '16 at 09:36