apartment 0.20.0 → 0.21.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|