apartment 0.11.1 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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
 
@@ -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 postgres schema
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
- ActiveRecord::Base.connection.execute("CREATE DATABASE #{environmentify(sanitize(database))}")
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
- excluded_model.establish_connection @config
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 base connection
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
- load_or_abort("#{Rails.root}/db/seeds.rb")
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
- end
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
- # Return a new config that is multi-tenanted
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
- # Don't log the output of loading files (such as schema or seeds)
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
@@ -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 declaration to help with this. Hope to fix that soon.
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 requestion dev
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
@@ -1,3 +1,3 @@
1
1
  module Apartment
2
- VERSION = "0.11.1"
2
+ VERSION = "0.12.0"
3
3
  end
@@ -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
- ActiveRecord::Base.connection.execute("SELECT nspname FROM pg_namespace;").collect{|row| row['nspname']}.should include(schema)
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.11.1
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: 3705726283453355884
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: 3705726283453355884
238
+ hash: 1213020352034196308
239
239
  segments:
240
240
  - 0
241
241
  version: "0"