penthouse 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 190533a0da2c8d16b425d2ffa4bde288e19a97da
4
- data.tar.gz: 69a6e1b680e9525c513e386d427de26f5b6667fd
3
+ metadata.gz: 5fdce337d0e5a5c6662f7d2fbfd2a3f14877fc7a
4
+ data.tar.gz: ca32b3019f2bdf8b105f962c8f1f40767a86cf95
5
5
  SHA512:
6
- metadata.gz: 46a2dce89a0a2577808f9bc325766a010b19f58f0fa566c414a9aaa5ba16352e08ae84f25ad0b5d8b55f689b2119ed15156654fa22203527ab5c0f58e2da130f
7
- data.tar.gz: 0a502681b521e29b05fbdb96b78b5b86821ea35924a05693557566b95ae127ede976bd866efa8e2c7df977b47ff99823aca7720a9de0eaf915c256eb92d6fb6e
6
+ metadata.gz: fbc0c2e3951d8acd63ffe18c6a2dd2d3ea4a32b508001767ce64cfbff1c30e8d63e913f0401688c157bf88b95f0c69a977a735c4cc6f1c8c3d2433283bf644f5
7
+ data.tar.gz: 2eab6ab62ea17238ab22999b6c7a0f6f8bc7228583ee06f29bef91e9e3a2676a42fc25b681d649f5b737c4b141826ca75562e07a40b2575331151be4e76d97b1
@@ -12,17 +12,25 @@
12
12
 
13
13
  module Penthouse
14
14
  class Configuration
15
- attr_accessor :router, :runner, :migrate_tenants, :tenant_identifiers
15
+ attr_accessor :router, :runner, :migrate_tenants, :db_schema_file, :tenant_identifiers
16
16
 
17
17
  # @param router [Penthouse::Routers::BaseRouter] the default router for your application to use
18
18
  # @param runner [Penthouse::Runners::BaseRunner] the default runner for your application to use
19
19
  # @param migrate_tenants [Boolean] whether you want Penthouse to automatically migrate all tenants
20
+ # @param db_schema_file [String] a path to your schema file (typically `db/schema.rb` or `db/structure.sql` in Rails)
20
21
  # @param tenant_identifiers [Proc] some code which must return an array of tenant identifiers (strings/symbols)
21
- def initialize(router: nil, runner: nil, migrate_tenants: false, tenant_identifiers: -> { raise NotImplementedError })
22
+ def initialize(router: nil, runner: nil, migrate_tenants: false, db_schema_file: nil, tenant_identifiers: -> { raise NotImplementedError })
22
23
  self.router = router
23
24
  self.runner = runner
24
25
  self.migrate_tenants = migrate_tenants
26
+ self.db_schema_file = db_schema_file
25
27
  self.tenant_identifiers = tenant_identifiers
28
+
29
+ if migrate_tenants? && !db_schema_file
30
+ raise ArgumentError, "If you want to migrate tenants, we need a path to a DB schema file"
31
+ elsif migrate_tenants? && !File.exist?(db_schema_file)
32
+ raise ArgumentError, "#{db_schema_file} is not readable"
33
+ end
26
34
  end
27
35
 
28
36
  # @return [Boolean] whether or not Penthouse should automatically migrate all tenants
@@ -11,7 +11,7 @@ module Penthouse
11
11
  module Routers
12
12
  class BaseRouter
13
13
 
14
- # Typically used by the App to return a tenant that can be switched to
14
+ # @abstract typically used by the App to receive a request and return a tenant that can be switched to
15
15
  # @param request [Rack::Request] The request from the Rack app, used to determine the tenant
16
16
  # @return [String, Symbol] A tenant identifier
17
17
  # @raise [Penthouse::TenantNotFound] if the tenant cannot be found/switched to
@@ -22,6 +22,7 @@ module Penthouse
22
22
  end
23
23
  end
24
24
 
25
+ # @abstract returns the tenant object
25
26
  # @param tenant_identifier [String, Symbol] The identifier for the tenant
26
27
  # @return [Penthouse::Tenants::BaseTenant] An instance of a tenant
27
28
  # @raise [Penthouse::TenantNotFound] if the tenant cannot be switched to
@@ -18,12 +18,22 @@ module Penthouse
18
18
  self.identifier = identifier
19
19
  end
20
20
 
21
- # placeholder for the relevant tenant-switching code
21
+ # @abstract placeholder for the relevant tenant-switching code
22
22
  # @param block [Block] The code to execute within the tenant
