apartment 0.23.2 → 0.24.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/HISTORY.md +12 -1
  3. data/README.md +16 -13
  4. data/Rakefile +6 -1
  5. data/TODO.md +9 -14
  6. data/lib/apartment.rb +17 -3
  7. data/lib/apartment/adapters/abstract_adapter.rb +53 -44
  8. data/lib/apartment/adapters/jdbc_postgresql_adapter.rb +9 -9
  9. data/lib/apartment/adapters/mysql2_adapter.rb +15 -15
  10. data/lib/apartment/adapters/postgresql_adapter.rb +26 -18
  11. data/lib/apartment/adapters/sqlite3_adapter.rb +13 -13
  12. data/lib/apartment/database.rb +2 -2
  13. data/lib/apartment/elevators/host_hash.rb +3 -3
  14. data/lib/apartment/elevators/subdomain.rb +2 -3
  15. data/lib/apartment/migrator.rb +3 -3
  16. data/lib/apartment/railtie.rb +2 -1
  17. data/lib/apartment/tasks/enhancements.rb +26 -0
  18. data/lib/apartment/version.rb +1 -1
  19. data/lib/generators/apartment/install/templates/apartment.rb +13 -8
  20. data/lib/tasks/apartment.rake +34 -38
  21. data/spec/adapters/jdbc_mysql_adapter_spec.rb +2 -4
  22. data/spec/adapters/jdbc_postgresql_adapter_spec.rb +6 -6
  23. data/spec/adapters/mysql2_adapter_spec.rb +5 -5
  24. data/spec/adapters/postgresql_adapter_spec.rb +6 -6
  25. data/spec/adapters/sqlite3_adapter_spec.rb +2 -2
  26. data/spec/database_spec.rb +2 -2
  27. data/spec/dummy/config/initializers/apartment.rb +2 -2
  28. data/spec/examples/connection_adapter_examples.rb +2 -2
  29. data/spec/examples/generic_adapter_examples.rb +15 -15
  30. data/spec/examples/schema_adapter_examples.rb +6 -6
  31. data/spec/integration/apartment_rake_integration_spec.rb +4 -4
  32. data/spec/integration/query_caching_spec.rb +2 -2
  33. data/spec/support/requirements.rb +2 -5
  34. data/spec/tasks/apartment_rake_spec.rb +8 -9
  35. data/spec/unit/config_spec.rb +17 -10
  36. data/spec/unit/elevators/subdomain_spec.rb +26 -6
  37. metadata +3 -2
@@ -15,37 +15,37 @@ module Apartment
15
15
 
16
16
  protected
17
17
 
18
- # Connect to new database
18
+ # Connect to new tenant
19
19
  # Abstract adapter will catch generic ActiveRecord error
20
20
  # Catch specific adapter errors here
21
21
  #
22
- # @param {String} database Database name
22
+ # @param {String} tenant Tenant name
23
23
  #
24
- def connect_to_new(database = nil)
24
+ def connect_to_new(tenant = nil)
25
25
  super
26
26
  rescue Mysql2::Error
27
27
  Apartment::Database.reset
28
- raise DatabaseNotFound, "Cannot find database #{environmentify(database)}"
28
+ raise DatabaseNotFound, "Cannot find tenant #{environmentify(tenant)}"
29
29
  end
30
30
  end
31
31
 
32
32
  class Mysql2SchemaAdapter < AbstractAdapter
33
- attr_reader :default_database
33
+ attr_reader :default_tenant
34
34
 
35
35
  def initialize(config)
36
36
  super
37
37
 
38
- @default_database = config[:database]
38
+ @default_tenant = config[:database]
39
39
  reset
40
40
  end
41
41
 
42
- # Reset current_database to the default_database
42
+ # Reset current_tenant to the default_tenant
43
43
  #
44
44
  def reset
45
- Apartment.connection.execute "use #{default_database}"
45
+ Apartment.connection.execute "use #{default_tenant}"
46
46
  end
