Giter Club home page Giter Club logo

apartment's Introduction

Apartment

Gem Version Code Climate Build Status

Multitenancy for Rails and ActiveRecord

Apartment provides tools to help you deal with multiple tenants in your Rails application. If you need to have certain data sequestered based on account or company, but still allow some data to exist in a common tenant, Apartment can help.

HELP!

In order to help drive the direction of development and clean up the codebase, we'd like to take a poll on how people are currently using Apartment. If you can take 5 seconds (1 question) to answer this poll, we'd greatly appreciated it.

View Poll

Excessive Memory Issues on ActiveRecord 4.x

If you're noticing ever growing memory issues (ie growing with each tenant you add) when using Apartment, that's because there's an issue with how ActiveRecord maps Postgresql data types into AR data types. This has been patched and will be released for AR 4.2.2. It's apparently hard to backport to 4.1 unfortunately. If you're noticing high memory usage from ActiveRecord with Apartment please upgrade.

gem 'rails', '4.2.1', github: 'influitive/rails', tag: 'v4.2.1.memfix'

Installation

Rails

Add the following to your Gemfile:

gem 'apartment'

Then generate your Apartment config file using

bundle exec rails generate apartment:install

This will create a config/initializers/apartment.rb initializer file. Configure as needed using the docs below.

That's all you need to set up the Apartment libraries. If you want to switch tenants on a per-user basis, look under "Usage - Switching tenants per request", below.

NOTE: If using postgresql schemas you must use:

  • for Rails 3.1.x: Rails ~> 3.1.2, it contains a patch that makes prepared statements work with multiple schemas

Usage

Video Tutorial

How to separate your application data into different accounts or companies. GoRails #47

Creating new Tenants

Before you can switch to a new apartment tenant, you will need to create it. Whenever you need to create a new tenant, you can run the following command:

Apartment::Tenant.create('tenant_name')

If you're using the prepend environment config option or you AREN'T using Postgresql Schemas, this will create a tenant in the following format: "#{environment}_tenant_name". In the case of a sqlite database, this will be created in your 'db/' folder. With other databases, the tenant will be created as a new DB within the system.

When you create a new tenant, all migrations will be run against that tenant, so it will be up to date when create returns.

Notes on PostgreSQL

PostgreSQL works slightly differently than other databases when creating a new tenant. If you are using PostgreSQL, Apartment by default will set up a new schema and migrate into there. This provides better performance, and allows Apartment to work on systems like Heroku, which would not allow a full new database to be created.

One can optionally use the full database creation instead if they want, though this is not recommended

Switching Tenants

To switch tenants using Apartment, use the following command:

Apartment::Tenant.switch('tenant_name') do
  # ...
end

When switch is called, all requests coming to ActiveRecord will be routed to the tenant you specify (with the exception of excluded models, see below). The tenant is automatically switched back at the end of the block to what it was before.

There is also switch! which doesn't take a block, but it's recommended to use switch. To return to the default tenant, you can call switch with no arguments.

Switching Tenants per request

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.

NOTE: when switching tenants per-request, keep in mind that the order of your Rack middleware is important. See the Middleware Considerations section for more.

The initializer above will generate the appropriate code for the Subdomain elevator by default. You can see this in config/initializers/apartment.rb after running that generator. If you're not using the generator, you can specify your elevator below. Note that in this case you will need to require the elevator manually in your application.rb like so

# config/application.rb
require 'apartment/elevators/subdomain' # or 'domain', 'first_subdomain', 'host'

Switch on subdomain

In house, we use the subdomain elevator, which analyzes the subdomain of the request and switches to a tenant schema of the same name. It can be used like so:

# application.rb
module MyApplication
  class Application < Rails::Application
    config.middleware.use Apartment::Elevators::Subdomain
  end
end

If you want to exclude a domain, for example if you don't want your application to treat www like a subdomain, in an initializer in your application, you can set the following:

# config/initializers/apartment/subdomain_exclusions.rb
Apartment::Elevators::Subdomain.excluded_subdomains = ['www']

This functions much in the same way as Apartment.excluded_models. This example will prevent switching your tenant when the subdomain is www. Handy for subdomains like: "public", "www", and "admin" :)

Switch on first subdomain

To switch on the first subdomain, which analyzes the chain of subdomains of the request and switches to a tenant schema of the first name in the chain (e.g. owls.birds.animals.com would switch to "owls"). It can be used like so:

# application.rb
module MyApplication
  class Application < Rails::Application
    config.middleware.use Apartment::Elevators::FirstSubdomain
  end
end

If you want to exclude a domain, for example if you don't want your application to treat www like a subdomain, in an initializer in your application, you can set the following:

# config/initializers/apartment/subdomain_exclusions.rb
Apartment::Elevators::FirstSubdomain.excluded_subdomains = ['www']