23
23
  # @yield [BaseTenant] The current tenant instance
24
24
  def call(&block)
25
25
  raise NotImplementedError
26
26
  end
27
+
28
+ # @abstract creates the tenant data store
29
+ def create(*)
30
+ raise NotImplementedError
31
+ end
32
+
33
+ # @abstract deletes the tenant data store
34
+ def delete(*)
35
+ raise NotImplementedError
36
+ end
27
37
  end
28
38
  end
29
39
  end
@@ -0,0 +1,31 @@
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
+ module Migratable
13
+
14
+ # @param db_schema_file [String] a path to the DB schema file to load, defaults to Penthouse.configuration.db_schema_file
15
+ def migrate(db_schema_file: Penthouse.configuration.db_schema_file)
16
+ if File.exist?(db_schema_file)
17
+ # turn off logging schemas
18
+ ActiveRecord::Schema.verbose = false
19
+ # run the migrations within this schema
20
+ call do
21
+ puts ActiveRecord::Base.connection.schema_search_path
22
+ load(db_schema_file)
23
+ end
24
+ else
25
+ raise ArgumentError, "#{db_schema_file} does not exist"
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -23,6 +23,24 @@ module Penthouse
23
23
  super
24
24
  end
25
25
  end
26
+
27
+ # creates the tenant schema within the master shard
28
+ # @see Penthouse::Tenants::SchemaTenant#create
29
+ def create(*)
30
+ call { super }
31
+ end
32
+
33
+ # drops the tenant schema within the master shard
34
+ # @see Penthouse::Tenants::SchemaTenant#delete
35
+ def delete(*)
36
+ call { super }
37
+ end
38
+
39
+ # returns whether or not the schema exists
40
+ # @see Penthouse::Tenants::SchemaTenant#exists?
41
+ def exists?(*)
42
+ call { super }
43
+ end
26
44
  end
27
45
  end
28
46
  end
@@ -8,10 +8,13 @@
8
8
 
9
9
  require_relative './base_tenant'
10
10
  require 'octopus'
11
+ require_relative './migratable'
11
12
 
12
13
  module Penthouse
13
14
  module Tenants
14
15
  class OctopusShardTenant < BaseTenant
16
+ include Migratable
17
+
15
18
  attr_accessor :shard
16
19
  private :shard=
17
20
 
@@ -6,40 +6,69 @@
6
6
  #
7
7
 
8
8
  require_relative './base_tenant'
9
+ require_relative './migratable'
9
10
 
10
11
  module Penthouse
11
12
  module Tenants
12
13
  class SchemaTenant < BaseTenant
14
+ include Migratable
15
+
13
16
  attr_accessor :tenant_schema, :persistent_schemas, :default_schema
14
17
  private :tenant_schema=, :persistent_schemas=, :default_schema=
15
18
 
16
19
  # @param identifier [String, Symbol] An identifier for the tenant
17
20
  # @param tenant_schema [String] your tenant's schema name in Postgres
18
- # @param tenant_schema [String] your tenant's schema name in Postgres
19
21
  # @param persistent_schemas [Array<String>] The schemas you always want in the search path
20
22
  # @param default_schema [String] The global schema name, usually 'public'
21
23
  def initialize(identifier, tenant_schema:, persistent_schemas: ["shared_extensions"], default_schema: "public")
22
24
  super(identifier)
23
- self.tenant_schema = tenant_schema
24
- self.persistent_schemas = Array(persistent_schemas).flatten
25
- self.default_schema = default_schema
25
+ self.tenant_schema = tenant_schema.freeze
26
+ self.persistent_schemas = Array(persistent_schemas).flatten.freeze
27
+ self.default_schema = default_schema.freeze
26
28
  freeze
27
29
  end
28
30
 
29
- # ensures we're on the master Octopus shard, just updates the schema name
30
- # with the tenant name
31
+ # switches to the tenant schema to run the block, ensuring we switch back
32
+ # afterwards, regardless of whether an exception occurs
31
33
  # @param block [Block] The code to execute within the schema
32
34
  # @yield [SchemaTenant] The current tenant instance
33
35
  def call(&block)
34
36
  begin
35
37
  # set the search path to include the tenant
36
- ActiveRecord::Base.connection.schema_search_path = persistent_schemas.unshift(tenant_schema).join(", ")
38
+ ActiveRecord::Base.connection.schema_search_path = persistent_schemas.dup.unshift(tenant_schema).join(", ")
37
39
  block.yield(self)
