connection_manager 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGE.md +12 -1
  3. data/README.md +99 -82
  4. data/connection_manager.gemspec +7 -11
  5. data/lib/connection_manager/builder.rb +56 -0
  6. data/lib/connection_manager/connection_adapters/abstract_adapter.rb +50 -0
  7. data/lib/connection_manager/connection_adapters/mysql_adapter.rb +39 -0
  8. data/lib/connection_manager/connection_handling.rb +91 -0
  9. data/lib/connection_manager/core.rb +24 -0
  10. data/lib/connection_manager/querying.rb +13 -0
  11. data/lib/connection_manager/railtie.rb +10 -0
  12. data/lib/connection_manager/relation.rb +21 -0
  13. data/lib/connection_manager/replication.rb +56 -85
  14. data/lib/connection_manager/shards.rb +2 -1
  15. data/lib/connection_manager/using.rb +30 -24
  16. data/lib/connection_manager/version.rb +1 -1
  17. data/lib/connection_manager.rb +26 -16
  18. data/spec/{mysql2_database.yml → database.yml} +30 -26
  19. data/spec/factories.rb +1 -1
  20. data/spec/helpers/database_spec_helper.rb +91 -62
  21. data/spec/helpers/models_spec_helper.rb +23 -12
  22. data/spec/lib/builder_spec.rb +31 -0
  23. data/spec/lib/connection_adapters/abstract_adapter_spec.rb +48 -0
  24. data/spec/lib/connection_adapters/mysql_adapter_spec.rb +13 -0
  25. data/spec/lib/connection_handling_spec.rb +65 -0
  26. data/spec/lib/core_spec.rb +10 -0
  27. data/spec/lib/integration/cross_schema_spec.rb +35 -0
  28. data/spec/lib/querying_spec.rb +19 -0
  29. data/spec/lib/relation_spec.rb +21 -0
  30. data/spec/lib/replication_spec.rb +19 -57
  31. data/spec/lib/shards_spec.rb +3 -3
  32. data/spec/lib/using_proxy_spec.rb +27 -0
  33. data/spec/lib/using_spec.rb +28 -15
  34. data/spec/spec_helper.rb +2 -10
  35. metadata +73 -35
  36. data/lib/connection_manager/connection_builder.rb +0 -82
  37. data/lib/connection_manager/connection_manager_railtie.rb +0 -8
  38. data/lib/connection_manager/helpers/abstract_adapter_helper.rb +0 -95
  39. data/lib/connection_manager/helpers/connection_helpers.rb +0 -119
  40. data/lib/connection_manager/patches/cross_schema_patch.rb +0 -67
  41. data/spec/jdbcmysql_database.yml +0 -50
  42. data/spec/lib/connection_builder_spec.rb +0 -28
  43. data/spec/lib/connection_helpers_spec.rb +0 -79
  44. data/spec/lib/patches/cross_schema_path_spec.rb +0 -74
  45. data/spec/sqlite_database.yml +0 -26
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MTEwZjBmOTI5ZTVjMTI0OGJkZDQwODg0NzcwYTIzZGUxYTI5M2M4NQ==
4
+ MGZjYTQyMmJjZDkyODU2MDMyYWU4OGJmYjRkZTg4ZmNjMmM3YmYwNw==
5
5
  data.tar.gz: !binary |-
6
- ZGJjMWY5YWU4Zjc0OTI3MWMxMTczYTAwMTNmYzI0MTY2OGJhYTEyNQ==
6
+ MjVhMjBlN2NhZGU3ZmVlYmJiOGRjODUxZmYwMDFjOTE3N2I3OGI0ZA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NjIxODExY2IwYzEzY2Y4MjU3ODAyMGVhZTZmYTM0YTcwODAzMDYxMWYyNjU5
10
- NWE3N2M2YzBkYTYxYzk3ZDdiYzA5NzgzYjQ0ODU5Y2E5MmMwMDM5OWU5MTA4
11
- MGUzMGNiNjE3NDdiNGYwYzUwYjZlM2NhNWM3ZDAyOWE2ZWQxYzE=
9
+ NjFmMDZkNDE3NjgxMWU2NTRiNDZlMGZhMmJkN2JmZmE4YjQwMDdhY2UxMDY3
10
+ NDEzYmM0OTk2MmZlNGRiNTYyZWM4ZWE4OTc1MTc3NjRmY2ZhMzI1OTk5YWQ3
11
+ MWNkYjY2MGNmNDdjMTdhMjZhODE1ZWY4ZTE5NTRmZDRlNDMzMGE=
12
12
  data.tar.gz: !binary |-
13
- NGFiOWQ5NTQyZGYyZmNkYzQ0NWI0ZjVmYjg3NDhmZTE1YjY5MzVlY2Y0NmFh
14
- OTY1ZTczNjE5ZWFlZDU4YmQ5OWIxZmUxNmMyYzI2MTg3N2QzNmMwOWIzMmY0
15
- NTI1ZGUzNWFiNWJhZDc3ZjQ0NGFjZjEwYTNkYzU1MjhjOWUzZjE=
13
+ MzYwMjJjMzU5NWRlODk3MGRjMmEzMWI2ODJkNThkMGRiMjUzNWE1NmMxMjQ5
14
+ YzkzMjQzNjAyMmEzZDg5MTVmMjI0ZWMwMDdhMjcyY2E4ZjVmZTY2OWFkZTVi
15
+ MGI5MWUwZTkxYTAyZTZlYWYwZjBlMmZmYzE2MDkwMzE0MTYxMjE=
data/CHANGE.md CHANGED
@@ -3,7 +3,18 @@ ConnectionManager Changelog
3
3
 
4
4
  HEAD
5
5
  =======
6
- - None yet!
6
+ - Nothing yet!
7
+
8
+ 1.1.0
9
+ =======
10
+ - BREAKING CHANGE - Code has been organized to mirror as much a possible their ActiveRecord 4 counter parts.
11
+ - BREAKING CHANGE - creates AR::Relation for :slaves and :masters, replication method names are no longer customizable
12
+ - BREAKING CHANGE - Drop support for forced readonly, should be enforced by DMS and or using #readonly ActiveRecord::Relation
13
+ - BREAKING CHANGE - Drop use_schema in favor of schema_name=
14
+ - BREAKING CHANGE - Drop current_database_name in favor of schema_name
15
+ - Make sure all connections are checked in to managed_connections by patch establish_connection
16
+ - Use thread-safe for managed_connections
17
+ - Don't try and fetch schema using query, too slow and buggy, we want a light weight implementation, but still get it from database.yml if Mysql and not set
7
18
 
8
19
  1.0.4
9
20
  =======
data/README.md CHANGED
@@ -1,15 +1,10 @@
1
1
  # ConnectionManager
2
- Multi-Database, Replication and Sharding for ActiveRecord.
3
-
4
- ## Background
5
- ActiveRecord, for quite some time now, has supported multiple database connections
6
- through the use of #establish_connection and connection classes [more info](http://api.rubyonrails.org/classes/ActiveRecord/Base.html)
7
- Multiple databases, replication and shards can be implemented directly in rails without
8
- patching, but a gem helps to reduce redundant code and ensure consistency.
9
- ConnectionManager replaces all the connection classes and subclasses required
10
- for multiple database support in Rails with a few class methods and simple
11
- database.yml configuration. Since ConnectionManager does not alter
12
- ActiveRecord's connection pool, thread safety is not a concern.
2
+ Improved cross-schema, replication and mutli-DMS gem for ActiveRecord.
3
+
4
+ ## Features
5
+ * Threadsafe connection switching
6
+ * Replication can be defined in the database.yml or in the model, adds #slaves and #masters as ActiveRecord::Relations.
7
+ * Automatically builds connection classes, if configured.
13
8
 
14
9
  ## Installation
15
10
 
@@ -31,129 +26,152 @@ Run bundle install:
31
26
 
32
27
  common: &common
33
28
  adapter: mysql2
34
- username: root
35
- password: *****
36
29
  pool: 20
37
- connect_timeout: 20
38
- timeout: 900
30
+ reconnect: true
39
31
  socket: /tmp/mysql.sock
40
- build_connection_class: true
41
-
42
- development:
43
- <<: *common
44
- database: test_app
45
- slaves: [slave_1_test_app_development, slave_2_test_app_development]
46
-
47
- slave_1_test_app_development:
48
- <<: *common
49
- database: test_app
50
- readonly: true
51
32
 
52
- slave_2_test_app_development:
33
+ production:
53
34
  <<: *common
54
- database: test_app
55
- readonly: true
56
-
57
- user_data_development
35
+ database: myapp
36
+ host: <%=ENV['DB_HOST']%>
37
+ username: <%=ENV['DB_USER']%>
38
+ password: <%=ENV['DB_PASS']%>
39
+ slaves: [slave_1_production, slave_2_production]
40
+ build_connection_class: true
41
+
42
+ slave_1_production:
58
43
  <<: *common
59
- database: user_data
60
- slaves: [slave_1_user_data_development, slave_2_user_data_development]
44
+ host: <%=ENV['SLAVE_1_DB_HOST']%>
45
+ username: <%=ENV['SLAVE_1_DB_USER']%>
46
+ password: <%=ENV['SLAVE_1_DB_PASS']%>
47
+ database: myapp
48
+ build_connection_class: true
61
49
 
62
- slave_1_user_data_development
50
+ slave_2_production:
63
51
  <<: *common
64
- database: user_data
65
- readonly: true
52
+ host: <%=ENV['SLAVE_2_DB_HOST']%>
53
+ username: <%=ENV['SLAVE_2_DB_USER']%>
54
+ password: <%=ENV['SLAVE_2_DB_PASS']%>
55
+ database: myapp
56
+ build_connection_class: true
66
57
 
67
- slave_2_user_data_development
58
+ foo_data_production
68
59
  <<: *common
60
+ host: <%=ENV['USER_DATA_DB_HOST']%>
61
+ username: <%=ENV['USER_DATA_DB_USER']%>
62
+ password: <%=ENV['USER_DATA_DB_PASS']%>
69
63
  database: user_data
70
- readonly: true
64
+ build_connection_class: true
71
65
 
72
66
  In the above database.yml the Master databases are listed as "development" and "user_data_development".
73
- Replication databases are defined as normally connections and are added to the 'replications:' option for
74
- their master. The readonly option ensures all ActiveRecord objects returned from this connection are ALWAYS readonly.
75
-
67
+ Replication databases are defined as normally connections and are added to the `replication_connections` for
68
+ their master.
76
69
 
77
70
  ## Building Connection Classes
78
71
 
79
72
  ### Manually
80
- ConnectionManager provides establish_managed_connection for build connection
81
- classes and connection to multiple databases.
73
+ ConnectionManager provides `establish_managed_connection` for build connection
74
+ classes and connection to multiple databases. The `establish_managed_connection method`,
75
+ runs `establish_connection` with the supplied database.yml key, sets `abstract_class` to true.
82
76
 
83
- class MyConnection < ActiveRecord::Base
84
- establish_managed_connection("my_database_#{Rails.env}", :readonly => true)
77
+ class MySlaveConnection < ActiveRecord::Base
78
+ establish_managed_connection("slave_1_#{Rails.env}")
85
79
  end
86
80
 
87
- class User < MyConnection
88
- end
81
+ class User < MySlaveConnection;end
89
82
 
90
- MyConnection => MyConnection(abstract)
83
+ MyConnection # => MyConnection(abstract)
91
84
  @user = User.first
92
- @user.readonly? => true
93
-
94
- The establish_managed_connection method, runs establish_connection with the supplied
95
- database.yml key, sets abstract_class to true, and (since :readonly is set to true) ensures
96
- all ActiveRecord objects build using this connection class are readonly. If readonly is set
97
- to true in the database.yml, passing the readonly option is not necessary.
85
+ @user # => #<User id:1>
98
86
 
99
87
 
88
+
100
89
  ### Automatically
101
90
  ActiveRecord can build all your connection classes for you.
102
- The connection class names will be based on the database.yml keys.ActiveRecord will
91
+ The connection class names will be based on the database.yml keys. ActiveRecord will
103
92
  build connection classes for all the entries in the database.yml where
104
- "build_connection_class" is true, and match the current environment settings
93
+ `build_connection_class: = true`, and match the current environment settings
94
+
95
+ # Class names derived from YML keys
96
+ 'production' = 'BaseConnection'
97
+ 'slave_1_production' = 'Save1Connection'
98
+ 'slave_2_production' = 'Save2Connection'
99
+ 'foo_data_production' = 'FooDataConnection'
100
+
105
101
 
106
102
  ## Using
107
103
 
108
104
  The using method allows you specify the connection class to use
109
- for query. The return objects will have the correct model name, but the instance's
110
- class's superclass will be the connection class and all database actions performed
111
- on the instance will use the connection class's connection.
105
+ for query.
112
106
 
113
107
  User.using("Slave1Connection").first
114
108
 
115
109
  search = User.where(disabled => true)
116
- @legacy_users = search.using("Shard1Connection").all #=> [<User::Shard1ConnectionDup...>,<User::Shard1ConnectionDup..]
117
- @legacy_users.first.save #=> uses the Shard1Connection connection
110
+ @legacy_users = search.using("Slave1Connection").all # => [<User...>,<User...>]
111
+ @legacy_users.first.save #=> uses the SlaveConnection connection
118
112
 
119
113
  @new_users = search.page(params[:page]).all => [<User...>,<User...>]
120
114
 
121
115
  ## Replication
122
116
 
123
- Simply add 'replicated' to your model.
117
+ ConnectionManager creates ActiveRecord::Relation methods :slaves and :masters.
118
+
119
+ If you specify your replication model in your database.yml there is nothing more you need to do. If you looking for more
120
+ granular control you describe the replication setup on a per-model level.
124
121
 
125
122
  class User < UserDataConnection
126
123
  has_one :job
127
124
  has_many :teams
128
- replicated # implement replication
129
- # model code ...
125
+ replicated :slaves => [MySlave1Connection, MySlave2Connection]
130
126
  end
131
127
 
132
- The replicated method builds models who inherit from the main model.
133
- User::Slave1UserDataConnectionDup.superclass => Slave1UserDataConnection(abstract)
134
- User::Slave1UserDataDup.first => returns results from slave_1_user_data_development
135
- User::Slave2UserDataDup.where(['created_at BETWEEN ? and ?',Time.now - 3.hours, Time.now]).all => returns results from slave_2_user_data_development
128
+ User.limit(2).slaves.all # => [<User...>,<User...>] results from MySlave1Connection or MySlave2Connection
136
129
 
137
- Finally, ConnectionManager creates an additional class method that shifts through your
138
- available slave connections each time it is called using a different connection on each action.
130
+
131
+ If there are multiple replication connections the system will pick a connection at random using Array#sample.
139
132
 
140
133
  User.slaves.first => returns results from slave_1_use_data_development
141
- User.slaves.last => => returns results from slave_2_use_data_development
142
- User.slaves.where(['created_at BETWEEN ? and ?',Time.now - 3.hours, Time.now]).all => returns results from slave_1_user_data_development
143
- User.slaves.where(['created_at BETWEEN ? and ?',Time.now - 5.days, Time.now]).all => returns results from slave_2_user_data_development
134
+ User.slaves.last => returns results from slave_2_user_data_development
135
+ User.slaves.where('id BETWEEN ? and ?',1,100]).all => returns results from slave_1_user_data_development
136
+ User.slaves.where('id BETWEEN ? and ?',1,100]).all => returns results from slave_2_user_data_development
144
137
 
145
- Replicated defaults to the slaves replication type,so if you have only masters and a combination
146
- of masters and slaves for replication, you have set the replication type to masters
138
+ ### Repliation with cross-schema queries
139
+ Setup replication as you would normally
147
140
 
148
- class User < UserDataConnection
149
- replicated #slaves replication
150
- replicated :type => :masters, :name => 'masters' # masters replication
141
+ Next build connection classes that inherit from you base connection classes for each of your schemas
142
+ EX
143
+ class UserSchema < ActiveRecord::Base
144
+ self.abstract_class = true
145
+ self.table_name_prefix = 'user_schema.'
146
+
147
+ def self.inherited(base)
148
+ base.use_schema(self.schema_name)
149
+ end
150
+ end
151
+
152
+ class User < UserSchema
153
+ has_many :bars
154
+ end
155
+
156
+ class FooSchema < ActiveRecord::Base
157
+ self.abstract_class = true
158
+ self.table_name_prefix = 'foo.'
159
+
160
+ def self.inherited(base)
161
+ base.use_schema(self.schema_name)
162
+ end
151
163
  end
152
164
 
165
+ class Bar < FooSchema
166
+ belongs_to :user
167
+ end
168
+
169
+ User.joins(:bars).limit(1).to_sql # => SELECT * FROM `user_schema`.`users` INNER JOIN `foo.bars` ON `foo.bars`.`user_id` = `user_schema`.`users` LIMIT 1"
170
+
153
171
  ## Sharding
154
172
 
155
173
  After tinkering with some solutions for shards, I've come to a similar conclusion as [DataFabric] (https://github.com/mperham/data_fabric):
156
- "Sharding should be implemented at the application level". The #shards method is very basic and
174
+ "Sharding should be implemented at the application level". The `shards` method is very basic and
157
175
  while it may be useful to most folks, it should really serve as an example of a possible solutions
158
176
  to your shard requirements.
159
177
 
@@ -176,7 +194,7 @@ originate from classes that used establish_connection you must surround your cod
176
194
  Some queries...
177
195
  }
178
196
 
179
- In Rails for less complicated schemas you could simply create an around filter for your controllers
197
+ In Rails, you can create an around filter for your controllers
180
198
 
181
199
  class ApplicationController < ActionController::Base
182
200
  around_filter :cache_slaves
@@ -186,8 +204,7 @@ In Rails for less complicated schemas you could simply create an around filter f
186
204
  end
187
205
 
188
206
  ## Migrations
189
-
190
- Nothing implement now to help but there are lots of potential solutions [here] (http://stackoverflow.com/questions/1404620/using-rails-migration-on-different-database-than-standard-production-or-devel)
207
+ There are lots of potential solutions [here] (http://stackoverflow.com/questions/1404620/using-rails-migration-on-different-database-than-standard-production-or-devel)
191
208
 
192
209
  ## TODOs
193
210
  * Maybe add migration support for Rails AR implementations.
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
8
8
  s.authors = ["Joshua Mckinney"]
9
9
  s.email = ["joshmckin@gmail.com"]
10
10
  s.homepage = ""
11
- s.summary = %q{Simplifies connecting to Multiple and Replication databases with rails and active_record}
12
- s.description = %q{Simplifies connecting to Multiple and Replication databases with rails and active_record}
11
+ s.summary = %q{Cross-schema, replication and mutli-DMS gem for ActiveRecord.}
12
+ s.description = %q{Improves support for cross-schema, replication and mutli-DMS applications using ActiveRecord.}
13
13
 
14
14
  s.rubyforge_project = "connection_manager"
15
15
 
@@ -18,16 +18,12 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
20
  s.add_runtime_dependency 'activerecord', '>= 3.0', '<= 4.1'
21
- s.add_development_dependency 'rspec'
21
+ s.add_runtime_dependency 'activesupport', '>= 3.0', '<= 4.1'
22
+ s.add_runtime_dependency 'thread_safe'
23
+ s.add_development_dependency 'rspec', '~> 2.0'
22
24
  s.add_development_dependency 'autotest'
23
25
  s.add_development_dependency 'mocha'
24
26
  s.add_development_dependency 'factory_girl'
25
- s.add_development_dependency 'activesupport', '>= 3.0', '<= 4.1'
26
-
27
- if(defined? RUBY_ENGINE and 'jruby' == RUBY_ENGINE)
28
- s.add_development_dependency 'jruby-openssl'
29
- s.add_development_dependency 'activerecord-jdbcmysql-adapter'
30
- else
31
- s.add_development_dependency "mysql2"
32
- end
27
+ s.add_development_dependency 'mysql2'
28
+ s.add_development_dependency 'pg'
33
29
  end
@@ -0,0 +1,56 @@
1
+ module ConnectionManager
2
+ class Builder
3
+ class << self
4
+ # Grab only those configurations that correspond to the current env (If env
5
+ # is blank it grabs all the connection keys) and where :build_connection_class is true
6
+ def database_keys_for_auto_build
7
+ ActiveRecord::Base.configurations.select{ |k,v| v[:build_connection_class] && k.match(env_regex)}.keys
8
+ end
9
+
10
+ # Builds connection classes using the database keys provided; expects an array.
11
+ def build_connection_classes(database_keys_to_use=database_keys_for_auto_build)
12
+ database_keys_to_use.each do |key|
13
+ build_connection_class(connection_class_name(key),key.to_sym)
14
+ end
15
+ end
16
+
17
+ # Build connection classes base on the supplied class name and connection
18
+ # key from database.yml
19
+ def build_connection_class(class_name,connection_key)
20
+ begin
21
+ class_name.constantize
22
+ rescue NameError
23
+ klass = Class.new(ActiveRecord::Base)
24
+ new_connection_class = Object.const_set(class_name, klass)
25
+ new_connection_class.abstract_class = true
26
+ new_connection_class.establish_connection(connection_key.to_sym)
27
+ ConnectionManager.logger.info "Connection::Manager built: #{class_name} for #{connection_key}" if ConnectionManager.logger
28
+ new_connection_class
29
+ end
30
+ end
31
+
32
+ def env_regex
33
+ return @env_regex if @env_regex
34
+ s = "#{ConnectionManager.env}$"
35
+ @env_regex = Regexp.new("(#{s})")
36
+ end
37
+
38
+ private
39
+ # Creates a string to be used for the class name. Removes the current env.
40
+ def clean_yml_key(name)
41
+ new_name = "#{name}".gsub(env_regex,'')
42
+ new_name = "Base" if new_name.blank?
43
+ new_name.gsub(/\_$/,'')
44
+ end
45
+
46
+ # Given an connection key name from the database.yml, returns the string
47
+ # equivalent of the class name for that entry.
48
+ def connection_class_name(name_from_yml)
49
+ new_class_name = clean_yml_key(name_from_yml)
50
+ new_class_name = new_class_name.gsub(/\_/,' ').titleize.gsub(/ /,'')
51
+ new_class_name << "Connection"
52
+ new_class_name
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,50 @@
1
+ module ConnectionManager
2
+ module AbstractAdapter
3
+ def config
4
+ @config
5
+ end
6
+ # Determines if connection supports cross database queries
7
+ def cross_schema_support?
8
+ @cross_schema_support ||= (config[:adapter].match(/(mysql)|(postgres)|(sqlserver)/i))
9
+ end
10
+ alias :cross_database_support? :cross_schema_support?
11
+
12
+ def mysql?
13
+ @is_mysql ||= (config[:adapter].match(/(mysql)/i))
14
+ end
15
+
16
+ def postgresql?
17
+ @is_postgresql ||= (config[:adapter].match(/(postgres)/i))
18
+ end
19
+
20
+ def sqlserver?
21
+ @is_sqlserver ||= (config[:adapter].match(/(sqlserver)/i))
22
+ end
23
+
24
+ def replicated?
25
+ (!slave_keys.blank? || !master_keys.blank?)
26
+ end
27
+
28
+ def database_name
29
+ config[:database]
30
+ end
31
+
32
+ def slave_keys
33
+ @slave_keys ||= (config[:slaves] ? config[:slaves].collect{|r| r.to_sym} : [] )
34
+ end
35
+
36
+ def master_keys
37
+ @master_keys ||= (config[:masters] ? config[:masters].collect{|r| r.to_sym} : [])
38
+ end
39
+
40
+ def replication_keys(type=:slaves)
41
+ return slave_keys if type == :slaves
42
+ master_keys
43
+ end
44
+
45
+ def replications
46
+ {:slaves => slave_keys, :masters => master_keys}
47
+ end
48
+ end
49
+ end
50
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include,(ConnectionManager::AbstractAdapter))
@@ -0,0 +1,39 @@
1
+ module ConnectionManager
2
+ module MysqlAdapter
3
+
4
+ # Force all tables to be cached for the life connection
5
+ def cached_tables
6
+ @cached_tables ||= {}
7
+ end
8
+
9
+ def new_tables(name = nil, database = nil, like =nil)
10
+ return cached_tables[database] if cached_tables[database] && like.nil?
11
+ cached_tables[database] ||= []
12
+ return [like] if like && cached_tables[database].include?(like)
13
+ sql = "SHOW TABLES "
14
+ sql << "IN #{database} " if database
15
+ sql << "LIKE #{quote(like)}" if like
16
+ result = execute(sql, 'SCHEMA')
17
+ cached_tables[database] = (cached_tables[database] | result.collect { |field| field[0] }).compact
18
+ end
19
+ alias :tables :new_tables
20
+
21
+
22
+ # We have to clean the name of '`' and fetch table name with schema
23
+ def table_exists?(name)
24
+ return false unless name
25
+ name = name.to_s
26
+ schema, table = name.split('.', 2)
27
+ unless table # A table was provided without a schema
28
+ table = schema
29
+ schema = nil
30
+ end
31
+ new_tables(nil, schema, table).include?(table)
32
+ end
33
+ end
34
+ end
35
+ if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR <= 1
36
+ require 'active_record/connection_adapters/mysql2_adapter'
37
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:include,(ConnectionManager::MysqlAdapter)) if defined?(ActiveRecord::ConnectionAdapters::MysqlAdapter)
38
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:include,(ConnectionManager::MysqlAdapter))
39
+ end
@@ -0,0 +1,91 @@
1
+ require 'thread'
2
+ require 'active_record/relation'
3
+ module ConnectionManager
4
+ module ConnectionHandling
5
+ @@managed_connections = ThreadSafe::Cache.new
6
+
7
+ # Attempts to return the schema from table_name and table_name_prefix
8
+ def schema_name
9
+ return self.table_name.split('.')[0] if self.table_name && self.table_name.match(/\./)
10
+ return self.table_name_prefix.to_s.gsub(/\./,'') if self.table_name_prefix && self.table_name_prefix.match(/\./)
11
+ return self.connection.config[:database] if self.connection.mysql?
12
+ end
13
+ alias :database_name :schema_name
14
+
15
+ # Set the unformatted schema name for the given model / connection class
16
+ # EX: class User < ActiveRecord::Base
17
+ # self.schema_name = 'users_db'
18
+ # end
19
+ #
20
+ # User.schema_name # => 'users_db'
21
+ # User.table_name_prefix # => 'users_db.'
22
+ # User.table_name # => 'users_db.users'
23
+ def schema_name=schema_name
24
+ self.table_name_prefix = "#{schema_name}." if schema_name && schema_name.to_s != ""
25
+ self.table_name = "#{self.table_name_prefix}#{self.table_name}" unless self.abstract_class?
26
+ end
27
+ alias :database_name= :schema_name=
28
+
29
+ # A place to store managed connections
30
+ def managed_connections
31
+ @@managed_connections
32
+ end
33
+
34
+ def managed_connection_classes
35
+ managed_connections.values.flatten
36
+ end
37
+
38
+ # Establishes and checks in a connection, normally for abstract classes AKA connection classes.
39
+ #
40
+ # Options:
41
+ # * :abstract_class - used the set #abstract_class, default is true
42
+ # * :schema_name - the unformatted schema name for connection, is inherited but child classes
43
+ # * :table_name - the table name for the model, should not be used on abstract classes
44
+ #
45
+ # EX:
46
+ # class MyConnection < ActiveRecord::Base
47
+ # establish_managed_connection :key_from_db_yml,{:readonly => true}
48
+ # end
49
+ #
50
+ def establish_managed_connection(yml_key,opts={})
51
+ opts = {:class_name => self.name,
52
+ :abstract_class => true}.merge(opts)
53
+ establish_connection(yml_key)
54
+ self.abstract_class = opts[:abstract_class]
55
+ self.table_name = opts[:table_name] if opts[:table_name]
56
+ if (opts[:schema_name] || opts[:database_name])
57
+ self.schema_name = (opts[:schema_name] || opts[:database_name])
58
+ else
59
+ self.schema_name = self.schema_name unless !self.connection.cross_schema_support?
60
+ end
61
+ end
62
+
63
+ def self.included(base)
64
+ base.alias_method_chain :establish_connection, :managed_connections
65
+ end
66
+
67
+ def self.extended(base)
68
+ class << base
69
+ self.alias_method_chain :establish_connection, :managed_connections
70
+ end
71
+ end
72
+
73
+ def establish_connection_with_managed_connections(spec = nil)
74
+ result = establish_connection_without_managed_connections(spec)
75
+ if spec && (spec.is_a?(Symbol) || spec.is_a?(String))
76
+ self.managed_connections[spec.to_sym] = self.name
77
+ elsif spec.nil? && ConnectionManager.env
78
+ self.managed_connections[ConnectionManager.env.to_sym] = self.name
79
+ else
80
+ self.managed_connections[self.name] = self.name
81
+ end
82
+ result
83
+ end
84
+ end
85
+ end
86
+ if ActiveRecord::VERSION::MAJOR == 4
87
+ ActiveRecord::ConnectionHandling.send(:include, ConnectionManager::ConnectionHandling)
88
+ else
89
+ require 'active_record/base'
90
+ ActiveRecord::Base.extend(ConnectionManager::ConnectionHandling)
91
+ end
@@ -0,0 +1,24 @@
1
+ module ConnectionManager
2
+ module Core
3
+ # We want to make sure we get the full table name with schema
4
+ def arel_table_with_check_name # :nodoc:
5
+ @arel_table = Arel::Table.new(table_name, arel_engine) unless (@arel_table && (@arel_table.name == self.table_name))
6
+ @arel_table
7
+ end
8
+
9
+ def self.included(base)
10
+ base.alias_method_chain :arel_table, :check_name
11
+ end
12
+
13
+ def self.extended(base)
14
+ class << base
15
+ self.alias_method_chain :arel_table, :check_name
16
+ end
17
+ end
18
+ end
19
+ end
20
+ if ActiveRecord::VERSION::MAJOR == 4
21
+ ActiveRecord::Core::ClassMethods.send(:include,ConnectionManager::Core)
22
+ else
23
+ ActiveRecord::Base.extend ConnectionManager::Core
24
+ end
@@ -0,0 +1,13 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+ module ConnectionManager
3
+ module Querying
4
+ delegate :using, :to => (ActiveRecord::VERSION::MAJOR == 4 ? :all : :scoped)
5
+ delegate :slaves, :to => (ActiveRecord::VERSION::MAJOR == 4 ? :all : :scoped)
6
+ delegate :masters, :to => (ActiveRecord::VERSION::MAJOR == 4 ? :all : :scoped)
7
+ end
8
+ end
9
+ if ActiveRecord::VERSION::MAJOR == 4
10
+ ActiveRecord::Querying.send(:include, ConnectionManager::Querying)
11
+ else
12
+ ActiveRecord::Base.send(:extend, ConnectionManager::Querying)
13
+ end
@@ -0,0 +1,10 @@
1
+ module ConnectionManager
2
+ class Railtie < ::Rails::Railtie
3
+ initializer "connection_manager.build_connection_classes" do
4
+ require 'connection_manager/connection_adapters/mysql_adapter' if (ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR <= 1 && (defined?(Mysql2::VERSION) || defined?(Mysql2::VERSION)))
5
+ ConnectionManager.env = Rails.env
6
+ ConnectionManager.logger = Rails.logger
7
+ ConnectionManager::Builder.build_connection_classes(Rails.application.config.database_configuration.select{ |k,v| v['build_connection_class'] && k.match(ConnectionManager::Builder.env_regex)}.keys)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ module ConnectionManager
2
+ module Relation
3
+ # Specify connection class to used for query. For
4
+ # example:
5
+ #
6
+ # users = User.using(MySlaveConnection).first
7
+ def using(connection_class_name)
8
+ @klass = ConnectionManager::Using::Proxy.new(@klass,connection_class_name)
9
+ self
10
+ end
11
+
12
+ def slaves
13
+ using(@klass.send(:fetch_slave_connection))
14
+ end
15
+
16
+ def masters
17
+ using(@klass.send(:fetch_master_connection))
18
+ end
19
+ end
20
+ end
21
+ ActiveRecord::Relation.send(:include, ConnectionManager::Relation)