This functions much in the same way as the Subdomain elevator. NOTE: in fact, at the time of this writing, the Subdomain and FirstSubdomain elevators both use the first subdomain (#339). If you need to switch on larger parts of a Subdomain, consider using a Custom Elevator.

Switch on domain

To switch based on full domain (excluding the 'www' subdomains and top level domains ie '.com' ) use the following:

# application.rb
module MyApplication
  class Application < Rails::Application
    config.middleware.use Apartment::Elevators::Domain
  end
end

Note that if you have several subdomains, then it will match on the first non-www subdomain:

Switch on full host using a hash

To switch based on full host with a hash to find corresponding tenant name use the following:

# application.rb
module MyApplication
  class Application < Rails::Application
    config.middleware.use Apartment::Elevators::HostHash, {'example.com' => 'example_tenant'}
  end
end

Switch on full host, ignoring given first subdomains

To switch based on full host to find corresponding tenant name use the following:

# application.rb
module MyApplication
  class Application < Rails::Application
    config.middleware.use Apartment::Elevators::Host
  end
end

If you want to exclude a first-subdomain, for example if you don't want your application to include www in the matching, in an initializer in your application, you can set the following:

Apartment::Elevators::Host.ignored_first_subdomains = ['www']

With the above set, these would be the results:

Custom Elevator

A Generic Elevator exists that allows you to pass a Proc (or anything that responds to call) to the middleware. This Object will be passed in an ActionDispatch::Request object when called for you to do your magic. Apartment will use the return value of this proc to switch to the appropriate tenant. Use like so:

# application.rb
module MyApplication
  class Application < Rails::Application
    # Obviously not a contrived example
    config.middleware.use Apartment::Elevators::Generic, Proc.new { |request| request.host.reverse }
  end
end

Your other option is to subclass the Generic elevator and implement your own switching mechanism. This is exactly how the other elevators work. Look at the subdomain.rb elevator to get an idea of how this should work. Basically all you need to do is subclass the generic elevator and implement your own parse_tenant_name method that will ultimately return the name of the tenant based on the request being made. It could look something like this:

# app/middleware/my_custom_elevator.rb
class MyCustomElevator < Apartment::Elevators::Generic

  # @return {String} - The tenant to switch to
  def parse_tenant_name(request)
    # request is an instance of Rack::Request

    # example: look up some tenant from the db based on this request
    tenant_name = SomeModel.from_request(request)

    return tenant_name
  end
end

Middleware Considerations

In the examples above, we show the Apartment middleware being appended to the Rack stack with

Rails.application.config.middleware.use Apartment::Elevators::Subdomain

By default, the Subdomain middleware switches into a Tenant based on the subdomain at the beginning of the request, and when the request is finished, it switches back to the "public" Tenant. This happens in the Generic elevator, so all elevators that inherit from this elevator will operate as such.

It's also good to note that Apartment switches back to the "public" tenant any time an error is raised in your application.

This works okay for simple applications, but it's important to consider that you may want to maintain the "selected" tenant through different parts of the Rack application stack. For example, the Devise gem adds the Warden::Manager middleware at the end of the stack in the examples above, our Apartment::Elevators::Subdomain middleware would come after it. Trouble is, Apartment resets the selected tenant after the request is finish, so some redirects (e.g. authentication) in Devise will be run in the context of the "public" tenant. The same issue would also effect a gem such as the better_errors gem which inserts a middleware quite early in the Rails middleware stack.

To resolve this issue, consider adding the Apartment middleware at a location in the Rack stack that makes sense for your needs, e.g.:

Rails.application.config.middleware.insert_before Warden::Manager, Apartment::Elevators::Subdomain

Now work done in the Warden middleware is wrapped in the Apartment::Tenant.switch context started in the Generic elevator.

Dropping Tenants

To drop tenants using Apartment, use the following command:

Apartment::Tenant.drop('tenant_name')

When method is called, the schema is dropped and all data from itself will be lost. Be careful with this method.

Config

The following config options should be set up in a Rails initializer such as:

config/initializers/apartment.rb

To set config options, add this to your initializer:

Apartment.configure do |config|
  # set your options (described below) here
end

Excluding models

If you have some models that should always access the 'public' tenant, you can specify this by configuring Apartment using Apartment.configure. This will yield a config object for you. You can set excluded models like so:

config.excluded_models = ["User", "Company"]        # these models will not be multi-tenanted, but remain in the global (public) namespace

Note that a string representation of the model name is now the standard so that models are properly constantized when reloaded in development

Rails will always access the 'public' tenant when accessing these models, but note that tables will be created in all schemas. This may not be ideal, but its done this way because otherwise rails wouldn't be able to properly generate the schema.rb file.

NOTE - Many-To-Many Excluded Models: Since model exclusions must come from referencing a real ActiveRecord model, has_and_belongs_to_many is NOT supported. In order to achieve a many-to-many relationship for excluded models, you MUST use has_many :through. This way you can reference the join model in the excluded models configuration.

Postgresql Schemas

Providing a Different default_schema

By default, ActiveRecord will use "$user", public as the default schema_search_path. This can be modified if you wish to use a different default schema be setting:

config.default_schema = "some_other_schema"

With that set, all excluded models will use this schema as the table name prefix instead of public and reset on Apartment::Tenant will return to this schema as well.

Persistent Schemas

Apartment will normally just switch the schema_search_path whole hog to the one passed in. This can lead to problems if you want other schemas to always be searched as well. Enter persistent_schemas. You can configure a list of other schemas that will always remain in the search path, while the default gets swapped out:

config.persistent_schemas = ['some', 'other', 'schemas']

Installing Extensions into Persistent Schemas

Persistent Schemas have numerous useful applications. Hstore, for instance, is a popular storage engine for Postgresql. In order to use extensions such as Hstore, you have to install it to a specific schema and have that always in the schema_search_path.

When using extensions, keep in mind:

  • Extensions can only be installed into one schema per database, so we will want to install it into a schema that is always available in the schema_search_path
  • The schema and extension need to be created in the database before they are referenced in migrations, database.yml or apartment.
  • There does not seem to be a way to create the schema and extension using standard rails migrations.
  • Rails db:test:prepare deletes and recreates the database, so it needs to be easy for the extension schema to be recreated here.

1. Ensure the extensions schema is created when the database is created

# lib/tasks/db_enhancements.rake

####### Important information ####################
# This file is used to setup a shared extensions #
# within a dedicated schema. This gives us the   #
# advantage of only needing to enable extensions #
# in one place.                                  #
#                                                #
# This task should be run AFTER db:create but    #
# BEFORE db:migrate.                             #
##################################################

namespace :db do
  desc 'Also create shared_extensions Schema'
  task :extensions => :environment  do
    # Create Schema
    ActiveRecord::Base.connection.execute 'CREATE SCHEMA IF NOT EXISTS shared_extensions;'
    # Enable Hstore
    ActiveRecord::Base.connection.execute 'CREATE EXTENSION IF NOT EXISTS HSTORE SCHEMA shared_extensions;'
    # Enable UUID-OSSP
    ActiveRecord::Base.connection.execute 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp" SCHEMA shared_extensions;'
    # Grant usage to public
    ActiveRecord::Base.connection.execute 'GRANT usage ON SCHEMA shared_extensions to public;'
  end
end

Rake::Task["db:create"].enhance do
  Rake::Task["db:extensions"].invoke
end

Rake::Task["db:test:purge"].enhance do
  Rake::Task["db:extensions"].invoke
end

2. Ensure the schema is in Rails' default connection

Next, your database.yml file must mimic what you've set for your default and persistent schemas in Apartment. When you run migrations with Rails, it won't know about the extensions schema because Apartment isn't injected into the default connection, it's done on a per-request basis, therefore Rails doesn't know about hstore or uuid-ossp during migrations. To do so, add the following to your database.yml for all environments

# database.yml
...
adapter: postgresql
schema_search_path: "public,shared_extensions"
...

This would be for a config with default_schema set to public and persistent_schemas set to ['shared_extensions']. Note: This only works on Heroku with Rails 4.1+. For apps that use older Rails versions hosted on Heroku, the only way to properly setup is to start with a fresh PostgreSQL instance:

  1. Append ?schema_search_path=public,hstore to your DATABASE_URL environment variable, by this you don't have to revise the database.yml file (which is impossible since Heroku regenerates a completely different and immutable database.yml of its own on each deploy)
  2. Run heroku pg:psql from your command line
  3. And then DROP EXTENSION hstore; (Note: This will drop all columns that use hstore type, so proceed with caution; only do this with a fresh PostgreSQL instance)
  4. Next: CREATE SCHEMA IF NOT EXISTS hstore;
  5. Finally: CREATE EXTENSION IF NOT EXISTS hstore SCHEMA hstore; and hit enter (\q to exit)

To double check, login to the console of your Heroku app and see if Apartment.connection.schema_search_path is public,hstore

3. Ensure the schema is in the apartment config

# config/initializers/apartment.rb
...
config.persistent_schemas = ['shared_extensions']
...

Alternative: Creating schema by default

Another way that we've successfully configured hstore for our applications is to add it into the postgresql template1 database so that every tenant that gets created has it by default.

One caveat with this approach is that it can interfere with other projects in development using the same extensions and template, but not using apartment with this approach.

You can do so using a command like so

psql -U postgres -d template1 -c "CREATE SCHEMA shared_extensions AUTHORIZATION some_username;"
psql -U postgres -d template1 -c "CREATE EXTENSION IF NOT EXISTS hstore SCHEMA shared_extensions;"

The ideal setup would actually be to install hstore into the public schema and leave the public schema in the search_path at all times. We won't be able to do this though until public doesn't also contain the tenanted tables, which is an open issue with no real milestone to be completed. Happy to accept PR's on the matter.

Alternative: Creating new schemas by using raw SQL dumps

Apartment can be forced to use raw SQL dumps insted of schema.rb for creating new schemas. Use this when you are using some extra features in postgres that can't be represented in schema.rb, like materialized views etc.

This only applies while using postgres adapter and config.use_schemas is set to true. (Note: this option doesn't use db/structure.sql, it creates SQL dump by executing pg_dump)

Enable this option with:

config.use_sql = true

Managing Migrations

In order to migrate all of your tenants (or postgresql schemas) you need to provide a list of dbs to Apartment. You can make this dynamic by providing a Proc object to be called on migrations. This object should yield an array of string representing each tenant name. Example:

# Dynamically get tenant names to migrate
config.tenant_names = lambda{ Customer.pluck(:tenant_name) }

# Use a static list of tenant names for migrate
config.tenant_names = ['tenant1', 'tenant2']

You can then migrate your tenants using the normal rake task:

rake db:migrate

This just invokes Apartment::Tenant.migrate(#{tenant_name}) for each tenant name supplied from Apartment.tenant_names

Note that you can disable the default migrating of all tenants with db:migrate by setting Apartment.db_migrate_tenants = false in your Rakefile. Note this must be done before the rake tasks are loaded. ie. before YourApp::Application.load_tasks is called

Parallel Migrations

Apartment supports parallelizing migrations into multiple threads when you have a large number of tenants. By default, parallel migrations is turned off. You can enable this by setting parallel_migration_threads to the number of threads you want to use in your initializer.

Keep in mind that because migrations are going to access the database, the number of threads indicated here should be less than the pool size that Rails will use to connect to your database.

Handling Environments

By default, when not using postgresql schemas, Apartment will prepend the environment to the tenant name to ensure there is no conflict between your environments. This is mainly for the benefit of your development and test environments. If you wish to turn this option off in production, you could do something like:

config.prepend_environment = !Rails.env.production?

Tenants on different servers

You can store your tenants in different databases on one or more servers. To do it, specify your tenant_names as a hash, keys being the actual tenant names, values being a hash with the database configuration to use.

Example:

config.with_multi_server_setup = true
config.tenant_names = {
  'tenant1' => {
    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
  }
}
# or using a lambda:
config.tenant_names = lambda do
  Tenant.all.each_with_object({}) do |tenant, hash|
    hash[tenant.name] = tenant.db_configuration
  end
end

Background workers

See apartment-sidekiq or apartment-activejob.

Callbacks

You can execute callbacks when switching between tenants or creating a new one, Apartment provides the following callbacks:

  • before_create
  • after_create
  • before_switch
  • after_switch

You can register a callback using ActiveSupport::Callbacks the following way:

require 'apartment/adapters/abstract_adapter'

module Apartment
  module Adapters
    class AbstractAdapter
      set_callback :switch, :before do |object|
        ...
      end
    end
  end
end

Contributing

  • In both spec/dummy/config and spec/config, you will see database.yml.sample files

    • Copy them into the same directory but with the name database.yml
    • Edit them to fit your own settings
  • Rake tasks (see the Rakefile) will help you setup your dbs necessary to run tests

  • Please issue pull requests to the development branch. All development happens here, master is used for releases.

  • Ensure that your code is accompanied with tests. No code will be merged without tests

  • If you're looking to help, check out the TODO file for some upcoming changes I'd like to implement in Apartment.

License

Apartment is released under the MIT License.

apartment's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

apartment's Issues

PG::Error: ERROR: prepared statement "aX" does not exist

Hi,

Thanks for Apartment !
In production, I'm an error :

PG::Error: ERROR: prepared statement "a1" does not exist
: SELECT "groups".* FROM "groups" WHERE "groups"."group_id" = $1
F, [2013-08-26T10:08:58.509620 #8195] FATAL -- :
ActionView::Template::Error (PG::Error: ERROR: prepared statement "a1" does not exist

This code is in a before_filter in application_controller.rb

begin
Apartment::Database.switch(schema_name)
rescue
Apartment::Database.create(schema_name)
Apartment::Database.switch(schema_name)
end

I'm using ruby 1.9.3, postgresql 9.1, pg (0.14.0 or 0.16.0) , Rails 4.0.0 and Passenger + Nginx.

Regards,
Rivsc

Best practice to migrate on specific database

Since there is no mailing list, I'll post this here.

Whats the best way of forcing a migration to ONLY run on one (or more) of my tenants/schemas?

Say I have three a,b and c, And I make a migration that I only wan't to run un b.

Thanks,

PS. I'm running on postgres 9.2

Can we Use Apartment with Rails 4?

well, as Rails4 beta is here, how about using Apartment with it ?
will we be able to use it smoothly ? or the code will need to be upgraded
to suite rails4 changes from rails3.x ?

Hardecoded schema.rb

In the AbstractAdapter.import_database_schema method it is looking for a hardcoded db/schema.rb file.

However it is possible that this file does not exist (If the rails config.active_record.schema_format = :sql is used which will be very common when using hstore and hstore related indexes) and that the associated structure.sql file should be used instead to load the schema.

This issue has been copied from the old repo.
If I find the time in the near future I will see if I can fix it but do not hold your breath.

Duplicate tables & relationships between public & tenant postgres schemas

In our in-house multi-tenant system, we overwrote several of the rails db rake tasks so that we don't have duplicate tables between public and tenant postgres schemas. We dump the SQL schemas into two files (one for public, one for tenant). We also usually include public in the postgres schema search path. This allows for active record associations to just work even between the public & tenant postgres schemas.

It sounds like getting Apartment to work this way would require more than just adding public to the persistent_schemas. Also, some of the other Apartment issues sound related. Is this a configuration option that would be valuable to add?

Mongoid DB supported?

I see that it is clearly documented this gem works with Active record, however we are migrating our app to Mongoid from MySQL, and wanted to know if the same functionality would work with Mongoid DB?

Thanks and regards,
Kartik

What license?

Didn't see any mentions about the license for your gem.

Tables are being dropped in the wrong schema

I'm using Apartment with PostgreSQL's schemas. When I have Apartment configured to have public as a persisted schema:

Apartment.persisted_schemas = ["public"]

And then I call Apartment::Database.create("testing") and it proceeds to drop the tables within the public schema and re-create them within the new schema. This is not what I want to happen:

irb(main):004:0> Apartment::Database.create("testing")
   (2.0ms)  CREATE SCHEMA "testing"
   (51.8ms)  DROP TABLE "subscribem_accounts"
   (11.3ms)  CREATE TABLE "subscribem_accounts" ("id" serial primary key, "name" character varying(255), "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, "owner_id" integer, "subdomain" character varying(255)) 

The subscribem_accounts table should not be being dropped at all, imo.

How can I make Apartment stop doing this?

How to show 404 if database/schema doesnt exists?

I'm trying put the follow code in the application controller but doesnt works. Is that the right way?

#postgresql
rescue_from Apartment::SchemaNotFound do
  raise ActionController::RoutingError.new('Not Found')
end

#mysql
rescue_from Apartment::DatabaseNotFound do
  raise ActionController::RoutingError.new('Not Found')
end

connection caching using where and postgres

Hi I'm having an issue when using 'where' in a query. It appears to be returning the same results for each subsequent query on different database connections. For example:

%W{company_1 company_2}.map {|n| Apartment::Database.switch(n); User.where(:id => 100) }
# => #<User id: 100 name: "bob"> #<User id: 100 name: "bob"> 

However if I write the query using find_by_id, I get the results I expect having different names:

%W{company_1 company_2}.map {|n| Apartment::Database.switch(n); User.find_by_id(100) }
# => #<User id: 100 name: "bob"> #<User id: 100 name: "alan"> 

I have also tried calling Apartment.connection.clear_query_cache after each switch which doesn't affect the outcome. I've tried this with:

apartment 0.21.0
pg 0.14.0
rails 3.2.11

Is anyone able to reproduce this issue ? I'll try updating these gems and report back.

mysql-adapter is not supported

i'm in development mode and tried to add apartment to my app when i tried rails c it raised an exception:

/var/lib/gems/1.9.1/gems/apartment-0.21.0/lib/apartment/database.rb:42:in `rescue in adapter': The adapter `mysql_adapter` is not yet supported (RuntimeError)
    from /var/lib/gems/1.9.1/gems/apartment-0.21.0/lib/apartment/database.rb:38:in `adapter'
    from /var/lib/gems/1.9.1/gems/apartment-0.21.0/lib/apartment/database.rb:19:in `init'
    from /var/lib/gems/1.9.1/gems/apartment-0.21.0/lib/apartment/railtie.rb:26:in `block in <class:Railtie>'
    from /var/lib/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:440:in `_run__1014811277__prepare__300075444__callbacks'
    from /var/lib/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:405:in `__run_callback'
    from /var/lib/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:385:in `_run_prepare_callbacks'
    from /var/lib/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/callbacks.rb:81:in `run_callbacks'
    from /var/lib/gems/1.9.1/gems/actionpack-3.2.13/lib/action_dispatch/middleware/reloader.rb:74:in `prepare!'
    from /var/lib/gems/1.9.1/gems/actionpack-3.2.13/lib/action_dispatch/middleware/reloader.rb:48:in `prepare!'
    from /var/lib/gems/1.9.1/gems/railties-3.2.13/lib/rails/application/finisher.rb:47:in `block in <module:Finisher>'
    from /var/lib/gems/1.9.1/gems/railties-3.2.13/lib/rails/initializable.rb:30:in `instance_exec'
    from /var/lib/gems/1.9.1/gems/railties-3.2.13/lib/rails/initializable.rb:30:in `run'
    from /var/lib/gems/1.9.1/gems/railties-3.2.13/lib/rails/initializable.rb:55:in `block in run_initializers'
    from /var/lib/gems/1.9.1/gems/railties-3.2.13/lib/rails/initializable.rb:54:in `each'
    from /var/lib/gems/1.9.1/gems/railties-3.2.13/lib/rails/initializable.rb:54:in `run_initializers'
    from /var/lib/gems/1.9.1/gems/railties-3.2.13/lib/rails/application.rb:136:in `initialize!'
    from /var/lib/gems/1.9.1/gems/railties-3.2.13/lib/rails/railtie/configurable.rb:30:in `method_missing'
    from /mnt/data/projects/lawyer/config/environment.rb:5:in `<top (required)>'
    from /var/lib/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/dependencies.rb:251:in `require'
    from /var/lib/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/dependencies.rb:251:in `block in require'
    from /var/lib/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/dependencies.rb:236:in `load_dependency'
    from /var/lib/gems/1.9.1/gems/activesupport-3.2.13/lib/active_support/dependencies.rb:251:in `require'
    from /var/lib/gems/1.9.1/gems/railties-3.2.13/lib/rails/application.rb:103:in `require_environment!'
    from /var/lib/gems/1.9.1/gems/railties-3.2.13/lib/rails/commands.rb:40:in `<top (required)>'
    from script/rails:6:in `require'
    from script/rails:6:in `<main>'

my gem file as follows

source 'https://rubygems.org'

gem 'rails', '3.2.13'
gem 'sqlite3'
gem 'mysql'

# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'sass-rails',   '~> 3.2.3'
  gem 'coffee-rails', '~> 3.2.1'
  gem 'uglifier', '>= 1.0.3'
end

gem 'jquery-rails'
gem 'activeadmin'
# gem 'thin'
gem 'apartment'

any ideas? what are the supported adapters then? as i tried sqlite on windows and it said the same for it.

Apartment migration fail with postgis when we create new database

Configuration :
Rails 3.2.8
PostgreSQL 8.4
Database configuration :

development:
adapter: postgresql
database: roads
username: name
password: password
min_messages: WARNING
template: template_postgis
schema_search_path: public

I have an apartment database with postgis tables (http://postgis.refractions.net/documentation/manual-1.3/ch04.html) in public schema.

When I create a new database with apartment it runs migration and fails. It fails beacause I need access to schema public with the postgis tables and functions.

It seems like we haven't access to public schema when we run migrations. Do you know why?

apartment, threads and postgres schemas

I'm having a problem with the schema path not reflecting what I expect it to when I'm using threads. Either the schema path is not being set, or it's being changed out from under me, or I'm testing it wrong.

I'm put together the minimum code to reproduce the bug here:
https://gist.github.com/4177578#file_thread_schema_problem.rb

The gist of it is this: if I set the schema path manually, I'm OK. If I let apartment set the schema path, the results are inconsistent. Thoughts ?

Migrate new table

I create a new table, and run "rake apartment:migrate". And I get the error that the command as well try to create a table already exists.

So how to migrate a new table only to all the schemas without touching the old existing tables.

Thanks.

Duplicate Key After Bulk Data Load

I set up a pg database on Heroku with apartment, loaded in data to an apartment and now, on only that apartment, I get the following error anytime I try to create a new record:

ActiveRecord::RecordNotUnique (PG::Error: ERROR: duplicate key value violates unique constraint

Any ideas how I can go into the pg database on Heroku for this particular apartment and fix it?

Thanks for the help and for the fantastically useful gem!

Remote DB

Hi,

sorry for asking this question as it is clearly not a problem, but din't know where else to post it.

Is it possible to connect to remote DB with apartment?
I haven't yet go into details, but is seems DBs are created on the same server.
I would like to achieve exactly the same functionality but with an optional connections to remote DBs - let's say a customer, if wishes, can setup connection to its own server.

Thanks

Question about aggregation

Hello,

is there a possibility to aggregate rows from all tables which have the same name in all schemas? I have tried to set the search_path to first,second which each have a projects table but it only fetched the rows from first.

Thanks

hstore extension not being dumped to structure.sql

Hi guys

I'm not sure if this is specific for the apartment gem, but there seem to be a bug with dumping the hstore extension to structure.sql. I've followed the guidelines on how to setup hstore in an hstore-specific schema, but are having trouble preparing the test database. The problem is that structure.sql is missing CREATE EXTENSION HSTORE SCHEMA hstore. It does, however, contain the hstore schema and the table with the hstore column.

Apartment configuration:

Apartment.configure do |config|
  config.database_names = ['da', 'se', 'de']
  config.prepend_environment = false
  config.default_schema = "da"
  config.persistent_schemas = ['hstore']
end

database.yml:

development:
  adapter: postgresql
  encoding: utf8
  host: localhost
  database: my_development
  username: postgres
  password: postgres
  template: template0 # Required for UTF8 encoding
  schema_search_path: "da,hstore" # default_schema, persistent_schema
  pool: 10

test:
  adapter: postgresql
  encoding: utf8
  host: localhost
  database: my_test
  username: postgres
  password: postgres
  template: template0 # Required for UTF8 encoding
  schema_search_path: "da,hstore"
  pool: 10

$ rake apartment:migrate succeeds as usual in development.

But, $ rake db:structure:dump only dumbs the hstore schema and table with hstore column, not the extension:

--
-- PostgreSQL database dump
--

SET statement_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SET check_function_bodies = false;
SET client_min_messages = warning;

--
-- Name: da; Type: SCHEMA; Schema: -; Owner: -
--

CREATE SCHEMA da;


--
-- Name: SCHEMA da; Type: COMMENT; Schema: -; Owner: -
--

COMMENT ON SCHEMA da IS 'standard public schema';


--
-- Name: hstore; Type: SCHEMA; Schema: -; Owner: -
--

CREATE SCHEMA hstore;


SET search_path = da, pg_catalog;

--
-- Name: _final_mode(anyarray); Type: FUNCTION; Schema: da; Owner: -
--

CREATE FUNCTION _final_mode(anyarray) RETURNS anyelement
    LANGUAGE sql IMMUTABLE
    AS $_$
      SELECT a
      FROM unnest($1) a
      GROUP BY 1
      ORDER BY COUNT(1) DESC, 1
      LIMIT 1;
    $_$;


--
-- Name: mode(anyelement); Type: AGGREGATE; Schema: da; Owner: -
--

CREATE AGGREGATE mode(anyelement) (
    SFUNC = array_append,
    STYPE = anyarray,
    INITCOND = '{}',
    FINALFUNC = _final_mode
);


SET default_tablespace = '';

SET default_with_oids = false;

--
-- Name: email_subscriptions; Type: TABLE; Schema: da; Owner: -; Tablespace: 
--

CREATE TABLE email_subscriptions (
    id integer NOT NULL,
    type character varying(255),
    search hstore.hstore,
    created_at timestamp without time zone NOT NULL,
    updated_at timestamp without time zone NOT NULL
);

Naturally, this causes $ rake db:test:prepare to fail.

Is the setup guide in the readme incomplete? And how can I make the dump include the extension so that the test db can be prepared?

Thanks!

Add instructions on how to prepare RSpec

Hi!

It would be great to have a paragraph in the README on how to setup your RSpec in order to run tests under different tenants.

I'm having tons of issues and I can't get it to work.

I'm not sure where the problem lies, but mainly there are issues with creating and dropping schemas. Does DatabaseCleaner work with Apartment? How stuff should be configured in spec_helper?

Delayed job doesn't switch to the current_database

Hi,

i have a delayed job which looks like that:

class ImportJob < Struct.new(:my_datasource)
  include Apartment::Delayed::Job::Hooks

  def initialize(my_datasource)
    @database = Apartment::Database.current_database
    @my_datasource = my_datasource
  end


  def failure(job)
    DataSource.transaction do
      d = DataSource.find(my_datasource)
      d.update_attribute(:import_message, "The job could not be comleted due to a technical issue. Please contact the admin (datasource = #{my_datasource})")
      d.update_attribute(:import_status,3)
    end
  end

  def perform
    ...
  end
end

my model:

class DataSource < ActiveRecord::Base
  include Apartment::Delayed::Requirements
  ...
end

if I inspect my variables:

puts database #=> 37 (the expected value)
puts my_datasource #=> "demo" (the expected value)
puts Apartment::Database.current_database #=> "public" (?!)

So when the d = DataSource.find(my_datasource) line is executed, it is on the public schema, not not my tenant's schema.

Is this a bug or a flaw in my design?

thanks, p.

Add Activesupport to gemspec?

Hi,

Just a quick note - Apartment dies with a NoMethodError: undefined methoddelegate' for #Class:Apartment` if Activesupport isn't loaded (I've just seen this in a Padrino application where I put Apartment near the top of the Gemfile, before the framework loads Activesupport for me). I guess it'd do the same in a bare-bones Sinatra app as well.

It's not a huge deal but may save people some time if you either add Activesupport to the gemspec's dependencies or just note it in the docs.

Concurrency problem with Sidekiq

Hi

I'm having concurrency problems with the Apartment's Sidekiq hooks. Some ActiveRecord::ConnectionNotEstablished exception are raised, specially when one worker enqueues new tasks. I'd tried configuring the database pool (at database.yml) to a number bigger than the sidekiq concurrency but no effect. I'm using mysql2 database adapter.

My sidekiq config:

require 'apartment/sidekiq/client/database_middleware'
require 'apartment/sidekiq/server/database_middleware'

Sidekiq.configure_client do |config|
  config.client_middleware do |chain|
    chain.add Apartment::Sidekiq::Client::DatabaseMiddleware
  end
end

Sidekiq.configure_server do |config|
  config.server_middleware do |chain|
    chain.add Apartment::Sidekiq::Server::DatabaseMiddleware
  end
  config.client_middleware do |chain|
    chain.add Apartment::Sidekiq::Client::DatabaseMiddleware
  end
end

The hooks are working as we can see at redis register that the apartment is set::

"{\"retry\":true,\"queue\":\"default\",\"class\":\"TransmitterWorker\",\"args\":[11],\"jid\":\"0a4dfa89b25e8b209dd11f89\",\"apartment\":\"sample_development_foo\",\"error_message\":\"ActiveRecord::ConnectionNotEstablished\",\"error_class\":\"ActiveRecord::ConnectionNotEstablished\",\"failed_at\":\"2012-10-26T04:23:32Z\",\"retry_count\":0}"

When running sidekiq with one thread I have no problems.

Sometimes I even have a RecordNotFound. In this case, I noticed that the apartment was set to the default database.

Have you been able to use the hooks? Am I missing something?

Can we use apartment with SQLite3?

It seems adapter problem exitst.

I recieve error message like this when I execute rake db:migrate

** Invoke db:migrate (first_time)
** Invoke environment (first_time)
** Execute environment
rake aborted!
The adapter `sqlite3_adapter` is not yet supported

Importing apartment-created multi-schema db

Hi guys!

I've been using your gem for a couple of weeks and everything works perfectly. Thanks for that! ๐Ÿ‘

I have a little problem though, not directly related to the apartment gem. I've been using the "taps" gem to transfer db data from production to development environment and apparently it doesn't support multiple schema export. Do you know of any workarounds here? What are you guys using internally for db backups, imports/exports?

Apartment creating schemas twice

Or so it seems. I have this after_create callback on my Client model:

def create_database
  Apartment::Database.create(subdomain)
end

And when I test I get an error saying that the schema already exists:

Apartment::SchemaExists:
       The schema client1 already exists.

In a previous app, I worked around this issue with the following code:

def create_client_database
  begin
    Apartment::Database.create(self.account_name)
  rescue Apartment::SchemaExists
    return
  rescue
    self.destroy
  end
end

But I would like to avoid that now.

RSpec Test Patterns

Hi, this is not an issue. I'm having a hard time trying to make my tests passing.

Need "=>" and ":to" in lib/apartment.rb

I have a bug when I deploy apartment v0.18. It fails at line 12 in lib/apartment.rb :

delegate :connection, :establish_connection, to: :connection_class

home/luc/.rbenv/versions/1.8.7-debian/gems/gems/bundler-1.2.2/lib/bundler/runtime.rb:68:in `require': /home/luc/.rbenv/versions/1.8.7-debian/gems/gems/apartment-0.18.0/lib/apartment.rb:12: syntax error, unexpected ':', expecting kEND (SyntaxError)
delegate :connection, :establish_connection, to: :connection_class

The solution I think is :
delegate :connection, :establish_connection, :to => :connection_class

Migrations fail in rails 3.2.9

I'm just installed apartment in our appliaction, I followed the steps in your readme:

# config/initializers/apartment.rb

##
# Apartment Configuration
Apartment.configure do |config|

  # these models will not be multi-tenanted,
  # but remain in the global (public) namespace
  config.excluded_models = %w{
    ActiveRecord::SessionStore::Session
    Account
  }

  # use postgres schemas?
  config.use_postgres_schemas = true

  # configure persistent schemas (E.g. hstore )
  # config.persistent_schemas = %w{ hstore }

  # add the Rails environment to database names?
  # config.prepend_environment = true
  # config.append_environment = true

  # supply list of database names
  config.database_names = lambda{ Account.pluck :subdomain }
end

I added config.middleware.use 'Apartment::Elevators::Subdomain' to config/application.rb

However after creating a demo account (tenant) and running rake apartment:migrate I get the following message:

Migrating demo database
rake aborted!
PG::Error: ERROR:  no schema has been selected to create in
: CREATE TABLE "schema_migrations" ("version" character varying(255) NOT NULL) 
Tasks: TOP => apartment:migrate

Am I missing anything besides this? Here's a the full stack trace of the migrate task:

** Invoke apartment:migrate (first_time)
** Invoke db:migrate (first_time)
** Invoke environment (first_time)
** Execute environment
** Invoke db:load_config (first_time)
** Execute db:load_config
** Execute db:migrate
** Invoke db:_dump (first_time)
** Execute db:_dump
** Invoke db:schema:dump (first_time)
** Invoke environment 
** Invoke db:load_config 
** Execute db:schema:dump
** Execute apartment:migrate
Migrating demo database
rake aborted!
PG::Error: ERROR:  no schema has been selected to create in
: CREATE TABLE "schema_migrations" ("version" character varying(255) NOT NULL) 
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/connection_adapters/postgresql_adapter.rb:652:in `async_exec'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/connection_adapters/postgresql_adapter.rb:652:in `block in execute'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/connection_adapters/abstract_adapter.rb:280:in `block in log'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/activesupport-3.2.9/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/connection_adapters/abstract_adapter.rb:275:in `log'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/connection_adapters/postgresql_adapter.rb:651:in `execute'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/connection_adapters/abstract/schema_statements.rb:170:in `create_table'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/connection_adapters/abstract/schema_statements.rb:426:in `initialize_schema_migrations_table'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/migration.rb:663:in `initialize'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/migration.rb:570:in `new'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/migration.rb:570:in `up'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/activerecord-3.2.9/lib/active_record/migration.rb:551:in `migrate'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/apartment-0.17.3/lib/apartment/migrator.rb:10:in `block in migrate'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/apartment-0.17.3/lib/apartment/adapters/abstract_adapter.rb:63:in `process'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/apartment-0.17.3/lib/apartment/database.rb:11:in `process'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/apartment-0.17.3/lib/apartment/migrator.rb:9:in `migrate'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/apartment-0.17.3/lib/tasks/apartment.rake:8:in `block (3 levels) in <top (required)>'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/apartment-0.17.3/lib/tasks/apartment.rake:6:in `each'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/apartment-0.17.3/lib/tasks/apartment.rake:6:in `block (2 levels) in <top (required)>'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/task.rb:227:in `call'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/task.rb:227:in `block in execute'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/task.rb:222:in `each'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/task.rb:222:in `execute'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/task.rb:166:in `block in invoke_with_call_chain'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/1.9.1/monitor.rb:211:in `mon_synchronize'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/task.rb:159:in `invoke_with_call_chain'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/task.rb:152:in `invoke'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/application.rb:140:in `invoke_task'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/application.rb:98:in `block (2 levels) in top_level'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/application.rb:98:in `each'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/application.rb:98:in `block in top_level'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/application.rb:107:in `run_with_threads'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/application.rb:92:in `top_level'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/application.rb:70:in `block in run'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/application.rb:157:in `standard_exception_handling'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/lib/rake/application.rb:67:in `run'
/Users/enrique/.rbenv/versions/1.9.3-p286/lib/ruby/gems/1.9.1/gems/rake-10.0.0/bin/rake:37:in `<top (required)>'
/Users/enrique/.rbenv/versions/1.9.3-p286/bin/rake:23:in `load'
/Users/enrique/.rbenv/versions/1.9.3-p286/bin/rake:23:in `<main>'
Tasks: TOP => apartment:migrate

Problems with ActiveRecord Cache

Assuming we have the following model

class User < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :email
end

if in some peace of code I want to do something like this

['tenant1', 'tenant2'].map do |tenant|
  Apartment::Database.switch(tenant)
  User.find(1).first_name
end

I will always get the same first_name because the first time the query is executed but the second time active record caches the method invocation an retuns the first value

Seeding a specific tentant db also seeds the public schema

Hi there,

If I seed a specific tenant DB, this also causes the public schema to be seeded:

DB=testdb rake apartment:seed

The problem with that is that now I can't run it a 2nd time:

DB=testdb2 rake apartment:seed

Because I will get all sorts of constraint errors on the public schema. I have to go into the database and remove the records from the public schema.

I suppose I could have written my seeds file so it detects those issues, but it seems too much work, and I'd rather it throw an exception on duplicates so I can easily debug issues in the seeds file.

I can see why you want to keep the public schema structure in sync, but don't see why the seed data needs to be in sync as well.

Missing Database name in excluded models' many to many relationship query

I have two models

User, and Tenant connected with a join table for many to many relationshop. User and Tenant are excluded and in public db.

class User < ActiveRecord::Base
  has_and_belongs_to_many :tenants

end

class Tenant < ActiveRecord::Base
  has_and_belongs_to_many :users
end

When i query user.tenants :

SELECT `app_dev`.`tenants`.* FROM `app_dev`.`tenants` INNER JOIN `tenants_users` ON `app_dev`.`tenants`.`id` = `tenants_users`.`tenant_id` WHERE `tenants_users`.`user_id` = 1

is being executed. Note that INNER JOIN tenants_users missing db name. so it returns empty results.

workaround is to switch to public and do the query. but then it defeats the purpose of excluding models.

Sendu

Rake task should only migrate schemas specified in Apartment.database_names

When running rake apartment:migrate there is a dependency to run the normal db:migrate which will migrate the public schema.

I want to migrate the schemas in database_names only as my public (pg)schema is a different/separate schema from my apartment schemas.

Is the normal db:migrate dependency on this task necessary?

 task :migrate => 'db:migrate' do

to

 task :migrate => :environment do

Problems with getting tests passed on dev branch

Why don't the following tests pass on a fresh clone?

Failures:

  1) Apartment::Adapters::PostgresqlAdapter using schemas it should behave like a generic apartment adapter #switch should raise an error if database is invalid
     Failure/Error: expect {
       expected Apartment::ApartmentError but nothing was raised
     Shared Example Group: "a generic apartment adapter" called from ./spec/adapters/postgresql_adapter_spec.rb:20
     # ./spec/examples/generic_adapter_examples.rb:94:in `block (3 levels) in <top (required)>'

  2) Apartment::Adapters::PostgresqlAdapter using schemas it should behave like a schema based apartment adapter #switch should raise an error if schema is invalid
     Failure/Error: expect {
       expected Apartment::SchemaNotFound but nothing was raised
     Shared Example Group: "a schema based apartment adapter" called from ./spec/adapters/postgresql_adapter_spec.rb:21
     # ./spec/examples/schema_adapter_examples.rb:155:in `block (3 levels) in <top (required)>'

  3) Apartment::Migrator postgresql using schemas #migrate should connect to new db, then reset when done
     Failure/Error: Apartment::Migrator.migrate(schema_name)
       #<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter:0x007fe575c78140> received :schema_search_path= with unexpected arguments
         expected: ("public")
              got: ("db196")
     # ./lib/apartment/adapters/postgresql_adapter.rb:94:in `connect_to_new'
     # ./lib/apartment/adapters/abstract_adapter.rb:102:in `switch'
     # ./lib/apartment/adapters/abstract_adapter.rb:69:in `ensure in process'
     # ./lib/apartment/adapters/abstract_adapter.rb:69:in `process'
     # ./lib/apartment/migrator.rb:9:in `migrate'
     # ./spec/unit/migrator_spec.rb:37:in `block (5 levels) in <top (required)>'

  4) Apartment::Migrator postgresql using schemas #run up should connect to new db, then reset when done
     Failure/Error: Apartment::Migrator.run(:up, schema_name, version)
       #<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter:0x007fe578a17998> received :schema_search_path= with unexpected arguments
         expected: ("public")
              got: ("db196")
     # ./lib/apartment/adapters/postgresql_adapter.rb:94:in `connect_to_new'
     # ./lib/apartment/adapters/abstract_adapter.rb:102:in `switch'
     # ./lib/apartment/adapters/abstract_adapter.rb:69:in `ensure in process'
     # ./lib/apartment/adapters/abstract_adapter.rb:69:in `process'
     # ./lib/apartment/migrator.rb:18:in `run'
     # ./spec/unit/migrator_spec.rb:52:in `block (6 levels) in <top (required)>'

  5) Apartment::Migrator postgresql using schemas #run down should connect to new db, then reset when done
     Failure/Error: Apartment::Migrator.run(:down, schema_name, version)
       #<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter:0x007fe576df1e08> received :schema_search_path= with unexpected arguments
         expected: ("public")
              got: ("db196")
     # ./lib/apartment/adapters/postgresql_adapter.rb:94:in `connect_to_new'
     # ./lib/apartment/adapters/abstract_adapter.rb:102:in `switch'
     # ./lib/apartment/adapters/abstract_adapter.rb:69:in `ensure in process'
     # ./lib/apartment/adapters/abstract_adapter.rb:69:in `process'
     # ./lib/apartment/migrator.rb:18:in `run'
     # ./spec/unit/migrator_spec.rb:66:in `block (6 levels) in <top (required)>'

It seems like the database is not really reset after the tests are run?

Exclude tables from being created in certain schemas/databases (PostgreSQL/MySQL)

I don't know if this is a bug or a feature request bug when I use config.excluded_models = ["User", "Company"], I expect the users and companies tables to NOT be created in any schema except the public schema.

In my case, I have a a lot of models that belong only in the public schema, so I end up with a lot of empty tables in all the other schemas (beside the public one).

Hooks for delayed_job not working on Heroku?

I have an application hosted on Heroku and it looks like the hooks are not called when running a delayed_job.

My job class looks like this:

class ImportJob < Struct.new(:my_datasource)
  include Apartment::Delayed::Job::Hooks

  def initialize(my_datasource)
    @database = Apartment::Database.current_database
    @my_datasource = my_datasource
  end


  def failure(job)
    DataSource.transaction do
      d = DataSource.find(my_datasource)
      d.update_attribute(:import_message, "The job could not be comleted due to a technical issue. Please contact the admin (datasource = #{my_datasource})")
      d.update_attribute(:import_status,3)
    end
  end

  def perform
    ...
  end
end

my model:

class DataSource < ActiveRecord::Base
  include Apartment::Delayed::Requirements
  ...
end

When I inspect my variables in fail I see that Apartment:: Database.current_database always return public.

is it a bug or a flaw in my design?

thanks, p.

possible threading issue with postgres schema adapter

Hi there, I'm having so problems with switching databases in apartment when using multiple threads.

I have an example of what I'm doing (and I may very well be doing something wrong):
https://gist.github.com/4177578#file_thread_schema_problem.rb

I have 5 schemas available company_1..company_5.
I spawn 25 worker threads which stash a schema_name in a thread variable
Each thread

  • grabs a connection from the pool
  • calls Apartment::Database.perform with that name
    *waits a random amount of time and then check to see if the schema search path has been changed on us.
  • returns its connection

What I'm seeing is that the schema search path is being changed out from under me. Is the schema_search_path not thread safe ?

I'm reaching in to an area I don't really understand that well and I'm hoping someone can provide some insight.

Multi database server configuration

Hi Guys,

Does apartment work with multiple physical database servers?
If so, is there any previous experience I can follow in configuring the setup? Also, do I need to revise elevators for this situation?

Any input is appreciated!

Thanks,
Yan

Don't load schema.rb into private schema in postgres

Awesome gem! Thanks Guys.

The following method:

def create(database)
  create_database(database)

  process(database) do
    import_database_schema
    # Seed data if appropriate
    seed_data if Apartment.seed_after_create
    yield if block_given?
  end
end

in Apartment::Adapters::Abstract adapters currently loads the existing db/schema.rb and dumps the database schema into any new private schema I might create in Postgres (this #create also includes tables specified by the excluded_models config setting)

I'm trying to understand if there is currently a setting within apartment that I can use to skip the import_database_schema call (seems like there is none as per my involvement with the gem)

I would like the tables specified by excluded_models config setting to not creep into any private schemas. If anyone else out there is interested I might submit a pull request for the feature, but wanted to assess if this is even a good idea according to others?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.