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/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