dynashard 0.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.
@@ -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