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.
@@ -1,114 +1,115 @@
1
1
  module Apartment
2
-
2
+
3
3
  module Database
4
-
4
+
5
5
  def self.postgresql_adapter(config)
6
- Apartment.use_postgres_schemas ?
6
+ Apartment.use_postgres_schemas ?
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
11
  end
12
-
12
+
13
13
  module Adapters
14
-
14
+
15
15
  # Default adapter when not using Postgresql Schemas
16
16
  class PostgresqlAdapter < AbstractAdapter
17
-
17
+
18
18
  protected
19
-
19
+
20
20
  # Connect to new database
21
21
  # Abstract adapter will catch generic ActiveRecord error
22
22
  # Catch specific adapter errors here
23
- #
23
+ #
24
24
  # @param {String} database Database name
25
- #
25
+ #
26
26
  def connect_to_new(database)
27
27
  super
28
- rescue PGError
28
+ rescue PGError => e
29
29
  raise DatabaseNotFound, "Cannot find database #{environmentify(database)}"
30
- end
31
-
30
+ end
31
+
32
32
  end
33
-
33
+
34
34
  # Separate Adapter for Postgresql when using schemas
35
35
  class PostgresqlSchemaAdapter < AbstractAdapter
36
-
36
+
37
37
  # Get the current schema search path
38
- #
38
+ #
39
39
  # @return {String} current schema search path
40
- #
40
+ #
41
41
  def current_database
42
42
  ActiveRecord::Base.connection.schema_search_path
43
43
  end
44
-
44
+
45
45
  # Drop the database schema
46
- #
46
+ #
47
47
  # @param {String} database Database (schema) to drop
48
- #
48
+ #
49
49
  def drop(database)
50
50
  ActiveRecord::Base.connection.execute("DROP SCHEMA #{database} CASCADE")
51
-
52
- rescue ActiveRecord::StatementInvalid
51
+
52
+ rescue ActiveRecord::StatementInvalid => e
53
53
  raise SchemaNotFound, "The schema #{database.inspect} cannot be found."
54
54
  end
55
55
 
56
56
  # Reset search path to default search_path
57
57
  # Set the table_name to always use the public namespace for excluded models
58
- #
58
+ #
59
59
  def process_excluded_models
60
- Apartment.excluded_models.each do |excluded_model|
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
63
63
  if excluded_model.is_a? Class
64
64
  warn "[Deprecation Warning] Passing class references to excluded models is now deprecated, please use a string instead"
65
65
  excluded_model = excluded_model.name
66
66
  end
67
-
67
+
68
68
  excluded_model.constantize.tap do |klass|
69
- # some models (such as delayed_job) seem to load and cache their column names before this,
69
+ # some models (such as delayed_job) seem to load and cache their column names before this,
70
70
  # so would never get the public prefix, so reset first
71
- klass.reset_column_information
71
+ klass.reset_column_information
72
72
 
73
73
  # Ensure that if a schema *was* set, we override
74
- table_name = klass.table_name.split('.', 2).last
74
+ table_name = klass.table_name.split('.', 2).last
75
75
 
76
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
79
- end
77
+ klass.table_name = "public.#{table_name}"
78
+ end
79
+ end
80
80
  end
81
-
81
+
82
82
  # Reset schema search path to the default schema_search_path
83
- #
83
+ #
84
84
  # @return {String} default schema search path
85
- #
86
- def reset
87
- ActiveRecord::Base.connection.schema_search_path = @defaults[:schema_search_path]
88
- end
85
+ #
86
+ def reset
87
+ ActiveRecord::Base.connection.schema_search_path = @defaults[:schema_search_path]
88
+ end
89
89
 
90
- protected
90
+ protected
91
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
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.clear_cache!
97
+ ActiveRecord::Base.connection.schema_search_path = database
97
98
 
98
99
  rescue ActiveRecord::StatementInvalid => e
99
100
  raise SchemaNotFound, "The schema #{database.inspect} cannot be found."
100
- end
101
+ end
101
102
 
102
103
  # Create the new schema
103
- #
104
- def create_database(database)
105
- ActiveRecord::Base.connection.execute("CREATE SCHEMA #{database}")
104
+ #
105
+ def create_database(database)
106
+ ActiveRecord::Base.connection.execute("CREATE SCHEMA #{database}")
106
107
 
107
- rescue ActiveRecord::StatementInvalid => e
108
- raise SchemaExists, "The schema #{database} already exists."
108
+ rescue ActiveRecord::StatementInvalid => e
109
+ raise SchemaExists, "The schema #{database} already exists."
109
110
  end
110
-
111
+
111
112
  end
112
-
113
+
113
114
  end
114
115
  end
@@ -1,59 +1,57 @@
1
1
  require 'active_support/core_ext/module/delegation'
2
2
 
3
3
  module Apartment
4
-
4
+
5
5
  # The main entry point to Apartment functions
6
- module Database
7
-
6
+ module Database
7
+
8
8
  extend self
9
9
 
