penthouse 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: