apartment 0.11.0 → 0.11.1

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.11.1
2
+ * Sep 22, 2011
3
+
4
+ - Better use of Railties for initializing apartment
5
+ - The following changes were necessary as I haven't figured out how to properly hook into Rails reloading
6
+ - Added reloader middleware in development to init Apartment on each request
7
+ - Override `reload!` in console to also init Apartment
8
+
1
9
  # 0.11.0
2
10
  * Sep 20, 2011
3
11
 
data/lib/apartment.rb CHANGED
@@ -9,7 +9,6 @@ module Apartment
9
9
  # configure apartment with available options
10
10
  def configure
11
11
  yield self if block_given?
12
- Database.init
13
12
  end
14
13
 
15
14
  # Be careful not to use `return` here so both Proc and lambda can be used without breaking
@@ -30,6 +29,7 @@ module Apartment
30
29
 
31
30
  autoload :Database, 'apartment/database'
32
31
  autoload :Migrator, 'apartment/migrator'
32
+ autoload :Reloader, 'apartment/reloader'
33
33
 
34
34
  module Adapters
35
35
  autoload :AbstractAdapter, 'apartment/adapters/abstract_adapter'
@@ -46,6 +46,7 @@ module Apartment
46
46
  current_db = current_database
47
47
  switch(database)
48
48
  yield if block_given?
49
+
49
50
  ensure
50
51
  switch(current_db)
51
52
  end
@@ -87,6 +88,8 @@ module Apartment
87
88
  database
88
89
  end
89
90
 
91
+ # Load the rails seed file into the db
92
+ #
90
93
  def seed_data
91
94
  load_or_abort("#{Rails.root}/db/seeds.rb")
92
95
  end
@@ -7,6 +7,7 @@ module Apartment
7
7
  Adapters::PostgresqlSchemaAdapter.new(config, :schema_search_path => ActiveRecord::Base.connection.schema_search_path) :
8
8
  Adapters::PostgresqlAdapter.new(config)
9
9
  end
10
+
10
11
  end
11
12
 
12
13
  module Adapters
@@ -18,15 +19,18 @@ module Apartment
18
19
  # Separate Adapter for Postgresql when using schemas
19
20
  class PostgresqlSchemaAdapter < AbstractAdapter
20
21
 
21
- # Set schema path or connect to new db
22
- # TODO sanitize method doesn't work with schemas as the default schema uses "$user", stripping out the quotes makes it fail
22
+ # Set schema path or connect to new db
23
+ #
23
24
  def connect_to_new(database = nil)
24
25
  return reset if database.nil?
25
26
  ActiveRecord::Base.connection.schema_search_path = database
27
+
26
28
  rescue ActiveRecord::StatementInvalid => e
27
29
  raise SchemaNotFound, "The Schema #{database.inspect} cannot be found."
28
30
  end
29
31
 
32
+ # Create a db schema
33
+ #
30
34
  def create(database)
31
35
  ActiveRecord::Base.connection.execute("CREATE SCHEMA #{database}")
32
36
 
@@ -36,10 +40,15 @@ module Apartment
36
40
  # Seed data if appropriate
37
41
  seed_data if Apartment.seed_after_create
38
42
  end
43
+
39
44
  rescue ActiveRecord::StatementInvalid => e
40
45
  raise SchemaExists, "The schema #{database} already exists."
41
46
  end
42
47
 
48
+ # Get the current schema search path
49
+ #
50
+ # @return {String} current schema search path
51
+ #
43
52
  def current_database
44
53
  ActiveRecord::Base.connection.schema_search_path
45
54
  end
@@ -47,17 +56,26 @@ module Apartment
47
56
  # Set the table_name to always use the public namespace for excluded models
48
57
  #
49
58
  def process_excluded_models
50
-
59
+
51
60
  Apartment.excluded_models.each do |excluded_model|