47
47
 
48
- # Set the table_name to always use the default database for excluded models
48
+ # Set the table_name to always use the default tenant for excluded models
49
49
  #
50
50
  def process_excluded_models
51
51
  Apartment.excluded_models.each{ |model| process_excluded_model(model) }
@@ -53,16 +53,16 @@ module Apartment
53
53
 
54
54
  protected
55
55
 
56
- # Set schema current_database to new db
56
+ # Set schema current_tenant to new db
57
57
  #
58
- def connect_to_new(database)
59
- return reset if database.nil?
58
+ def connect_to_new(tenant)
59
+ return reset if tenant.nil?
60
60
 
61
- Apartment.connection.execute "use #{environmentify(database)}"
61
+ Apartment.connection.execute "use #{environmentify(tenant)}"
62
62
 
63
63
  rescue ActiveRecord::StatementInvalid
64
64
  Apartment::Database.reset
65
- raise DatabaseNotFound, "Cannot find database #{environmentify(database)}"
65
+ raise DatabaseNotFound, "Cannot find tenant #{environmentify(tenant)}"
66
66
  end
67
67
 
68
68
  def process_excluded_model(model)
@@ -74,7 +74,7 @@ module Apartment
74
74
  # Ensure that if a schema *was* set, we override
75
75
  table_name = klass.table_name.split('.', 2).last
76
76
 
77
- klass.table_name = "#{default_database}.#{table_name}"
77
+ klass.table_name = "#{default_tenant}.#{table_name}"
78
78
  end
79
79
  end
80
80
  end
@@ -14,6 +14,14 @@ module Apartment
14
14
  # Default adapter when not using Postgresql Schemas
15
15
  class PostgresqlAdapter < AbstractAdapter
16
16
 
17
+ def drop(tenant)
18
+ # Apartment.connection.drop_database note that drop_database will not throw an exception, so manually execute
19
+ Apartment.connection.execute(%{DROP DATABASE "#{tenant}"})
20
+
21
+ rescue *rescuable_exceptions
22
+ raise DatabaseNotFound, "The tenant #{tenant} cannot be found"
23
+ end
24
+
17
25
  private
18
26
 
19
27
  def rescue_from
@@ -30,15 +38,15 @@ module Apartment
30
38
  reset
31
39
  end
32
40
 
33
- # Drop the database schema
41
+ # Drop the tenant
34
42
  #
35
- # @param {String} database Database (schema) to drop
43
+ # @param {String} tenant Database (schema) to drop
36
44
  #
37
- def drop(database)
38
- Apartment.connection.execute(%{DROP SCHEMA "#{database}" CASCADE})
45
+ def drop(tenant)
46
+ Apartment.connection.execute(%{DROP SCHEMA "#{tenant}" CASCADE})
39
47
 
40
48
  rescue *rescuable_exceptions
41
- raise SchemaNotFound, "The schema #{database.inspect} cannot be found."
49
+ raise SchemaNotFound, "The schema #{tenant.inspect} cannot be found."
42
50
  end
43
51
 
44
52
  # Reset search path to default search_path
@@ -64,36 +72,36 @@ module Apartment
64
72
  # @return {String} default schema search path
65
73
  #
66
74
  def reset
67
- @current_database = Apartment.default_schema
75
+ @current_tenant = Apartment.default_schema
68
76
  Apartment.connection.schema_search_path = full_search_path
69
77
  end
70
78
 
71
- def current_database
72
- @current_database || Apartment.default_schema
79
+ def current_tenant
80
+ @current_tenant || Apartment.default_schema
73
81
  end
74
82
 
75
83
  protected
76
84
 
77
85
  # Set schema search path to new schema
78
86
  #
79
- def connect_to_new(database = nil)
80
- return reset if database.nil?
81
- raise ActiveRecord::StatementInvalid.new("Could not find schema #{database}") unless Apartment.connection.schema_exists? database
87
+ def connect_to_new(tenant = nil)
88
+ return reset if tenant.nil?
89
+ raise ActiveRecord::StatementInvalid.new("Could not find schema #{tenant}") unless Apartment.connection.schema_exists? tenant
82
90
 
83
- @current_database = database.to_s
91
+ @current_tenant = tenant.to_s
84
92
  Apartment.connection.schema_search_path = full_search_path
85
93
 
86
94
  rescue *rescuable_exceptions
87
- raise SchemaNotFound, "One of the following schema(s) is invalid: #{database}, #{full_search_path}"
95
+ raise SchemaNotFound, "One of the following schema(s) is invalid: #{tenant}, #{full_search_path}"
88
96
  end
89
97
 
90
98
  # Create the new schema
91
99
  #
92
- def create_tenant(database)
93
- Apartment.connection.execute(%{CREATE SCHEMA "#{database}"})
100
+ def create_tenant(tenant)
101
+ Apartment.connection.execute(%{CREATE SCHEMA "#{tenant}"})
94
102
 
95
103
  rescue *rescuable_exceptions
96
- raise SchemaExists, "The schema #{database} already exists."
104
+ raise SchemaExists, "The schema #{tenant} already exists."
97
105
  end
98
106
 
99
107
  private
@@ -103,9 +111,9 @@ module Apartment
103
111
  def full_search_path
104
112
  persistent_schemas.map(&:inspect).join(", ")
105
113
  end
106
-
114
+
107
115
  def persistent_schemas
108
- [@current_database, Apartment.persistent_schemas].flatten
116
+ [@current_tenant, Apartment.persistent_schemas].flatten
109
117
  end
110
118
  end
111
119
  end
@@ -15,38 +15,38 @@ module Apartment
15
15
  super
16
16
  end
17
17
 
18
- def drop(database)
18
+ def drop(tenant)
19
19
  raise DatabaseNotFound,
20
- "The database #{environmentify(database)} cannot be found." unless File.exists?(database_file(database))
20
+ "The tenant #{environmentify(tenant)} cannot be found." unless File.exists?(database_file(tenant))
21
21
 
22
- File.delete(database_file(database))
22
+ File.delete(database_file(tenant))
23
23
  end
24
24
 
25
- def current_database
25
+ def current_tenant
26
26
  File.basename(Apartment.connection.instance_variable_get(:@config)[:database], '.sqlite3')
27
27
  end
28
28
 
29
29
  protected
30
30
 
31
- def connect_to_new(database)
31
+ def connect_to_new(tenant)
32
32
  raise DatabaseNotFound,
33
- "The database #{environmentify(database)} cannot be found." unless File.exists?(database_file(database))
33
+ "The tenant #{environmentify(tenant)} cannot be found." unless File.exists?(database_file(tenant))
34
34
 
35
- super database_file(database)
35
+ super database_file(tenant)
36
36
  end
37
37
 
38
- def create_tenant(database)
38
+ def create_tenant(tenant)
39
39
  raise DatabaseExists,
40
- "The database #{environmentify(database)} already exists." if File.exists?(database_file(database))
40
+ "The tenant #{environmentify(tenant)} already exists." if File.exists?(database_file(tenant))
41
41
 
42
- f = File.new(database_file(database), File::CREAT)
42
+ f = File.new(database_file(tenant), File::CREAT)
43
43
  f.close
44
44
  end
45
45
 
46
- private
46
+ private
47
47
 
48
- def database_file(database)
49
- "#{@default_dir}/#{database}.sqlite3"
48
+ def database_file(tenant)
49
+ "#{@default_dir}/#{tenant}.sqlite3"
50
50
  end
51
51
  end
52
52
  end
@@ -8,7 +8,7 @@ module Apartment
8
8
  extend self
9
9
  extend Forwardable
10
10
 
11
- def_delegators :adapter, :create, :current_database, :current, :drop, :process, :process_excluded_models, :reset, :seed, :switch
11
+ def_delegators :adapter, :create, :current_tenant, :current, :current_database, :drop, :process, :process_excluded_models, :reset, :seed, :switch
12
12
 
13
13
  attr_writer :config
14
14
 
@@ -63,4 +63,4 @@ module Apartment
63
63
  @config ||= Rails.configuration.database_configuration[Rails.env].symbolize_keys
64
64
  end
65
65
  end
66
- end
66
+ end
@@ -2,8 +2,8 @@ require 'apartment/elevators/generic'
2
2
 
3
3
  module Apartment
4
4
  module Elevators
5
- # Provides a rack based db switching solution based on hosts
6
- # Uses a hash to find the corresponding database name for the host
5
+ # Provides a rack based tenant switching solution based on hosts
6
+ # Uses a hash to find the corresponding tenant name for the host
7
7
  #
8
8
  class HostHash < Generic
9
9
  def initialize(app, hash = {}, processor = nil)
@@ -13,7 +13,7 @@ module Apartment
13
13
 
14
14
  def parse_tenant_name(request)
15
15
  raise DatabaseNotFound,
16
- "Cannot find database for host #{request.host}" unless @hash.has_key?(request.host)
16
+ "Cannot find tenant for host #{request.host}" unless @hash.has_key?(request.host)
17
17
 
18
18
  @hash[request.host]
19
19
  end
@@ -37,11 +37,10 @@ module Apartment
37
37
  subdomains(host).first
38
38
  end
39
39
 
40
- # Assuming tld_length of 1, might need to make this configurable in Apartment in the future for things like .co.uk
41
- def subdomains(host, tld_length = 1)
40
+ def subdomains(host)
42
41
  return [] unless named_host?(host)
43
42
 
44
- host.split('.')[0..-(tld_length + 2)]
43
+ host.split('.')[0..-(Apartment.tld_length + 2)]
45
44
  end
46
45
 
47
46
  def named_host?(host)
@@ -10,7 +10,7 @@ module Apartment
10
10
  Database.process(database) do
11
11
  version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
12
12
 
13
- ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_path, version) do |migration|
13
+ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, version) do |migration|
14
14
  ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
15
15
  end
16
16
  end
@@ -19,14 +19,14 @@ module Apartment
19
19
  # Migrate up/down to a specific version
20
20
  def run(direction, database, version)
21
21
  Database.process(database) do
22
- ActiveRecord::Migrator.run(direction, ActiveRecord::Migrator.migrations_path, version)
22
+ ActiveRecord::Migrator.run(direction, ActiveRecord::Migrator.migrations_paths, version)
23
23
  end
24
24
  end
25
25
 
26
26
  # rollback latest migration `step` number of times
27
27
  def rollback(database, step = 1)
28
28
  Database.process(database) do
29
- ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_path, step)
29
+ ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
30
30
  end
31
31
  end
32
32
  end
@@ -13,10 +13,11 @@ module Apartment
13
13
  Apartment.configure do |config|
14
14
  config.excluded_models = []
15
15
  config.use_schemas = true
16
- config.database_names = []
16
+ config.tenant_names = []
17
17
  config.seed_after_create = false
18
18
  config.prepend_environment = false
19
19
  config.append_environment = false
20
+ config.tld_length = 1
20
21
  end
21
22
 
22
23
  ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a
@@ -0,0 +1,26 @@
1
+ # Require this file to append Apartment rake tasks to ActiveRecord db rake tasks
2
+ # Enabled by default in the initializer
3
+
4
+ Rake::Task["db:migrate"].enhance do
5
+ Rake::Task["apartment:migrate"].invoke
6
+ end
7
+
8
+ Rake::Task["db:rollback"].enhance do
9
+ Rake::Task["apartment:rollback"].invoke
10
+ end
11
+
12
+ Rake::Task["db:migrate:up"].enhance do
13
+ Rake::Task["apartment:migrate:up"].invoke
14
+ end
15
+
16
+ Rake::Task["db:migrate:down"].enhance do
17
+ Rake::Task["apartment:migrate:down"].invoke
18
+ end
19
+
20
+ Rake::Task["db:migrate:redo"].enhance do
21
+ Rake::Task["apartment:migrate:redo"].invoke
22
+ end
23
+
24
+ Rake::Task["db:seed"].enhance do
25
+ Rake::Task["apartment:seed"].invoke
26
+ end
@@ -1,3 +1,3 @@
1
1
  module Apartment
