apartment 0.26.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -1
- data/.ruby-version +1 -1
- data/.travis.yml +9 -0
- data/HISTORY.md +8 -0
- data/README.md +30 -7
- data/TODO.md +9 -0
- data/apartment.gemspec +9 -8
- data/lib/apartment.rb +9 -18
- data/lib/apartment/adapters/abstract_adapter.rb +63 -30
- data/lib/apartment/adapters/jdbc_mysql_adapter.rb +2 -2
- data/lib/apartment/adapters/jdbc_postgresql_adapter.rb +3 -3
- data/lib/apartment/adapters/mysql2_adapter.rb +10 -6
- data/lib/apartment/adapters/postgresql_adapter.rb +15 -21
- data/lib/apartment/adapters/sqlite3_adapter.rb +4 -4
- data/lib/apartment/deprecation.rb +13 -0
- data/lib/apartment/elevators/generic.rb +3 -2
- data/lib/apartment/elevators/host_hash.rb +1 -1
- data/lib/apartment/migrator.rb +3 -3
- data/lib/apartment/tasks/enhancements.rb +31 -21
- data/lib/apartment/tenant.rb +10 -7
- data/lib/apartment/version.rb +1 -1
- data/lib/generators/apartment/install/templates/apartment.rb +41 -22
- data/lib/tasks/apartment.rake +1 -1
- data/spec/adapters/jdbc_mysql_adapter_spec.rb +1 -1
- data/spec/adapters/jdbc_postgresql_adapter_spec.rb +2 -2
- data/spec/adapters/mysql2_adapter_spec.rb +4 -2
- data/spec/adapters/postgresql_adapter_spec.rb +3 -3
- data/spec/adapters/sqlite3_adapter_spec.rb +1 -1
- data/spec/dummy_engine/.gitignore +8 -0
- data/spec/dummy_engine/Gemfile +15 -0
- data/spec/dummy_engine/Rakefile +34 -0
- data/spec/dummy_engine/bin/rails +12 -0
- data/spec/dummy_engine/config/initializers/apartment.rb +51 -0
- data/spec/dummy_engine/dummy_engine.gemspec +24 -0
- data/spec/dummy_engine/lib/dummy_engine.rb +4 -0
- data/spec/dummy_engine/lib/dummy_engine/engine.rb +4 -0
- data/spec/dummy_engine/lib/dummy_engine/version.rb +3 -0
- data/spec/dummy_engine/test/dummy/Rakefile +6 -0
- data/spec/dummy_engine/test/dummy/config.ru +4 -0
- data/spec/dummy_engine/test/dummy/config/application.rb +22 -0
- data/spec/dummy_engine/test/dummy/config/boot.rb +5 -0
- data/spec/dummy_engine/test/dummy/config/database.yml +25 -0
- data/spec/dummy_engine/test/dummy/config/environment.rb +5 -0
- data/spec/dummy_engine/test/dummy/config/environments/development.rb +37 -0
- data/spec/dummy_engine/test/dummy/config/environments/production.rb +78 -0
- data/spec/dummy_engine/test/dummy/config/environments/test.rb +39 -0
- data/spec/dummy_engine/test/dummy/config/initializers/assets.rb +8 -0
- data/spec/dummy_engine/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy_engine/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy_engine/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy_engine/test/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy_engine/test/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy_engine/test/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy_engine/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy_engine/test/dummy/config/locales/en.yml +23 -0
- data/spec/dummy_engine/test/dummy/config/routes.rb +56 -0
- data/spec/dummy_engine/test/dummy/config/secrets.yml +22 -0
- data/spec/examples/connection_adapter_examples.rb +5 -5
- data/spec/examples/generic_adapter_examples.rb +75 -37
- data/spec/examples/schema_adapter_examples.rb +19 -24
- data/spec/integration/query_caching_spec.rb +3 -3
- data/spec/integration/use_within_an_engine_spec.rb +28 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/setup.rb +9 -9
- data/spec/{database_spec.rb → tenant_spec.rb} +20 -30
- data/spec/unit/config_spec.rb +2 -2
- data/spec/unit/elevators/domain_spec.rb +1 -1
- data/spec/unit/elevators/generic_spec.rb +2 -2
- data/spec/unit/elevators/host_hash_spec.rb +5 -5
- data/spec/unit/elevators/subdomain_spec.rb +2 -2
- data/spec/unit/migrator_spec.rb +7 -7
- metadata +104 -43
@@ -13,6 +13,12 @@ module Apartment
|
|
13
13
|
module Adapters
|
14
14
|
class Mysql2Adapter < AbstractAdapter
|
15
15
|
|
16
|
+
def initialize(config)
|
17
|
+
super
|
18
|
+
|
19
|
+
@default_tenant = config[:database]
|
20
|
+
end
|
21
|
+
|
16
22
|
protected
|
17
23
|
|
18
24
|
# Connect to new tenant
|
@@ -25,13 +31,11 @@ module Apartment
|
|
25
31
|
super
|
26
32
|
rescue Mysql2::Error
|
27
33
|
Apartment::Tenant.reset
|
28
|
-
raise
|
34
|
+
raise TenantNotFound, "Cannot find tenant #{environmentify(tenant)}"
|
29
35
|
end
|
30
36
|
end
|
31
37
|
|
32
38
|
class Mysql2SchemaAdapter < AbstractAdapter
|
33
|
-
attr_reader :default_tenant
|
34
|
-
|
35
39
|
def initialize(config)
|
36
40
|
super
|
37
41
|
|
@@ -39,7 +43,7 @@ module Apartment
|
|
39
43
|
reset
|
40
44
|
end
|
41
45
|
|
42
|
-
# Reset
|
46
|
+
# Reset current tenant to the default_tenant
|
43
47
|
#
|
44
48
|
def reset
|
45
49
|
Apartment.connection.execute "use #{default_tenant}"
|
@@ -53,7 +57,7 @@ module Apartment
|
|
53
57
|
|
54
58
|
protected
|
55
59
|
|
56
|
-
#
|
60
|
+
# Connect to new tenant
|
57
61
|
#
|
58
62
|
def connect_to_new(tenant)
|
59
63
|
return reset if tenant.nil?
|
@@ -62,7 +66,7 @@ module Apartment
|
|
62
66
|
|
63
67
|
rescue ActiveRecord::StatementInvalid
|
64
68
|
Apartment::Tenant.reset
|
65
|
-
raise
|
69
|
+
raise TenantNotFound, "Cannot find tenant #{environmentify(tenant)}"
|
66
70
|
end
|
67
71
|
|
68
72
|
def process_excluded_model(model)
|
@@ -20,7 +20,7 @@ module Apartment
|
|
20
20
|
Apartment.connection.execute(%{DROP DATABASE "#{tenant}"})
|
21
21
|
|
22
22
|
rescue *rescuable_exceptions
|
23
|
-
raise
|
23
|
+
raise TenantNotFound, "The tenant #{tenant} cannot be found"
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
@@ -47,7 +47,7 @@ module Apartment
|
|
47
47
|
Apartment.connection.execute(%{DROP SCHEMA "#{tenant}" CASCADE})
|
48
48
|
|
49
49
|
rescue *rescuable_exceptions
|
50
|
-
raise
|
50
|
+
raise TenantNotFound, "The schema #{tenant.inspect} cannot be found."
|
51
51
|
end
|
52
52
|
|
53
53
|
# Reset search path to default search_path
|
@@ -59,7 +59,7 @@ module Apartment
|
|
59
59
|
# Ensure that if a schema *was* set, we override
|
60
60
|
table_name = klass.table_name.split('.', 2).last
|
61
61
|
|
62
|
-
klass.table_name = "#{
|
62
|
+
klass.table_name = "#{default_tenant}.#{table_name}"
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
@@ -69,12 +69,12 @@ module Apartment
|
|
69
69
|
# @return {String} default schema search path
|
70
70
|
#
|
71
71
|
def reset
|
72
|
-
@
|
72
|
+
@current = default_tenant
|
73
73
|
Apartment.connection.schema_search_path = full_search_path
|
74
74
|
end
|
75
75
|
|
76
|
-
def
|
77
|
-
@
|
76
|
+
def current
|
77
|
+
@current || default_tenant
|
78
78
|
end
|
79
79
|
|
80
80
|
protected
|
@@ -85,11 +85,11 @@ module Apartment
|
|
85
85
|
return reset if tenant.nil?
|
86
86
|
raise ActiveRecord::StatementInvalid.new("Could not find schema #{tenant}") unless Apartment.connection.schema_exists? tenant
|
87
87
|
|
88
|
-
@
|
88
|
+
@current = tenant.to_s
|
89
89
|
Apartment.connection.schema_search_path = full_search_path
|
90
90
|
|
91
91
|
rescue *rescuable_exceptions
|
92
|
-
raise
|
92
|
+
raise TenantNotFound, "One of the following schema(s) is invalid: \"#{tenant}\" #{full_search_path}"
|
93
93
|
end
|
94
94
|
|
95
95
|
# Create the new schema
|
@@ -98,7 +98,7 @@ module Apartment
|
|
98
98
|
Apartment.connection.execute(%{CREATE SCHEMA "#{tenant}"})
|
99
99
|
|
100
100
|
rescue *rescuable_exceptions
|
101
|
-
raise
|
101
|
+
raise TenantExists, "The schema #{tenant} already exists."
|
102
102
|
end
|
103
103
|
|
104
104
|
private
|
@@ -110,7 +110,7 @@ module Apartment
|
|
110
110
|
end
|
111
111
|
|
112
112
|
def persistent_schemas
|
113
|
-
[@
|
113
|
+
[@current, Apartment.persistent_schemas].flatten
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
@@ -155,9 +155,9 @@ module Apartment
|
|
155
155
|
# .map! {|t| "-T #{t}"}
|
156
156
|
# .join(' ')
|
157
157
|
|
158
|
-
# `pg_dump -s -x -O -n #{
|
158
|
+
# `pg_dump -s -x -O -n #{default_tenant} #{excluded_tables} #{dbname}`
|
159
159
|
|
160
|
-
`pg_dump -s -x -O -n #{
|
160
|
+
`pg_dump -s -x -O -n #{default_tenant} #{dbname}`
|
161
161
|
end
|
162
162
|
|
163
163
|
# Dump data from schema_migrations table
|
@@ -165,7 +165,7 @@ module Apartment
|
|
165
165
|
# @return {String} raw SQL contaning inserts with data from schema_migrations
|
166
166
|
#
|
167
167
|
def pg_dump_schema_migrations_data
|
168
|
-
`pg_dump -a --inserts -t schema_migrations -n #{
|
168
|
+
`pg_dump -a --inserts -t schema_migrations -n #{default_tenant} #{dbname}`
|
169
169
|
end
|
170
170
|
|
171
171
|
# Remove "SET search_path ..." line from SQL dump and prepend search_path set to current tenant
|
@@ -173,7 +173,7 @@ module Apartment
|
|
173
173
|
# @return {String} patched raw SQL dump
|
174
174
|
#
|
175
175
|
def patch_search_path(sql)
|
176
|
-
search_path = "SET search_path = \"#{current}\", #{
|
176
|
+
search_path = "SET search_path = \"#{current}\", #{default_tenant};"
|
177
177
|
|
178
178
|
sql
|
179
179
|
.split("\n")
|
@@ -199,13 +199,7 @@ module Apartment
|
|
199
199
|
# Convenience method for current database name
|
200
200
|
#
|
201
201
|
def dbname
|
202
|
-
|
203
|
-
end
|
204
|
-
|
205
|
-
# Convenience method for the default schema
|
206
|
-
#
|
207
|
-
def default_schema
|
208
|
-
Apartment.default_schema
|
202
|
+
Apartment.connection_config[:database]
|
209
203
|
end
|
210
204
|
end
|
211
205
|
end
|
@@ -16,27 +16,27 @@ module Apartment
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def drop(tenant)
|
19
|
-
raise
|
19
|
+
raise TenantNotFound,
|
20
20
|
"The tenant #{environmentify(tenant)} cannot be found." unless File.exists?(database_file(tenant))
|
21
21
|
|
22
22
|
File.delete(database_file(tenant))
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
25
|
+
def current
|
26
26
|
File.basename(Apartment.connection.instance_variable_get(:@config)[:database], '.sqlite3')
|
27
27
|
end
|
28
28
|
|
29
29
|
protected
|
30
30
|
|
31
31
|
def connect_to_new(tenant)
|
32
|
-
raise
|
32
|
+
raise TenantNotFound,
|
33
33
|
"The tenant #{environmentify(tenant)} cannot be found." unless File.exists?(database_file(tenant))
|
34
34
|
|
35
35
|
super database_file(tenant)
|
36
36
|
end
|
37
37
|
|
38
38
|
def create_tenant(tenant)
|
39
|
-
raise
|
39
|
+
raise TenantExists,
|
40
40
|
"The tenant #{environmentify(tenant)} already exists." if File.exists?(database_file(tenant))
|
41
41
|
|
42
42
|
f = File.new(database_file(tenant), File::CREAT)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rack/request'
|
2
2
|
require 'apartment/tenant'
|
3
|
+
require 'apartment/deprecation'
|
3
4
|
|
4
5
|
module Apartment
|
5
6
|
module Elevators
|
@@ -17,7 +18,7 @@ module Apartment
|
|
17
18
|
|
18
19
|
database = @processor.call(request)
|
19
20
|
|
20
|
-
Apartment::Tenant.switch database if database
|
21
|
+
Apartment::Tenant.switch! database if database
|
21
22
|
|
22
23
|
@app.call(env)
|
23
24
|
end
|
@@ -41,7 +42,7 @@ module Apartment
|
|
41
42
|
end
|
42
43
|
|
43
44
|
def deprecation_warning
|
44
|
-
warn "[DEPRECATED::Apartment] Use #parse_tenant_name instead of #parse_database_name -> #{self.class.name}"
|
45
|
+
Apartment::Deprecation.warn "[DEPRECATED::Apartment] Use #parse_tenant_name instead of #parse_database_name -> #{self.class.name}"
|
45
46
|
end
|
46
47
|
end
|
47
48
|
end
|
data/lib/apartment/migrator.rb
CHANGED
@@ -7,7 +7,7 @@ module Apartment
|
|
7
7
|
|
8
8
|
# Migrate to latest
|
9
9
|
def migrate(database)
|
10
|
-
Tenant.
|
10
|
+
Tenant.switch(database) do
|
11
11
|
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
|
12
12
|
|
13
13
|
ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, version) do |migration|
|
@@ -18,14 +18,14 @@ module Apartment
|
|
18
18
|
|
19
19
|
# Migrate up/down to a specific version
|
20
20
|
def run(direction, database, version)
|
21
|
-
Tenant.
|
21
|
+
Tenant.switch(database) do
|
22
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
|
-
Tenant.
|
28
|
+
Tenant.switch(database) do
|
29
29
|
ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
|
30
30
|
end
|
31
31
|
end
|
@@ -1,26 +1,36 @@
|
|
1
1
|
# Require this file to append Apartment rake tasks to ActiveRecord db rake tasks
|
2
2
|
# Enabled by default in the initializer
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
module Apartment
|
5
|
+
class RakeTaskEnhancer
|
6
|
+
|
7
|
+
TASKS = %w(db:migrate db:rollback db:migrate:up db:migrate:down db:migrate:redo db:seed)
|
8
|
+
|
9
|
+
# This is a bit convoluted, but helps solve problems when using Apartment within an engine
|
10
|
+
# See spec/integration/use_within_an_engine.rb
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def enhance!
|
14
|
+
TASKS.each do |name|
|
15
|
+
task = Rake::Task[name]
|
16
|
+
task.enhance do
|
17
|
+
if should_enhance?
|
18
|
+
enhance_task(task)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def should_enhance?
|
25
|
+
Apartment.db_migrate_tenants
|
26
|
+
end
|
27
|
+
|
28
|
+
def enhance_task(task)
|
29
|
+
Rake::Task[task.name.sub(/db:/, 'apartment:')].invoke
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
6
34
|
end
|
7
35
|
|
8
|
-
|
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
|
36
|
+
Apartment::RakeTaskEnhancer.enhance!
|
data/lib/apartment/tenant.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'forwardable'
|
2
|
+
require 'apartment/deprecation'
|
2
3
|
|
3
4
|
module Apartment
|
4
5
|
# The main entry point to Apartment functions
|
@@ -8,7 +9,7 @@ module Apartment
|
|
8
9
|
extend self
|
9
10
|
extend Forwardable
|
10
11
|
|
11
|
-
def_delegators :adapter, :create, :current_tenant, :current, :current_database, :drop, :
|
12
|
+
def_delegators :adapter, :create, :current_tenant, :current, :current_database, :default_tenant, :drop, :switch, :process_excluded_models, :reset, :seed, :switch!
|
12
13
|
|
13
14
|
attr_writer :config
|
14
15
|
|
@@ -60,14 +61,16 @@ module Apartment
|
|
60
61
|
# Fetch the rails database configuration
|
61
62
|
#
|
62
63
|
def config
|
63
|
-
@config ||=
|
64
|
-
Rails.application.config.database_configuration[Rails.env]).symbolize_keys
|
64
|
+
@config ||= Apartment.connection_config
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
68
|
def self.const_missing(const_name)
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
if const_name == :Database
|
70
|
+
Apartment::Deprecation.warn "`Apartment::Database` has been deprecated. Use `Apartment::Tenant` instead."
|
71
|
+
Tenant
|
72
|
+
else
|
73
|
+
super
|
74
|
+
end
|
72
75
|
end
|
73
|
-
end
|
76
|
+
end
|
data/lib/apartment/version.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# You can have Apartment route to the appropriate Tenant by adding some Rack middleware.
|
2
|
+
# Apartment can support many different "Elevators" that can take care of this routing to your data.
|
3
|
+
# Require whichever Elevator you're using below or none if you have a custom one.
|
2
4
|
#
|
3
5
|
# require 'apartment/elevators/generic'
|
4
6
|
# require 'apartment/elevators/domain'
|
@@ -9,40 +11,57 @@ require 'apartment/elevators/subdomain'
|
|
9
11
|
#
|
10
12
|
Apartment.configure do |config|
|
11
13
|
|
12
|
-
#
|
13
|
-
#
|
14
|
+
# Add any models that you do not want to be multi-tenanted, but remain in the global (public) namespace.
|
15
|
+
# A typical example would be a Customer or Tenant model that stores each Tenant's information.
|
14
16
|
#
|
15
|
-
#
|
16
|
-
|
17
|
+
# config.excluded_models = %w{ Tenant }
|
18
|
+
|
19
|
+
# In order to migrate all of your Tenants you need to provide a list of Tenant names to Apartment.
|
20
|
+
# You can make this dynamic by providing a Proc object to be called on migrations.
|
21
|
+
# This object should yield an array of strings representing each Tenant name.
|
17
22
|
#
|
18
|
-
# config.
|
23
|
+
# config.tenant_names = lambda{ Customer.pluck(:tenant_name) }
|
24
|
+
# config.tenant_names = ['tenant1', 'tenant2']
|
19
25
|
#
|
20
|
-
config.
|
26
|
+
config.tenant_names = lambda { ToDo_Tenant_Or_User_Model.pluck :database }
|
21
27
|
|
22
|
-
#
|
23
|
-
|
28
|
+
#
|
29
|
+
# ==> PostgreSQL only options
|
24
30
|
|
25
|
-
#
|
26
|
-
#
|
31
|
+
# Specifies whether to use PostgreSQL schemas or create a new database per Tenant.
|
32
|
+
# The default behaviour is true.
|
33
|
+
#
|
34
|
+
# config.use_schemas = true
|
35
|
+
|
36
|
+
# Apartment can be forced to use raw SQL dumps instead of schema.rb for creating new schemas.
|
37
|
+
# Use this when you are using some extra features in PostgreSQL that can't be respresented in
|
38
|
+
# schema.rb, like materialized views etc. (only applies with use_schemas set to true).
|
39
|
+
# (Note: this option doesn't use db/structure.sql, it creates SQL dump by executing pg_dump)
|
40
|
+
#
|
41
|
+
# config.use_sql = false
|
27
42
|
|
28
|
-
#
|
43
|
+
# There are cases where you might want some schemas to always be in your search_path
|
44
|
+
# e.g when using a PostgreSQL extension like hstore.
|
45
|
+
# Any schemas added here will be available along with your selected Tenant.
|
46
|
+
#
|
29
47
|
# config.persistent_schemas = %w{ hstore }
|
30
48
|
|
31
|
-
#
|
32
|
-
#
|
33
|
-
# config.append_environment = true
|
49
|
+
# <== PostgreSQL only options
|
50
|
+
#
|
34
51
|
|
35
|
-
#
|
36
|
-
|
52
|
+
# By default, and only when not using PostgreSQL schemas, Apartment will prepend the environment
|
53
|
+
# to the tenant name to ensure there is no conflict between your environments.
|
54
|
+
# This is mainly for the benefit of your development and test environments.
|
55
|
+
# Uncomment the line below if you want to disable this behaviour in production.
|
56
|
+
#
|
57
|
+
# config.prepend_environment = !Rails.env.production?
|
37
58
|
end
|
38
59
|
|
39
|
-
|
40
|
-
#
|
41
|
-
|
60
|
+
# Setup a custom Tenant switching middleware. The Proc should return the name of the Tenant that
|
61
|
+
# you want to switch to.
|
42
62
|
# Rails.application.config.middleware.use 'Apartment::Elevators::Generic', lambda { |request|
|
43
|
-
#
|
63
|
+
# request.host.split('.').first
|
44
64
|
# }
|
45
65
|
|
46
66
|
# Rails.application.config.middleware.use 'Apartment::Elevators::Domain'
|
47
|
-
|
48
67
|
Rails.application.config.middleware.use 'Apartment::Elevators::Subdomain'
|