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/apartment.gemspec CHANGED
@@ -20,4 +20,4 @@ Gem::Specification.new do |s|
20
20
 
21
21
  s.add_dependency 'activerecord', '>= 3.1.2' # must be >= 3.1.2 due to bug in prepared_statements
22
22
  s.add_dependency 'rack', '>= 1.3.6'
23
- end
23
+ end
@@ -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
@@ -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 `#{config[:adapter]}` is not yet supported"
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
- private
60
+ private
52
61
 
53
62
  # Fetch the rails database configuration
54
63
  #
@@ -1,3 +1,3 @@
1
1
  module Apartment
2
- VERSION = "0.20.0"
2
+ VERSION = "0.21.0"
3
3
  end
@@ -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
- let(:config){ Apartment::Test.config['connections']['mysql'].symbolize_keys }
7
- subject(:adapter){ Apartment::Database.mysql2_adapter config }
7
+ let(:config){ Apartment::Test.config['connections']['mysql'].symbolize_keys }
8
+ subject(:adapter){ Apartment::Database.mysql2_adapter config }
8
9
 
9
- def database_names
10
- ActiveRecord::Base.connection.execute("SELECT schema_name FROM information_schema.schemata").collect{|row| row[0]}
11
- end
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
- let(:default_database){ subject.process{ ActiveRecord::Base.connection.current_database } }
14
+ let(:default_database) { subject.process { ActiveRecord::Base.connection.current_database } }
14
15
 
15
- context "using - the equivalent of - schemas" do
16
- before { Apartment.use_schemas = true }
16
+ context "using - the equivalent of - schemas" do
17
+ before { Apartment.use_schemas = true }
17
18
 
18
- it_should_behave_like "a generic apartment adapter"
19
+ it_should_behave_like "a generic apartment adapter"
19
20
 
20
- describe "#default_database" do
21
- its(:default_database){ should == config[:database] }
22
- end
21
+ describe "#default_database" do
22
+ its(:default_database){ should == config[:database] }
23
+ end
23
24
 
24
- describe "#init" do
25
- include Apartment::Spec::AdapterRequirements
25
+ describe "#init" do
26
+ include Apartment::Spec::AdapterRequirements
26
27
 
27
- before do
28
- Apartment.configure do |config|
29
- config.excluded_models = ["Company"]
28
+ before do
29
+ Apartment.configure do |config|
30
+ config.excluded_models = ["Company"]
31
+ end
30
32
  end
31
- end
32
33
 
33
- it "should process model exclusions" do
34
- Apartment::Database.init
34
+ it "should process model exclusions" do
35
+ Apartment::Database.init
35
36
 
36
- Company.table_name.should == "#{default_database}.companies"
37
+ Company.table_name.should == "#{default_database}.companies"
38
+ end
37
39
  end
38
40
  end
39
- end
40
41
 
41
- context "using connections" do
42
- before { Apartment.use_schemas = false }
42
+ context "using connections" do
43
+ before { Apartment.use_schemas = false }
43
44
 
44
- it_should_behave_like "a generic apartment adapter"
45
- it_should_behave_like "a connection based apartment adapter"
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
- let(:config){ Apartment::Test.config['connections']['postgresql'].symbolize_keys }
7
- subject{ Apartment::Database.postgresql_adapter config }
7
+ let(:config){ Apartment::Test.config['connections']['postgresql'].symbolize_keys }
8
+ subject{ Apartment::Database.postgresql_adapter config }
8
9
 
9
- context "using schemas" do
10
+ context "using schemas" do
10
11
 
11
- before{ Apartment.use_schemas = true }
12
+ before{ Apartment.use_schemas = true }
12
13
 
13
- # Not sure why, but somehow using let(:database_names) memoizes for the whole example group, not just each test
14
- def database_names
15
- ActiveRecord::Base.connection.execute("SELECT nspname FROM pg_namespace;").collect{|row| row['nspname']}
16
- end
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
- let(:default_database){ subject.process{ ActiveRecord::Base.connection.schema_search_path } }
19
+ let(:default_database) { subject.process { ActiveRecord::Base.connection.schema_search_path } }
19
20
 
20
- it_should_behave_like "a generic apartment adapter"
21
- it_should_behave_like "a schema based apartment adapter"
22
- end
21
+ it_should_behave_like "a generic apartment adapter"
22
+ it_should_behave_like "a schema based apartment adapter"
23
+ end
23
24
 
24
- context "using connections" do
25
+ context "using connections" do
25
26
 
26
- before{ Apartment.use_schemas = false }
27
+ before{ Apartment.use_schemas = false }
27
28
 
28
- # Not sure why, but somehow using let(:database_names) memoizes for the whole example group, not just each test
29
- def database_names
30
- connection.execute("select datname from pg_database;").collect{|row| row['datname']}
31
- end
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
- let(:default_database){ subject.process{ ActiveRecord::Base.connection.current_database } }
34
+ let(:default_database) { subject.process { ActiveRecord::Base.connection.current_database } }
34
35
 
35
- it_should_behave_like "a generic apartment adapter"
36
- it_should_behave_like "a connection based apartment adapter"
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