apartment 0.20.0 → 0.21.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/.gitignore +3 -0
- data/.rspec +1 -0
- data/.travis.yml +2 -1
- data/.vagrant +1 -0
- data/Cheffile +24 -0
- data/Cheffile.lock +43 -0
- data/Gemfile +24 -10
- data/HISTORY.md +5 -0
- data/README.md +123 -68
- data/Vagrantfile +112 -0
- data/apartment.gemspec +1 -1
- data/lib/apartment/adapters/abstract_jdbc_adapter.rb +51 -0
- data/lib/apartment/adapters/jdbc_mysql_adapter.rb +28 -0
- data/lib/apartment/adapters/jdbc_postgresql_adapter.rb +127 -0
- data/lib/apartment/database.rb +11 -2
- data/lib/apartment/version.rb +1 -1
- data/spec/adapters/jdbc_mysql_adapter_spec.rb +23 -0
- data/spec/adapters/jdbc_postgresql_adapter_spec.rb +43 -0
- data/spec/adapters/mysql2_adapter_spec.rb +29 -27
- data/spec/adapters/postgresql_adapter_spec.rb +23 -21
- data/spec/config/database.yml.sample +23 -0
- data/spec/dummy/config/database.yml.sample +22 -0
- data/spec/integration/delayed_job_integration_spec.rb +60 -58
- data/spec/spec_helper.rb +6 -3
- data/spec/support/config.rb +1 -1
- data/spec/unit/migrator_spec.rb +2 -0
- metadata +48 -36
data/apartment.gemspec
CHANGED
@@ -0,0 +1,51 @@
|
|
1
|
+
module Apartment
|
2
|
+
|
3
|
+
module Adapters
|
4
|
+
|
5
|
+
class AbstractJDBCAdapter < AbstractAdapter
|
6
|
+
|
7
|
+
# Drop the database
|
8
|
+
#
|
9
|
+
# @param {String} database Database name
|
10
|
+
#
|
11
|
+
def drop(database)
|
12
|
+
super(database)
|
13
|
+
|
14
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::JDBCError
|
15
|
+
raise DatabaseNotFound, "The database #{environmentify(database)} cannot be found"
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
# Create the database
|
21
|
+
#
|
22
|
+
# @param {String} database Database name
|
23
|
+
#
|
24
|
+
def create_database(database)
|
25
|
+
super(database)
|
26
|
+
|
27
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::JDBCError
|
28
|
+
raise DatabaseExists, "The database #{environmentify(database)} already exists."
|
29
|
+
end
|
30
|
+
|
31
|
+
# Connect to new database
|
32
|
+
#
|
33
|
+
# @param {String} database Database name
|
34
|
+
#
|
35
|
+
def connect_to_new(database)
|
36
|
+
super(database)
|
37
|
+
|
38
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::JDBCError
|
39
|
+
raise DatabaseNotFound, "The database #{environmentify(database)} cannot be found."
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return a new config that is multi-tenanted
|
43
|
+
#
|
44
|
+
def multi_tenantify(database)
|
45
|
+
@config.clone.tap do |config|
|
46
|
+
config[:url] = "#{config[:url].gsub(/(\S+)\/.+$/, '\1')}/#{environmentify(database)}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Apartment
|
2
|
+
|
3
|
+
module Database
|
4
|
+
def self.jdbc_mysql_adapter(config)
|
5
|
+
Adapters::JDBCMysqlAdapter.new config
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Adapters
|
10
|
+
class JDBCMysqlAdapter < AbstractJDBCAdapter
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
# Connect to new database
|
15
|
+
# Abstract adapter will catch generic ActiveRecord error
|
16
|
+
# Catch specific adapter errors here
|
17
|
+
#
|
18
|
+
# @param {String} database Database name
|
19
|
+
#
|
20
|
+
def connect_to_new(database)
|
21
|
+
super
|
22
|
+
rescue DatabaseNotFound
|
23
|
+
Apartment::Database.reset
|
24
|
+
raise DatabaseNotFound, "Cannot find database #{environmentify(database)}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Apartment
|
2
|
+
|
3
|
+
module Database
|
4
|
+
|
5
|
+
def self.jdbc_postgresql_adapter(config)
|
6
|
+
Apartment.use_schemas ?
|
7
|
+
Adapters::JDBCPostgresqlSchemaAdapter.new(config) :
|
8
|
+
Adapters::JDBCPostgresqlAdapter.new(config)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Adapters
|
13
|
+
|
14
|
+
# Default adapter when not using Postgresql Schemas
|
15
|
+
class JDBCPostgresqlAdapter < AbstractJDBCAdapter
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
# Connect to new database
|
20
|
+
# Abstract adapter will catch generic ActiveRecord error
|
21
|
+
# Catch specific adapter errors here
|
22
|
+
#
|
23
|
+
# @param {String} database Database name
|
24
|
+
#
|
25
|
+
def connect_to_new(database)
|
26
|
+
super(database)
|
27
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::JDBCError
|
28
|
+
raise DatabaseNotFound, "Cannot find database #{environmentify(database)}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_database(database)
|
32
|
+
# There is a bug in activerecord-jdbcpostgresql-adapter (1.2.5) that will cause
|
33
|
+
# an exception if no options are passed into the create_database call.
|
34
|
+
Apartment.connection.create_database(environmentify(database), { :thisisahack => '' })
|
35
|
+
|
36
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::JDBCError
|
37
|
+
raise DatabaseExists, "The database #{environmentify(database)} already exists."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Separate Adapter for Postgresql when using schemas
|
42
|
+
class JDBCPostgresqlSchemaAdapter < AbstractJDBCAdapter
|
43
|
+
|
44
|
+
# Drop the database schema
|
45
|
+
#
|
46
|
+
# @param {String} database Database (schema) to drop
|
47
|
+
#
|
48
|
+
def drop(database)
|
49
|
+
Apartment.connection.execute(%{DROP SCHEMA "#{database}" CASCADE})
|
50
|
+
|
51
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::JDBCError
|
52
|
+
raise SchemaNotFound, "The schema #{database.inspect} cannot be found."
|
53
|
+
end
|
54
|
+
|
55
|
+
# Reset search path to default search_path
|
56
|
+
# Set the table_name to always use the default namespace for excluded models
|
57
|
+
#
|
58
|
+
def process_excluded_models
|
59
|
+
Apartment.excluded_models.each do |excluded_model|
|
60
|
+
# Note that due to rails reloading, we now take string references to classes rather than
|
61
|
+
# actual object references. This way when we contantize, we always get the proper class reference
|
62
|
+
if excluded_model.is_a? Class
|
63
|
+
warn "[Deprecation Warning] Passing class references to excluded models is now deprecated, please use a string instead"
|
64
|
+
excluded_model = excluded_model.name
|
65
|
+
end
|
66
|
+
|
67
|
+
excluded_model.constantize.tap do |klass|
|
68
|
+
# some models (such as delayed_job) seem to load and cache their column names before this,
|
69
|
+
# so would never get the default prefix, so reset first
|
70
|
+
klass.reset_column_information
|
71
|
+
|
72
|
+
# Ensure that if a schema *was* set, we override
|
73
|
+
table_name = klass.table_name.split('.', 2).last
|
74
|
+
|
75
|
+
# Not sure why, but Delayed::Job somehow ignores table_name_prefix... so we'll just manually set table name instead
|
76
|
+
klass.table_name = "#{Apartment.default_schema}.#{table_name}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Reset schema search path to the default schema_search_path
|
82
|
+
#
|
83
|
+
# @return {String} default schema search path
|
84
|
+
#
|
85
|
+
def reset
|
86
|
+
@current_database = Apartment.default_schema
|
87
|
+
Apartment.connection.schema_search_path = full_search_path
|
88
|
+
end
|
89
|
+
|
90
|
+
def current_database
|
91
|
+
@current_database || Apartment.default_schema
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
# Set schema search path to new schema
|
97
|
+
#
|
98
|
+
def connect_to_new(database = nil)
|
99
|
+
return reset if database.nil?
|
100
|
+
|
101
|
+
@current_database = database.to_s
|
102
|
+
Apartment.connection.schema_search_path = full_search_path
|
103
|
+
|
104
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::JDBCError
|
105
|
+
raise SchemaNotFound, "One of the following schema(s) is invalid: #{full_search_path}"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Create the new schema
|
109
|
+
#
|
110
|
+
def create_database(database)
|
111
|
+
Apartment.connection.execute(%{CREATE SCHEMA "#{database}"})
|
112
|
+
|
113
|
+
rescue ActiveRecord::StatementInvalid, ActiveRecord::JDBCError
|
114
|
+
raise SchemaExists, "The schema #{database} already exists."
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# Generate the final search path to set including persistent_schemas
|
120
|
+
#
|
121
|
+
def full_search_path
|
122
|
+
persistent_schemas = Apartment.persistent_schemas.join(', ')
|
123
|
+
@current_database.to_s + (persistent_schemas.empty? ? "" : ", #{persistent_schemas}")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/apartment/database.rb
CHANGED
@@ -27,10 +27,19 @@ module Apartment
|
|
27
27
|
Thread.current[:apartment_adapter] ||= begin
|
28
28
|
adapter_method = "#{config[:adapter]}_adapter"
|
29
29
|
|
30
|
+
if defined?(JRUBY_VERSION)
|
31
|
+
if config[:adapter] =~ /mysql/
|
32
|
+
adapter_method = 'jdbc_mysql_adapter'
|
33
|
+
elsif config[:adapter] =~ /postgresql/
|
34
|
+
adapter_method = 'jdbc_postgresql_adapter'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
30
38
|
begin
|
39
|
+
require "apartment/adapters/abstract_jdbc_adapter" if defined?(JRUBY_VERSION)
|
31
40
|
require "apartment/adapters/#{adapter_method}"
|
32
41
|
rescue LoadError
|
33
|
-
raise "The adapter `#{
|
42
|
+
raise "The adapter `#{adapter_method}` is not yet supported"
|
34
43
|
end
|
35
44
|
|
36
45
|
unless respond_to?(adapter_method)
|
@@ -48,7 +57,7 @@ module Apartment
|
|
48
57
|
@config = config
|
49
58
|
end
|
50
59
|
|
51
|
-
|
60
|
+
private
|
52
61
|
|
53
62
|
# Fetch the rails database configuration
|
54
63
|
#
|
data/lib/apartment/version.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
if defined?(JRUBY_VERSION)
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'lib/apartment/adapters/jdbc_mysql_adapter'
|
5
|
+
|
6
|
+
describe Apartment::Adapters::JDBCMysqlAdapter do
|
7
|
+
|
8
|
+
|
9
|
+
let(:config) { Apartment::Test.config['connections']['mysql'] }
|
10
|
+
subject { Apartment::Database.jdbc_mysql_adapter config.symbolize_keys }
|
11
|
+
|
12
|
+
def database_names
|
13
|
+
ActiveRecord::Base.connection.execute("SELECT schema_name FROM information_schema.schemata").collect { |row| row['schema_name'] }
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:default_database) { subject.process { ActiveRecord::Base.connection.current_database } }
|
17
|
+
|
18
|
+
it_should_behave_like "a generic apartment adapter"
|
19
|
+
it_should_behave_like "a connection based apartment adapter"
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
if defined?(JRUBY_VERSION)
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'lib/apartment/adapters/jdbc_postgresql_adapter'
|
5
|
+
|
6
|
+
describe Apartment::Adapters::JDBCPostgresqlAdapter do
|
7
|
+
|
8
|
+
|
9
|
+
let(:config) { Apartment::Test.config['connections']['postgresql'] }
|
10
|
+
subject { Apartment::Database.jdbc_postgresql_adapter config.symbolize_keys }
|
11
|
+
|
12
|
+
context "using schemas" do
|
13
|
+
|
14
|
+
before { Apartment.use_schemas = true }
|
15
|
+
|
16
|
+
# Not sure why, but somehow using let(:database_names) memoizes for the whole example group, not just each test
|
17
|
+
def database_names
|
18
|
+
ActiveRecord::Base.connection.execute("SELECT nspname FROM pg_namespace;").collect { |row| row['nspname'] }
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:default_database) { subject.process { ActiveRecord::Base.connection.schema_search_path } }
|
22
|
+
|
23
|
+
it_should_behave_like "a generic apartment adapter"
|
24
|
+
it_should_behave_like "a schema based apartment adapter"
|
25
|
+
end
|
26
|
+
|
27
|
+
context "using databases" do
|
28
|
+
|
29
|
+
before { Apartment.use_schemas = false }
|
30
|
+
|
31
|
+
# Not sure why, but somehow using let(:database_names) memoizes for the whole example group, not just each test
|
32
|
+
def database_names
|
33
|
+
connection.execute("select datname from pg_database;").collect { |row| row['datname'] }
|
34
|
+
end
|
35
|
+
|
36
|
+
let(:default_database) { subject.process { ActiveRecord::Base.connection.current_database } }
|
37
|
+
|
38
|
+
it_should_behave_like "a generic apartment adapter"
|
39
|
+
it_should_behave_like "a connection based apartment adapter"
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -2,46 +2,48 @@ require 'spec_helper'
|
|
2
2
|
require 'apartment/adapters/mysql2_adapter'
|
3
3
|
|
4
4
|
describe Apartment::Adapters::Mysql2Adapter do
|
5
|
+
unless defined?(JRUBY_VERSION)
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
let(:config){ Apartment::Test.config['connections']['mysql'].symbolize_keys }
|
8
|
+
subject(:adapter){ Apartment::Database.mysql2_adapter config }
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def database_names
|
11
|
+
ActiveRecord::Base.connection.execute("SELECT schema_name FROM information_schema.schemata").collect { |row| row[0] }
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
+
let(:default_database) { subject.process { ActiveRecord::Base.connection.current_database } }
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
context "using - the equivalent of - schemas" do
|
17
|
+
before { Apartment.use_schemas = true }
|
17
18
|
|
18
|
-
|
19
|
+
it_should_behave_like "a generic apartment adapter"
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
describe "#default_database" do
|
22
|
+
its(:default_database){ should == config[:database] }
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
|
25
|
+
describe "#init" do
|
26
|
+
include Apartment::Spec::AdapterRequirements
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
before do
|
29
|
+
Apartment.configure do |config|
|
30
|
+
config.excluded_models = ["Company"]
|
31
|
+
end
|
30
32
|
end
|
31
|
-
end
|
32
33
|
|
33
|
-
|
34
|
-
|
34
|
+
it "should process model exclusions" do
|
35
|
+
Apartment::Database.init
|
35
36
|
|
36
|
-
|
37
|
+
Company.table_name.should == "#{default_database}.companies"
|
38
|
+
end
|
37
39
|
end
|
38
40
|
end
|
39
|
-
end
|
40
41
|
|
41
|
-
|
42
|
-
|
42
|
+
context "using connections" do
|
43
|
+
before { Apartment.use_schemas = false }
|
43
44
|
|
44
|
-
|
45
|
-
|
45
|
+
it_should_behave_like "a generic apartment adapter"
|
46
|
+
it_should_behave_like "a connection based apartment adapter"
|
47
|
+
end
|
46
48
|
end
|
47
|
-
end
|
49
|
+
end
|
@@ -2,37 +2,39 @@ require 'spec_helper'
|
|
2
2
|
require 'apartment/adapters/postgresql_adapter'
|
3
3
|
|
4
4
|
describe Apartment::Adapters::PostgresqlAdapter do
|
5
|
+
unless defined?(JRUBY_VERSION)
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
let(:config){ Apartment::Test.config['connections']['postgresql'].symbolize_keys }
|
8
|
+
subject{ Apartment::Database.postgresql_adapter config }
|
8
9
|
|
9
|
-
|
10
|
+
context "using schemas" do
|
10
11
|
|
11
|
-
|
12
|
+
before{ Apartment.use_schemas = true }
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
# Not sure why, but somehow using let(:database_names) memoizes for the whole example group, not just each test
|
15
|
+
def database_names
|
16
|
+
ActiveRecord::Base.connection.execute("SELECT nspname FROM pg_namespace;").collect { |row| row['nspname'] }
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
+
let(:default_database) { subject.process { ActiveRecord::Base.connection.schema_search_path } }
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
it_should_behave_like "a generic apartment adapter"
|
22
|
+
it_should_behave_like "a schema based apartment adapter"
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
+
context "using connections" do
|
25
26
|
|
26
|
-
|
27
|
+
before{ Apartment.use_schemas = false }
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
# Not sure why, but somehow using let(:database_names) memoizes for the whole example group, not just each test
|
30
|
+
def database_names
|
31
|
+
connection.execute("select datname from pg_database;").collect { |row| row['datname'] }
|
32
|
+
end
|
32
33
|
|
33
|
-
|
34
|
+
let(:default_database) { subject.process { ActiveRecord::Base.connection.current_database } }
|
34
35
|
|
35
|
-
|
36
|
-
|
36
|
+
it_should_behave_like "a generic apartment adapter"
|
37
|
+
it_should_behave_like "a connection based apartment adapter"
|
38
|
+
end
|
37
39
|
end
|
38
40
|
end
|