connection_manager 0.2.6 → 0.3.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/.gitignore +2 -1
- data/Gemfile +0 -1
- data/LICENSE.txt +1 -1
- data/README.md +115 -55
- data/Rakefile +2 -0
- data/connection_manager.gemspec +9 -3
- data/lib/connection_manager/connection_builder.rb +81 -0
- data/lib/connection_manager/connection_manager_railtie.rb +4 -3
- data/lib/connection_manager/helpers/abstract_adapter_helper.rb +40 -0
- data/lib/connection_manager/helpers/connection_helpers.rb +105 -0
- data/lib/connection_manager/{cross_schema_patch.rb → patches/cross_schema_patch.rb} +2 -3
- data/lib/connection_manager/replication.rb +165 -0
- data/lib/connection_manager/shards.rb +25 -0
- data/lib/connection_manager/using.rb +95 -0
- data/lib/connection_manager/version.rb +1 -1
- data/lib/connection_manager.rb +19 -8
- data/spec/factories.rb +7 -11
- data/spec/helpers/database_spec_helper.rb +52 -41
- data/spec/helpers/models_spec_helper.rb +23 -0
- data/spec/jdbcmysql_database.yml +55 -0
- data/spec/lib/connection_builder_spec.rb +29 -0
- data/spec/lib/connection_helpers_spec.rb +74 -0
- data/spec/lib/replication_spec.rb +134 -0
- data/spec/lib/shards_spec.rb +40 -0
- data/spec/lib/using_spec.rb +77 -0
- data/spec/mysql2_database.yml +28 -2
- data/spec/spec_helper.rb +20 -6
- metadata +42 -39
- data/Gemfile.lock +0 -56
- data/lib/connection_manager/associations.rb +0 -28
- data/lib/connection_manager/connections.rb +0 -124
- data/lib/connection_manager/method_recorder.rb +0 -57
- data/lib/connection_manager/secondary_connection_builder.rb +0 -180
- data/spec/integration/secondary_connection_builder_spec.rb +0 -115
- data/spec/lib/associations_spec.rb +0 -30
- data/spec/lib/connections_spec.rb +0 -92
- data/spec/lib/method_recorder_spec.rb +0 -43
- data/spec/lib/secondary_connection_builder_spec.rb +0 -77
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
2
|
+
module ConnectionManager
|
3
|
+
module Replication
|
4
|
+
|
5
|
+
# Replication methods (replication_method_name, which is the option[:name] for the
|
6
|
+
# #replication method) and all thier associated connections. The key is the
|
7
|
+
# replication_method_name and the value is an array of all the replication_classes
|
8
|
+
# the replication_method has access to.
|
9
|
+
#
|
10
|
+
# EX: replication_methods[:slaves] => ['Slave1Connection',Slave2Connection]
|
11
|
+
def replication_methods
|
12
|
+
@replication_methods ||= HashWithIndifferentAccess.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def replicant_classes
|
16
|
+
@replicant_classes ||= HashWithIndifferentAccess.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Is this class replicated
|
20
|
+
def replicated?
|
21
|
+
(@replicated == true)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Builds a class method that returns an ActiveRecord::Relation for use with
|
25
|
+
# in ActiveRecord method chaining.
|
26
|
+
#
|
27
|
+
# EX:
|
28
|
+
# class MyClass < ActiveRecord::Base
|
29
|
+
# replicated :my_readonly_db, "FooConnection", :method_name => 'slaves'
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# Options:
|
34
|
+
# * :name - name of class method to call to access replication, default to slaves
|
35
|
+
# * :readonly - forces all results to readonly
|
36
|
+
# * :type - the type of replication; :slave or :master, defaults to :slave
|
37
|
+
# * :using - list of connections to use; can be database.yml key or the name of the connection class --- DEPRECIATED
|
38
|
+
# * A Block may be passed that will be called on each of the newly created child classes
|
39
|
+
def replicated(*connections)
|
40
|
+
@replicated = true
|
41
|
+
options = {:name => "slaves"}.merge!(connections.extract_options!)
|
42
|
+
options[:type] ||= :slaves
|
43
|
+
options[:build_replicants] = true if (options[:build_replicants].blank? && options[:type] == :masters)
|
44
|
+
if options[:using]
|
45
|
+
connections = options[:using]
|
46
|
+
warn "[DEPRECATION] :using option is deprecated. Please list replication connections instead. EX: replicated :slave_connection,'OtherSlaveConnectionChild', :name => 'my_slaves'."
|
47
|
+
end
|
48
|
+
|
49
|
+
# Just incase its blank. Should be formally set by using connection class or at the model manually
|
50
|
+
self.table_name_prefix = "#{database_name}." if self.table_name_prefix.blank?
|
51
|
+
|
52
|
+
connections = connection.replication_keys(options[:type]) if connections.blank?
|
53
|
+
set_replications_to_method(connections,options[:name])
|
54
|
+
build_repliciation_class_method(options)
|
55
|
+
build_replication_association_class(options)
|
56
|
+
build_query_method_alias_method(options[:name])
|
57
|
+
build_repliciation_instance_method(options[:name])
|
58
|
+
options[:name]
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# Get a connection class name from out replication_methods pool
|
63
|
+
# could add mutex but not sure blocking is with it.
|
64
|
+
def fetch_replication_method(method_name)
|
65
|
+
available_connections = @replication_methods[method_name]
|
66
|
+
raise ArgumentError, "No connections found for #{method_name}." if available_connections.blank?
|
67
|
+
available_connections.rotate!
|
68
|
+
available_connections[0]
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
# Builds replication connection classes and methods
|
73
|
+
def set_replications_to_method(connections,replication_method_name='slaves')
|
74
|
+
raise ArgumentError, "connections could not be found for #{self.name}." if connections.blank?
|
75
|
+
connections.each do |to_use|
|
76
|
+
connection_class_name = fetch_connection_class_name(to_use)
|
77
|
+
replication_methods[replication_method_name] ||= []
|
78
|
+
replication_methods[replication_method_name] << connection_class_name
|
79
|
+
end
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
def fetch_connection_class_name(to_use)
|
84
|
+
connection_class_name = to_use
|
85
|
+
connection_class_name = fetch_connection_class_name_from_yml_key(connection_class_name) if connection_class_name.to_s.match(/_/)
|
86
|
+
raise ArgumentError, "For #{self.name}, the class #{connection_class_name} could not be found." unless (managed_connection_classes.include?(connection_class_name))
|
87
|
+
connection_class_name
|
88
|
+
end
|
89
|
+
|
90
|
+
def fetch_connection_class_name_from_yml_key(yml_key)
|
91
|
+
found = managed_connections[yml_key]
|
92
|
+
connection_class_name = nil
|
93
|
+
if found
|
94
|
+
connection_class_name = found.first
|
95
|
+
else
|
96
|
+
raise ArgumentError, "For #{self.name}, a connection class for #{yml_key} could not be found."
|
97
|
+
end
|
98
|
+
connection_class_name
|
99
|
+
end
|
100
|
+
|
101
|
+
# Adds a class method, that calls #using with the correct connection class name
|
102
|
+
# by calling fetch_replication_method with the method name. Adds readonly
|
103
|
+
# query method class if replication is specified as readonly.
|
104
|
+
def build_repliciation_class_method(options)
|
105
|
+
class_eval <<-STR
|
106
|
+
class << self
|
107
|
+
def #{options[:name]}
|
108
|
+
using(fetch_replication_method("#{options[:name]}"))#{options[:readonly] ? '.readonly' : ''}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
STR
|
112
|
+
end
|
113
|
+
|
114
|
+
# Builds a class within the model with the name of replication method. Use this
|
115
|
+
# class as the :class_name options for associations when it is nesseccary to
|
116
|
+
# ensure eager loading uses a replication connection.
|
117
|
+
#
|
118
|
+
# EX:
|
119
|
+
#
|
120
|
+
# class Foo < ActiveRecord::Base
|
121
|
+
# belongs_to :user
|
122
|
+
# replicated # use default name .slaves
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# class MyClass < ActiveRecord::Base
|
126
|
+
# has_many :foos
|
127
|
+
# has_many :foo_slaves, :class_name => 'Foo::Slaves'
|
128
|
+
# replicated
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# a = MyClass.include(:foo_slaves).first
|
132
|
+
# a.foo_slaves => [<Foo::Slave1ConnectionDup...>,<Foo::Slave1ConnectionDup...>...]
|
133
|
+
def build_replication_association_class(options)
|
134
|
+
class_eval <<-STR
|
135
|
+
class #{options[:name].titleize}
|
136
|
+
class << self
|
137
|
+
def method_missing(name, *args)
|
138
|
+
#{self.name}.#{options[:name]}.klass.send(name, *args)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
STR
|
143
|
+
end
|
144
|
+
|
145
|
+
# Build a query method with the name of our replicaiton method. This method
|
146
|
+
# uses the relation.klass to fetch the appropriate connection, ensuring the
|
147
|
+
# correct connection is used even if the method is already defined by another class.
|
148
|
+
# We want to make sure we don't override existing methods in ActiveRecord::QueryMethods
|
149
|
+
def build_query_method_alias_method(replication_relation_name)
|
150
|
+
unless ActiveRecord::QueryMethods.instance_methods.include?(replication_relation_name.to_sym)
|
151
|
+
ActiveRecord::QueryMethods.module_eval do
|
152
|
+
define_method replication_relation_name.to_sym do
|
153
|
+
using(self.klass.fetch_replication_method(replication_relation_name))
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def build_repliciation_instance_method(replication_relation_name)
|
160
|
+
define_method replication_relation_name.to_sym do
|
161
|
+
using(self.class.fetch_replication_method(replication_relation_name))
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ConnectionManager
|
2
|
+
module Shards
|
3
|
+
@shard_class_names = []
|
4
|
+
|
5
|
+
def shard_class_names(*shard_class_names)
|
6
|
+
@shard_class_names = shard_class_names
|
7
|
+
end
|
8
|
+
|
9
|
+
# Takes a block that is call on all available shards.
|
10
|
+
def shards(*opts,&shards_block)
|
11
|
+
opts = {:include_self => true}.merge(opts.extract_options!)
|
12
|
+
raise ArgumentError, "shard_class_names have not been defined for #{self.class.name}" if @shard_class_names.length == 0
|
13
|
+
if block_given?
|
14
|
+
results = []
|
15
|
+
@shard_class_names.each do |s|
|
16
|
+
results << shards_block.call(s.constantize)
|
17
|
+
end
|
18
|
+
results << shards_block.call(self) if opts[:include_self]
|
19
|
+
return results.flatten
|
20
|
+
else
|
21
|
+
raise ArgumentError, 'shards method requires a block.'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module ConnectionManager
|
2
|
+
module Using
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
def using(connection_class_name)
|
6
|
+
d = fetch_duplicate_class(connection_class_name)
|
7
|
+
r = ActiveRecord::Relation.new(d, d.arel_table)
|
8
|
+
r = r.readonly if d.connection.readonly?
|
9
|
+
r
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
# We use dup here because its just too tricky to make sure we override
|
14
|
+
# all the methods nesseccary when using a child class of the model. This
|
15
|
+
# action is lazy and the created sub is named to a constant so we only
|
16
|
+
# have to do it once.
|
17
|
+
def fetch_duplicate_class(connection_class_name)
|
18
|
+
begin
|
19
|
+
return "#{self.name}::#{connection_class_name}Dup".constantize
|
20
|
+
rescue NameError
|
21
|
+
return build_dup_class(connection_class_name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_dup_class(connection_class_name)
|
26
|
+
dup_klass = class_eval <<-STR
|
27
|
+
#{connection_class_name}Dup = dup
|
28
|
+
STR
|
29
|
+
|
30
|
+
dup_klass.class_eval <<-STR
|
31
|
+
class << self
|
32
|
+
def model_name
|
33
|
+
'#{self.model_name}'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
STR
|
37
|
+
|
38
|
+
extend_dup_class(dup_klass,connection_class_name)
|
39
|
+
dup_klass.table_name = table_name.to_s.split('.').last
|
40
|
+
dup_klass.table_name_prefix = connection_class_name.constantize.table_name_prefix
|
41
|
+
dup_klass
|
42
|
+
end
|
43
|
+
|
44
|
+
# Extend the connection override module from the connetion to the supplied class
|
45
|
+
def extend_dup_class(dup_class,connection_class_name)
|
46
|
+
begin
|
47
|
+
mod = "#{connection_class_name}::ConnectionOverrideMod".constantize
|
48
|
+
dup_class.extend(mod)
|
49
|
+
rescue NameError
|
50
|
+
built = build_connection_override_module(connection_class_name).constantize
|
51
|
+
dup_class.extend(built)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Added a module to the connection class. The module is extended on dup class
|
56
|
+
# to override the connection and superclass
|
57
|
+
def build_connection_override_module(connection_class_name)
|
58
|
+
connection_class_name.constantize.class_eval <<-STR
|
59
|
+
module ConnectionOverrideMod
|
60
|
+
def connection_class
|
61
|
+
"#{connection_class_name}".constantize
|
62
|
+
end
|
63
|
+
|
64
|
+
def connection
|
65
|
+
connection_class.connection
|
66
|
+
end
|
67
|
+
|
68
|
+
def superclass
|
69
|
+
connection_class
|
70
|
+
end
|
71
|
+
end
|
72
|
+
STR
|
73
|
+
"#{connection_class_name}::ConnectionOverrideMod"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Instance method for casting to a duplication class
|
78
|
+
def using(connection_class)
|
79
|
+
becomes(self.class.using(connection_class).klass)
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.included(host_class)
|
83
|
+
host_class.extend(ClassMethods)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
module UsingQueryMethod
|
88
|
+
def using(connection_class_name)
|
89
|
+
d = klass.using(connection_class_name)
|
90
|
+
relation = clone
|
91
|
+
relation.instance_variable_set(:@klass, d.klass)
|
92
|
+
relation
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/connection_manager.rb
CHANGED
@@ -2,15 +2,26 @@ require "connection_manager/version"
|
|
2
2
|
|
3
3
|
module ConnectionManager
|
4
4
|
require 'active_record'
|
5
|
-
require '
|
6
|
-
require 'connection_manager/
|
7
|
-
require 'connection_manager/
|
8
|
-
require 'connection_manager/
|
9
|
-
require 'connection_manager/
|
5
|
+
require 'active_support'
|
6
|
+
require 'connection_manager/helpers/abstract_adapter_helper'
|
7
|
+
require 'connection_manager/connection_builder'
|
8
|
+
require 'connection_manager/helpers/connection_helpers'
|
9
|
+
require 'connection_manager/using'
|
10
|
+
require 'connection_manager/shards'
|
11
|
+
require 'connection_manager/replication'
|
12
|
+
require 'connection_manager/patches/cross_schema_patch'
|
13
|
+
require 'connection_manager/connection_manager_railtie' if defined?(Rails)
|
10
14
|
|
11
|
-
ActiveRecord::
|
12
|
-
ActiveRecord::Base.extend(ConnectionManager::
|
15
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include,(ConnectionManager::AbstractAdapterHelper))
|
16
|
+
ActiveRecord::Base.extend(ConnectionManager::ConnectionHelpers)
|
17
|
+
ActiveRecord::Base.extend(ConnectionManager::ConnectionBuilder)
|
18
|
+
ActiveRecord::Base.send(:include,ConnectionManager::Using)
|
19
|
+
ActiveRecord::QueryMethods.send(:include,ConnectionManager::UsingQueryMethod)
|
20
|
+
ActiveRecord::Base.extend(ConnectionManager::Replication)
|
21
|
+
ActiveRecord::Base.extend(ConnectionManager::Shards)
|
13
22
|
|
14
|
-
|
23
|
+
ActiveSupport.on_load(:active_record) do
|
24
|
+
ActiveRecord::Base.build_connection_classes
|
25
|
+
end
|
15
26
|
end
|
16
27
|
|
data/spec/factories.rb
CHANGED
@@ -1,31 +1,27 @@
|
|
1
|
-
FactoryGirl.define do
|
1
|
+
FactoryGirl.define do
|
2
|
+
sequence :rand_name do |n|
|
3
|
+
"foo#{(n + (n+rand)).to_s[2..6]}"
|
4
|
+
end
|
5
|
+
|
2
6
|
factory :basket do
|
3
7
|
name "MyString"
|
4
8
|
end
|
5
|
-
end
|
6
9
|
|
7
|
-
FactoryGirl.define do
|
8
10
|
factory :fruit_basket do
|
9
11
|
fruit
|
10
12
|
basket
|
11
13
|
end
|
12
|
-
end
|
13
14
|
|
14
|
-
FactoryGirl.define do
|
15
15
|
factory :fruit do
|
16
16
|
name "MyString"
|
17
17
|
region
|
18
18
|
end
|
19
|
-
end
|
20
19
|
|
21
|
-
FactoryGirl.define do
|
22
20
|
factory :region do
|
23
21
|
name "MyString"
|
24
22
|
end
|
25
|
-
end
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
name "MyString"
|
24
|
+
factory :type do |f|
|
25
|
+
f.sequence(:name) {FactoryGirl.generate(:rand_name)}
|
30
26
|
end
|
31
27
|
end
|
@@ -1,8 +1,14 @@
|
|
1
1
|
class TestDB
|
2
|
-
def self.
|
3
|
-
|
2
|
+
def self.yml(driver='sqlite')
|
3
|
+
YAML::load(File.open(File.join(File.dirname(__FILE__),'..',"#{driver}_database.yml")))
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.connect(driver='sqlite',logging=false)
|
7
|
+
ActiveRecord::Base.configurations = yml(driver)
|
4
8
|
ActiveRecord::Base.establish_connection('test')
|
9
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT) if logging
|
5
10
|
end
|
11
|
+
|
6
12
|
def self.clean
|
7
13
|
[:foos,:fruits,:baskets,:fruit_baskets,:regions,:types].each do |t|
|
8
14
|
DBSpecManagement.connection.execute("DELETE FROM #{t.to_s}")
|
@@ -16,41 +22,43 @@ end
|
|
16
22
|
|
17
23
|
#Put all the test migrations here
|
18
24
|
class TestMigrations < ActiveRecord::Migration
|
19
|
-
|
20
25
|
# all the ups
|
21
|
-
def self.up(connection_name='test', user_connection_name='cm_user_test')
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
def self.up(connection_name='test',master_2_connection_name='master_2_cm_test', user_connection_name='cm_user_test')
|
27
|
+
[connection_name,master_2_connection_name].each do |c|
|
28
|
+
ActiveRecord::Base.establish_connection(c)
|
29
|
+
begin
|
30
|
+
create_table :foos do |t|
|
31
|
+
t.string :name
|
32
|
+
t.integer :user_id
|
33
|
+
end
|
34
|
+
create_table :fruits do |t|
|
35
|
+
t.string :name
|
36
|
+
t.integer :region_id
|
37
|
+
t.timestamps
|
38
|
+
end
|
39
|
+
create_table :baskets do |t|
|
40
|
+
t.string :name
|
41
|
+
t.timestamps
|
42
|
+
end
|
43
|
+
create_table :fruit_baskets do |t|
|
44
|
+
t.integer :fruit_id
|
45
|
+
t.integer :basket_id
|
46
|
+
t.timestamps
|
47
|
+
end
|
48
|
+
create_table :regions do |t|
|
49
|
+
t.string :name
|
50
|
+
t.integer :type_id
|
51
|
+
t.timestamps
|
52
|
+
end
|
53
|
+
create_table :types do |t|
|
54
|
+
t.string :name
|
55
|
+
t.timestamps
|
56
|
+
end
|
57
|
+
rescue => e
|
58
|
+
puts "tables failed to create: #{e}"
|
36
59
|
end
|
37
|
-
create_table :fruit_baskets do |t|
|
38
|
-
t.integer :fruit_id
|
39
|
-
t.integer :basket_id
|
40
|
-
t.timestamps
|
41
|
-
end
|
42
|
-
create_table :regions do |t|
|
43
|
-
t.string :name
|
44
|
-
|
45
|
-
t.timestamps
|
46
|
-
end
|
47
|
-
create_table :types do |t|
|
48
|
-
t.string :name
|
49
|
-
t.timestamps
|
50
|
-
end
|
51
|
-
rescue => e
|
52
|
-
puts "tables failed to create: #{e}"
|
53
60
|
end
|
61
|
+
|
54
62
|
ActiveRecord::Base.establish_connection(user_connection_name)
|
55
63
|
begin
|
56
64
|
create_table :users do |t|
|
@@ -59,18 +67,21 @@ class TestMigrations < ActiveRecord::Migration
|
|
59
67
|
rescue => e
|
60
68
|
puts "tables failed to create: #{e}"
|
61
69
|
end
|
70
|
+
|
62
71
|
ActiveRecord::Base.establish_connection(connection_name)
|
63
72
|
end
|
64
73
|
|
65
74
|
# all the downs
|
66
|
-
def self.down(connection_name='test',user_connection_name='cm_user_test')
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
75
|
+
def self.down(connection_name='test',master_2_connection_name='master_2_cm_test',user_connection_name='cm_user_test')
|
76
|
+
[connection_name,master_2_connection_name].each do |c|
|
77
|
+
ActiveRecord::Base.establish_connection(c)
|
78
|
+
begin
|
79
|
+
[:foos,:fruits,:baskets,:fruit_baskets,:regions,:types].each do |t|
|
80
|
+
drop_table t
|
81
|
+
end
|
82
|
+
rescue => e
|
83
|
+
puts "tables were not dropped: #{e}"
|
71
84
|
end
|
72
|
-
rescue => e
|
73
|
-
puts "tables were not dropped: #{e}"
|
74
85
|
end
|
75
86
|
ActiveRecord::Base.establish_connection(user_connection_name)
|
76
87
|
begin
|
@@ -1,3 +1,10 @@
|
|
1
|
+
class CmReplicationConnection < ActiveRecord::Base
|
2
|
+
establish_managed_connection(:slave_1_cm_test)
|
3
|
+
end
|
4
|
+
|
5
|
+
class CmReplication2Connection < ActiveRecord::Base
|
6
|
+
establish_managed_connection(:slave_2_cm_test)
|
7
|
+
end
|
1
8
|
|
2
9
|
class Basket < ActiveRecord::Base
|
3
10
|
has_many :fruit_baskets
|
@@ -23,3 +30,19 @@ class Region < ActiveRecord::Base
|
|
23
30
|
has_one :fruit
|
24
31
|
#replicated
|
25
32
|
end
|
33
|
+
|
34
|
+
class Type < ActiveRecord::Base
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
class SouthernFruit < Fruit
|
39
|
+
self.table_name = 'fruits'
|
40
|
+
end
|
41
|
+
|
42
|
+
class ModelsHelper
|
43
|
+
def self.models
|
44
|
+
["Basket", "Fruit", "FruitBasket", "Region","SouthernFruit", "Type"]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# Warning: The database defined as "test" will be erased and
|
2
|
+
# re-generated from your development database when you run "rake".
|
3
|
+
# Do not set this db to the same as development or production.
|
4
|
+
common: &common
|
5
|
+
adapter: jdbcmysql
|
6
|
+
username: root
|
7
|
+
password: omegared
|
8
|
+
pool: 100
|
9
|
+
timeout: 5000
|
10
|
+
build_connection_class: true
|
11
|
+
|
12
|
+
master: &master
|
13
|
+
username: root
|
14
|
+
password: omegared
|
15
|
+
|
16
|
+
readonly: &readonly
|
17
|
+
username: readonly
|
18
|
+
password: omegared
|
19
|
+
|
20
|
+
test:
|
21
|
+
<<: *common
|
22
|
+
<<: *master
|
23
|
+
database: cm_test
|
24
|
+
slaves: [slave_1_cm_test, slave_2_cm_test]
|
25
|
+
|
26
|
+
cm_user_test:
|
27
|
+
<<: *common
|
28
|
+
<<: *master
|
29
|
+
database: cm_user_test
|
30
|
+
slaves: [slave_1_cm_user_test]
|
31
|
+
|
32
|
+
slave_1_cm_test:
|
33
|
+
<<: *common
|
34
|
+
<<: *readonly
|
35
|
+
database: cm_test
|
36
|
+
|
37
|
+
slave_2_cm_test:
|
38
|
+
<<: *common
|
39
|
+
<<: *readonly
|
40
|
+
database: cm_test
|
41
|
+
|
42
|
+
master_2_cm_test:
|
43
|
+
<<: *common
|
44
|
+
<<: *master
|
45
|
+
database: cm_master_2_test
|
46
|
+
|
47
|
+
shard_1_cm_test:
|
48
|
+
<<: *common
|
49
|
+
<<: *master
|
50
|
+
database: legacy_cm_test
|
51
|
+
|
52
|
+
slave_1_cm_user_test:
|
53
|
+
<<: *common
|
54
|
+
<<: *readonly
|
55
|
+
database: cm_user_test
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe ConnectionManager::ConnectionBuilder do
|
3
|
+
|
4
|
+
describe '#connection_class_name' do
|
5
|
+
it "should return a string for a class name appended with 'Connection' " do
|
6
|
+
ActiveRecord::Base.send(:connection_class_name,"my_database").should eql("MyDatabaseConnection")
|
7
|
+
end
|
8
|
+
it "should return remove the appended rails env" do
|
9
|
+
ActiveRecord::Base.send(:connection_class_name,"my_database_test").should eql("MyDatabaseConnection")
|
10
|
+
end
|
11
|
+
it "should use the database name from the database.yml if supplied string is only is only the Rails.env" do
|
12
|
+
ActiveRecord::Base.send(:connection_class_name,"test").should eql("BaseConnection")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#build_connection_class' do
|
17
|
+
before(:all) do
|
18
|
+
ActiveRecord::Base.build_connection_class("MyConnectionClass", :test)
|
19
|
+
end
|
20
|
+
it "should add a class with supplied class name to ConnectionManager::ConnectionBuilder" do
|
21
|
+
defined?(MyConnectionClass).should be_true
|
22
|
+
MyConnectionClass.is_a?(Class).should be_true
|
23
|
+
end
|
24
|
+
it "should have a super class of ActiveRecord::Base" do
|
25
|
+
MyConnectionClass.superclass.should eql(ActiveRecord::Base)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe ConnectionManager::ConnectionHelpers do
|
3
|
+
before(:all) do
|
4
|
+
|
5
|
+
class MyConnectionClass < ActiveRecord::Base
|
6
|
+
establish_managed_connection(:test)
|
7
|
+
end
|
8
|
+
|
9
|
+
class MyReadonlyConnectionClass < ActiveRecord::Base
|
10
|
+
establish_managed_connection({
|
11
|
+
:database => "cm_test",
|
12
|
+
:adapter => "mysql2",
|
13
|
+
:username => TestDB.yml("mysql2")["test"]["username"],
|
14
|
+
:password => TestDB.yml("mysql2")["test"]["password"]
|
15
|
+
|
16
|
+
}, {:readonly => true})
|
17
|
+
end
|
18
|
+
|
19
|
+
class MyFoo < MyConnectionClass
|
20
|
+
self.table_name = 'foos'
|
21
|
+
end
|
22
|
+
|
23
|
+
class MyReadonlyFoo < MyReadonlyConnectionClass
|
24
|
+
self.table_name = 'foos'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
describe '#establish_managed_connection' do
|
28
|
+
context 'the connection class' do
|
29
|
+
|
30
|
+
it "should create abstract class" do
|
31
|
+
MyConnectionClass.abstract_class.should be_true
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should prefix table name with database" do
|
35
|
+
MyConnectionClass.table_name_prefix.should eql('cm_test.')
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should checkin the connection" do
|
39
|
+
ActiveRecord::Base.managed_connection_classes.include?("MyConnectionClass").should be_true
|
40
|
+
ActiveRecord::Base.managed_connection_classes.include?("MyReadonlyConnectionClass").should be_true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
context 'the model' do
|
44
|
+
it "should not be readonly" do
|
45
|
+
u = MyFoo.new
|
46
|
+
u.readonly?.should_not be_true
|
47
|
+
end
|
48
|
+
it "should be readonly if readonly option for establish_managed_connection from connaction class is true" do
|
49
|
+
u = MyReadonlyFoo.new
|
50
|
+
u.readonly?.should be_true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#use_database' do
|
56
|
+
it "should set the database/schema for the model to the supplied schema_name" do
|
57
|
+
Fruit.use_database('my_schema')
|
58
|
+
Fruit.database_name.should eql('my_schema')
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should set the contactinate the schema_name and table_name; and set the table_name to that value" do
|
62
|
+
Fruit.use_database('my_schema')
|
63
|
+
Fruit.table_name.should eql('fruits')
|
64
|
+
Fruit.table_name_prefix.should eql('my_schema.')
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should set the table_name if one is supplied" do
|
68
|
+
Fruit.use_database('my_schema',{:table_name => 'apples'})
|
69
|
+
Fruit.table_name.should eql('apples')
|
70
|
+
Fruit.table_name_prefix.should eql('my_schema.')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|