Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flexible switching with rails 5.2 #1

Closed
wants to merge 22 commits into from

Conversation

derosm2
Copy link
Owner

@derosm2 derosm2 commented Mar 18, 2019

Based off of this fork with thread safe fixes, so please review those changes as well!

lib/apartment/adapters/abstract_adapter.rb Show resolved Hide resolved
lib/apartment/adapters/abstract_adapter.rb Outdated Show resolved Hide resolved
lib/apartment/adapters/abstract_adapter.rb Outdated Show resolved Hide resolved
lib/apartment/adapters/abstract_adapter.rb Show resolved Hide resolved
lib/apartment/adapters/abstract_adapter.rb Show resolved Hide resolved
lib/apartment/adapters/abstract_adapter.rb Show resolved Hide resolved
lib/apartment/adapters/abstract_adapter.rb Outdated Show resolved Hide resolved
lib/apartment/adapters/postgresql_adapter.rb Show resolved Hide resolved
lib/apartment/tenant.rb Show resolved Hide resolved
- If the default tenant db does not exist, setup_connection_specification_name was not getting called
  - Other tenants would fail to get created as a result because the switching mechanism was not triggering
Copy link
Collaborator

@AJMiller AJMiller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although we agree it isn't ideal, this is a fair compromise that meets our needs right now. Ideally, I think we have a hardcoded connection to the postgres db that happens on init so we don't have to deal with the db not being found initially.

@muyiwaoyeniyi
Copy link

@derosm2 Moving the conversation from influitive#438 over here.

I'm running rails 5.2.3 and ruby 2.6.1.

My config file isn't set up yet as I can't run generate apartment:install. Once I installed the gem from the branch, my app is unable to start or run any rails commands. I created the config file manually and tried to set it up but it doesn't appear to make a difference.

My question really is if there is a different setup or config that I need to get this gem working or the same config options from the original repo woks? I have a situation where I need to support tenants on different hosts running postgres db.

I tried config.tenant_resolver = Apartment::Resolvers::Database and use_schema options per the documentation and that's doesn't work.

Thanks!

@muyiwaoyeniyi
Copy link

@derosm2 Just checking if I could please get some help. Thanks!

@derosm2
Copy link
Owner Author

derosm2 commented Jul 6, 2019

@derosm2 Just checking if I could please get some help. Thanks!

@muyiwaoyeniyi What issues are you seeing when you try to run the rails commands? Also are you able to use Rails 6?

@muyiwaoyeniyi
Copy link

@derosm2 Thanks for responding. I haven't tested with Rails 6. I'm running rails 5.2.3 and ruby 2.6.1. When I start my rails server, I get the error in the attachment below. It is coming from this file - abstract_adapter.rb:81 ...
Screenshot from 2019-07-06 12-45-09

When I try running - bundle exec rails generate apartment:install - I get basically the same error and the command hangs
Screenshot from 2019-07-06 12-46-27

And so I think there is a configuration you might have on your end that I'm missing which is causing the error. Could be related to this #1 (review) ?

This is what I have in my gemfile:
gem 'apartment', git: 'https://github.com/PatientWisdom/apartment.git', branch: 'flexible-switching-rails-5-2'

Thanks for the help!

@derosm2
Copy link
Owner Author

derosm2 commented Jul 6, 2019

@muyiwaoyeniyi Looks like it's entering an infinite loop because it trying to connect to the default tenant, but there isn't one. Might be worth starting with an example config from the main repo

# You can have Apartment route to the appropriate Tenant by adding some Rack middleware.
# Apartment can support many different "Elevators" that can take care of this routing to your data.
# Require whichever Elevator you're using below or none if you have a custom one.
#
# require 'apartment/elevators/generic'
# require 'apartment/elevators/domain'
require 'apartment/elevators/subdomain'
# require 'apartment/resolvers/schema'

#
# Apartment Configuration
#
Apartment.configure do |config|
  # Add any models that you do not want to be multi-tenanted, but remain in the global (public) namespace.
  # A typical example would be a Customer or Tenant model that stores each Tenant's information.
  #
  # config.excluded_models = %w{ Tenant }

  # In order to migrate all of your Tenants you need to provide a list of Tenant names to Apartment.
  # You can make this dynamic by providing a Proc object to be called on migrations.
  # This object should yield either:
  # - an array of strings representing each Tenant name.
  # - a hash which keys are tenant names, and values custom db config (must contain all key/values required in database.yml)
  #
  # config.tenant_names = lambda{ Customer.pluck(:tenant_name) }
  # config.tenant_names = ['tenant1', 'tenant2']
  # config.tenant_names = [
  #   {
  #     adapter: 'postgresql',
  #     host: 'some_server',
  #     port: 5555,
  #     database: 'postgres' # this is not the name of the tenant's db
  #                          # but the name of the database to connect to before creating the tenant's db
  #                          # mandatory in postgresql
  #     schema_search_path: '"tenant1"'
  #   },
  #   'tenant2' => {
  #     adapter:  'postgresql',
  #     database: 'postgres' # this is not the name of the tenant's db
  #                          # but the name of the database to connect to before creating the tenant's db
  #                          # mandatory in postgresql
  #     
  #   }
  # }
  #
  config.tenant_names = lambda { ToDo_Tenant_Or_User_Model.pluck :database }

  # The tenant decorator setting should be a callable which receives the tenant
  # as an argument, and returns the a modified version of the tenant name which
  # you want to use in the resolver as a database or schema name, for example.
  #
  # A typical use-case might be prepending or appending the rails environment,
  # as shown below.
  #
  # config.tenant_decorator = ->(tenant){ "#{Rails.env}_#{tenant}" }

  # The resolver is used to convert a tenant name into a full spec. The two
  # provided resolvers are Database and Schema. When you issue
  # Apartment.switch("some_tenant"){ ... }, Apartment passes "some_tenant" to
  # the selected resolver (after it's been decorated). The Database resolver
  # takes the decorated tenant name, and inserts it into the :database key of a
  # full connection specification (the full spec is whatever the database spec
  # was at Apartment initialization. The schema resolver, does the same but
  # for the :schema_search_path option in the configuration.
  #
  # config.tenant_resolver = Apartment::Resolvers::Schema