52
- # some models (such as delayed_job) seem to load and cache their column names before this,
53
- # so would never get the public prefix, so reset first
54
- excluded_model.reset_column_information
61
+ # Note that due to rails reloading, we now take string references to classes rather than
62
+ # actual object references. This way when we contantize, we always get the proper class reference
63
+ if excluded_model.is_a? Class
64
+ warn "[Deprecation Warning] Passing class references to excluded models is now deprecated, please use a string instead"
65
+ excluded_model = excluded_model.name
66
+ end
67
+
68
+ excluded_model.constantize.tap do |klass|
69
+ # some models (such as delayed_job) seem to load and cache their column names before this,
70
+ # so would never get the public prefix, so reset first
71
+ klass.reset_column_information
55
72
 
56
- # Ensure that if a schema *was* set, we override
57
- table_name = excluded_model.table_name.split('.', 2).last
73
+ # Ensure that if a schema *was* set, we override
74
+ table_name = klass.table_name.split('.', 2).last
58
75
 
59
- # Not sure why, but Delayed::Job somehow ignores table_name_prefix... so we'll just manually set table name instead
60
- excluded_model.table_name = "public.#{table_name}"
76
+ # Not sure why, but Delayed::Job somehow ignores table_name_prefix... so we'll just manually set table name instead
77
+ klass.table_name = "public.#{table_name}"
78
+ end
61
79
  end
62
80
  end
63
81
 
@@ -0,0 +1,12 @@
1
+ # A workaraound to get `reload!` to also call Apartment::Database.init
2
+ # This is unfortunate, but I haven't figured out how to hook into the reload process *after* files are reloaded
3
+
4
+ # reloads the environment
5
+ def reload!(print=true)
6
+ puts "Reloading..." if print
7
+ # This triggers the to_prepare callbacks
8
+ ActionDispatch::Callbacks.new(Proc.new {}, false).call({})
9
+ # Manually init Apartment again once classes are reloaded
10
+ Apartment::Database.init
11
+ true
12
+ end
@@ -1,49 +1,57 @@
1
1
  require 'active_support/core_ext/module/delegation'
2
2
 
3
3
  module Apartment
4
+
5
+ # The main entry point to Apartment functions
4
6
  module Database
5
7
 
6
- class << self
8
+ extend self
7
9
 
8
- # pass these methods to our adapter
9
- delegate :create, :current_database, :process, :process_excluded_models, :reset, :seed, :switch, :to => :adapter
10
+ delegate :create, :current_database, :process, :process_excluded_models, :reset, :seed, :switch, :to => :adapter
10
11
 
11
- # Call init to establish a connection to the public schema on all excluded models
12
- # This must be done before creating any new schemas or switching
13
- def init
14
- process_excluded_models
15
- end
16
-
17
- def adapter
18
- @adapter ||= begin
19
- adapter_method = "#{config[:adapter]}_adapter"
20
-
21
- begin
22
- require "apartment/adapters/#{adapter_method}"
23
- rescue LoadError => e
24
- raise "The adapter `#{config[:adapter]}` is not yet supported"
25
- end
12
+ # Initialize Apartment config options such as excluded_models
13
+ #
14
+ def init
15
+ process_excluded_models
16
+ end
17
+
18
+ # Fetch the proper multi-tenant adapter based on Rails config
19
+ #
20
+ # @return {subclass of Apartment::AbstractAdapter}
21
+ #
22
+ def adapter
23
+ @adapter ||= begin
24
+ adapter_method = "#{config[:adapter]}_adapter"
25
+
26
+ begin
27
+ require "apartment/adapters/#{adapter_method}"
28
+ rescue LoadError => e
29
+ raise "The adapter `#{config[:adapter]}` is not yet supported"
30
+ end
26
31
 
27
- unless respond_to?(adapter_method)
28
- raise AdapterNotFound, "database configuration specifies nonexistent #{config[:adapter]} adapter"
29
- end
30
-
31
- send(adapter_method, config)
32
+ unless respond_to?(adapter_method)
33
+ raise AdapterNotFound, "database configuration specifies nonexistent #{config[:adapter]} adapter"
32
34
  end
33
- end
34
-
35
- def reload!
36
- @adapter = nil
37
- @config = nil
38
- end
39
35
 
40
- private
41
-
42
- def config
43
- @config ||= Rails.configuration.database_configuration[Rails.env].symbolize_keys
36
+ send(adapter_method, config)
44
37
  end
45
38
  end
