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 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"