2
- VERSION = "0.23.2"
2
+ VERSION = "0.24.0"
3
3
  end
@@ -9,13 +9,15 @@ require 'apartment/elevators/subdomain'
9
9
  #
10
10
  Apartment.configure do |config|
11
11
 
12
- # these models will not be multi-tenanted,
12
+ # These models will not be multi-tenanted,
13
13
  # but remain in the global (public) namespace
14
- # Note that ActiveRecord::SessionStore::Session is just an example
15
- # you may not even use the AR Session Store, in which case you'd remove that line
16
- config.excluded_models = %w{
17
- ActiveRecord::SessionStore::Session
18
- }
14
+ #
15
+ # An example might be a Customer or Tenant model that stores each tenant information
16
+ # ex:
17
+ #
18
+ # config.excluded_models = %w{Tenant}
19
+ #
20
+ config.excluded_models = %w{}
19
21
 
20
22
  # use postgres schemas?
21
23
  config.use_schemas = true
@@ -28,8 +30,7 @@ Apartment.configure do |config|
28
30
  # config.append_environment = true
29
31
 
30
32
  # supply list of database names for migrations to run on
31
- config.database_names = lambda{ ToDo_Tenant_Or_User_Model.pluck :database }
32
-
33
+ config.tenant_names = lambda{ ToDo_Tenant_Or_User_Model.pluck :database }
33
34
  end
34
35
 
35
36
  ##
@@ -42,3 +43,7 @@ end
42
43
  # Rails.application.config.middleware.use 'Apartment::Elevators::Domain'
43
44
 
44
45
  Rails.application.config.middleware.use 'Apartment::Elevators::Subdomain'
46
+
47
+ ##
48
+ # Rake enhancements so that db:migrate etc... also runs migrations on all tenants
49
+ require 'apartment/tasks/enhancements'
@@ -2,38 +2,36 @@ require 'apartment/migrator'
2
2
 
3
3
  apartment_namespace = namespace :apartment do
4
4
 
5
- desc "Create all multi-tenant databases"
6
- task :create => 'db:migrate' do
7
- database_names.each do |db|
5
+ desc "Create all tenants"
6
+ task create: 'db:migrate' do
7
+ tenants.each do |tenant|
8
8
  begin
9
- puts("Creating #{db} database")
10
- quietly { Apartment::Database.create(db) }
9
+ puts("Creating #{tenant} tenant")
10
+ quietly { Apartment::Database.create(tenant) }
11
11
  rescue Apartment::TenantExists => e
12
12
  puts e.message
13
13
  end
14
14
  end
15
15
  end
16
16
 
17
- desc "Migrate all multi-tenant databases"
18
- task :migrate => 'db:migrate' do
19
-
20
- database_names.each do |db|
17
+ desc "Migrate all tenants"
18
+ task :migrate do
19
+ tenants.each do |tenant|
21
20
  begin
22
- puts("Migrating #{db} database")
23
- Apartment::Migrator.migrate db
21
+ puts("Migrating #{tenant} tenant")
22
+ Apartment::Migrator.migrate tenant
24
23
  rescue Apartment::TenantNotFound => e
25
24
  puts e.message
26
25
  end
27
26
  end
28
27
  end
29
28
 
30
- desc "Seed all multi-tenant databases"
31
- task :seed => 'db:seed' do
32
-
33
- database_names.each do |db|
29
+ desc "Seed all tenants"
30
+ task :seed do
31
+ tenants.each do |tenant|
34
32
  begin