46
-
47
- end
39
+
40
+ # Reset config and adapter so they are regenerated
41
+ #
42
+ def reload!
43
+ @adapter = nil
44
+ @config = nil
45
+ end
46
+
47
+ private
48
+
49
+ # Fetch the rails database configuration
50
+ #
51
+ def config
52
+ @config ||= Rails.configuration.database_configuration[Rails.env].symbolize_keys
53
+ end
54
+
55
+ end
48
56
 
49
57
  end
@@ -3,23 +3,44 @@ require 'rails'
3
3
  module Apartment
4
4
  class Railtie < Rails::Railtie
5
5
 
6
- # Ensure that active_record is loaded, then run default config
7
- initializer 'apartment.configure' do
8
- # ActiveSupport.on_load(:active_record) do
9
-
10
- Apartment.configure do |config|
11
- config.excluded_models = []
12
- config.use_postgres_schemas = true
13
- config.database_names = []
14
- config.seed_after_create = false
15
- config.prepend_environment = true
16
- end
17
-
18
- # end
6
+ # Set up our default config options
7
+ # Do this before the app initializers run so we don't override custom settings
8
+ config.before_initialize do
9
+ Apartment.configure do |config|
10
+ config.excluded_models = []
11
+ config.use_postgres_schemas = true
12
+ config.database_names = []
13
+ config.seed_after_create = false
14
+ config.prepend_environment = true
15
+ end
16
+ end
17
+
18
+ # Hook into ActionDispatch::Reloader to ensure Apartment is properly initialized
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.
21
+ config.to_prepare do
22
+ Apartment::Database.init
19
23
  end
20
24
 
21
25
  rake_tasks do
22
26
  load 'tasks/apartment.rake'
23
27
  end
28
+
29
+ # The following initializers are a workaround to the fact that I can't properly hook into the rails reloader
30
+ # Note this is technically valid for any environment where cache_classes is false, for us, it's just development
31
+ if Rails.env.development?
32
+
33
+ # Apartment::Reloader is middleware to initialize things properly on each requestion dev
34
+ initializer 'apartment.init' do |app|
35
+ app.config.middleware.use "Apartment::Reloader"
36
+ end
37
+
38
+ # Overrides reload! to also call Apartment::Database.init as well so that the reloaded classes have the proper table_names
39
+ console do
40
+ require 'apartment/console'
41
+ end
42
+
43
+ end
44
+
24
45
  end
25
46
  end
@@ -0,0 +1,23 @@
1
+ module Apartment
2
+
3
+ class Reloader
4
+
5
+ # Middleware used in development to init Apartment for each request
6
+ # Necessary due to code reload (annoying). I couldn't figure out how to properly hook into
7
+ # the Rails reload process *after* files are reloaded, so I've used this in the meantime.
8
+ #
9
+ # Note that this has one MAJOR caveat. Doing `reload!` in the console in development WILL NOT run init again
10
+ # Thus, excluded models will not be processed again and will be queried from the current_schema rather than public.
11
+ # I hope to fix this soon
12
+ def initialize(app)
13
+ @app = app
14
+ end
15
+
16
+ def call(env)
17
+ Database.init
18
+ @app.call(env)
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -1,3 +1,3 @@
1
1
  module Apartment
2
- VERSION = "0.11.0"
2
+ VERSION = "0.11.1"
3
3
  end
@@ -29,6 +29,8 @@ describe Apartment::Database do
29
29
  config.excluded_models = [Company]
30
30
  end
31
31
 
32
+ Apartment::Database.init
33
+
32
34
  Company.table_name.should == "public.companies"
33
35
  end
34
36
 
@@ -158,8 +160,11 @@ describe Apartment::Database do
158
160
 
159
161
  context "with excluded models" do
160
162
 
161
- Apartment.configure do |config|
162
- config.excluded_models = [Company]
163
+ before do
164
+ Apartment.configure do |config|
165
+ config.excluded_models = [Company]
166
+ end
167
+ Apartment::Database.init
163
168
  end
164
169
 
165
170
  it "should ignore excluded models" do
data/spec/spec_helper.rb CHANGED
@@ -23,8 +23,8 @@ RSpec.configure do |config|
23
23
  config.include RSpec::Integration::CapybaraSessions, :type => :request
24
24
 
25
25
  config.before(:all) do
