Skip to content

Manage PostgreSQL's schemas for a multi-tenant Rails app with ease

License

Notifications You must be signed in to change notification settings

ramontayag/storey

Repository files navigation

Build Status

Storey

Storey is used to manage multiple schemas in your multi-tenant Rails application.

Heavily inspired by the Apartment gem, Storey simplifies the implementation of managing a multi-tenant application. This simplifies things by doing away with the other implementations that Apartment has - like MysqlAdapter and managing multiple databases (instead of managing multiple schemas) which complicated development and testing.

Configuration

Typically set in an initializer: config/initializer/storey.rb

Storey.configure do |c|
  # Defines the tables that should stay available to all (ie in the public schema)
  # Note that there's currently no way to exclude tables that aren't linked to models
  # If you have any ideas on how to do this I'm open to suggestions
  c.excluded_models = %w(User Company Role Permission)

  # If set, all schemas are created with the suffix.
  # Used for obscuring the schema name - which is important when performing schema duplication.
  # c.suffix = "_suffix"

  # Defines schemas that should always stay in the search path, apart from the one you switched to.
  # c.persistent_schemas = %w(hstore)

  # If you use a connection string, set it here. When nil, it falls back to the configuration in database.yml
  c.database_url = ENV["DATABASE_URL"]
end

Switching in your Rack app

To switch in your application, you can just opt to call Storey.switch somewhere in your app. For example, in a Rails ApplicationController it would look like:

class ApplicationController
  before_action :switch_to_tenant

  def switch_to_tenant
    subdomain = request.subdomain
    Storey.switch(subdomain) if Website.exists?(subdomain: subdomain)
  end
end

There are some instances where you need to switch schemas before you get to your application. A good example is Devise. Devise uses Warden to authenticate users. Warden is inserted as a Rack application and checks to see if the user attempting to access the page signed in.

To do this, it must check the database. If your users live on separate schemas, there's a big chance that Warden will think the user does not exist. Especially in this scenario, use the Rack app found in this gem. In Rails, insert this somewhere in your application.rb or in an initializer:

Rails.application.config.middleware.
  insert_before Warden::Manager, Storey::RackSwitch

You must also define how to determine the schema to switch to. To do that, set switch_processor:

Storey.configure do |c|
  c.switch_processor = ->(env) do
    # find the schema name based on something in the env
    subdomain = find_in_env(env)
    return subdomain if Website.exists?(subdomain: schema)
  end

  # You can pass any object that responds to call and accepts one arg: the env.
  c.switch_processor = MyStoreySwitchProcessor
end

class MyStoreySwitchProcessor
  def self.call(env)
    subdomain = find_in_env(env)
    # ...
  end
end

When the result of switch_processor is a string, Storey.switch('the-string-it-returns') is called. If nil, no switching happens.

Methods

schema

Return the current schema.

Accepts options

array: true # return the schemas as an array

Usage:

Storey.schema # "\"$user\", public"
Storey.schema(array: true) ["\"$user\"", "public"]

schemas

Returns all postgres' schemas.

Accepts options:

:public => true

Usage:

Storey.schemas # defaults to :public => true
Storey.schemas(:public => true)

default_schema?

Returns true if the current schema is the default schema. Returns false otherwise. Useful for running migrations only for the public schema.

Usage:

Storey.default_schema?

create

Accepts:

String - name of schema

Usage:

Storey.create "schema_name"

drop

Accepts

String - name of schema

Usage:

Storey.drop "schema_name"

switch

Accepts

String - optional - schema name
Block - optional

If a block is passed, Storey will execute the block in the specified schema name. Then, it will switch back to the schema it was previously in.

Usage:

Storey.switch "some_other_schema"
Post.create "My new post"
Storey.switch # switch back to the original schema

Storey.switch "some_other_schema" do
  Post.create "My new post"
end

duplicate!(origin, copy)

Accepts

origin - name of old schema to copy
copy - name of new schema

Copies a schema with all data under a new name. Best used in conjunction with Storey.configuration.suffix set.

Usage:

Storey.duplicate!("original_schema", "new_schema")

Rake tasks

storey:hstore:install

Run rake storey:hstore:install to install hstore extension into the hstore schema. Ensure that 'hstore' is one of the persistent schemas.

Development

In the storey directory, after installing the gems:

  • docker-compose up db
  • `cp spec/dummy/config/database.yml{.sample,}
  • rspec spec

About

Manage PostgreSQL's schemas for a multi-tenant Rails app with ease

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages