ar-octopus 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ class CreateUsersOnCanada < ActiveRecord::Migration
2
+ using(:canada)
3
+
4
+ def self.up
5
+ User.create!(:name => "Sharding")
6
+ end
7
+
8
+ def self.down
9
+ User.delete_all()
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class CreateUsersOnBothShards < ActiveRecord::Migration
2
+ using(:brazil, :canada)
3
+
4
+ def self.up
5
+ User.create!(:name => "Both")
6
+ end
7
+
8
+ def self.down
9
+ User.delete_all()
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class CreateUsersOnShardsOfAGroup < ActiveRecord::Migration
2
+ using_group(:country_shards)
3
+
4
+ def self.up
5
+ User.create!(:name => "Group")
6
+ end
7
+
8
+ def self.down
9
+ User.delete_all()
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class CreateUsersOnMultiplesGroups < ActiveRecord::Migration
2
+ using_group(:country_shards, :history_shards)
3
+
4
+ def self.up
5
+ User.create!(:name => "MultipleGroup")
6
+ end
7
+
8
+ def self.down
9
+ User.delete_all()
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class RaiseExceptionWithInvalidShardName < ActiveRecord::Migration
2
+ using(:amazing_shard)
3
+
4
+ def self.up
5
+ User.create!(:name => "Error")
6
+ end
7
+
8
+ def self.down
9
+ User.delete_all()
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class RaiseExceptionWithInvalidMultipleShardNames < ActiveRecord::Migration
2
+ using(:brazil, :invalid_shard)
3
+
4
+ def self.up
5
+ User.create!(:name => "Error")
6
+ end
7
+
8
+ def self.down
9
+ User.delete_all()
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class RaiseExceptionWithInvalidGroupName < ActiveRecord::Migration
2
+ using_group(:invalid_group)
3
+
4
+ def self.up
5
+ User.create!(:name => "Error")
6
+ end
7
+
8
+ def self.down
9
+ User.delete_all()
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class RaiseExceptionWithMultipleInvalidGroupNames < ActiveRecord::Migration
2
+ using_group(:country_shards,:invalid_group)
3
+
4
+ def self.up
5
+ User.create!(:name => "Error")
6
+ end
7
+
8
+ def self.down
9
+ User.delete_all()
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Octopus::Controller do
4
+ it "should have the using method to select the shard" do
5
+ pending()
6
+ end
7
+ end
@@ -0,0 +1,79 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Octopus::Migration do
4
+ it "should run just in the master shard" do
5
+ ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 1)
6
+
7
+ User.using(:master).find_by_name("Master").should_not be_nil
8
+ User.using(:canada).find_by_name("Master").should be_nil
9
+
10
+ ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT, 1)
11
+ end
12
+
13
+ it "should run on specific shard" do
14
+ ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 2)
15
+
16
+ User.using(:master).find_by_name("Sharding").should be_nil
17
+ User.using(:canada).find_by_name("Sharding").should_not be_nil
18
+
19
+ ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT, 2)
20
+ end
21
+
22
+ it "should run on specifieds shards" do
23
+ ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 3)
24
+
25
+ User.using(:brazil).find_by_name("Both").should_not be_nil
26
+ User.using(:canada).find_by_name("Both").should_not be_nil
27
+
28
+ ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT, 3)
29
+ end
30
+
31
+ it "should run on specified group" do
32
+ ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 4)
33
+
34
+ User.using(:canada).find_by_name("Group").should_not be_nil
35
+ User.using(:brazil).find_by_name("Group").should_not be_nil
36
+ User.using(:russia).find_by_name("Group").should_not be_nil
37
+
38
+ ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT, 4)
39
+ end
40
+
41
+ it "should run on multiples groups" do
42
+ ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 5)
43
+
44
+ User.using(:canada).where(:name => "MultipleGroup").all.size.should == 2
45
+ User.using(:brazil).where(:name => "MultipleGroup").all.size.should == 2
46
+ User.using(:russia).where(:name => "MultipleGroup").all.size.should == 2
47
+
48
+ ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT, 5)
49
+ end
50
+
51
+ describe "should raise a exception when" do
52
+ it "you specify a invalid shard name" do
53
+ lambda { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 6) }.should raise_error("Nonexistent Shard Name: amazing_shard")
54
+ end
55
+
56
+ it "you specify a invalid shard name, even if you have multiple shards, and one of them are right" do
57
+ lambda { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 7) }.should raise_error("Nonexistent Shard Name: invalid_shard")
58
+ end
59
+
60
+ it "you specify a invalid group name" do
61
+ lambda { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 8) }.should raise_error("Nonexistent Group Name: invalid_group")
62
+ end
63
+
64
+ it "you specify a invalid group name, even if you have multiple groups, and one of them are right" do
65
+ lambda { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT, 9) }.should raise_error("Nonexistent Group Name: invalid_group")
66
+ end
67
+ end
68
+
69
+ describe "when using replication" do
70
+ it "should run writes on master when you use replication" do
71
+ pending()
72
+ end
73
+
74
+ it "should run in all shards, master or another shards" do
75
+ pending()
76
+ end
77
+ end
78
+ end
79
+
@@ -0,0 +1,122 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Octopus::Model do
4
+ describe "#using method" do
5
+ it "should return self after calling the #using method" do
6
+ User.using(:canada).should == User
7
+ end
8
+
9
+ it "should allow selecting the shards on scope" do
10
+ User.using(:canada).create!(:name => 'oi')
11
+ User.using(:canada).count.should == 1
12
+ User.count.should == 0
13
+ end
14
+
15
+ it "should allow scoping dynamically" do
16
+ User.using(:canada).using(:master).using(:canada).create!(:name => 'oi')
17
+ User.using(:canada).using(:master).count.should == 0
18
+ User.using(:master).using(:canada).count.should == 1
19
+ end
20
+
21
+ it "should clean the current_shard after executing the current query" do
22
+ User.using(:canada).create!(:name => "oi")
23
+ User.count.should == 0
24
+ end
25
+
26
+ it "should support both groups and alone shards" do
27
+ User.using(:alone_shard).create!(:name => "Alone")
28
+ User.using(:alone_shard).count.should == 1
29
+ User.using(:canada).count.should == 0
30
+ User.using(:brazil).count.should == 0
31
+ User.count.should == 0
32
+ end
33
+
34
+ describe "passing a block" do
35
+ it "should allow queries be executed inside the block, ponting to a specific shard" do
36
+ User.using(:canada) do
37
+ User.create(:name => "oi")
38
+ end
39
+
40
+ User.using(:canada).count.should == 1
41
+ User.using(:master).count.should == 0
42
+ User.count.should == 0
43
+ end
44
+
45
+ it "should allow execute queries inside a model" do
46
+ u = User.new
47
+ u.awesome_queries()
48
+ User.using(:canada).count.should == 1
49
+ User.count.should == 0
50
+ end
51
+ end
52
+
53
+ describe "when you have a relationship" do
54
+ it "should find all models in the specified shard" do
55
+ pending()
56
+ # brazil_client = Client.using(:brazil).create!(:name => "Brazil Client")
57
+ # master_client = Client.create!(:name => "Master Client")
58
+ #
59
+ # item_brazil = Item.using(:brazil).create!(:name => "Brazil Item", :client => brazil_client)
60
+ # item_master = Item.create!(:name => "Master Item", :client => master_client)
61
+ # c = Client.using(:brazil).find_by_name("Brazil Client")
62
+ # Client.using(:master).create!(:name => "teste")
63
+ # c.items.should == [item_brazil]
64
+ end
65
+ end
66
+
67
+ describe "raising errors" do
68
+ it "should raise a error when you specify a shard that doesn't exist" do
69
+ lambda { User.using(:crazy_shard) }.should raise_error("Nonexistent Shard Name: crazy_shard")
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "using a postgresql shard" do
75
+ after(:each) do
76
+ User.using(:postgresql_shard).delete_all
77
+ end
78
+
79
+ it "should update the Arel Engine" do
80
+ User.using(:postgresql_shard).arel_engine.connection.adapter_name.should == "PostgreSQL"
81
+ User.using(:alone_shard).arel_engine.connection.adapter_name.should == "MySQL"
82
+ end
83
+
84
+ it "should works with writes and reads" do
85
+ pending()
86
+ #u = User.using(:postgresql_shard).create!(:name => "PostgreSQL User")
87
+ # #User.using(:postgresql_shard).arel_table.columns.should == ""
88
+ # User.using(:postgresql_shard).scoped.should == ""
89
+ # User.using(:alone_shard).find(:all).should == []
90
+ end
91
+ end
92
+
93
+ describe "#replicated_model method" do
94
+ before(:each) do
95
+ Octopus.stub!(:env).and_return("production_replicated")
96
+ @proxy = Octopus::Proxy.new(Octopus.config())
97
+ #TODO - This is ugly, but is better than mocking
98
+ ActiveRecord::Base.class_eval("@@connection_proxy = nil")
99
+ #TODO - This is ugly, but is better than mocking
100
+ end
101
+
102
+ after(:each) do
103
+ #TODO - One little Kitten dies each time this code is executed.
104
+ ActiveRecord::Base.class_eval("@@connection_proxy = nil")
105
+ end
106
+
107
+ it "should be replicated" do
108
+ ActiveRecord::Base.connection_proxy.replicated.should be_true
109
+ end
110
+
111
+ it "should mark the Cat model as replicated" do
112
+ Cat.all.should == []
113
+ ActiveRecord::Base.connection_proxy.replicated_models.first.should == "Cat"
114
+ end
115
+ end
116
+
117
+ describe "#sharded_by method" do
118
+ it "should send all queries to the specify shard" do
119
+ pending()
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,15 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Octopus do
4
+ describe "#config method" do
5
+ it "should load shards.yml file to start working" do
6
+ Octopus.config().should be_kind_of(Hash)
7
+ end
8
+ end
9
+
10
+ describe "#directory method" do
11
+ it "should return the directory that contains the shards.yml file" do
12
+ Octopus.directory().should == File.expand_path(File.dirname(__FILE__) + "/../")
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,112 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Octopus::Proxy do
4
+ before(:each) do
5
+ @proxy = Octopus::Proxy.new(Octopus.config())
6
+ end
7
+
8
+ describe "creating a new instance" do
9
+ it "should initialize all shards and groups" do
10
+ @proxy.shards.keys.to_set.should == [:postgresql_shard, :alone_shard, :aug2011, :canada, :brazil, :aug2009, :russia, :aug2010, :master].to_set
11
+ @proxy.groups.should == {:country_shards=>[:canada, :brazil, :russia], :history_shards=>[:aug2009, :aug2010, :aug2011]}
12
+ end
13
+
14
+ it "should initialize the block attribute as false" do
15
+ @proxy.block.should be_false
16
+ end
17
+ it "should initialize replicated attribute as false" do
18
+ @proxy.replicated.should be_false
19
+ end
20
+
21
+ describe "should raise error if you have duplicated shard names" do
22
+ before(:each) do
23
+ Octopus.stub!(:env).and_return("production_raise_error")
24
+ end
25
+
26
+ it "should raise the error" do
27
+ lambda { Octopus::Proxy.new(Octopus.config()) }.should raise_error("You have duplicated shard names!")
28
+ end
29
+ end
30
+ end
31
+
32
+ describe "returning the correct connection" do
33
+ describe "should return the shard name" do
34
+ it "when current_shard is empty" do
35
+ @proxy.shard_name.should == :master
36
+ end
37
+
38
+ it "when current_shard is a single shard" do
39
+ @proxy.current_shard = :canada
40
+ @proxy.shard_name.should == :canada
41
+ end
42
+
43
+ it "when current_shard is more than one shard" do
44
+ @proxy.current_shard = [:russia, :brazil]
45
+ @proxy.shard_name.should == :russia
46
+ end
47
+ end
48
+
49
+ describe "should return the connection based on shard_name" do
50
+ it "when current_shard is empty" do
51
+ @proxy.select_connection().should == @proxy.shards[:master].connection()
52
+ end
53
+
54
+ it "when current_shard is a single shard" do
55
+ @proxy.current_shard = :canada
56
+ @proxy.select_connection().should == @proxy.shards[:canada].connection()
57
+ end
58
+ end
59
+ end
60
+
61
+ describe "when the database is replicated" do
62
+ before(:each) do
63
+ Octopus.stub!(:env).and_return("production_replicated")
64
+ @proxy = Octopus::Proxy.new(Octopus.config())
65
+ #TODO - THIS IS SO UGLY
66
+ ActiveRecord::Base.class_eval("@@connection_proxy = nil")
67
+ end
68
+
69
+ it "should have the replicated attribute as true" do
70
+ @proxy.replicated.should be_true
71
+ end
72
+
73
+ it "should initialize the list of shards" do
74
+ @proxy.slaves_list.should == ["slave1", "slave2", "slave3", "slave4"]
75
+ end
76
+
77
+ it "should send all writes queries to master" do
78
+ u = User.create!(:name => "Replicated")
79
+
80
+ [:slave4, :slave1, :slave2, :slave3].each do |sym|
81
+ User.using(sym).count.should == 0
82
+ end
83
+
84
+ User.using(:master).count.should == 1
85
+ end
86
+
87
+ it "should send read queries to slaves, using a round robin algorithm" do
88
+ u = Cat.create!(:name => "master")
89
+ c = Client.create!(:name => "client_master")
90
+
91
+ [:slave4, :slave1, :slave2, :slave3].each do |sym|
92
+ Cat.using(sym).create!(:name => "Replicated_#{sym}")
93
+ end
94
+
95
+ Client.find(:first).should_not be_nil
96
+ Cat.find(:first).name.should == "Replicated_slave1"
97
+ Client.find(:first).should_not be_nil
98
+ Cat.find(:first).name.should == "Replicated_slave2"
99
+ Client.find(:first).should_not be_nil
100
+ Cat.find(:first).name.should == "Replicated_slave3"
101
+ Client.find(:first).should_not be_nil
102
+ Cat.find(:first).name.should == "Replicated_slave4"
103
+ Client.find(:first).should_not be_nil
104
+ Cat.find(:first).name.should == "Replicated_slave1"
105
+ Client.find(:first).should_not be_nil
106
+
107
+ [:slave4, :slave1, :slave2, :slave3].each do |sym|
108
+ Cat.using(sym).find_by_name("master").should be_false
109
+ end
110
+ end
111
+ end
112
+ end