connection_manager 1.0.4 → 1.1.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.
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)