apartment 0.13.0.1 → 0.13.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +1 -1
- data/HISTORY.md +28 -29
- data/apartment.gemspec +12 -15
- data/lib/apartment.rb +21 -34
- data/lib/apartment/adapters/abstract_adapter.rb +64 -64
- data/lib/apartment/adapters/postgresql_adapter.rb +52 -51
- data/lib/apartment/database.rb +20 -22
- data/lib/apartment/delayed_job/hooks.rb +5 -5
- data/lib/apartment/elevators/subdomain.rb +11 -27
- data/lib/apartment/version.rb +1 -1
- data/spec/adapters/postgresql_adapter_spec.rb +29 -38
- data/spec/apartment_spec.rb +1 -1
- data/spec/dummy/db/schema.rb +1 -7
- data/spec/integration/database_integration_spec.rb +44 -44
- data/spec/integration/middleware/subdomain_elevator_spec.rb +15 -15
- data/spec/spec_helper.rb +3 -7
- data/spec/support/apartment_helpers.rb +8 -8
- data/spec/tasks/apartment_rake_spec.rb +1 -0
- data/spec/unit/config_spec.rb +18 -48
- data/spec/unit/middleware/subdomain_elevator_spec.rb +8 -11
- data/spec/unit/reloader_spec.rb +6 -6
- metadata +92 -107
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm 1.9.2@apartment
|
1
|
+
rvm --create ruby-1.9.2-p180@apartment
|
data/HISTORY.md
CHANGED
@@ -1,20 +1,19 @@
|
|
1
|
-
# 0.13.
|
2
|
-
*
|
3
|
-
|
4
|
-
-
|
5
|
-
|
6
|
-
- `drop` was somehow missed from the methods proxied through to the adapter, added that in.
|
1
|
+
# 0.13.1
|
2
|
+
* Nov 8, 2011
|
3
|
+
|
4
|
+
- Reset prepared statement cache for rails 3.1.1 before switching dbs when using postgresql schemas
|
5
|
+
- Only necessary until the next release which will be more schema aware
|
7
6
|
|
8
7
|
# 0.13.0
|
9
8
|
* Oct 25, 2011
|
10
|
-
|
9
|
+
|
11
10
|
- `process` will now rescue with reset if the previous schema/db is no longer available
|
12
11
|
- `create` now takes an optional block which allows you to process within the newly created db
|
13
12
|
- Fixed Rails version >= 3.0.10 and < 3.1 because there have been significant testing problems with 3.1, next version will hopefully fix this
|
14
|
-
|
13
|
+
|
15
14
|
# 0.12.0
|
16
15
|
* Oct 4, 2011
|
17
|
-
|
16
|
+
|
18
17
|
- Added a `drop` method for removing databases/schemas
|
19
18
|
- Refactored abstract adapter to further remove duplication in concrete implementations
|
20
19
|
- Excluded models now take string references so they are properly reloaded in development
|
@@ -22,7 +21,7 @@
|
|
22
21
|
|
23
22
|
# 0.11.1
|
24
23
|
* Sep 22, 2011
|
25
|
-
|
24
|
+
|
26
25
|
- Better use of Railties for initializing apartment
|
27
26
|
- The following changes were necessary as I haven't figured out how to properly hook into Rails reloading
|
28
27
|
- Added reloader middleware in development to init Apartment on each request
|
@@ -30,88 +29,88 @@
|
|
30
29
|
|
31
30
|
# 0.11.0
|
32
31
|
* Sep 20, 2011
|
33
|
-
|
32
|
+
|
34
33
|
- Excluded models no longer use a different connection when using postgresql schemas. Instead their table_name is prefixed with `public.`
|
35
34
|
|
36
35
|
# 0.10.3
|
37
36
|
* Sep 20, 2011
|
38
|
-
|
37
|
+
|
39
38
|
- Fix improper raising of exceptions on create and reset
|
40
39
|
|
41
40
|
# 0.10.2
|
42
41
|
* Sep 15, 2011
|
43
|
-
|
42
|
+
|
44
43
|
- Remove all the annoying logging for loading db schema and seeding on create
|
45
44
|
|
46
45
|
# 0.10.1
|
47
46
|
* Aug 11, 2011
|
48
|
-
|
47
|
+
|
49
48
|
- Fixed bug in DJ where new objects (that hadn't been pulled from the db) didn't have the proper database assigned
|
50
49
|
|
51
50
|
# 0.10.0
|
52
51
|
* July 29, 2011
|
53
|
-
|
52
|
+
|
54
53
|
- Added better support for Delayed Job
|
55
54
|
- New config option that enables Delayed Job wrappers
|
56
55
|
- Note that DJ support uses a work-around in order to get queues stored in the public schema, not sure why it doesn't work out of the box, will look into it, until then, see documentation on queue'ng jobs
|
57
|
-
|
56
|
+
|
58
57
|
# 0.9.2
|
59
58
|
* July 4, 2011
|
60
|
-
|
59
|
+
|
61
60
|
- Migrations now run associated rails migration fully, fixes schema.rb not being reloaded after migrations
|
62
61
|
|
63
62
|
# 0.9.1
|
64
63
|
* June 24, 2011
|
65
|
-
|
64
|
+
|
66
65
|
- Hooks now take the payload object as an argument to fetch the proper db for DJ hooks
|
67
66
|
|
68
67
|
# 0.9.0
|
69
68
|
* June 23, 2011
|
70
|
-
|
69
|
+
|
71
70
|
- Added module to provide delayed job hooks
|
72
71
|
|
73
72
|
# 0.8.0
|
74
73
|
* June 23, 2011
|
75
|
-
|
74
|
+
|
76
75
|
- Added #current_database which will return the current database (or schema) name
|
77
76
|
|
78
77
|
# 0.7.0
|
79
78
|
* June 22, 2011
|
80
|
-
|
79
|
+
|
81
80
|
- Added apartment:seed rake task for seeding all dbs
|
82
81
|
|
83
82
|
# 0.6.0
|
84
83
|
* June 21, 2011
|
85
|
-
|
84
|
+
|
86
85
|
- Added #process to connect to new db, perform operations, then ensure a reset
|
87
86
|
|
88
87
|
# 0.5.1
|
89
88
|
* June 21, 2011
|
90
|
-
|
89
|
+
|
91
90
|
- Fixed db migrate up/down/rollback
|
92
91
|
- added db:redo
|
93
92
|
|
94
93
|
# 0.5.0
|
95
94
|
* June 20, 2011
|
96
|
-
|
95
|
+
|
97
96
|
- Added the concept of an "Elevator", a rack based strategy for db switching
|
98
97
|
- Added the Subdomain Elevator middleware to enabled db switching based on subdomain
|
99
98
|
|
100
99
|
# 0.4.0
|
101
100
|
* June 14, 2011
|
102
|
-
|
101
|
+
|
103
102
|
- Added `configure` method on Apartment instead of using yml file, allows for dynamic setting of db names to migrate for rake task
|
104
103
|
- Added `seed_after_create` config option to import seed data to new db on create
|
105
|
-
|
104
|
+
|
106
105
|
# 0.3.0
|
107
106
|
* June 10, 2011
|
108
|
-
|
107
|
+
|
109
108
|
- Added full support for database migration
|
110
109
|
- Added in method to establish new connection for excluded models on startup rather than on each switch
|
111
|
-
|
110
|
+
|
112
111
|
# 0.2.0
|
113
112
|
* June 6, 2011 *
|
114
|
-
|
113
|
+
|
115
114
|
- Refactor to use more rails/active_support functionality
|
116
115
|
- Refactor config to lazily load apartment.yml if exists
|
117
116
|
- Remove OStruct and just use hashes for fetching methods
|
data/apartment.gemspec
CHANGED
@@ -7,28 +7,25 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = Apartment::VERSION
|
8
8
|
|
9
9
|
s.authors = ["Ryan Brunner", "Brad Robertson"]
|
10
|
-
s.summary = %q{A Ruby gem for managing database multitenancy in
|
11
|
-
s.description = %q{Apartment allows applications
|
10
|
+
s.summary = %q{A Ruby gem for managing database multitenancy in Rails applications}
|
11
|
+
s.description = %q{Apartment allows Rails applications to deal with database multitenancy}
|
12
12
|
s.email = %w{ryan@ryanbrunner.com bradleyrobertson@gmail.com}
|
13
13
|
s.files = `git ls-files`.split("\n")
|
14
14
|
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
15
|
-
|
15
|
+
|
16
16
|
s.homepage = %q{http://github.com/bradrobertson/apartment}
|
17
17
|
s.licenses = ["MIT"]
|
18
18
|
s.require_paths = ["lib"]
|
19
19
|
s.rubygems_version = %q{1.3.7}
|
20
|
-
|
21
|
-
s.add_dependency '
|
22
|
-
s.
|
23
|
-
|
24
|
-
s.add_development_dependency 'rake', '~> 0.8.7'
|
25
|
-
s.add_development_dependency 'rails', '~> 3.0.10'
|
20
|
+
|
21
|
+
s.add_dependency 'rails', '~> 3.1.1' # must be >= 3.0.10 due to poor schema support pre 3.0.10, but < 3.1 because it hasn't been fully tested yet
|
22
|
+
s.add_development_dependency 'rake', '~> 0.8.7'
|
26
23
|
s.add_development_dependency 'sqlite3'
|
27
|
-
s.add_development_dependency 'rspec',
|
28
|
-
s.add_development_dependency 'rspec-rails',
|
29
|
-
s.add_development_dependency 'capybara',
|
30
|
-
s.add_development_dependency 'pg',
|
31
|
-
s.add_development_dependency 'mysql2',
|
24
|
+
s.add_development_dependency 'rspec', '~> 2.6.0'
|
25
|
+
s.add_development_dependency 'rspec-rails', '~> 2.6.1'
|
26
|
+
s.add_development_dependency 'capybara', '1.0.0'
|
27
|
+
s.add_development_dependency 'pg', '~> 0.11.0'
|
28
|
+
s.add_development_dependency 'mysql2', '0.2.7'
|
32
29
|
s.add_development_dependency "silent-postgres", "~> 0.1.1"
|
33
|
-
s.add_development_dependency 'delayed_job',
|
30
|
+
s.add_development_dependency 'delayed_job', '~> 2.1.4'
|
34
31
|
end
|
data/lib/apartment.rb
CHANGED
@@ -1,19 +1,16 @@
|
|
1
1
|
require 'apartment/railtie' if defined?(Rails)
|
2
2
|
|
3
3
|
module Apartment
|
4
|
-
|
4
|
+
|
5
5
|
class << self
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
attr_accessor(*ACCESSOR_METHODS)
|
10
|
-
attr_writer(*WRITER_METHODS)
|
11
|
-
|
6
|
+
attr_accessor :use_postgres_schemas, :seed_after_create, :prepend_environment
|
7
|
+
attr_writer :database_names, :excluded_models
|
8
|
+
|
12
9
|
# configure apartment with available options
|
13
10
|
def configure
|
14
11
|
yield self if block_given?
|
15
12
|
end
|
16
|
-
|
13
|
+
|
17
14
|
# Be careful not to use `return` here so both Proc and lambda can be used without breaking
|
18
15
|
def database_names
|
19
16
|
if @database_names.respond_to?(:call)
|
@@ -22,65 +19,55 @@ module Apartment
|
|
22
19
|
@database_names
|
23
20
|
end
|
24
21
|
end
|
25
|
-
|
22
|
+
|
26
23
|
# Default to none
|
27
24
|
def excluded_models
|
28
25
|
@excluded_models || []
|
29
26
|
end
|
30
|
-
|
31
|
-
# Defaults to true if not set
|
32
|
-
def load_schema
|
33
|
-
@load_schema != false
|
34
|
-
end
|
35
|
-
|
36
|
-
# Reset all the config for Apartment
|
37
|
-
def reset
|
38
|
-
(ACCESSOR_METHODS + WRITER_METHODS).each{|method| instance_variable_set(:"@#{method}", nil) }
|
39
|
-
end
|
40
|
-
|
27
|
+
|
41
28
|
end
|
42
|
-
|
29
|
+
|
43
30
|
autoload :Database, 'apartment/database'
|
44
31
|
autoload :Migrator, 'apartment/migrator'
|
45
32
|
autoload :Reloader, 'apartment/reloader'
|
46
|
-
|
33
|
+
|
47
34
|
module Adapters
|
48
35
|
autoload :AbstractAdapter, 'apartment/adapters/abstract_adapter'
|
49
36
|
# Specific adapters will be loaded dynamically based on adapter in config
|
50
37
|
end
|
51
|
-
|
38
|
+
|
52
39
|
module Elevators
|
53
40
|
autoload :Subdomain, 'apartment/elevators/subdomain'
|
54
41
|
end
|
55
|
-
|
42
|
+
|
56
43
|
module Delayed
|
57
|
-
|
44
|
+
|
58
45
|
autoload :Requirements, 'apartment/delayed_job/requirements'
|
59
|
-
|
46
|
+
|
60
47
|
module Job
|
61
48
|
autoload :Hooks, 'apartment/delayed_job/hooks'
|
62
49
|
end
|
63
50
|
end
|
64
|
-
|
51
|
+
|
65
52
|
# Exceptions
|
66
53
|
class ApartmentError < StandardError; end
|
67
|
-
|
54
|
+
|
68
55
|
# Raised when apartment cannot find the adapter specified in <tt>config/database.yml</tt>
|
69
56
|
class AdapterNotFound < ApartmentError; end
|
70
|
-
|
57
|
+
|
71
58
|
# Raised when database cannot find the specified database
|
72
59
|
class DatabaseNotFound < ApartmentError; end
|
73
|
-
|
60
|
+
|
74
61
|
# Raised when trying to create a database that already exists
|
75
62
|
class DatabaseExists < ApartmentError; end
|
76
|
-
|
63
|
+
|
77
64
|
# Raised when database cannot find the specified schema
|
78
65
|
class SchemaNotFound < ApartmentError; end
|
79
|
-
|
66
|
+
|
80
67
|
# Raised when trying to create a schema that already exists
|
81
68
|
class SchemaExists < ApartmentError; end
|
82
|
-
|
69
|
+
|
83
70
|
# Raised when an ActiveRecord object does not have the required database field on it
|
84
71
|
class DJSerializationError < ApartmentError; end
|
85
|
-
|
72
|
+
|
86
73
|
end
|
@@ -1,81 +1,81 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
|
3
3
|
module Apartment
|
4
|
-
|
4
|
+
|
5
5
|
module Adapters
|
6
|
-
|
6
|
+
|
7
7
|
class AbstractAdapter
|
8
|
-
|
8
|
+
|
9
9
|
# @constructor
|
10
10
|
# @param {Hash} config Database config
|
11
11
|
# @param {Hash} defaults Some default options
|
12
|
-
#
|
12
|
+
#
|
13
13
|
def initialize(config, defaults = {})
|
14
14
|
@config = config
|
15
15
|
@defaults = defaults
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
# Create a new database, import schema, seed if appropriate
|
19
|
-
#
|
19
|
+
#
|
20
20
|
# @param {String} database Database name
|
21
|
-
#
|
22
|
-
|
23
|
-
|
21
|
+
#
|
22
|
+
def create(database)
|
23
|
+
create_database(database)
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
process(database) do
|
26
|
+
import_database_schema
|
27
27
|
|
28
28
|
# Seed data if appropriate
|
29
29
|
seed_data if Apartment.seed_after_create
|
30
|
-
|
30
|
+
|
31
31
|
yield if block_given?
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
35
|
# Get the current database name
|
36
|
-
#
|
36
|
+
#
|
37
37
|
# @return {String} current database name
|
38
|
-
#
|
38
|
+
#
|
39
39
|
def current_database
|
40
40
|
ActiveRecord::Base.connection.current_database
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
# Drop the database
|
44
|
-
#
|
44
|
+
#
|
45
45
|
# @param {String} database Database name
|
46
|
-
#
|
46
|
+
#
|
47
47
|
def drop(database)
|
48
48
|
# ActiveRecord::Base.connection.drop_database note that drop_database will not throw an exception, so manually execute
|
49
49
|
ActiveRecord::Base.connection.execute("DROP DATABASE #{environmentify(database)}" )
|
50
|
-
|
51
|
-
rescue ActiveRecord::StatementInvalid
|
50
|
+
|
51
|
+
rescue ActiveRecord::StatementInvalid => e
|
52
52
|
raise DatabaseNotFound, "The database #{environmentify(database)} cannot be found"
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
# Prepend the environment if configured and the environment isn't already there
|
56
|
-
#
|
56
|
+
#
|
57
57
|
# @param {String} database Database name
|
58
58
|
# @return {String} database name with Rails environment *optionally* prepended
|
59
|
-
#
|
59
|
+
#
|
60
60
|
def environmentify(database)
|
61
61
|
Apartment.prepend_environment && !database.include?(Rails.env) ? "#{Rails.env}_#{database}" : database
|
62
|
-
|
63
|
-
|
62
|
+
end
|
63
|
+
|
64
64
|
# Connect to db, do your biz, switch back to previous db
|
65
|
-
#
|
65
|
+
#
|
66
66
|
# @param {String?} database Database or schema to connect to
|
67
|
-
#
|
67
|
+
#
|
68
68
|
def process(database = nil)
|
69
69
|
current_db = current_database
|
70
70
|
switch(database)
|
71
71
|
yield if block_given?
|
72
|
-
|
73
|
-
|
72
|
+
|
73
|
+
ensure
|
74
74
|
switch(current_db) rescue reset
|
75
|
-
|
76
|
-
|
75
|
+
end
|
76
|
+
|
77
77
|
# Establish a new connection for each specific excluded model
|
78
|
-
#
|
78
|
+
#
|
79
79
|
def process_excluded_models
|
80
80
|
# All other models will shared a connection (at ActiveRecord::Base) and we can modify at will
|
81
81
|
Apartment.excluded_models.each do |excluded_model|
|
@@ -85,77 +85,77 @@ module Apartment
|
|
85
85
|
warn "[Deprecation Warning] Passing class references to excluded models is now deprecated, please use a string instead"
|
86
86
|
excluded_model = excluded_model.name
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
excluded_model.constantize.establish_connection @config
|
90
90
|
end
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
# Reset the database connection to the default
|
94
|
-
#
|
94
|
+
#
|
95
95
|
def reset
|
96
96
|
ActiveRecord::Base.establish_connection @config
|
97
97
|
end
|
98
|
-
|
98
|
+
|
99
99
|
# Switch to new connection (or schema if appopriate)
|
100
|
-
#
|
100
|
+
#
|
101
101
|
# @param {String} database Database name
|
102
|
-
#
|
102
|
+
#
|
103
103
|
def switch(database = nil)
|
104
104
|
# Just connect to default db and return
|
105
105
|
return reset if database.nil?
|
106
106
|
|
107
107
|
connect_to_new(database)
|
108
|
-
|
108
|
+
end
|
109
109
|
|
110
110
|
# Load the rails seed file into the db
|
111
|
-
#
|
112
|
-
|
111
|
+
#
|
112
|
+
def seed_data
|
113
113
|
silence_stream(STDOUT){ load_or_abort("#{Rails.root}/db/seeds.rb") } # Don't log the output of seeding the db
|
114
114
|
end
|
115
115
|
alias_method :seed, :seed_data
|
116
|
-
|
116
|
+
|
117
117
|
protected
|
118
|
-
|
118
|
+
|
119
119
|
# Create the database
|
120
|
-
#
|
120
|
+
#
|
121
121
|
# @param {String} database Database name
|
122
|
-
#
|
122
|
+
#
|
123
123
|
def create_database(database)
|
124
124
|
ActiveRecord::Base.connection.create_database( environmentify(database) )
|
125
125
|
|
126
|
-
rescue ActiveRecord::StatementInvalid
|
126
|
+
rescue ActiveRecord::StatementInvalid => e
|
127
127
|
raise DatabaseExists, "The database #{environmentify(database)} already exists."
|
128
128
|
end
|
129
|
-
|
129
|
+
|
130
130
|
# Connect to new database
|
131
|
-
#
|
131
|
+
#
|
132
132
|
# @param {String} database Database name
|
133
|
-
#
|
133
|
+
#
|
134
134
|
def connect_to_new(database)
|
135
135
|
ActiveRecord::Base.establish_connection multi_tenantify(database)
|
136
136
|
ActiveRecord::Base.connection.active? # call active? to manually check if this connection is valid
|
137
137
|
|
138
|
-
rescue ActiveRecord::StatementInvalid
|
138
|
+
rescue ActiveRecord::StatementInvalid => e
|
139
139
|
raise DatabaseNotFound, "The database #{environmentify(database)} cannot be found."
|
140
|
-
|
141
|
-
|
140
|
+
end
|
141
|
+
|
142
142
|
# Import the database schema
|
143
|
-
#
|
143
|
+
#
|
144
144
|
def import_database_schema
|
145
145
|
ActiveRecord::Schema.verbose = false # do not log schema load output.
|
146
146
|
load_or_abort("#{Rails.root}/db/schema.rb")
|
147
147
|
end
|
148
|
-
|
148
|
+
|
149
149
|
# Return a new config that is multi-tenanted
|
150
|
-
#
|
150
|
+
#
|
151
151
|
def multi_tenantify(database)
|
152
152
|
@config.clone.tap do |config|
|
153
153
|
config[:database] = environmentify(database)
|
154
154
|
end
|
155
|
-
|
156
|
-
|
155
|
+
end
|
156
|
+
|
157
157
|
# Load a file or abort if it doesn't exists
|
158
|
-
#
|
158
|
+
#
|
159
159
|
def load_or_abort(file)
|
160
160
|
if File.exists?(file)
|
161
161
|
load(file)
|
@@ -163,14 +163,14 @@ module Apartment
|
|
163
163
|
abort %{#{file} doesn't exist yet}
|
164
164
|
end
|
165
165
|
end
|
166
|
-
|
166
|
+
|
167
167
|
# Remove all non-alphanumeric characters
|
168
|
-
#
|
169
|
-
|
168
|
+
#
|
169
|
+
def sanitize(database)
|
170
170
|
warn "[Deprecation Warning] Sanitize is no longer used, client should ensure proper database names"
|
171
171
|
database.gsub(/[\W]/,'')
|
172
172
|
end
|
173
|
-
|
173
|
+
|
174
174
|
end
|
175
175
|
end
|
176
176
|
end
|