apartment 0.11.1 → 0.12.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/HISTORY.md +8 -0
- data/README.md +3 -1
- data/lib/apartment.rb +6 -0
- data/lib/apartment/adapters/abstract_adapter.rb +65 -26
- data/lib/apartment/adapters/postgresql_adapter.rb +48 -27
- data/lib/apartment/railtie.rb +2 -2
- data/lib/apartment/version.rb +1 -1
- data/spec/adapters/postgresql_adapter_spec.rb +21 -1
- data/spec/support/apartment_helpers.rb +1 -1
- metadata +3 -3
data/HISTORY.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
# 0.12.0
|
2
|
+
* Oct 4, 2001
|
3
|
+
|
4
|
+
- Added a `drop` method for removing databases/schemas
|
5
|
+
- Refactored abstract adapter to further remove duplication in concrete implementations
|
6
|
+
- Excluded models now take string references so they are properly reloaded in development
|
7
|
+
- Better silencing of `schema.rb` loading using `verbose` flag
|
8
|
+
|
1
9
|
# 0.11.1
|
2
10
|
* Sep 22, 2011
|
3
11
|
|
data/README.md
CHANGED
@@ -86,7 +86,9 @@ To set config options, add this to your initializer:
|
|
86
86
|
If you have some models that should always access the 'root' database, you can specify this by configuring
|
87
87
|
Apartment using `Apartment.configure`. This will yield a config object for you. You can set excluded models like so:
|
88
88
|
|
89
|
-
config.excluded_models = [User, Company] # these models will not be multi-tenanted, but remain in the global (public) namespace
|
89
|
+
config.excluded_models = ["User", "Company"] # these models will not be multi-tenanted, but remain in the global (public) namespace
|
90
|
+
|
91
|
+
Note that a string representation of the model name is now the standard so that models are properly constantized when reloaded in development
|
90
92
|
|
91
93
|
### Handling Environments
|
92
94
|
|
data/lib/apartment.rb
CHANGED
@@ -55,6 +55,12 @@ module Apartment
|
|
55
55
|
# Raised when apartment cannot find the adapter specified in <tt>config/database.yml</tt>
|
56
56
|
class AdapterNotFound < ApartmentError; end
|
57
57
|
|
58
|
+
# Raised when database cannot find the specified database
|
59
|
+
class DatabaseNotFound < ApartmentError; end
|
60
|
+
|
61
|
+
# Raised when trying to create a database that already exists
|
62
|
+
class DatabaseExists < ApartmentError; end
|
63
|
+
|
58
64
|
# Raised when database cannot find the specified schema
|
59
65
|
class SchemaNotFound < ApartmentError; end
|
60
66
|
|
@@ -15,12 +15,12 @@ module Apartment
|
|
15
15
|
@defaults = defaults
|
16
16
|
end
|
17
17
|
|
18
|
-
# Create new
|
18
|
+
# Create a new database, import schema, seed if appropriate
|
19
19
|
#
|
20
20
|
# @param {String} database Database name
|
21
21
|
#
|
22
22
|
def create(database)
|
23
|
-
|
23
|
+
create_database(database)
|
24
24
|
|
25
25
|
process(database) do
|
26
26
|
import_database_schema
|
@@ -29,7 +29,7 @@ module Apartment
|
|
29
29
|
seed_data if Apartment.seed_after_create
|
30
30
|
end
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
# Get the current database name
|
34
34
|
#
|
35
35
|
# @return {String} current database name
|
@@ -38,6 +38,27 @@ module Apartment
|
|
38
38
|
ActiveRecord::Base.connection.current_database
|
39
39
|
end
|
40
40
|
|
41
|
+
# Drop the database
|
42
|
+
#
|
43
|
+
# @param {String} database Database name
|
44
|
+
#
|
45
|
+
def drop(database)
|
46
|
+
# ActiveRecord::Base.connection.drop_database note that drop_database will not throw an exception, so manually execute
|
47
|
+
ActiveRecord::Base.connection.execute("DROP DATABASE #{environmentify(database)}" )
|
48
|
+
|
49
|
+
rescue ActiveRecord::StatementInvalid => e
|
50
|
+
raise DatabaseNotFound, "The database #{environmentify(database)} cannot be found"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Prepend the environment if configured and the environment isn't already there
|
54
|
+
#
|
55
|
+
# @param {String} database Database name
|
56
|
+
# @return {String} database name with Rails environment *optionally* prepended
|
57
|
+
#
|
58
|
+
def environmentify(database)
|
59
|
+
Apartment.prepend_environment && !database.include?(Rails.env) ? "#{Rails.env}_#{database}" : database
|
60
|
+
end
|
61
|
+
|
41
62
|
# Connect to db, do your biz, switch back to previous db
|
42
63
|
#
|
43
64
|
# @param {String?} database Database or schema to connect to
|
@@ -56,11 +77,18 @@ module Apartment
|
|
56
77
|
def process_excluded_models
|
57
78
|
# All other models will shared a connection (at ActiveRecord::Base) and we can modify at will
|
58
79
|
Apartment.excluded_models.each do |excluded_model|
|
59
|
-
|
80
|
+
# Note that due to rails reloading, we now take string references to classes rather than
|
81
|
+
# actual object references. This way when we contantize, we always get the proper class reference
|
82
|
+
if excluded_model.is_a? Class
|
83
|
+
warn "[Deprecation Warning] Passing class references to excluded models is now deprecated, please use a string instead"
|
84
|
+
excluded_model = excluded_model.name
|
85
|
+
end
|
86
|
+
|
87
|
+
excluded_model.constantize.establish_connection @config
|
60
88
|
end
|
61
89
|
end
|
62
90
|
|
63
|
-
# Reset the
|
91
|
+
# Reset the database connection to the default
|
64
92
|
#
|
65
93
|
def reset
|
66
94
|
ActiveRecord::Base.establish_connection @config
|
@@ -77,56 +105,67 @@ module Apartment
|
|
77
105
|
connect_to_new(database)
|
78
106
|
end
|
79
107
|
|
80
|
-
# Prepend the environment if configured and the environment isn't already there
|
81
|
-
#
|
82
|
-
# @param {String} database Database name
|
83
|
-
# @return {String} database name with Rails environment *optionally* prepended
|
84
|
-
#
|
85
|
-
def environmentify(database)
|
86
|
-
return "#{Rails.env}_#{database}" if Apartment.prepend_environment && !database.include?(Rails.env)
|
87
|
-
|
88
|
-
database
|
89
|
-
end
|
90
|
-
|
91
108
|
# Load the rails seed file into the db
|
92
109
|
#
|
93
110
|
def seed_data
|
94
|
-
|
111
|
+
silence_stream(STDOUT){ load_or_abort("#{Rails.root}/db/seeds.rb") } # Don't log the output of seeding the db
|
95
112
|
end
|
96
113
|
alias_method :seed, :seed_data
|
97
114
|
|
98
115
|
protected
|
99
116
|
|
117
|
+
# Create the database
|
118
|
+
#
|
119
|
+
# @param {String} database Database name
|
120
|
+
#
|
121
|
+
def create_database(database)
|
122
|
+
ActiveRecord::Base.connection.create_database( environmentify(database) )
|
123
|
+
|
124
|
+
rescue ActiveRecord::StatementInvalid => e
|
125
|
+
raise DatabaseExists, "The database #{environmentify(database)} already exists."
|
126
|
+
end
|
127
|
+
|
128
|
+
# Connect to new database
|
129
|
+
#
|
130
|
+
# @param {String} database Database name
|
131
|
+
#
|
100
132
|
def connect_to_new(database)
|
101
133
|
ActiveRecord::Base.establish_connection multi_tenantify(database)
|
102
|
-
|
134
|
+
ActiveRecord::Base.connection.active? # call active? to manually check if this connection is valid
|
135
|
+
|
136
|
+
rescue ActiveRecord::StatementInvalid => e
|
137
|
+
raise DatabaseNotFound, "The database #{environmentify(database)} cannot be found."
|
138
|
+
end
|
103
139
|
|
140
|
+
# Import the database schema
|
141
|
+
#
|
104
142
|
def import_database_schema
|
143
|
+
ActiveRecord::Schema.verbose = false # do not log schema load output. Note that this is slightly duplicated below with silenct_stream, except that this also works with Spork
|
105
144
|
load_or_abort("#{Rails.root}/db/schema.rb")
|
106
145
|
end
|
107
146
|
|
108
|
-
#
|
147
|
+
# Return a new config that is multi-tenanted
|
148
|
+
#
|
109
149
|
def multi_tenantify(database)
|
110
150
|
@config.clone.tap do |config|
|
111
151
|
config[:database] = environmentify(database)
|
112
152
|
end
|
113
153
|
end
|
114
154
|
|
155
|
+
# Load a file or abort if it doesn't exists
|
156
|
+
#
|
115
157
|
def load_or_abort(file)
|
116
158
|
if File.exists?(file)
|
117
|
-
|
118
|
-
silence_stream(STDOUT) do
|
119
|
-
load(file)
|
120
|
-
end
|
159
|
+
load(file)
|
121
160
|
else
|
122
161
|
abort %{#{file} doesn't exist yet}
|
123
162
|
end
|
124
163
|
end
|
125
164
|
|
126
|
-
|
127
|
-
|
128
|
-
# Remove all non-alphanumeric characters
|
165
|
+
# Remove all non-alphanumeric characters
|
166
|
+
#
|
129
167
|
def sanitize(database)
|
168
|
+
warn "[Deprecation Warning] Sanitize is no longer used, client should ensure proper database names"
|
130
169
|
database.gsub(/[\W]/,'')
|
131
170
|
end
|
132
171
|
|
@@ -14,37 +14,26 @@ module Apartment
|
|
14
14
|
|
15
15
|
# Default adapter when not using Postgresql Schemas
|
16
16
|
class PostgresqlAdapter < AbstractAdapter
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
# Connect to new database
|
21
|
+
# Abstract adapter will catch generic ActiveRecord error
|
22
|
+
# Catch specific adapter errors here
|
23
|
+
#
|
24
|
+
# @param {String} database Database name
|
25
|
+
#
|
26
|
+
def connect_to_new(database)
|
27
|
+
super
|
28
|
+
rescue PGError => e
|
29
|
+
raise DatabaseNotFound, "Cannot find database #{environmentify(database)}"
|
30
|
+
end
|
31
|
+
|
17
32
|
end
|
18
33
|
|
19
34
|
# Separate Adapter for Postgresql when using schemas
|
20
35
|
class PostgresqlSchemaAdapter < AbstractAdapter
|
21
36
|
|
22
|
-
# Set schema path or connect to new db
|
23
|
-
#
|
24
|
-
def connect_to_new(database = nil)
|
25
|
-
return reset if database.nil?
|
26
|
-
ActiveRecord::Base.connection.schema_search_path = database
|
27
|
-
|
28
|
-
rescue ActiveRecord::StatementInvalid => e
|
29
|
-
raise SchemaNotFound, "The Schema #{database.inspect} cannot be found."
|
30
|
-
end
|
31
|
-
|
32
|
-
# Create a db schema
|
33
|
-
#
|
34
|
-
def create(database)
|
35
|
-
ActiveRecord::Base.connection.execute("CREATE SCHEMA #{database}")
|
36
|
-
|
37
|
-
process(database) do
|
38
|
-
import_database_schema
|
39
|
-
|
40
|
-
# Seed data if appropriate
|
41
|
-
seed_data if Apartment.seed_after_create
|
42
|
-
end
|
43
|
-
|
44
|
-
rescue ActiveRecord::StatementInvalid => e
|
45
|
-
raise SchemaExists, "The schema #{database} already exists."
|
46
|
-
end
|
47
|
-
|
48
37
|
# Get the current schema search path
|
49
38
|
#
|
50
39
|
# @return {String} current schema search path
|
@@ -53,10 +42,21 @@ module Apartment
|
|
53
42
|
ActiveRecord::Base.connection.schema_search_path
|
54
43
|
end
|
55
44
|
|
45
|
+
# Drop the database schema
|
46
|
+
#
|
47
|
+
# @param {String} database Database (schema) to drop
|
48
|
+
#
|
49
|
+
def drop(database)
|
50
|
+
ActiveRecord::Base.connection.execute("DROP SCHEMA #{database} CASCADE")
|
51
|
+
|
52
|
+
rescue ActiveRecord::StatementInvalid => e
|
53
|
+
raise SchemaNotFound, "The schema #{database.inspect} cannot be found."
|
54
|
+
end
|
55
|
+
|
56
|
+
# Reset search path to default search_path
|
56
57
|
# Set the table_name to always use the public namespace for excluded models
|
57
58
|
#
|
58
59
|
def process_excluded_models
|
59
|
-
|
60
60
|
Apartment.excluded_models.each do |excluded_model|
|
61
61
|
# Note that due to rails reloading, we now take string references to classes rather than
|
62
62
|
# actual object references. This way when we contantize, we always get the proper class reference
|
@@ -87,6 +87,27 @@ module Apartment
|
|
87
87
|
ActiveRecord::Base.connection.schema_search_path = @defaults[:schema_search_path]
|
88
88
|
end
|
89
89
|
|
90
|
+
protected
|
91
|
+
|
92
|
+
# Set schema search path to new schema
|
93
|
+
#
|
94
|
+
def connect_to_new(database = nil)
|
95
|
+
return reset if database.nil?
|
96
|
+
ActiveRecord::Base.connection.schema_search_path = database
|
97
|
+
|
98
|
+
rescue ActiveRecord::StatementInvalid => e
|
99
|
+
raise SchemaNotFound, "The schema #{database.inspect} cannot be found."
|
100
|
+
end
|
101
|
+
|
102
|
+
# Create the new schema
|
103
|
+
#
|
104
|
+
def create_database(database)
|
105
|
+
ActiveRecord::Base.connection.execute("CREATE SCHEMA #{database}")
|
106
|
+
|
107
|
+
rescue ActiveRecord::StatementInvalid => e
|
108
|
+
raise SchemaExists, "The schema #{database} already exists."
|
109
|
+
end
|
110
|
+
|
90
111
|
end
|
91
112
|
|
92
113
|
end
|
data/lib/apartment/railtie.rb
CHANGED
@@ -17,7 +17,7 @@ module Apartment
|
|
17
17
|
|
18
18
|
# Hook into ActionDispatch::Reloader to ensure Apartment is properly initialized
|
19
19
|
# Note that this doens't entirely work as expected in Development, because this is called before classes are reloaded
|
20
|
-
# See the above middleware
|
20
|
+
# See the above middleware/console declarations below to help with this. Hope to fix that soon.
|
21
21
|
config.to_prepare do
|
22
22
|
Apartment::Database.init
|
23
23
|
end
|
@@ -30,7 +30,7 @@ module Apartment
|
|
30
30
|
# Note this is technically valid for any environment where cache_classes is false, for us, it's just development
|
31
31
|
if Rails.env.development?
|
32
32
|
|
33
|
-
# Apartment::Reloader is middleware to initialize things properly on each
|
33
|
+
# Apartment::Reloader is middleware to initialize things properly on each request to dev
|
34
34
|
initializer 'apartment.init' do |app|
|
35
35
|
app.config.middleware.use "Apartment::Reloader"
|
36
36
|
end
|
data/lib/apartment/version.rb
CHANGED
@@ -15,6 +15,8 @@ describe Apartment::Adapters::PostgresqlAdapter do
|
|
15
15
|
context "using schemas" do
|
16
16
|
|
17
17
|
let(:schema){ 'first_db_schema' }
|
18
|
+
let(:schema2){ 'another_db_schema' }
|
19
|
+
let(:database_names){ ActiveRecord::Base.connection.execute("SELECT nspname FROM pg_namespace;").collect{|row| row['nspname']} }
|
18
20
|
|
19
21
|
subject{ Apartment::Database.postgresql_adapter Apartment::Test.config['connections']['postgresql'].symbolize_keys }
|
20
22
|
|
@@ -30,7 +32,7 @@ describe Apartment::Adapters::PostgresqlAdapter do
|
|
30
32
|
describe "#create" do
|
31
33
|
|
32
34
|
it "should create the new schema" do
|
33
|
-
|
35
|
+
database_names.should include(schema)
|
34
36
|
end
|
35
37
|
|
36
38
|
it "should load schema.rb to new schema" do
|
@@ -43,6 +45,24 @@ describe Apartment::Adapters::PostgresqlAdapter do
|
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
48
|
+
describe "#drop" do
|
49
|
+
|
50
|
+
it "should delete the database" do
|
51
|
+
subject.create schema2
|
52
|
+
subject.switch schema # can't drop db we're currently connected to, ensure these are different
|
53
|
+
subject.drop schema2
|
54
|
+
|
55
|
+
database_names.should_not include(schema2)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should raise an error for unkown database" do
|
59
|
+
expect {
|
60
|
+
subject.drop "unknown_database"
|
61
|
+
}.to raise_error(Apartment::SchemaNotFound)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
46
66
|
describe "#process" do
|
47
67
|
it "should connect" do
|
48
68
|
subject.process(schema) do
|
@@ -9,7 +9,7 @@ module Apartment
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def drop_schema(schema)
|
12
|
-
ActiveRecord::Base.silence{ ActiveRecord::Base.connection.execute("DROP SCHEMA IF EXISTS #{schema} CASCADE") }
|
12
|
+
ActiveRecord::Base.silence{ ActiveRecord::Base.connection.execute("DROP SCHEMA IF EXISTS #{schema} CASCADE") } rescue true
|
13
13
|
end
|
14
14
|
|
15
15
|
def create_schema(schema)
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: apartment
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.12.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Ryan Brunner
|
@@ -226,7 +226,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
226
226
|
requirements:
|
227
227
|
- - ">="
|
228
228
|
- !ruby/object:Gem::Version
|
229
|
-
hash:
|
229
|
+
hash: 1213020352034196308
|
230
230
|
segments:
|
231
231
|
- 0
|
232
232
|
version: "0"
|
@@ -235,7 +235,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
235
235
|
requirements:
|
236
236
|
- - ">="
|
237
237
|
- !ruby/object:Gem::Version
|
238
|
-
hash:
|
238
|
+
hash: 1213020352034196308
|
239
239
|
segments:
|
240
240
|
- 0
|
241
241
|
version: "0"
|