35
- puts("Seeding #{db} database")
36
- Apartment::Database.process(db) do
33
+ puts("Seeding #{tenant} tenant")
34
+ Apartment::Database.process(tenant) do
37
35
  Apartment::Database.seed
38
36
  end
39
37
  rescue Apartment::TenantNotFound => e
@@ -42,14 +40,14 @@ apartment_namespace = namespace :apartment do
42
40
  end
43
41
  end
44
42
 
45
- desc "Rolls the schema back to the previous version (specify steps w/ STEP=n) across all multi-tenant dbs."
46
- task :rollback => 'db:rollback' do
43
+ desc "Rolls the migration back to the previous version (specify steps w/ STEP=n) across all tenants."
44
+ task :rollback do
47
45
  step = ENV['STEP'] ? ENV['STEP'].to_i : 1
48
46
 
49
- database_names.each do |db|
47
+ tenants.each do |tenant|
50
48
  begin
51
- puts("Rolling back #{db} database")
52
- Apartment::Migrator.rollback db, step
49
+ puts("Rolling back #{tenant} tenant")
50
+ Apartment::Migrator.rollback tenant, step
53
51
  rescue Apartment::TenantNotFound => e
54
52
  puts e.message
55
53
  end
@@ -57,39 +55,38 @@ apartment_namespace = namespace :apartment do
57
55
  end
58
56
 
59
57
  namespace :migrate do
60
-
61
- desc 'Runs the "up" for a given migration VERSION across all multi-tenant dbs.'
62
- task :up => 'db:migrate:up' do
58
+ desc 'Runs the "up" for a given migration VERSION across all tenants.'
59
+ task :up do
63
60
  version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
64
61
  raise 'VERSION is required' unless version
65
62
 
66
- database_names.each do |db|
63
+ tenants.each do |tenant|
67
64
  begin
68
- puts("Migrating #{db} database up")
69
- Apartment::Migrator.run :up, db, version
65
+ puts("Migrating #{tenant} tenant up")
66
+ Apartment::Migrator.run :up, tenant, version
70
67
  rescue Apartment::TenantNotFound => e
71
68
  puts e.message
72
69
  end
73
70
  end
74
71
  end
75
72
 
76
- desc 'Runs the "down" for a given migration VERSION across all multi-tenant dbs.'
77
- task :down => 'db:migrate:down' do
73
+ desc 'Runs the "down" for a given migration VERSION across all tenants.'
74
+ task :down do
78
75
  version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
79
76
  raise 'VERSION is required' unless version
80
77
 
81
- database_names.each do |db|
78
+ tenants.each do |tenant|
82
79
  begin
83
- puts("Migrating #{db} database down")
84
- Apartment::Migrator.run :down, db, version
80
+ puts("Migrating #{tenant} tenant down")
81
+ Apartment::Migrator.run :down, tenant, version
85
82
  rescue Apartment::TenantNotFound => e
86
83
  puts e.message
87
84
  end
88
85
  end
89
86
  end
90
87
 
91
- desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).'
92
- task :redo => 'db:migrate:redo' do
88
+ desc 'Rolls back the tenant one migration and re migrate up (options: STEP=x, VERSION=x).'
89
+ task :redo do
93
90
  if ENV['VERSION']
94
91
  apartment_namespace['migrate:down'].invoke
95
92
  apartment_namespace['migrate:up'].invoke
@@ -98,10 +95,9 @@ apartment_namespace = namespace :apartment do
98
95
  apartment_namespace['migrate'].invoke
99
96
  end
100
97
  end
101
-
102
98
  end
103
99
 
104
- def database_names
105
- ENV['DB'] ? ENV['DB'].split(',').map { |s| s.strip } : Apartment.database_names
100
+ def tenants
101
+ ENV['DB'] ? ENV['DB'].split(',').map { |s| s.strip } : Apartment.tenant_names
106
102
  end
107
103
  end