38
40
  ensure
39
41
  # reset the search path back to the default
40
- ActiveRecord::Base.connection.schema_search_path = persistent_schemas.unshift(default_schema).join(", ")
42
+ ActiveRecord::Base.connection.schema_search_path = persistent_schemas.dup.unshift(default_schema).join(", ")
43
+ end
44
+ end
45
+
46
+ # creates the tenant schema
47
+ # @param run_migrations [Boolean] whether or not to run migrations, defaults to Penthouse.configuration.migrate_tenants?
48
+ # @param db_schema_file [String] a path to the DB schema file to load, defaults to Penthouse.configuration.db_schema_file
49
+ def create(run_migrations: Penthouse.configuration.migrate_tenants?, db_schema_file: Penthouse.configuration.db_schema_file)
50
+ sql = ActiveRecord::Base.send(:sanitize_sql_array, ["create schema if not exists %s", tenant_schema])
51
+ ActiveRecord::Base.connection.exec_query(sql, 'Create Schema')
52
+ if !!run_migrations
53
+ migrate(db_schema_file: db_schema_file)
41
54
  end
42
55
  end
56
+
57
+ # drops the tenant schema
58
+ # @param force [Boolean] whether or not to drop the schema if not empty, defaults to true
59
+ def delete(force: true)
60
+ sql = ActiveRecord::Base.send(:sanitize_sql_array, ["drop schema if exists %s %s", tenant_schema, force ? 'cascade' : 'restrict'])
61
+ ActiveRecord::Base.connection.exec_query(sql, 'Delete Schema')
62
+ end
63
+
64
+ # returns whether or not this tenant's schema exists
65
+ # @return [Boolean] whether or not the tenant exists
66
+ def exists?
67
+ sql = ActiveRecord::Base.send(:sanitize_sql_array, ["select 1 from pg_namespace where nspname = '%s'", tenant_schema])
68
+ result = ActiveRecord::Base.connection.exec_query(sql, "Schema Exists")
69
+ !result.rows.empty?
70
+ end
71
+
43
72
  end
44
73
  end
45
74
  end
@@ -1,3 +1,3 @@
1
1
  module Penthouse
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/penthouse.rb CHANGED
@@ -47,10 +47,29 @@ module Penthouse
47
47
  # @param tenant_identifier [String, Symbol] the identifier for the tenant
48
48
  # @param runner [Penthouse::Runners::BaseRunner] an optional runner to use, defaults to the one configured
49
49
  # @param block [Block] the code to execute
50
+ # @yield [Penthouse::Tenants::BaseTenant] the tenant instance
50
51
  def switch(tenant_identifier, runner: configuration.runner, &block)
51
52
  runner.call(tenant_identifier, &block)
52
53
  end
53
54
 
55
+ # Loads the tenant and creates their data store
56
+ # @param tenant_identifier [String, Symbol] the identifier for the tenant
57
+ # @see Penthouse::Tenants::BaseTenant#delete
58
+ def create(tenant_identifier, runner: configuration.runner, **options)
59
+ switch(tenant_identifier, runner: runner) do |tenant|
60
+ tenant.create(**options)
61
+ end
62
+ end
63
+
64
+ # Loads the tenant and deletes their data store
65
+ # @param tenant_identifier [String, Symbol] the identifier for the tenant
66
+ # @see Penthouse::Tenants::BaseTenant#delete
67
+ def delete(tenant_identifier, runner: configuration.runner, **options)
68
+ switch(tenant_identifier, runner: runner) do |tenant|
69
+ tenant.delete(**options)
70
+ end
71
+ end
72
+
54
73
  # Allows you to configure the router of Penthouse
55
74
  # @yield [Penthouse::Configuration]
56
75
  def configure(&block)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: penthouse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Townsend
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-03-14 00:00:00.000000000 Z
11
+ date: 2016-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -180,6 +180,7 @@ files:
180
180
  - lib/penthouse/sidekiq/railtie.rb
181
181
  - lib/penthouse/tasks/enhancements.rb
182
182
  - lib/penthouse/tenants/base_tenant.rb
183
+ - lib/penthouse/tenants/migratable.rb
183
184
  - lib/penthouse/tenants/octopus_schema_tenant.rb
184
185
  - lib/penthouse/tenants/octopus_shard_tenant.rb
185
186
  - lib/penthouse/tenants/schema_tenant.rb