penthouse 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 89e508c4ecb8d4e9aaebf054202f87e0ef171578
4
+ data.tar.gz: d89449c14673b9a0764c82a4711018dc8db85fce
5
+ SHA512:
6
+ metadata.gz: 3c6cebc9a8e4d0d12331d4d801cbe87866c998c52695c17d5400b8102217f98c117b89bd52dff1eb23be7f09ef8e23827e38f56af4051f28904680d8500e3be0
7
+ data.tar.gz: 09bacbedcbbe99b6af2165e6501661267a0e6b7735a3234f9ef3c8c00b92385749c2b2ba9971dbe5a0e7a0c2460f46e4890c3352ff22f8fce0a743d6d9e59ee0
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ **.DS_Store
2
+ /.bundle/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /spec/support/database.yml
11
+ /tmp/
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ ryantownsend-penthouse-rubygem
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+ ruby '2.3.0'
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ Penthouse is an alternative to the excellent [Apartment gem](https://github.com/influitive/apartment) – however Penthouse is more of a framework for multi-tenancy than a library, in that it provides less out-of-the-box functionality, but should make for easier customisation.
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ ```ruby
8
+ gem 'penthouse'
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ If you're using Rails, you just need to configure an initializer at `config/initializers/penthouse.rb`
14
+
15
+ ```ruby
16
+ require 'penthouse'
17
+ # include the standard Rack app
18
+ require 'penthouse/app'
19
+ # include the automated Sidekiq integration, should you be using it
20
+ require 'penthouse/sidekiq' if defined?(Sidekiq)
21
+ # require the relevant router/runner you wish to use
22
+ require 'penthouse/routers/subdomain_router'
23
+ require 'penthouse/runners/schema_runner'
24
+
25
+ Penthouse.configure do |config|
26
+ config.router = Penthouse::Routers::SubdomainRouter
27
+ config.runner = Penthouse::Runners::SchemaRunner
28
+ end
29
+
30
+ Rails.application.config.middleware.use Penthouse::App
31
+ ```
32
+
33
+ It's advised that if you want to customise these classes, you do so by sub-classing `Penthouse::App`, `Penthouse::Routers::BaseRouter` and/or `Penthouse::Runners::BaseRunner` within this initializer.
34
+
35
+ ## Dictionary
36
+
37
+ * **Router** – this class receives a Rack request object and returns an identifier (just a string or symbol) for the tenant.
38
+ * **Runner** – this class receives the identifier (either from the router or manually switching), then looks up the tenant instance and runs the code within it.
39
+
40
+ ## Contributing
41
+
42
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ryantownsend/penthouse.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "penthouse"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,37 @@
1
+ #
2
+ # The Penthouse::App class defines a Rack middleware to be included into your
3
+ # stack before your main application is called.
4
+ #
5
+ # @example Typically in Rails you'd use:
6
+ # Rails.application.config.middleware.use Penthouse::App, router: Penthouse::Routers::BaseRouter
7
+ #
8
+ # This app uses the router to determine the tenant instance, then calls the
9
+ # application within that tenant.
10
+ #
11
+
12
+ require 'rack/request'
13
+
14
+ module Penthouse
15
+ class App
16
+ attr_accessor :app, :router, :runner
17
+ private :app=, :router=, :runner=
18
+
19
+ # @param app the Rack application
20
+ # @param router [#call] the class/proc to use as the router
21
+ # @param runner [#call] the class/proc to use as the runner
22
+ def initialize(app, router: Penthouse.configuration.router, runner: Penthouse.configuration.runner)
23
+ self.app = app
24
+ self.router = router
25
+ self.runner = runner
26
+ end
27
+
28
+ # @param env [Hash] the environment passed from Rack
29
+ # @raise [Penthouse::TenantNotFound] if the tenant cannot be found/switched to
30
+ def call(env)
31
+ request = Rack::Request.new(env)
32
+ runner.call(router.call(request)) do
33
+ app.call(env)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ #
2
+ # The Penthouse::Configuration class contains all configuration options for
3
+ # Penthouse, such as which router to use.
4
+ #
5
+ # @example
6
+ # Penthouse.configure do |config|
7
+ # config.router = Penthouse::Routers::BaseRouter
8
+ # config.runner = Penthouse::Runners::BaseRunner
9
+ # end
10
+ #
11
+
12
+ module Penthouse
13
+ class Configuration
14
+ attr_accessor :router, :runner
15
+
16
+ def initialize(router: nil, runner: nil)
17
+ self.router = router
18
+ self.runner = runner
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ #
2
+ # This class provides an abstract for the router interface. Whilst any Proc
3
+ # could be used, it's safest for people to sub-class to ensure that any future
4
+ # interface changes are catered for.
5
+ #
6
+ # A router class's responsibility in Penthouse is to receive a Rack::Request
7
+ # object from the App instance and return an identifier for that tenant
8
+ #
9
+
10
+ module Penthouse
11
+ module Routers
12
+ class BaseRouter
13
+
14
+ # Typically used by the App to return a tenant that can be switched to
15
+ # @param request [Rack::Request] The request from the Rack app, used to determine the tenant
16
+ # @return [String, Symbol] A tenant identifier
17
+ # @raise [Penthouse::TenantNotFound] if the tenant cannot be found/switched to
18
+ def self.call(request)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ #
2
+ # This router will load the tenant name based on a request's sub-domain
3
+ #
4
+
5
+ module Penthouse
6
+ module Routers
7
+ class SubdomainRouter < BaseRouter
8
+
9
+ # Determines the tenant identifier based on the sub-domain of the request
10
+ # @param request [Rack::Request] The request from the Rack app, used to determine the tenant
11
+ # @return [String, Symbol] A tenant identifier
12
+ def self.call(request)
13
+ request.host.split(".").first
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ #
2
+ # This class provides an abstract for the runner interface. Whilst any Proc
3
+ # could be used, it's easiest to sub-class then overwrite the #load_tenant
4
+ # method.
5
+ #
6
+ # A runner class's responsibility in Penthouse is to receive an identifier for
7
+ # a tenant and a block, and to execute that block within the tenant
8
+ #
9
+
10
+ module Penthouse
11
+ module Runners
12
+ class BaseRunner
13
+
14
+ # @param tenant_identifier [String, Symbol] The identifier for the tenant
15
+ # @param block [Block] The code to execute within the tenant
16
+ # @raise [Penthouse::TenantNotFound] if the tenant cannot be switched to
17
+ def self.call(tenant_identifier, &block)
18
+ load_tenant(tenant_identifier).call do |tenant|
19
+ Penthouse.with_tenant(tenant.identifier) do
20
+ block.yield(tenant)
21
+ end
22
+ end
23
+ end
24
+
25
+ # @param tenant_identifier [String, Symbol] The identifier for the tenant
26
+ # @return [Penthouse::Tenants::BaseTenant] An instance of a tenant
27
+ # @raise [Penthouse::TenantNotFound] if the tenant cannot be switched to
28
+ def self.load_tenant(tenant_identifier)
29
+ raise NotImplementedError
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ #
2
+ # This runner will simply use the SchemaTenant
3
+ #
4
+
5
+ require_relative './base_runner'
6
+ require_relative '../tenants/schema_tenant'
7
+
8
+ module Penthouse
9
+ module Runners
10
+ class SchemaRunner < BaseRunner
11
+
12
+ # @param tenant_identifier [String, Symbol] The identifier for the tenant
13
+ # @return [Penthouse::Tenants::BaseTenant] An instance of a tenant
14
+ def self.load_tenant(tenant_identifier)
15
+ Tenants::SchemaTenant.new(tenant_identifier, tenant_schema: tenant_identifier)
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ module Penthouse::Sidekiq::Middleware
2
+ class Client
3
+ def call(worker_class, item, queue, redis_pool=nil)
4
+ item['tenant'] ||= Penthouse.current_tenant
5
+ yield
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module Apartment::Sidekiq::Middleware
2
+ class Server
3
+ def call(worker_class, item, queue)
4
+ Penthouse.router.switch(item['tenant']) do
5
+ yield
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Penthouse::Sidekiq
2
+ class Railtie < Rails::Railtie
3
+ initializer "penthouse.sidekiq" do
4
+ Penthouse::Sidekiq::Middleware.run
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,30 @@
1
+ require 'sidekiq'
2
+ require 'penthouse/sidekiq/middleware/client'
3
+ require 'penthouse/sidekiq/middleware/server'
4
+
5
+ module Penthouse
6
+ module Sidekiq
7
+ module Middleware
8
+
9
+ def self.run
10
+ ::Sidekiq.configure_client do |config|
11
+ config.client_middleware do |chain|
12
+ chain.add Penthouse::Sidekiq::Middleware::Client
13
+ end
14
+ end
15
+
16
+ ::Sidekiq.configure_server do |config|
17
+ config.client_middleware do |chain|
18
+ chain.add Penthouse::Sidekiq::Middleware::Client
19
+ end
20
+
21
+ config.server_middleware do |chain|
22
+ chain.insert_before ::Sidekiq::Middleware::Server::RetryJobs, Penthouse::Sidekiq::Middleware::Server
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ require 'penthouse/sidekiq/railtie' if defined?(Rails)
@@ -0,0 +1,29 @@
1
+ #
2
+ # This class provides an abstract for the tenant interface. Whilst any Proc
3
+ # could be used, it's safest for people to sub-class to ensure that any future
4
+ # interface changes are catered for.
5
+ #
6
+ # A tenant class's responsibility is to receive a block, around which it should
7
+ # handle switching to the given tenant's configuration, ensuring that if an
8
+ # exception occurs, the configuration is reset back to the global configuration.
9
+ #
10
+ module Penthouse
11
+ module Tenants
12
+ class BaseTenant
13
+ attr_accessor :identifier
14
+ private :identifier=
15
+
16
+ # @param identifier [String, Symbol] An identifier for the tenant
17
+ def initialize(identifier)
18
+ self.identifier = identifier
19
+ end
20
+
21
+ # placeholder for the relevant tenant-switching code
22
+ # @param block [Block] The code to execute within the tenant
23
+ # @yield [BaseTenant] The current tenant instance
24
+ def call(&block)
25
+ raise NotImplementedError
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ #
2
+ # The OctopusSchemaTenant class relies upon Octopus [1], it uses the master
3
+ # database and simply switches the schema search path to allow for isolated
4
+ # data, but low overheads in terms of costs. Note: this means tenants will be
5
+ # sharing a single Postgres instance and therefore performance is shared.
6
+ #
7
+ # [1]: (https://github.com/thiagopradi/octopus)
8
+ #
9
+
10
+ require_relative './schema_tenant'
11
+ require 'octopus'
12
+
13
+ module Penthouse
14
+ module Tenants
15
+ class OctopusSchemaTenant < SchemaTenant
16
+
17
+ # ensures we're on the master Octopus shard, just updates the schema name
18
+ # with the tenant name
19
+ # @param block [Block] The code to execute within the schema
20
+ # @yield [SchemaTenant] The current tenant instance
21
+ def call(&block)
22
+ Octopus.using(:master) do
23
+ super
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,36 @@
1
+ #
2
+ # The ShardTenant class relies upon Octopus [1], it switches to a different
3
+ # shard per tenant, allowing for each tenant to have their own database, 100%
4
+ # isolated from other tenants in terms of data and performance.
5
+ #
6
+ # [1]: (https://github.com/thiagopradi/octopus)
7
+ #
8
+
9
+ require_relative './base_tenant'
10
+ require 'octopus'
11
+
12
+ module Penthouse
13
+ module Tenants
14
+ class OctopusShardTenant < BaseTenant
15
+ attr_accessor :shard
16
+ private :shard=
17
+
18
+ # @param identifier [String, Symbol] An identifier for the tenant
19
+ # @param shard [String, Symbol] the configured Octopus shard to use for this tenant
20
+ def initialize(identifer, shard:)
21
+ super(identifier)
22
+ self.shard = shard
23
+ freeze
24
+ end
25
+
26
+ # switches to the relevant Octopus shard, and processes the block
27
+ # @param block [Block] The code to execute within the connection to the shard
28
+ # @yield [ShardTenant] The current tenant instance
29
+ def call(&block)
30
+ Octopus.using(shard) do
31
+ block.yield(self)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ #
2
+ # The SchemaTenant class simply switches the schema search path to allow for
3
+ # isolated data, but low overheads in terms of costs. Note: this means tenants
4
+ # will be sharing a single Postgres instance and therefore performance is
5
+ # shared.
6
+ #
7
+
8
+ require_relative './base_tenant'
9
+
10
+ module Penthouse
11
+ module Tenants
12
+ class SchemaTenant < BaseTenant
13
+ attr_accessor :tenant_schema, :persistent_schemas, :default_schema
14
+ private :tenant_schema=, :persistent_schemas=, :default_schema=
15
+
16
+ # @param identifier [String, Symbol] An identifier for the tenant
17
+ # @param tenant_schema [String] your tenant's schema name in Postgres
18
+ # @param tenant_schema [String] your tenant's schema name in Postgres
19
+ # @param persistent_schemas [Array<String>] The schemas you always want in the search path
20
+ # @param default_schema [String] The global schema name, usually 'public'
21
+ def initialize(identifier, tenant_schema:, persistent_schemas: ["shared_extensions"], default_schema: "public")
22
+ super(identifier)
23
+ self.tenant_schema = tenant_schema
24
+ self.persistent_schemas = Array(persistent_schemas).flatten
25
+ self.default_schema = default_schema
26
+ freeze
27
+ end
28
+
29
+ # ensures we're on the master Octopus shard, just updates the schema name
30
+ # with the tenant name
31
+ # @param block [Block] The code to execute within the schema
32
+ # @yield [SchemaTenant] The current tenant instance
33
+ def call(&block)
34
+ begin
35
+ # set the search path to include the tenant
36
+ ActiveRecord::Base.connection.schema_search_path = persistent_schemas.unshift(tenant_schema).join(", ")
37
+ block.yield(self)
38
+ ensure
39
+ # reset the search path back to the default
40
+ ActiveRecord::Base.connection.schema_search_path = persistent_schemas.unshift(default_schema).join(", ")
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module Penthouse
2
+ VERSION = "0.1.0"
3
+ end
data/lib/penthouse.rb ADDED
@@ -0,0 +1,61 @@
1
+ require "penthouse/version"
2
+ require "penthouse/configuration"
3
+ require "penthouse/routers/base_router"
4
+ require "penthouse/runners/base_runner"
5
+
6
+ module Penthouse
7
+ class TenantNotFound < RuntimeError; end
8
+
9
+ class << self
10
+ # Retrieves the currently active tenant identifier
11
+ # @return [String, Symbol] the current tenant name
12
+ def tenant
13
+ Thread.current[:tenant]
14
+ end
15
+
16
+ # Sets the currently active tenant identifier
17
+ # @param tenant_identifier [String, Symbol] the identifier for the tenant
18
+ def tenant=(tenant_identifier)
19
+ Thread.current[:tenant] = tenant_identifier
20
+ end
21
+
22
+ # Similar to Penthouse.tenant=, except this will switch back after the given
23
+ # block has finished executing
24
+ # @param tenant_identifier [String, Symbol] the identifier for the tenant
25
+ # @param default_tenant [String, Symbol] the identifier for the tenant to return to
26
+ # @param block [Block] the code to execute
27
+ # @yield [String, Symbol] the identifier for the tenant
28
+ def with_tenant(tenant_identifier, default_tenant: tenant, &block)
29
+ self.tenant = tenant_identifier
30
+ block.yield(tenant_identifier)
31
+ ensure
32
+ self.tenant = default_tenant
33
+ end
34
+
35
+ # Executes the given block of code within a given tenant
36
+ # @param tenant_identifier [String, Symbol] the identifier for the tenant
37
+ # @param runner [Penthouse::Runners::BaseRunner] an optional runner to use, defaults to the one configured
38
+ # @param block [Block] the code to execute
39
+ def switch(tenant_identifier, runner: configuration.runner, &block)
40
+ runner.call(tenant_identifier, &block)
41
+ end
42
+
43
+ # Allows you to configure the router of Penthouse
44
+ # @yield [Penthouse::Configuration]
45
+ def configure(&block)
46
+ # allow the configuration by the block
47
+ block.yield(self.configuration)
48
+ # prevent modification of configuration once set
49
+ self.configuration.freeze
50
+ end
51
+
52
+ # Returns the current configuration of Penthouse
53
+ # @return [Penthouse::Configuration]
54
+ def configuration
55
+ @configuration ||= Configuration.new(
56
+ router: Routers::BaseRouter,
57
+ runner: Runners::BaseRunner
58
+ )
59
+ end
60
+ end
61
+ end
data/penthouse.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'penthouse/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'penthouse'
8
+ spec.version = Penthouse::VERSION
9
+ spec.authors = ['Ryan Townsend']
10
+ spec.email = ['ryan@ryantownsend.co.uk']
11
+
12
+ spec.summary = %q{Multi-tenancy framework. Out of the box, supports Postgres schemas and per-tenant databases}
13
+ spec.homepage = 'https://github.com/ryantownsend/penthouse'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = 'exe'
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_dependency 'bundler', '~> 1.11'
21
+
22
+ spec.add_development_dependency 'bundler', '~> 1.11'
23
+ spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'yard', '~> 0.8.7.6'
25
+ # testing
26
+ spec.add_development_dependency 'rspec', '~> 3.4.0'
27
+ spec.add_development_dependency 'simplecov', '~> 0.11.2'
28
+ # web
29
+ spec.add_development_dependency 'rack', '~> 1.6.4'
30
+ # db
31
+ spec.add_development_dependency 'ar-octopus', '~> 0.8.6'
32
+ spec.add_development_dependency 'activerecord', '~> 4.2.6'
33
+ spec.add_development_dependency 'pg'
34
+ end
metadata ADDED
@@ -0,0 +1,210 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: penthouse
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Townsend
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-03-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: yard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.8.7.6
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.8.7.6
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.4.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.4.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.11.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.11.2
97
+ - !ruby/object:Gem::Dependency
98
+ name: rack
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.6.4
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.6.4
111
+ - !ruby/object:Gem::Dependency
112
+ name: ar-octopus
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.8.6
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.8.6
125
+ - !ruby/object:Gem::Dependency
126
+ name: activerecord
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 4.2.6
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 4.2.6
139
+ - !ruby/object:Gem::Dependency
140
+ name: pg
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description:
154
+ email:
155
+ - ryan@ryantownsend.co.uk
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - ".gitignore"
161
+ - ".ruby-gemset"
162
+ - ".ruby-version"
163
+ - Gemfile
164
+ - README.md
165
+ - Rakefile
166
+ - bin/console
167
+ - bin/setup
168
+ - lib/penthouse.rb
169
+ - lib/penthouse/app.rb
170
+ - lib/penthouse/configuration.rb
171
+ - lib/penthouse/routers/base_router.rb
172
+ - lib/penthouse/routers/subdomain_router.rb
173
+ - lib/penthouse/runners/base_runner.rb
174
+ - lib/penthouse/runners/schema_runner.rb
175
+ - lib/penthouse/sidekiq.rb
176
+ - lib/penthouse/sidekiq/middleware/client.rb
177
+ - lib/penthouse/sidekiq/middleware/server.rb
178
+ - lib/penthouse/sidekiq/railtie.rb
179
+ - lib/penthouse/tenants/base_tenant.rb
180
+ - lib/penthouse/tenants/octopus_schema_tenant.rb
181
+ - lib/penthouse/tenants/octopus_shard_tenant.rb
182
+ - lib/penthouse/tenants/schema_tenant.rb
183
+ - lib/penthouse/version.rb
184
+ - penthouse.gemspec
185
+ homepage: https://github.com/ryantownsend/penthouse
186
+ licenses: []
187
+ metadata: {}
188
+ post_install_message:
189
+ rdoc_options: []
190
+ require_paths:
191
+ - lib
192
+ required_ruby_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ required_rubygems_version: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ requirements: []
203
+ rubyforge_project:
204
+ rubygems_version: 2.5.1
205
+ signing_key:
206
+ specification_version: 4
207
+ summary: Multi-tenancy framework. Out of the box, supports Postgres schemas and per-tenant
208
+ databases
209
+ test_files: []
210
+ has_rdoc: