dynashard 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,182 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Dynashard::ProxyExtensions' do
4
+ before(:each) do
5
+ Dynashard.enable
6
+ end
7
+
8
+ context 'with a sharding proxy owner' do
9
+ before(:each) do
10
+ @owner = Factory(:sharding_owner)
11
+ @shard = @owner.shard_dsn
12
+ end
13
+
14
+ context 'and a sharding proxy target' do
15
+ it 'uses a sharded reflection class' do
16
+ orig_reflection = @owner.class.reflections[:sharded_has_manys]
17
+ new_reflection = Dynashard.reflection_for(@owner, orig_reflection)
18
+ Dynashard.should_receive(:reflection_for).once.and_return(new_reflection)
19
+ proxy = @owner.sharded_has_manys
20
+ proxy.proxy_reflection.klass.dynashard_klass.should_not be_nil
21
+ proxy.proxy_reflection.should == new_reflection
22
+ end
23
+
24
+ context 'using a :has_one reflection' do
25
+ before(:each) do
26
+ # Ensure that all sharded model connections happen on the owner shard
27
+ Dynashard.shard_context[:owner] = @shard
28
+
29
+ @has_one = Factory(:sharded_has_one, :sharding_owner => @owner, :name => "Owned by #{@owner.name}")
30
+ end
31
+
32
+ after(:each) do
33
+ Dynashard.shard_context.clear
34
+ end
35
+
36
+ # TODO: figure out what to do about the classes being different
37
+ it 'reads from the shard' do
38
+ @owner.sharded_has_one.attributes.should == @has_one.attributes
39
+ end
40
+
41
+ it 'destroys from the shard' do
42
+ lambda do
43
+ @owner.sharded_has_one.destroy
44
+ end.should change(ShardedHasOne, :count).by(-1)
45
+ end
46
+
47
+ it 'creates_other on the shard' do
48
+ new_owner = Factory(:sharding_owner, :shard => @owner.shard)
49
+ lambda do
50
+ new_owner.create_sharded_has_one(:name => "Owned by #{new_owner.name}")
51
+ end.should change(ShardedHasOne, :count).by(1)
52
+ end
53
+
54
+ it 'saves built associations on the shard' do
55
+ new_owner = Factory(:sharding_owner, :shard => @owner.shard)
56
+ new_sharded_has_one = new_owner.build_sharded_has_one(:name => "Owned by #{new_owner.name}")
57
+ lambda do
58
+ new_sharded_has_one.save
59
+ end.should change(ShardedHasOne, :count).by(1)
60
+ end
61
+ end
62
+
63
+ context 'using a :has_many reflection' do
64
+ before(:each) do
65
+ # Ensure that all sharded model connections happen on the owner shard
66
+ Dynashard.shard_context[:owner] = @shard
67
+
68
+ @one_of_many = Factory(:sharded_has_many, :sharding_owner => @owner, :name => "Owned by #{@owner.name}")
69
+ end
70
+
71
+ after(:each) do
72
+ Dynashard.shard_context.clear
73
+ end
74
+
75
+ # TODO: figure out what to do about the classes being different
76
+ it 'reads from the shard' do
77
+ @owner.sharded_has_manys.detect{|m| m.attributes == @one_of_many.attributes}.should_not be_nil
78
+ end
79
+
80
+ it 'destroys from the shard' do
81
+ lambda do
82
+ one_of_many = @owner.sharded_has_manys.detect{|m| m.attributes == @one_of_many.attributes}
83
+ one_of_many.destroy
84
+ end.should change(ShardedHasMany, :count).by(-1)
85
+ end
86
+
87
+ it 'creates_other on the shard' do
88
+ new_owner = Factory(:sharding_owner, :shard => @owner.shard)
89
+ lambda do
90
+ new_owner.sharded_has_manys.create(:name => "Owned by #{new_owner.name}")
91
+ end.should change(ShardedHasMany, :count).by(1)
92
+ end
93
+
94
+ it 'saves built associations on the shard' do
95
+ new_owner = Factory(:sharding_owner, :shard => @owner.shard)
96
+ new_one_of_many = new_owner.sharded_has_manys.build(:name => "Owned by #{new_owner.name}")
97
+ lambda do
98
+ new_one_of_many.save
99
+ end.should change(ShardedHasMany, :count).by(1)
100
+ end
101
+ end
102
+
103
+ context 'using a :has_many_through reflection' do
104
+ before(:each) do
105
+ # Ensure that all sharded model connections happen on the owner shard
106
+ Dynashard.shard_context[:owner] = @shard
107
+
108
+ @one_of_many = Factory(:sharded_has_many_through, :name => "Owned by #{@owner.name}")
109
+ join = Factory(:sharded_join, :sharding_owner => @owner, :sharded_has_many_through => @one_of_many)
110
+ end
111
+
112
+ after(:each) do
113
+ Dynashard.shard_context.clear
114
+ end
115
+
116
+ # TODO: figure out what to do about the classes being different
117
+ it 'reads from the shard' do
118
+ @owner.sharded_has_many_throughs.detect{|m| m.attributes == @one_of_many.attributes}.should_not be_nil
119
+ end
120
+
121
+ it 'destroys from the shard' do
122
+ lambda do
123
+ one_of_many = @owner.sharded_has_many_throughs.detect{|m| m.attributes == @one_of_many.attributes}
124
+ one_of_many.destroy
125
+ end.should change(ShardedHasManyThrough, :count).by(-1)
126
+ end
127
+
128
+ it 'creates_other on the shard' do
129
+ new_owner = Factory(:sharding_owner, :shard => @owner.shard)
130
+ lambda do
131
+ new_owner.sharded_has_many_throughs.create(:name => "Owned by #{new_owner.name}")
132
+ end.should change(ShardedHasManyThrough, :count).by(1)
133
+ end
134
+
135
+ it 'saves built associations on the shard' do
136
+ new_owner = Factory(:sharding_owner, :shard => @owner.shard)
137
+ new_one_of_many = new_owner.sharded_has_many_throughs.build(:name => "Owned by #{new_owner.name}")
138
+ lambda do
139
+ new_one_of_many.save
140
+ end.should change(ShardedHasManyThrough, :count).by(1)
141
+ end
142
+ end
143
+ end
144
+
145
+ context 'and a non-sharding proxy target' do
146
+ it 'uses a non-sharded reflection class' do
147
+ orig_reflection = @owner.class.reflections[:non_sharded_has_manys]
148
+ Dynashard.should_receive(:reflection_for).never
149
+ proxy = @owner.non_sharded_has_manys
150
+ proxy.proxy_reflection.klass.should_not respond_to(:dynashard_klass)
151
+ proxy.proxy_reflection.should == orig_reflection
152
+ end
153
+ end
154
+
155
+ end
156
+
157
+ context 'with a non-sharding proxy owner' do
158
+ before(:each) do
159
+ @owner = Factory(:non_sharding_owner)
160
+ end
161
+
162
+ context 'and a sharding proxy target' do
163
+ it 'uses a non-sharded reflection class' do
164
+ orig_reflection = @owner.class.reflections[:sharded_has_manys]
165
+ Dynashard.should_receive(:reflection_for).never
166
+ proxy = Dynashard.with_context(:owner => Shard.find(:first).dsn){@owner.sharded_has_manys}
167
+ proxy.proxy_reflection.klass.should_not respond_to(:dynashard_klass)
168
+ proxy.proxy_reflection.should == orig_reflection
169
+ end
170
+ end
171
+
172
+ context 'and a non-sharding proxy target' do
173
+ it 'uses a non-sharded reflection class' do
174
+ orig_reflection = @owner.class.reflections[:non_sharded_has_manys]
175
+ Dynashard.should_receive(:reflection_for).never
176
+ proxy = @owner.non_sharded_has_manys
177
+ proxy.proxy_reflection.klass.should_not respond_to(:dynashard_klass)
178
+ proxy.proxy_reflection.should == orig_reflection
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Dynashard::ConnectionHandlerExtensions' do
4
+ before(:each) do
5
+ Dynashard.enable
6
+ end
7
+
8
+ context 'defining connection pool aliases' do
9
+ after(:each) do
10
+ ActiveRecord::Base.connection_handler.connection_pools.delete('Foo')
11
+ end
12
+
13
+ it 'succeeds' do
14
+ connection_handler = ActiveRecord::Base.connection_handler
15
+ connection_handler.dynashard_pool_alias('Foo', 'ActiveRecord::Base')
16
+ connection_handler.connection_pools['Foo'].should == connection_handler.connection_pools['ActiveRecord::Base']
17
+ end
18
+ end
19
+
20
+ context 'with a sharded association reflection' do
21
+ it 'retrieves the connection pool' do
22
+ owner = Factory(:sharding_owner)
23
+ reflection = Dynashard.reflection_for(owner, ShardingOwner.reflections[:sharded_has_one])
24
+ dynashard_klass = reflection.klass.dynashard_klass
25
+ pool = ActiveRecord::Base.connection_handler.connection_pools['ActiveRecord::Base']
26
+ ActiveRecord::Base.connection_handler.should_receive(:retrieve_connection_pool_without_dynashard).with(dynashard_klass).and_return(pool)
27
+ reflection.klass.connection.should be_kind_of(ActiveRecord::ConnectionAdapters::AbstractAdapter)
28
+ end
29
+ end
30
+
31
+ context 'with a sharded model' do
32
+ before(:each) do
33
+ @shard_klass = Dynashard.class_for('shard1')
34
+ end
35
+
36
+ it 'retrieves the connection' do
37
+ dsn = Shard.find(:first).dsn
38
+ shard_klass = Dynashard.class_for(dsn)
39
+ pool = ActiveRecord::Base.connection_handler.connection_pools['ActiveRecord::Base']
40
+ ActiveRecord::Base.connection_handler.should_receive(:retrieve_connection_pool_without_dynashard).with(shard_klass).and_return(pool)
41
+ connection = Dynashard.with_context(:owner => dsn) {ShardedHasOne.connection}
42
+ connection.should be_kind_of(ActiveRecord::ConnectionAdapters::AbstractAdapter)
43
+ end
44
+
45
+ it 'requires a defined shard context' do
46
+ Dynashard.shard_context.clear
47
+ ShardedHasOne.sharding_enabled?.should be_true
48
+ lambda do
49
+ connection = ShardedHasOne.find(:first)
50
+ end.should raise_error(RuntimeError)
51
+ end
52
+ end
53
+
54
+ context 'with a non-sharded model' do
55
+ it 'retrieves the connection' do
56
+ pool = ActiveRecord::Base.connection_handler.connection_pools['ActiveRecord::Base']
57
+ ActiveRecord::Base.connection_handler.should_receive(:retrieve_connection_pool_without_dynashard).with(NonShardedHasOne).and_return(pool)
58
+ NonShardedHasOne.connection.should be_kind_of(ActiveRecord::ConnectionAdapters::AbstractAdapter)
59
+ end
60
+ end
61
+ end
data/spec/db/schema.rb ADDED
@@ -0,0 +1,67 @@
1
+ ActiveRecord::Schema.define(:version => 1) do
2
+
3
+ create_table "shards", :force => true do |t|
4
+ t.string "name"
5
+ t.string "adapter"
6
+ t.string "host"
7
+ t.string "username"
8
+ t.string "password"
9
+ t.string "database"
10
+ t.integer "port"
11
+ t.integer "pool"
12
+ t.integer "timeout"
13
+ end
14
+
15
+ create_table "non_sharding_owners", :force => true do |t|
16
+ t.string "name"
17
+ end
18
+
19
+ create_table "sharding_owners", :force => true do |t|
20
+ t.string "name"
21
+ t.integer "shard_id"
22
+ end
23
+
24
+ create_table "non_sharded_has_ones", :force => true do |t|
25
+ t.integer "sharding_owner_id"
26
+ t.integer "non_sharding_owner_id"
27
+ t.string "name"
28
+ end
29
+
30
+ create_table "non_sharded_has_manies", :force => true do |t|
31
+ t.integer "sharding_owner_id"
32
+ t.integer "non_sharding_owner_id"
33
+ t.string "name"
34
+ end
35
+
36
+ create_table "non_sharded_joins", :force => true do |t|
37
+ t.integer "sharding_owner_id"
38
+ t.integer "non_sharding_owner_id"
39
+ t.integer "non_sharded_has_many_through_id"
40
+ end
41
+
42
+ create_table "non_sharded_has_many_throughs", :force => true do |t|
43
+ t.string "name"
44
+ end
45
+
46
+ create_table "sharded_has_ones", :force => true do |t|
47
+ t.integer "sharding_owner_id"
48
+ t.integer "non_sharding_owner_id"
49
+ t.string "name"
50
+ end
51
+
52
+ create_table "sharded_has_manies", :force => true do |t|
53
+ t.integer "sharding_owner_id"
54
+ t.integer "non_sharding_owner_id"
55
+ t.string "name"
56
+ end
57
+
58
+ create_table "sharded_joins", :force => true do |t|
59
+ t.integer "sharding_owner_id"
60
+ t.integer "non_sharding_owner_id"
61
+ t.integer "sharded_has_many_through_id"
62
+ end
63
+
64
+ create_table "sharded_has_many_throughs", :force => true do |t|
65
+ t.string "name"
66
+ end
67
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Dynashard' do
4
+ it 'can be enabled' do
5
+ Dynashard.should respond_to(:enable)
6
+ Dynashard.enable
7
+ Dynashard.should be_enabled
8
+ end
9
+
10
+ it 'can be disabled' do
11
+ Dynashard.should respond_to(:disable)
12
+ Dynashard.disable
13
+ Dynashard.should_not be_enabled
14
+ end
15
+
16
+ describe '#shard_context' do
17
+ it 'is threadsafe' do
18
+ Dynashard.shard_context[:test] = true
19
+ t = Thread.new {Dynashard.shard_context[:test] = false}
20
+ t.join
21
+ Dynashard.shard_context[:test].should be_true
22
+ end
23
+ end
24
+
25
+ describe '#class_for' do
26
+ before(:each) do
27
+ @shard_klass = Dynashard.class_for('shard1')
28
+ end
29
+
30
+ it 'returns a class' do
31
+ @shard_klass.should be_a(Class)
32
+ end
33
+
34
+ it 'has an established connection' do
35
+ ActiveRecord::Base.connection_handler.connection_pools.should have_key(@shard_klass.name)
36
+ end
37
+
38
+ it 'does not regenerate existing classes' do
39
+ other_klass = Dynashard.class_for('shard1')
40
+ other_klass.should == @shard_klass
41
+ end
42
+
43
+ # Ie. from config/database.yml
44
+ it 'accepts a string reference to a configured database' do
45
+ lambda do
46
+ Dynashard.class_for('shard2')
47
+ end.should_not raise_error
48
+ end
49
+
50
+ it 'accepts a hash with valid database connection parameters' do
51
+ lambda do
52
+ Dynashard.class_for({:adapter => 'sqlite3', :database => 'db/shard3.sqlite3'})
53
+ end.should_not raise_error
54
+ end
55
+ end
56
+
57
+ describe '#reflection_for' do
58
+ before(:each) do
59
+ @owner = Factory(:sharding_owner)
60
+ @reflection = Dynashard.reflection_for(@owner, ShardingOwner.reflections[:sharded_has_one])
61
+ @shard_klass = Dynashard.class_for(@owner.shard_dsn)
62
+ end
63
+
64
+ it 'returns a reflection' do
65
+ @reflection.should be_kind_of(ActiveRecord::Reflection::MacroReflection)
66
+ end
67
+
68
+ it 'has a sharded class' do
69
+ @reflection.klass.should respond_to(:dynashard_klass)
70
+ end
71
+ end
72
+
73
+ describe '#sharded_model_class' do
74
+ before(:each) do
75
+ @owner = Factory(:sharding_owner)
76
+ @reflection = Dynashard.reflection_for(@owner, ShardingOwner.reflections[:sharded_has_one])
77
+ @shard_klass = Dynashard.class_for(@owner.shard_dsn)
78
+ end
79
+
80
+ it 'identifies as a sharded model' do
81
+ @reflection.klass.should respond_to(:dynashard_model?)
82
+ @reflection.klass.dynashard_model?.should be_true
83
+ end
84
+
85
+ it 'returns the shard class' do
86
+ @reflection.klass.dynashard_klass.should == @shard_klass
87
+ end
88
+
89
+ it 'returns the shard class connection' do
90
+ @reflection.klass.connection.should == @shard_klass.connection
91
+ end
92
+
93
+ it 'are not regenerated' do
94
+ other_reflection = Dynashard.reflection_for(@owner, ShardingOwner.reflections[:sharded_has_one])
95
+ other_reflection.klass.should == @reflection.klass
96
+ end
97
+ end
98
+
99
+ describe '#with_context' do
100
+ before(:each) do
101
+ @test_context = {:foo => 'shard1'}
102
+ end
103
+
104
+ it 'sets the shard context for the given block' do
105
+ Dynashard.with_context(@test_context) do
106
+ Dynashard.shard_context[:foo].should == 'shard1'
107
+ end
108
+ end
109
+
110
+ it 'resets the shard context' do
111
+ Dynashard.with_context(@test_context) {}
112
+ Dynashard.shard_context.should_not have_key(:foo)
113
+ end
114
+
115
+ it 'returns the block result' do
116
+ Dynashard.with_context(@test_context) { 'foo' }.should == 'foo'
117
+ end
118
+
119
+ context 'when the block raises an exception' do
120
+ it 'resets the shard context' do
121
+ Dynashard.with_context(@test_context) { raise 'Aah!' } rescue nil
122
+ Dynashard.shard_context.should_not have_key(:foo)
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ActiveRecord Models' do
4
+
5
+ context 'with invalid sharding arguments' do
6
+ after(:each) do
7
+ Object.send(:remove_const, :DynashardTestClass) if Object.const_defined?(:DynashardTestClass)
8
+ end
9
+
10
+ context 'for association owners' do
11
+ it 'raises an exception' do
12
+ lambda do
13
+ class DynashardTestClass < ActiveRecord::Base
14
+ shard :associated, :without_using_arg => true
15
+ end
16
+ end.should raise_error(ArgumentError)
17
+ end
18
+ end
19
+
20
+ context 'for sharded models' do
21
+ it 'raises an exception' do
22
+ lambda do
23
+ class DynashardTestClass < ActiveRecord::Base
24
+ shard :without_using_by => true
25
+ end
26
+ end.should raise_error(ArgumentError)
27
+ end
28
+ end
29
+ end
30
+
31
+ context 'with Dynashard disabled' do
32
+ before(:each) do
33
+ Dynashard.disable
34
+ end
35
+
36
+ after(:each) do
37
+ Dynashard.enable
38
+ end
39
+
40
+ it 'uses the ActiveRecord::Base connection' do
41
+ ShardedHasOne.connection.should == ActiveRecord::Base.connection
42
+ end
43
+
44
+ it 'do not shard' do
45
+ ShardedHasOne.sharding_enabled?.should be_false
46
+ end
47
+
48
+ it 'do not shard associations' do
49
+ ShardingOwner.shards_associated?.should be_false
50
+ end
51
+ end
52
+
53
+ context 'with Dynashard enabled' do
54
+ before(:each) do
55
+ Dynashard.enable
56
+ end
57
+
58
+ it 'can be sharded' do
59
+ ShardedHasOne.sharding_enabled?.should be_true
60
+ end
61
+
62
+ it 'can have sharded associations' do
63
+ ShardingOwner.shards_associated?.should be_true
64
+ end
65
+
66
+ it 'shards associations using a specified arg' do
67
+ class DynashardTestClass < ActiveRecord::Base
68
+ shard :associated, :using => :foo
69
+ end
70
+ DynashardTestClass.dynashard_association_using.should == :foo
71
+ Object.send(:remove_const, :DynashardTestClass)
72
+ end
73
+
74
+ it 'shards models using a specified context' do
75
+ class DynashardTestClass < ActiveRecord::Base
76
+ shard :by => :foo
77
+ end
78
+ DynashardTestClass.dynashard_context.should == :foo
79
+ Object.send(:remove_const, :DynashardTestClass)
80
+ end
81
+
82
+ context 'and no shard context defined' do
83
+ it 'raises an exception' do
84
+ lambda do
85
+ ShardedHasOne.connection
86
+ end.should raise_error
87
+ end
88
+ end
89
+
90
+ context 'and the shard context defined' do
91
+ it 'uses the sharded connection' do
92
+ test_shard = 'shard1'
93
+ Dynashard.with_context(:owner => test_shard) do
94
+ shard_class = Dynashard.class_for(test_shard)
95
+ shard_config = shard_class.connection.instance_variable_get('@config')
96
+ ar_config = ActiveRecord::Base.connection.instance_variable_get('@config')
97
+ model_config = ShardedHasOne.connection.instance_variable_get('@config')
98
+ model_config.should_not == ar_config
99
+ model_config.should == shard_config
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,46 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+ plugin_test_dir = File.dirname(__FILE__)
3
+
4
+ require 'rubygems'
5
+ require 'bundler/setup'
6
+ require 'erb'
7
+
8
+ require 'rspec'
9
+ require 'logger'
10
+
11
+ require 'active_record'
12
+ require 'factory_girl_rails'
13
+
14
+ require 'dynashard'
15
+
16
+ ActiveRecord::Base.logger = Logger.new(plugin_test_dir + "/debug.log")
17
+
18
+ ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(plugin_test_dir + "/db/database.yml")).result(binding))
19
+ ActiveRecord::Base.establish_connection("test")
20
+ ActiveRecord::Migration.verbose = false
21
+
22
+ test_database = ActiveRecord::Base.configurations['test']['database']
23
+ File.unlink(test_database) if File.exists?(test_database)
24
+ load(File.join(plugin_test_dir, "db", "schema.rb"))
25
+
26
+ # create shards and databases that they point to
27
+ base_config = ActiveRecord::Base.configurations['test']
28
+ %w{shard1 shard2 shard3}.each do |shard|
29
+ database = "#{plugin_test_dir}/db/#{shard}.sqlite3"
30
+ File.unlink(database) if File.exists?(database)
31
+ shard_config = base_config.merge('database' => database)
32
+ ActiveRecord::Base.configurations['test'] = shard_config
33
+ ActiveRecord::Base.establish_connection("test")
34
+ load(File.join(plugin_test_dir, "db", "schema.rb"))
35
+ end
36
+ ActiveRecord::Base.configurations['test'] = base_config
37
+ ActiveRecord::Base.establish_connection("test")
38
+
39
+ require 'support/models'
40
+
41
+ %w{shard1 shard2 shard3}.each do |shard|
42
+ Shard.create(:adapter => 'sqlite3', :database => "#{plugin_test_dir}/db/#{shard}.sqlite3")
43
+ end
44
+
45
+ # This has to happen after the models have been defined and the shards have been created
46
+ require 'support/factories'
@@ -0,0 +1,32 @@
1
+ # Sequence for generating unique names
2
+ Factory.sequence :name do |n|
3
+ "Test Owner #{n}"
4
+ end
5
+
6
+ Factory.define(:sharding_owner) do |owner|
7
+ owner.name {Factory.next :name}
8
+ owner.shard Shard.find(:first)
9
+ end
10
+
11
+ Factory.define(:non_sharding_owner) do |owner|
12
+ owner.name {Factory.next :name}
13
+ end
14
+
15
+ Factory.define(:sharded_has_one) do |one|
16
+ one.name {Factory.next :name}
17
+ one.sharding_owner
18
+ end
19
+
20
+ Factory.define(:sharded_has_many) do |one_of_many|
21
+ one_of_many.name {Factory.next :name}
22
+ one_of_many.sharding_owner
23
+ end
24
+
25
+ Factory.define(:sharded_join) do |join|
26
+ join.sharding_owner
27
+ join.sharded_has_many_through
28
+ end
29
+
30
+ Factory.define(:sharded_has_many_through) do |one_of_many|
31
+ one_of_many.name {Factory.next :name}
32
+ end