10
- delegate :create, :current_database, :drop, :process, :process_excluded_models, :reset, :seed, :switch, :to => :adapter
11
-
12
- attr_writer :config
10
+ delegate :create, :current_database, :process, :process_excluded_models, :reset, :seed, :switch, :to => :adapter
13
11
 
14
12
  # Initialize Apartment config options such as excluded_models
15
- #
16
- def init
13
+ #
14
+ def init
17
15
  process_excluded_models
18
16
  end
19
-
17
+
20
18
  # Fetch the proper multi-tenant adapter based on Rails config
21
- #
19
+ #
22
20
  # @return {subclass of Apartment::AbstractAdapter}
23
- #
21
+ #
24
22
  def adapter
25
23
  @adapter ||= begin
26
24
  adapter_method = "#{config[:adapter]}_adapter"
27
-
28
- begin
25
+
26
+ begin
29
27
  require "apartment/adapters/#{adapter_method}"
30
- rescue LoadError
28
+ rescue LoadError => e
31
29
  raise "The adapter `#{config[:adapter]}` is not yet supported"
32
30
  end
33
31
 
34
32
  unless respond_to?(adapter_method)
35
33
  raise AdapterNotFound, "database configuration specifies nonexistent #{config[:adapter]} adapter"
36
34
  end
37
-
35
+
38
36
  send(adapter_method, config)
39
37
  end
40
38
  end
41
-
39
+
42
40
  # Reset config and adapter so they are regenerated
43
- #
41
+ #
44
42
  def reload!
45
43
  @adapter = nil
46
44
  @config = nil
47
45
  end
48
-
46
+
49
47
  private
50
-
48
+
51
49
  # Fetch the rails database configuration
52
- #
50
+ #
53
51
  def config
54
52
  @config ||= Rails.configuration.database_configuration[Rails.env].symbolize_keys
55
53
  end
56
-
54
+
57
55
  end
58
-
56
+
59
57
  end
@@ -3,22 +3,22 @@ require 'apartment/delayed_job/enqueue'
3
3
  module Apartment
4
4
  module Delayed
5
5
  module Job
6
-
6
+
7
7
  # Before and after hooks for performing Delayed Jobs within a particular apartment database
8
8
  # Include these in your delayed jobs models and make sure provide a @database attr that will be serialized by DJ
9
9
  # Note also that any models that are being serialized need the Apartment::Delayed::Requirements module mixed in to it
10
10
  module Hooks
11
-
11
+
12
12
  attr_accessor :database
13
-
13
+
14
14
  def before(job)
15
15
  Apartment::Database.switch(job.payload_object.database) if job.payload_object.database
16
16
  end
17
-
17
+
18
18
  def after
19
19
  Apartment::Database.reset
20
20
  end
21
-
21
+
22
22
  end
23
23
  end
24
24
  end
@@ -3,41 +3,25 @@ module Apartment
3
3
  # Provides a rack based db switching solution based on subdomains
4
4
  # Assumes that database name should match subdomain
5
5
  class Subdomain
6
-
6
+
7
7
  def initialize(app)
8
8
  @app = app
9
9
  end
10
-
10
+
11
11
  def call(env)
12
- host = Rack::Request.new(env).host
13
-
14
- database = subdomain(host)
15
-
12
+ request = ActionDispatch::Request.new(env)
13
+
14
+ database = subdomain(request)
15
+
16
16
  Apartment::Database.switch database if database
17
-
17
+
18
18
  @app.call(env)
19
19
  end
20
-
21
- private
22
-
23
- # *Almost* a direct ripoff of ActionDispatch::Request subdomain methods
24
-
25
- # Only care about the first subdomain for the database name
26
- def subdomain(host)
27
- subdomains(host).first
20
+
21
+ def subdomain(request)
22
+ request.subdomain.present? && request.subdomain || nil
28
23
  end
29
-
30
- # Assuming tld_length of 1, might need to make this configurable in Apartment in the future for things like .co.uk
31
- def subdomains(host, tld_length = 1)
32
- return [] unless named_host?(host)
33
-
34
- host.split('.')[0..-(tld_length + 2)]
35
- end
36
-
37
- def named_host?(host)
38
- !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
39
- end
40
-
24
+
41
25
  end
42
26
  end
43
27
  end
@@ -1,3 +1,3 @@
1
1
  module Apartment
2
- VERSION = "0.13.0.1"
2
+ VERSION = "0.13.1"
3
3
  end
@@ -2,76 +2,67 @@ require 'spec_helper'
2
2
  require 'apartment/adapters/postgresql_adapter' # specific adapters get dynamically loaded based on adapter name, so we must manually require here
3
3
 
4
4
  describe Apartment::Adapters::PostgresqlAdapter do
5
-
5
+
6
6
  before do
7
7
  ActiveRecord::Base.establish_connection Apartment::Test.config['connections']['postgresql']
8
8
  @schema_search_path = ActiveRecord::Base.connection.schema_search_path
9
9
  end
10
-
10
+
11
11
  after do
12
12
  ActiveRecord::Base.clear_all_connections!
13
13
  end
14
-
14
+
15
15
  context "using schemas" do
16
-
16
+
17
17
  let(:schema){ 'first_db_schema' }
18
18
  let(:schema2){ 'another_db_schema' }
19
19
  let(:database_names){ ActiveRecord::Base.connection.execute("SELECT nspname FROM pg_namespace;").collect{|row| row['nspname']} }
20
-
20
+
21
21
  subject{ Apartment::Database.postgresql_adapter Apartment::Test.config['connections']['postgresql'].symbolize_keys }
22
-
22
+
23
23
  before do
24
24
  Apartment.use_postgres_schemas = true
25
25
  subject.create(schema)
26
26
  subject.create(schema2)
27
27
  end
28
-
28
+
29
29
  after do
30
30
  # sometimes we manually drop these schemas in testing, dont' care if we can't drop hence rescue
31
- subject.drop(schema) rescue true
31
+ subject.drop(schema) rescue true
32
32
  subject.drop(schema2) rescue true
33
33
  end
34
-
34
+
35
35
  describe "#create" do
36
-
36
+
37
37
  it "should create the new schema" do
38
38
  database_names.should include(schema)
39
39
  end
40
-
40
+
41
41
  it "should load schema.rb to new schema" do
42
42
  ActiveRecord::Base.connection.schema_search_path = schema
43
43
  ActiveRecord::Base.connection.tables.should include('companies')
44
44
  end
45
-
46
- it "should not load schema.rb if load_schema is false" do
47
- Apartment.load_schema = false
48
- subject.create("schemax") do
49
- ActiveRecord::Base.connection.tables.should_not include('companies')
50
- end
51
- # Cleanup
52
- subject.drop("schemax")
53
- end
54
-
45
+
55
46
  it "should reset connection when finished" do
56
47
  ActiveRecord::Base.connection.schema_search_path.should_not == schema
57
48
  end
58
-
49
+
59
50
  it "should yield to block if passed" do
60
- Apartment::Test.migrate # ensure we have latest schema in the public
51
+ Apartment::Test.migrate # ensure we have latest schema in the public
61
52
  subject.drop(schema2) # so we don't get errors on creation
62
-
53
+
63
54
  @count = 0 # set our variable so its visible in and outside of blocks
64
-
55
+
65
56
  subject.create(schema2) do
66
57
  @count = User.count
67
58
  ActiveRecord::Base.connection.schema_search_path.should == schema2
68
59
  User.create
69
60
  end
70
-
61
+
71
62
  subject.process(schema2){ User.count.should == @count + 1 }
72
63
  end
73
64
  end
74
-
65
+
75
66
  describe "#drop" do
76
67
 
77
68
  it "should delete the database" do
@@ -87,31 +78,31 @@ describe Apartment::Adapters::PostgresqlAdapter do
87
78
  }.to raise_error(Apartment::SchemaNotFound)
88
79
  end
89
80
  end
90
-
91
-
81
+
82
+
92
83
  describe "#process" do
93
84
  it "should connect" do
94
85
  subject.process(schema) do
95
86
  ActiveRecord::Base.connection.schema_search_path.should == schema
96
87
  end
97
88
  end
98
-
89
+
99
90
  it "should reset" do
100
91
  subject.process(schema)
101
92
  ActiveRecord::Base.connection.schema_search_path.should == @schema_search_path
102
93
  end
103
-
94
+
104
95
  # We're often finding when using Apartment in tests, the `current_database` (ie the previously attached to schema)
105
96
  # gets dropped, but process will try to return to that schema in a test. We should just reset if it doesnt exist
106
97
  it "should not throw exception if current_database (schema) is no longer accessible" do
107
98
  subject.switch(schema2)
108
-
99
+
109
100
  expect {
110
101
  subject.process(schema){ subject.drop(schema2) }
111
102
  }.to_not raise_error(Apartment::SchemaNotFound)
112
103
  end
113
104
  end
114
-
105
+
115
106
  describe "#reset" do
116
107
  it "should reset connection" do
117
108
  subject.switch(schema)
@@ -119,28 +110,28 @@ describe Apartment::Adapters::PostgresqlAdapter do
119
110
  ActiveRecord::Base.connection.schema_search_path.should == @schema_search_path
120
111
  end
121
112
  end
122
-
113
+
123
114
  describe "#switch" do
124
115
  it "should connect to new schema" do
125
116
  subject.switch(schema)
126
117
  ActiveRecord::Base.connection.schema_search_path.should == schema
127
118
  end
128
-
119
+
129
120
  it "should reset connection if database is nil" do
130
121
  subject.switch
131
122
  ActiveRecord::Base.connection.schema_search_path.should == @schema_search_path
132
123
  end
133
124
  end
134
-
125
+
135
126
  describe "#current_database" do
136
127
  it "should return the current schema name" do
137
128
  subject.switch(schema)
138
129
  subject.current_database.should == schema
139
130
  end
140
131
  end
141
-
132
+
142
133
  end
143
-
134
+
144
135
  context "using databases" do
145
136
  # TODO
146
137
  end