26
- # Ensure that each test starts with a clean connect
27
- # Necessary as some tests will leak things like current_schema into the next
26
+ # Ensure that each test starts with a clean connection
27
+ # Necessary as some tests will leak things like current_schema into the next test
28
28
  ActiveRecord::Base.clear_all_connections!
29
29
  end
30
30
 
@@ -4,7 +4,6 @@ module Apartment
4
4
  extend self
5
5
 
6
6
  def reset
7
- Apartment::Database.instance_variable_set :@initialized, nil
8
7
  Apartment.excluded_models = nil
9
8
  Apartment.use_postgres_schemas = nil
10
9
  end
@@ -29,11 +28,5 @@ module Apartment
29
28
  ActiveRecord::Migrator.rollback(Rails.root + ActiveRecord::Migrator.migrations_path)
30
29
  end
31
30
 
32
- private
33
-
34
- def sanitize(database)
35
- database.gsub(/[\W]/,'')
36
- end
37
-
38
31
  end
39
32
  end
@@ -17,11 +17,6 @@ describe Apartment do
17
17
  end
18
18
  end
19
19
 
20
- it "should initialize Database" do
21
- Apartment::Database.should_receive(:init).once
22
- Apartment.configure
23
- end
24
-
25
20
  it "should set excluded models" do
26
21
  Apartment.configure do |config|
27
22
  config.excluded_models = excluded_models
@@ -44,7 +39,6 @@ describe Apartment do
44
39
  end
45
40
  Apartment.seed_after_create.should be_true
46
41
  end
47
-
48
42
 
49
43
  context "databases" do
50
44
  it "should return object if it doesnt respond_to call" do
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Apartment::Reloader do
4
+
5
+ context "using postgresql schemas" do
6
+
7
+ before do
8
+ Apartment.excluded_models = ["Company"]
9
+ Company.reset_table_name # ensure we're clean
10
+ end
11
+
12
+ subject{ Apartment::Reloader.new(mock("Rack::Application", :call => nil)) }
13
+
14
+ it "should initialize apartment when called" do
15
+ Company.table_name.should_not include('public.')
16
+ subject.call(mock('env'))
17
+ Company.table_name.should include('public.')
18
+ end
19
+ end
20
+
21
+
22
+ end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: apartment
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.11.0
5
+ version: 0.11.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Ryan Brunner
@@ -147,6 +147,7 @@ files:
147
147
  - lib/apartment/adapters/abstract_adapter.rb
148
148
  - lib/apartment/adapters/mysql_adapter.rb
149
149
  - lib/apartment/adapters/postgresql_adapter.rb
150
+ - lib/apartment/console.rb
150
151
  - lib/apartment/database.rb
151
152
  - lib/apartment/delayed_job/active_record.rb
152
153
  - lib/apartment/delayed_job/enqueue.rb
@@ -155,6 +156,7 @@ files:
155
156
  - lib/apartment/elevators/subdomain.rb
156
157
  - lib/apartment/migrator.rb
157
158
  - lib/apartment/railtie.rb
159
+ - lib/apartment/reloader.rb
158
160
  - lib/apartment/version.rb
159
161
  - lib/tasks/apartment.rake
160
162
  - spec/adapters/mysql_adapter_spec.rb
@@ -209,6 +211,7 @@ files:
209
211
  - spec/unit/config_spec.rb
210
212
  - spec/unit/middleware/subdomain_elevator_spec.rb
211
213
  - spec/unit/migrator_spec.rb
214
+ - spec/unit/reloader_spec.rb
212
215
  has_rdoc: true
213
216
  homepage: http://github.com/bradrobertson/apartment
214
217
  licenses:
@@ -223,7 +226,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
223
226
  requirements:
224
227
  - - ">="
225
228
  - !ruby/object:Gem::Version
226
- hash: -1056095168951008340
229
+ hash: 3705726283453355884
227
230
  segments:
228
231
  - 0
229
232
  version: "0"
@@ -232,7 +235,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
232
235
  requirements:
233
236
  - - ">="
234
237
  - !ruby/object:Gem::Version
235
- hash: -1056095168951008340
238
+ hash: 3705726283453355884
236
239
  segments:
237
240
  - 0
238
241
  version: "0"