end

# Setup a custom Tenant switching middleware. The Proc should return the name of the Tenant that
# you want to switch to.
# Rails.application.config.middleware.use 'Apartment::Elevators::Generic', lambda { |request|
#   request.host.split('.').first
# }

# Rails.application.config.middleware.use 'Apartment::Elevators::Domain'
Rails.application.config.middleware.use 'Apartment::Elevators::Subdomain'
# You can have Apartment route to the appropriate Tenant by adding some Rack middleware.
# Apartment can support many different "Elevators" that can take care of this routing to your data.
# Require whichever Elevator you're using below or none if you have a custom one.
#
# require 'apartment/elevators/generic'
# require 'apartment/elevators/domain'
require 'apartment/elevators/subdomain'
# require 'apartment/resolvers/schema'

#
# Apartment Configuration
#
Apartment.configure do |config|
  # Add any models that you do not want to be multi-tenanted, but remain in the global (public) namespace.
  # A typical example would be a Customer or Tenant model that stores each Tenant's information.
  #
  # config.excluded_models = %w{ Tenant }

  # In order to migrate all of your Tenants you need to provide a list of Tenant names to Apartment.
  # You can make this dynamic by providing a Proc object to be called on migrations.
  # This object should yield either:
  # - an array of strings representing each Tenant name.
  # - a hash which keys are tenant names, and values custom db config (must contain all key/values required in database.yml)
  #
  # config.tenant_names = lambda{ Customer.pluck(:tenant_name) }
  # config.tenant_names = ['tenant1', 'tenant2']
  # config.tenant_names = [
  #   {
  #     adapter: 'postgresql',
  #     host: 'some_server',
  #     port: 5555,
  #     database: 'postgres' # this is not the name of the tenant's db
  #                          # but the name of the database to connect to before creating the tenant's db
  #                          # mandatory in postgresql
  #     schema_search_path: '"tenant1"'
  #   },
  #   'tenant2' => {
  #     adapter:  'postgresql',
  #     database: 'postgres' # this is not the name of the tenant's db
  #                          # but the name of the database to connect to before creating the tenant's db
  #                          # mandatory in postgresql
  #     
  #   }
  # }
  #
  config.tenant_names = lambda { ToDo_Tenant_Or_User_Model.pluck :database }

  # The tenant decorator setting should be a callable which receives the tenant
  # as an argument, and returns the a modified version of the tenant name which
  # you want to use in the resolver as a database or schema name, for example.
  #
  # A typical use-case might be prepending or appending the rails environment,
  # as shown below.
  #
  # config.tenant_decorator = ->(tenant){ "#{Rails.env}_#{tenant}" }

  # The resolver is used to convert a tenant name into a full spec. The two
  # provided resolvers are Database and Schema. When you issue
  # Apartment.switch("some_tenant"){ ... }, Apartment passes "some_tenant" to
  # the selected resolver (after it's been decorated). The Database resolver
  # takes the decorated tenant name, and inserts it into the :database key of a
  # full connection specification (the full spec is whatever the database spec
  # was at Apartment initialization. The schema resolver, does the same but
  # for the :schema_search_path option in the configuration.
  #
  # config.tenant_resolver = Apartment::Resolvers::Schema
end

# Setup a custom Tenant switching middleware. The Proc should return the name of the Tenant that
# you want to switch to.
# Rails.application.config.middleware.use 'Apartment::Elevators::Generic', lambda { |request|
#   request.host.split('.').first
# }

# Rails.application.config.middleware.use 'Apartment::Elevators::Domain'
Rails.application.config.middleware.use 'Apartment::Elevators::Subdomain'

@muyiwaoyeniyi
Copy link

@derosm2 I will look at it again. I believe I set it up correctly but maybe not. I'll let you know.

Thanks.

@kaspernj
Copy link

I am running out of database connections. Is there a way of always establishing a single connect for each thread and not use a connection pool when connecting to tenant databases? I have a max connections in Postgres of 800 and I experience fork issues when raising it to 1000.

@derosm2
Copy link
Owner Author

derosm2 commented Aug 15, 2019

I am running out of database connections. Is there a way of always establishing a single connect for each thread and not use a connection pool when connecting to tenant databases? I have a max connections in Postgres of 800 and I experience fork issues when raising it to 1000.

This branch should obey the pool size and reuse connections where possible (and not create more pools). Not sure I'm following your situation exactly. can you provide some more details?

@derosm2 derosm2 closed this Feb 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants