connection_manager 0.2.1 → 0.2.2
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/Gemfile.lock +1 -1
- data/README.md +1 -0
- data/lib/connection_manager.rb +2 -1
- data/lib/connection_manager/connections.rb +1 -0
- data/lib/connection_manager/cross_schema_patch.rb +130 -0
- data/lib/connection_manager/secondary_connection_builder.rb +12 -7
- data/lib/connection_manager/version.rb +1 -1
- data/spec/helpers/database_spec_helper.rb +22 -7
- data/spec/helpers/models_spec_helper.rb +1 -1
- data/spec/lib/secondary_connection_builder_spec.rb +43 -15
- data/spec/mysql2_database.yml +6 -2
- metadata +17 -16
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -120,6 +120,7 @@ available slave connections each time it is called using a different connection
|
|
120
120
|
|
121
121
|
## TODO's
|
122
122
|
* sharding - IN 2.0 AS BETA
|
123
|
+
* cross schema joins - 2.2 AS BETA tested with Mysql2 ONLY
|
123
124
|
|
124
125
|
## Other activerecord Connection gems
|
125
126
|
* [Octopus](https://github.com/tchandy/octopus)
|
data/lib/connection_manager.rb
CHANGED
@@ -6,10 +6,11 @@ module ConnectionManager
|
|
6
6
|
require 'connection_manager/associations'
|
7
7
|
require 'connection_manager/secondary_connection_builder'
|
8
8
|
require 'connection_manager/method_recorder'
|
9
|
-
require 'connection_manager/connection_manager_railtie
|
9
|
+
require 'connection_manager/connection_manager_railtie' if defined?(Rails)
|
10
10
|
|
11
11
|
ActiveRecord::Base.extend(ConnectionManager::Associations)
|
12
12
|
ActiveRecord::Base.extend(ConnectionManager::SecondaryConnectionBuilder)
|
13
13
|
|
14
|
+
require 'connection_manager/cross_schema_patch.rb' if (ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0)
|
14
15
|
end
|
15
16
|
|
@@ -114,6 +114,7 @@ module ConnectionManager
|
|
114
114
|
self.abstract_class = true
|
115
115
|
end
|
116
116
|
new_connection_class = Object.const_set(class_name, klass)
|
117
|
+
new_connection_class.table_name_prefix=("#{ActiveRecord::Base.configurations[connection_key]['database']}.")
|
117
118
|
new_connection_class.establish_connection(connection_key)
|
118
119
|
(const_set class_name, new_connection_class)
|
119
120
|
all << class_name
|
@@ -0,0 +1,130 @@
|
|
1
|
+
### https://github.com/rails/rails/issues/539
|
2
|
+
### http://tamersalama.com/2010/09/27/nomethoderror-undefined-method-eq-for-nilnilclass/
|
3
|
+
### https://github.com/rails/rails/blob/3-0-stable/activerecord/lib/active_record/associations.rb
|
4
|
+
# ActiveRecord 3.0 ONLY
|
5
|
+
|
6
|
+
if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0
|
7
|
+
|
8
|
+
module ActiveRecord
|
9
|
+
module Associations
|
10
|
+
module ClassMethods
|
11
|
+
class JoinDependency
|
12
|
+
class JoinAssociation < JoinBase
|
13
|
+
|
14
|
+
def table_name_without_schema(t_name)
|
15
|
+
t_name.to_s.gsub(/.*\./, '')
|
16
|
+
end
|
17
|
+
|
18
|
+
def association_join
|
19
|
+
return @join if @join
|
20
|
+
aliased_table = Arel::Table.new(table_name.to_s.gsub(/.*\./, ''), :as => @aliased_table_name.to_s.gsub(/.*\./, ''),
|
21
|
+
:engine => arel_engine,
|
22
|
+
:columns => klass.columns)
|
23
|
+
|
24
|
+
parent_table = Arel::Table.new(parent.table_name.to_s.gsub(/.*\./, ''), :as => parent.aliased_table_name.to_s.gsub(/.*\./, ''),
|
25
|
+
:engine => arel_engine,
|
26
|
+
:columns => parent.active_record.columns)
|
27
|
+
as_conditions = reflection.options[:conditions] && process_conditions(reflection.options[:conditions], aliased_table_name)
|
28
|
+
|
29
|
+
@join = case reflection.macro
|
30
|
+
|
31
|
+
when :has_and_belongs_to_many
|
32
|
+
join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine)
|
33
|
+
fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
|
34
|
+
klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key
|
35
|
+
|
36
|
+
[
|
37
|
+
join_table[fk].eq(parent_table[reflection.active_record.primary_key]),
|
38
|
+
[aliased_table[klass.primary_key].eq(join_table[klass_fk]), as_conditions].reject{ |x| x.blank? }
|
39
|
+
]
|
40
|
+
when :has_many, :has_one
|
41
|
+
if reflection.options[:through]
|
42
|
+
join_table = Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine)
|
43
|
+
jt_as_conditions = through_reflection.options[:conditions] && process_conditions(through_reflection.options[:conditions], aliased_table_name)
|
44
|
+
jt_as_extra = jt_source_extra = jt_sti_extra = nil
|
45
|
+
first_key = second_key = as_extra = nil
|
46
|
+
|
47
|
+
if through_reflection.macro == :belongs_to
|
48
|
+
jt_primary_key = through_reflection.primary_key_name
|
49
|
+
jt_foreign_key = through_reflection.association_primary_key
|
50
|
+
else
|
51
|
+
jt_primary_key = through_reflection.active_record_primary_key
|
52
|
+
jt_foreign_key = through_reflection.primary_key_name
|
53
|
+
|
54
|
+
if through_reflection.options[:as] # has_many :through against a polymorphic join
|
55
|
+
jt_as_extra = join_table[through_reflection.options[:as].to_s + '_type'].eq(parent.active_record.base_class.name)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
case source_reflection.macro
|
60
|
+
when :has_many, :has_one
|
61
|
+
if source_reflection.options[:as]
|
62
|
+
first_key = "#{source_reflection.options[:as]}_id"
|
63
|
+
second_key = options[:foreign_key] || primary_key
|
64
|
+
as_extra = aliased_table["#{source_reflection.options[:as]}_type"].eq(source_reflection.active_record.base_class.name)
|
65
|
+
else
|
66
|
+
first_key = through_reflection.klass.base_class.to_s.foreign_key
|
67
|
+
second_key = options[:foreign_key] || primary_key
|
68
|
+
end
|
69
|
+
|
70
|
+
unless through_reflection.klass.descends_from_active_record?
|
71
|
+
jt_sti_extra = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name)
|
72
|
+
end
|
73
|
+
when :belongs_to
|
74
|
+
first_key = primary_key
|
75
|
+
if reflection.options[:source_type]
|
76
|
+
second_key = source_reflection.association_foreign_key
|
77
|
+
jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].eq(reflection.options[:source_type])
|
78
|
+
else
|
79
|
+
second_key = source_reflection.primary_key_name
|
80
|
+
end
|
81
|
+
end
|
82
|
+
[
|
83
|
+
[parent_table[jt_primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra, jt_as_conditions].reject{|x| x.blank? },
|
84
|
+
[aliased_table[first_key].eq(join_table[second_key]), as_extra, as_conditions].reject{ |x| x.blank? }
|
85
|
+
]
|
86
|
+
elsif reflection.options[:as]
|
87
|
+
id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key])
|
88
|
+
type_rel = aliased_table["#{reflection.options[:as]}_type"].eq(parent.active_record.base_class.name)
|
89
|
+
[id_rel, type_rel, as_conditions].reject{ |x| x.blank?}
|
90
|
+
else
|
91
|
+
foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
|
92
|
+
[aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key]), as_conditions].reject{ |x| x.blank? }
|
93
|
+
end
|
94
|
+
when :belongs_to
|
95
|
+
#aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name]), as_conditions].reject{ |x| x.blank? }
|
96
|
+
|
97
|
+
[aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(find_belongs_to_arel_column(parent_table)), as_conditions].reject{ |x| x.blank? }
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
unless klass.descends_from_active_record?
|
102
|
+
sti_column = aliased_table[klass.inheritance_column]
|
103
|
+
sti_condition = sti_column.eq(klass.sti_name)
|
104
|
+
klass.descendants.each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
|
105
|
+
|
106
|
+
@join << sti_condition
|
107
|
+
end
|
108
|
+
|
109
|
+
@join
|
110
|
+
end
|
111
|
+
|
112
|
+
def find_belongs_to_arel_column(parent_table)
|
113
|
+
parent_table.columns.select{|c| (c.name == options[:foreign_key].to_sym)}[0] ||
|
114
|
+
parent_table.columns.select{|c| (c.name == reflection.primary_key_name.to_sym)}[0]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
module Arel
|
123
|
+
class Table
|
124
|
+
private
|
125
|
+
def table_exists?
|
126
|
+
@table_exists ||= tables.key?(@name.to_s.gsub(/.*\./, '')) || engine.connection.table_exists?(name.to_s.gsub(/.*\./, ''))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module ConnectionManager
|
2
2
|
module SecondaryConnectionBuilder
|
3
|
-
|
3
|
+
|
4
4
|
def database_name
|
5
5
|
"#{connection.instance_variable_get(:@config)[:database].to_s}"
|
6
6
|
end
|
@@ -77,9 +77,9 @@ module ConnectionManager
|
|
77
77
|
method_name = method_name.insert(method_name.index(/\d/),"_")
|
78
78
|
class_name = method_name.classify
|
79
79
|
connection_methods << method_name.to_sym
|
80
|
+
# set_table_name_for_joins
|
80
81
|
build_secondary_class(class_name,c,options)
|
81
|
-
build_single_secondary_method(method_name,class_name)
|
82
|
-
#set_table_name_for_joins
|
82
|
+
build_single_secondary_method(method_name,class_name)
|
83
83
|
sub_classes << "#{self.name}::#{class_name}".constantize if options[:shards]
|
84
84
|
end
|
85
85
|
end
|
@@ -125,13 +125,18 @@ module ConnectionManager
|
|
125
125
|
end
|
126
126
|
end
|
127
127
|
STR
|
128
|
-
|
128
|
+
connection_sub_classes << sub_class
|
129
|
+
puts connection_sub_classes
|
129
130
|
sub_class
|
130
131
|
end
|
131
132
|
|
132
|
-
|
133
|
-
|
134
|
-
|
133
|
+
def connection_sub_classes
|
134
|
+
@connection_sub_classes ||= []
|
135
|
+
end
|
136
|
+
|
137
|
+
def set_table_name_for_joins
|
138
|
+
self.table_name_prefix = "#{database_name}." unless database_name.match(/\.sqlite3$/)
|
139
|
+
end
|
135
140
|
|
136
141
|
# Adds as class method to call a specific secondary conneciton.
|
137
142
|
# Usage:
|
@@ -18,11 +18,12 @@ end
|
|
18
18
|
class TestMigrations < ActiveRecord::Migration
|
19
19
|
|
20
20
|
# all the ups
|
21
|
-
def self.up(connection_name='test')
|
21
|
+
def self.up(connection_name='test', user_connection_name='cm_user_test')
|
22
22
|
ActiveRecord::Base.establish_connection(connection_name)
|
23
23
|
begin
|
24
24
|
create_table :foos do |t|
|
25
25
|
t.string :name
|
26
|
+
t.integer :user_id
|
26
27
|
end
|
27
28
|
create_table :fruits do |t|
|
28
29
|
t.string :name
|
@@ -50,12 +51,19 @@ class TestMigrations < ActiveRecord::Migration
|
|
50
51
|
rescue => e
|
51
52
|
puts "tables failed to create: #{e}"
|
52
53
|
end
|
53
|
-
|
54
|
+
ActiveRecord::Base.establish_connection(user_connection_name)
|
55
|
+
begin
|
56
|
+
create_table :users do |t|
|
57
|
+
t.string :name
|
58
|
+
end
|
59
|
+
rescue => e
|
60
|
+
puts "tables failed to create: #{e}"
|
61
|
+
end
|
62
|
+
ActiveRecord::Base.establish_connection(connection_name)
|
54
63
|
end
|
55
64
|
|
56
|
-
|
57
65
|
# all the downs
|
58
|
-
def self.down(connection_name='test')
|
66
|
+
def self.down(connection_name='test',user_connection_name='cm_user_test')
|
59
67
|
ActiveRecord::Base.establish_connection(connection_name)
|
60
68
|
begin
|
61
69
|
[:foos,:fruits,:baskets,:fruit_baskets,:regions,:types].each do |t|
|
@@ -64,7 +72,14 @@ class TestMigrations < ActiveRecord::Migration
|
|
64
72
|
rescue => e
|
65
73
|
puts "tables were not dropped: #{e}"
|
66
74
|
end
|
67
|
-
|
68
|
-
|
69
|
-
|
75
|
+
ActiveRecord::Base.establish_connection(user_connection_name)
|
76
|
+
begin
|
77
|
+
[:users].each do |t|
|
78
|
+
drop_table t
|
79
|
+
end
|
80
|
+
rescue => e
|
81
|
+
puts "tables were not dropped: #{e}"
|
82
|
+
end
|
83
|
+
ActiveRecord::Base.establish_connection(connection_name)
|
84
|
+
end
|
70
85
|
end
|
@@ -10,29 +10,29 @@ describe ConnectionManager::SecondaryConnectionBuilder do
|
|
10
10
|
|
11
11
|
context '#other_association_options' do
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
it "should add :class_name options set to the replication subclass if :class_name is blank" do
|
14
|
+
options = Fruit.secondary_association_options(:has_one, :plant, 'Slave')
|
15
|
+
options[:class_name].should eql("Plant::Slave")
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
it "should append :class_name with the replication subclass if :class_name is not bank" do
|
19
|
+
options = Fruit.secondary_association_options(:has_one, :plant, 'Slave', :class_name => 'Plant')
|
20
|
+
options[:class_name].should eql("Plant::Slave")
|
21
|
+
end
|
22
22
|
|
23
23
|
context "has_one or has_many" do
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
it "should add the :foreign_key if the :foreign_key options is not present" do
|
25
|
+
options = Fruit.secondary_association_options(:has_one, :plant, 'Slave')
|
26
|
+
options[:foreign_key].should eql('fruit_id')
|
27
|
+
options = Fruit.secondary_association_options(:has_many, :plant, 'Slave')
|
28
|
+
options[:foreign_key].should eql('fruit_id')
|
29
|
+
end
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
33
|
context '#secondary_connection_classes' do
|
34
34
|
it "should return the :using array with the array elements classified and append with Connection" do
|
35
|
-
|
35
|
+
Fruit.secondary_connection_classes({:using => ['slave_1_test_db','slave_2_test_db']}).
|
36
36
|
should eql(["Slave1TestDbConnection", "Slave2TestDbConnection"])
|
37
37
|
end
|
38
38
|
|
@@ -45,5 +45,33 @@ describe ConnectionManager::SecondaryConnectionBuilder do
|
|
45
45
|
end
|
46
46
|
|
47
47
|
end
|
48
|
+
|
49
|
+
context "cross database joins" do
|
50
|
+
ConnectionManager::Connections.initialize(:env => 'test')
|
51
|
+
|
52
|
+
class Foo < ActiveRecord::Base
|
53
|
+
set_table_name_for_joins
|
54
|
+
belongs_to :user
|
55
|
+
end
|
56
|
+
|
57
|
+
class User < CmUserConnection
|
58
|
+
set_table_name_for_joins
|
59
|
+
has_many :foos
|
60
|
+
end
|
61
|
+
|
62
|
+
before :all do
|
63
|
+
@user = User.new(:name => "Testing")
|
64
|
+
@user.save
|
65
|
+
@foo = Foo.new(:user_id => @user.id)
|
66
|
+
@foo.save
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should work" do
|
70
|
+
@user.foos.blank?.should be_false
|
71
|
+
found = Foo.select('users.name AS user_name').joins(:user).where(:id => @foo.id).first
|
72
|
+
puts Foo.select('users.name AS user_name').joins(:user).where(:id => @foo.id).to_sql
|
73
|
+
found.user_name.blank?.should be_false
|
74
|
+
end
|
75
|
+
end
|
48
76
|
end
|
49
77
|
|
data/spec/mysql2_database.yml
CHANGED
@@ -4,14 +4,18 @@
|
|
4
4
|
common: &common
|
5
5
|
adapter: mysql2
|
6
6
|
username: root
|
7
|
-
password:
|
7
|
+
password: omegared
|
8
8
|
pool: 100
|
9
9
|
timeout: 5000
|
10
10
|
|
11
11
|
test:
|
12
12
|
<<: *common
|
13
13
|
database: cm_test
|
14
|
-
|
14
|
+
|
15
|
+
cm_user_test:
|
16
|
+
<<: *common
|
17
|
+
database: cm_user_test
|
18
|
+
|
15
19
|
slave_1_cm_test:
|
16
20
|
<<: *common
|
17
21
|
database: cm_test
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: connection_manager
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-11 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
16
|
-
requirement: &
|
16
|
+
requirement: &70223799590600 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '3.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70223799590600
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: sqlite3
|
27
|
-
requirement: &
|
27
|
+
requirement: &70223799590180 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70223799590180
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rspec
|
38
|
-
requirement: &
|
38
|
+
requirement: &70223799589720 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70223799589720
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: autotest
|
49
|
-
requirement: &
|
49
|
+
requirement: &70223799589300 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70223799589300
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: mocha
|
60
|
-
requirement: &
|
60
|
+
requirement: &70223799588880 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70223799588880
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: factory_girl
|
71
|
-
requirement: &
|
71
|
+
requirement: &70223799588460 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70223799588460
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: mysql2
|
82
|
-
requirement: &
|
82
|
+
requirement: &70223799588040 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,7 +87,7 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *70223799588040
|
91
91
|
description: Simplifies connecting to Muliple and Replication databases with rails
|
92
92
|
and active_record
|
93
93
|
email:
|
@@ -108,6 +108,7 @@ files:
|
|
108
108
|
- lib/connection_manager/associations.rb
|
109
109
|
- lib/connection_manager/connection_manager_railtie.rb
|
110
110
|
- lib/connection_manager/connections.rb
|
111
|
+
- lib/connection_manager/cross_schema_patch.rb
|
111
112
|
- lib/connection_manager/method_recorder.rb
|
112
113
|
- lib/connection_manager/secondary_connection_builder.rb
|
113
114
|
- lib/connection_manager/version.rb
|