ar-octopus 0.0.1

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,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