apartment 0.1.3 → 0.5.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.
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +5 -12
- data/HISTORY.md +31 -0
- data/{README.markdown → README.md} +39 -21
- data/Rakefile +44 -47
- data/apartment.gemspec +19 -58
- data/lib/apartment.rb +57 -6
- data/lib/apartment/adapters/abstract_adapter.rb +106 -0
- data/lib/apartment/adapters/mysql_adapter.rb +11 -0
- data/lib/apartment/adapters/postgresql_adapter.rb +55 -0
- data/lib/apartment/database.rb +58 -74
- data/lib/apartment/elevators/subdomain.rb +27 -0
- data/lib/apartment/migrator.rb +23 -0
- data/lib/apartment/railtie.rb +1 -2
- data/lib/apartment/version.rb +3 -0
- data/lib/tasks/apartment.rake +36 -0
- data/spec/apartment_spec.rb +7 -0
- data/spec/config/database.yml +10 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +6 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/company.rb +3 -0
- data/spec/dummy/app/models/user.rb +3 -0
- data/spec/dummy/app/views/application/index.html.erb +1 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +47 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +8 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +35 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb +20 -0
- data/spec/dummy/db/schema.rb +26 -0
- data/spec/dummy/db/seeds.rb +8 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/{lib/apartment/associations/multi_tenant_association.rb → spec/dummy/public/favicon.ico} +0 -0
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/integration/adapters/postgresql_integration_spec.rb +73 -0
- data/spec/integration/apartment_rake_integration_spec.rb +62 -0
- data/spec/integration/database_integration_spec.rb +107 -0
- data/spec/integration/middleware/subdomain_elevator_spec.rb +63 -0
- data/spec/integration/setup_spec.rb +8 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/apartment_helpers.rb +24 -0
- data/spec/support/capybara_sessions.rb +15 -0
- data/spec/support/config.rb +11 -0
- data/spec/support/migrate.rb +9 -0
- data/spec/tasks/apartment_rake_spec.rb +110 -0
- data/spec/unit/config_spec.rb +84 -0
- data/spec/unit/middleware/subdomain_elevator_spec.rb +20 -0
- data/spec/unit/migrator_spec.rb +87 -0
- metadata +112 -62
- data/.bundle/config +0 -2
- data/.project +0 -11
- data/Gemfile.lock +0 -22
- data/VERSION +0 -1
- data/lib/apartment/config.rb +0 -10
- data/lib/apartment/config/default_config.yml +0 -17
- data/lib/tasks/multi_tenant_migrate.rake +0 -14
- data/pkg/apartment-0.1.3.gem +0 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
module Apartment
|
2
|
+
|
3
|
+
module Database
|
4
|
+
|
5
|
+
def self.postgresql_adapter(config)
|
6
|
+
Adapters::PostgresqlAdapter.new config, :schema_search_path => ActiveRecord::Base.connection.schema_search_path
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Adapters
|
11
|
+
|
12
|
+
class PostgresqlAdapter < AbstractAdapter
|
13
|
+
|
14
|
+
# Set schema path or connect to new db
|
15
|
+
def connect_to_new(database)
|
16
|
+
return ActiveRecord::Base.connection.schema_search_path = database if using_schemas?
|
17
|
+
|
18
|
+
super
|
19
|
+
rescue ActiveRecord::StatementInvalid => e
|
20
|
+
raise SchemaNotFound, e
|
21
|
+
end
|
22
|
+
|
23
|
+
def create(database)
|
24
|
+
reset
|
25
|
+
# Postgres will (optionally) use 'schemas' instead of actual dbs, create a new schema while connected to main (global) db
|
26
|
+
create_schema(database) if using_schemas?
|
27
|
+
super(database)
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset
|
31
|
+
if using_schemas?
|
32
|
+
ActiveRecord::Base.connection.schema_search_path = @defaults[:schema_search_path]
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def create_schema(database)
|
41
|
+
reset
|
42
|
+
|
43
|
+
ActiveRecord::Base.connection.execute("CREATE SCHEMA #{sanitize(database)}")
|
44
|
+
rescue Exception => e
|
45
|
+
raise SchemaExists, e
|
46
|
+
end
|
47
|
+
|
48
|
+
def using_schemas?
|
49
|
+
Apartment.use_postgres_schemas
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/lib/apartment/database.rb
CHANGED
@@ -1,80 +1,64 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections' # for `constantize`
|
1
2
|
|
2
3
|
module Apartment
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
if database.nil?
|
9
|
-
ActiveRecord::Base.establish_connection(config)
|
10
|
-
return
|
11
|
-
end
|
4
|
+
module Database
|
5
|
+
|
6
|
+
MULTI_TENANT_METHODS = [:create, :switch, :reset, :connect_and_reset]
|
7
|
+
|
8
|
+
class << self
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.multi_tenantify(configuration, database)
|
68
|
-
new_config = configuration.clone
|
69
|
-
|
70
|
-
if new_config['adapter'] == "postgresql"
|
71
|
-
new_config['schema_search_path'] = database
|
72
|
-
else
|
73
|
-
new_config['database'] = new_config['database'].gsub(Rails.env.to_s, "#{database}_#{Rails.env}")
|
74
|
-
end
|
75
|
-
|
76
|
-
new_config
|
77
|
-
end
|
10
|
+
# Call init to establish a connection to the public schema on all excluded models
|
11
|
+
# This must be done before creating any new schemas or switching
|
12
|
+
def init
|
13
|
+
connect_exclusions
|
14
|
+
end
|
15
|
+
|
16
|
+
MULTI_TENANT_METHODS.each do |method|
|
17
|
+
class_eval <<-RUBY
|
18
|
+
def #{method}(*args, &block)
|
19
|
+
adapter.send(:#{method}, *args, &block)
|
20
|
+
end
|
21
|
+
RUBY
|
22
|
+
end
|
23
|
+
|
24
|
+
def adapter
|
25
|
+
@adapter ||= begin
|
26
|
+
adapter_method = "#{config[:adapter]}_adapter"
|
27
|
+
|
28
|
+
begin
|
29
|
+
require "apartment/adapters/#{adapter_method}"
|
30
|
+
rescue LoadError => e
|
31
|
+
raise "The adapter `#{config[:adapter]}` is not yet supported"
|
32
|
+
end
|
33
|
+
|
34
|
+
unless respond_to?(adapter_method)
|
35
|
+
raise AdapterNotFound, "database configuration specifies nonexistent #{config[:adapter]} adapter"
|
36
|
+
end
|
37
|
+
|
38
|
+
send(adapter_method, config)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def reload!
|
43
|
+
@adapter = nil
|
44
|
+
@config = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def connect_exclusions
|
50
|
+
# Establish a connection for each specific excluded model
|
51
|
+
# Thus all other models will shared a connection (at ActiveRecord::Base) and we can modify at will
|
52
|
+
Apartment.excluded_models.each do |excluded_model|
|
53
|
+
excluded_model.establish_connection config
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def config
|
58
|
+
@config ||= Rails.configuration.database_configuration[Rails.env].symbolize_keys
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
78
62
|
end
|
79
63
|
|
80
64
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Apartment
|
2
|
+
module Elevators
|
3
|
+
# Provides a rack based db switching solution based on subdomains
|
4
|
+
# Assumes that database name should match subdomain
|
5
|
+
class Subdomain
|
6
|
+
|
7
|
+
def initialize(app)
|
8
|
+
@app = app
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
request = ActionDispatch::Request.new(env)
|
13
|
+
|
14
|
+
database = subdomain(request)
|
15
|
+
|
16
|
+
Apartment::Database.switch database if database
|
17
|
+
|
18
|
+
@app.call(env)
|
19
|
+
end
|
20
|
+
|
21
|
+
def subdomain(request)
|
22
|
+
request.subdomain.present? && request.subdomain || nil
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Apartment
|
2
|
+
|
3
|
+
module Migrator
|
4
|
+
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Migrate to latest
|
8
|
+
def migrate(database)
|
9
|
+
Database.connect_and_reset(database){ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_path) }
|
10
|
+
end
|
11
|
+
|
12
|
+
# Migrate up/down to a specific version
|
13
|
+
def run(direction, database, version)
|
14
|
+
Database.connect_and_reset(database){ ActiveRecord::Migrator.run(direction, ActiveRecord::Migrator.migrations_path, version) }
|
15
|
+
end
|
16
|
+
|
17
|
+
# rollback latest migration `step` number of times
|
18
|
+
def rollback(database, step = 1)
|
19
|
+
Database.connect_and_reset(database){ ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_path, step) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/lib/apartment/railtie.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
namespace :apartment do
|
2
|
+
|
3
|
+
desc "Migrate all multi-tenant databases"
|
4
|
+
task :migrate => :environment do
|
5
|
+
ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_path)
|
6
|
+
|
7
|
+
Apartment.database_names.each{ |db| Apartment::Migrator.migrate db }
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Rolls the schema back to the previous version (specify steps w/ STEP=n) across all multi-tenant dbs."
|
11
|
+
task :rollback => :environment do
|
12
|
+
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
13
|
+
Apartment.database_names.each{ |db| Apartment::Migrator.rollback db, step }
|
14
|
+
end
|
15
|
+
|
16
|
+
namespace :migrate do
|
17
|
+
|
18
|
+
desc 'Runs the "up" for a given migration VERSION across all multi-tenant dbs.'
|
19
|
+
task :up => :environment do
|
20
|
+
version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
|
21
|
+
raise 'VERSION is required' unless version
|
22
|
+
|
23
|
+
Apartment.database_names.each{ |db| Apartment::Migrator.run :up, db, version }
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'Runs the "down" for a given migration VERSION across all multi-tenant dbs.'
|
27
|
+
task :down => :environment do
|
28
|
+
version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
|
29
|
+
raise 'VERSION is required' unless version
|
30
|
+
|
31
|
+
Apartment.database_names.each{ |db| Apartment::Migrator.run :down, db, version }
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/spec/dummy/Rakefile
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
2
|
+
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
3
|
+
|
4
|
+
require File.expand_path('../config/application', __FILE__)
|
5
|
+
require 'rake'
|
6
|
+
|
7
|
+
Dummy::Application.load_tasks
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Index!!</h1>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require File.expand_path('../boot', __FILE__)
|
2
|
+
|
3
|
+
require "active_model/railtie"
|
4
|
+
require "active_record/railtie"
|
5
|
+
require "action_controller/railtie"
|
6
|
+
require "action_view/railtie"
|
7
|
+
require "action_mailer/railtie"
|
8
|
+
|
9
|
+
Bundler.require
|
10
|
+
require "apartment"
|
11
|
+
|
12
|
+
module Dummy
|
13
|
+
class Application < Rails::Application
|
14
|
+
# Settings in config/environments/* take precedence over those specified here.
|
15
|
+
# Application configuration should go into files in config/initializers
|
16
|
+
# -- all .rb files in that directory are automatically loaded.
|
17
|
+
|
18
|
+
config.middleware.use 'Apartment::Elevators::Subdomain'
|
19
|
+
|
20
|
+
# Custom directories with classes and modules you want to be autoloadable.
|
21
|
+
# config.autoload_paths += %W(#{config.root}/extras)
|
22
|
+
|
23
|
+
# Only load the plugins named here, in the order given (default is alphabetical).
|
24
|
+
# :all can be used as a placeholder for all plugins not explicitly named.
|
25
|
+
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
|
26
|
+
|
27
|
+
# Activate observers that should always be running.
|
28
|
+
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
|
29
|
+
|
30
|
+
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
|
31
|
+
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
|
32
|
+
# config.time_zone = 'Central Time (US & Canada)'
|
33
|
+
|
34
|
+
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
35
|
+
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
36
|
+
# config.i18n.default_locale = :de
|
37
|
+
|
38
|
+
# JavaScript files you want as :defaults (application.js is always included).
|
39
|
+
# config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
|
40
|
+
|
41
|
+
# Configure the default encoding used in templates for Ruby 1.9.
|
42
|
+
config.encoding = "utf-8"
|
43
|
+
|
44
|
+
# Configure sensitive parameters which will be filtered from the log file.
|
45
|
+
config.filter_parameters += [:password]
|
46
|
+
end
|
47
|
+
end
|