README.md in apartment-0.24.3 vs README.md in apartment-0.25.0

- old
+ new

@@ -41,11 +41,11 @@ 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: ```ruby -Apartment::Database.create('tenant_name') +Apartment::Tenant.create('tenant_name') ``` If you're using the [prepend environment](https://github.com/influitive/apartment#handling-environments) 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. @@ -65,11 +65,11 @@ ### Switching Tenants To switch tenants using Apartment, use the following command: ```ruby -Apartment::Database.switch('tenant_name') +Apartment::Tenant.switch('tenant_name') ``` 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). To return to the 'root' tenant, call switch with no arguments. @@ -177,69 +177,124 @@ > **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** +## 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: ```ruby 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::Database` will return to this schema also +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 also -**Persistent Schemas** +## 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: ```ruby config.persistent_schemas = ['some', 'other', 'schemas'] ``` -This has numerous useful applications. [Hstore](http://www.postgresql.org/docs/9.1/static/hstore.html), for instance, is a popular storage engine for Postgresql. In order to use Hstore, you have to install it to a specific schema and have that always in the `schema_search_path`. This could be achieved like so: +### Installing Extensions into Persistent Schemas +Persistent Schemas have numerous useful applications. [Hstore](http://www.postgresql.org/docs/9.1/static/hstore.html), 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 + ```ruby -# NOTE do not do this in a migration, must be done -# manually before you configure apartment with hstore -# In a rake task, or on the console... -ActiveRecord::Base.connection.execute("CREATE SCHEMA hstore; CREATE EXTENSION HSTORE SCHEMA hstore") +# lib/tasks/db_enhancements.rake -# configure Apartment to maintain the `hstore` schema in the `schema_search_path` -config.persistent_schemas = ['hstore'] -``` +####### 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. # +################################################## -There are a few caveats to be aware of when using `hstore`. First off, the hstore schema and extension creation need to be done manually *before* you reference it in any way in your migrations, database.yml or apartment. This is an unfortunate manual step, but I haven't found a way around it. You can achieve this from the command line using something like: - rails r 'ActiveRecord::Base.connection.execute("CREATE SCHEMA hstore; CREATE EXTENSION HSTORE SCHEMA hstore")' +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;' + end +end -Next, your `database.yml` file must mimic what you've set for your default and persistent schemas in Apartment. When you run migrataions with Rails, it won't know about the hstore 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` during migrations. To do so, add the following to your `database.yml` for all environments +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 migrataions 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 + ```yaml # database.yml ... adapter: postgresql -schema_search_path: "public,hstore" +schema_search_path: "public,shared_extensions" ... ``` -This would be for a config with `default_schema` set to `public` and `persistent_schemas` set to `['hstore']` +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+](https://devcenter.heroku.com/changelog-items/427). For older Rails versions Heroku regenerates a completely different `database.yml` for each deploy and your predefined `schema_search_path` will be deleted. ActiveRecord's `schema_search_path` will be the default `\"$user\",public`. +#### 3. Ensure the schema is in the apartment config +```ruby +# 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 ```bash -psql -U postgres -d template1 -c "CREATE SCHEMA hstore AUTHORIZATION some_username;" -psql -U postgres -d template1 -c "CREATE EXTENSION IF NOT EXISTS hstore SCHEMA hstore;" +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 respresented 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: +```ruby +config.use_sql = true +``` + + ### Managing Migrations In order to migrate all of your tenants (or posgresql 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: @@ -256,10 +311,10 @@ ```ruby rake db:migrate ``` -This just invokes `Apartment::Database.migrate(#{tenant_name})` for each tenant name supplied +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