activerecord-slave 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
1
+ require "active_support/concern"
2
+
3
+ module ActiveRecord
4
+ module Slave
5
+ module Model
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ private_class_method :define_slave_class
10
+ end
11
+
12
+ module ClassMethods
13
+ def use_slave(replication_name)
14
+ replication_config = ActiveRecord::Slave.config.fetch_replication_config replication_name
15
+ @replication_router = ActiveRecord::Slave::ReplicationRouter.new replication_config
16
+
17
+ establish_connection replication_config.master_connection_name
18
+
19
+ @slave_class_repository = {}
20
+ base_class = self
21
+ replication_config.slave_connection_names.keys.each do |connection_name|
22
+ @slave_class_repository[connection_name] = define_slave_class base_class, connection_name
23
+ end
24
+ end
25
+
26
+ def slave_for
27
+ @slave_class_repository.fetch @replication_router.slave_connection_name
28
+ end
29
+
30
+ def define_slave_class(base_class, connection_name)
31
+ model = Class.new(base_class) do
32
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
33
+ def self.name
34
+ "#{base_class.name}::Slave::#{connection_name}"
35
+ end
36
+ RUBY
37
+ end
38
+ model.class_eval { establish_connection(connection_name) }
39
+ model
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveRecord
2
+ module Slave
3
+ class Railtie < ::Rails::Railtie
4
+ rake_tasks do
5
+ load File.expand_path("../../../tasks/activerecord-slave.rake", __FILE__)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,28 @@
1
+ module ActiveRecord
2
+ module Slave
3
+ class ReplicationConfig
4
+ attr_reader :name, :master_connection_name
5
+
6
+ def initialize(replication_name)
7
+ @name = replication_name
8
+ end
9
+
10
+ def validate_config!
11
+ fail "Nothing register master connection." unless @master_connection_name
12
+ end
13
+
14
+ def register_master(connection_name)
15
+ @master_connection_name = connection_name
16
+ end
17
+
18
+ def register_slave(connection_name, weight)
19
+ @slave_connection_registory ||= {}
20
+ @slave_connection_registory.store connection_name, weight
21
+ end
22
+
23
+ def slave_connection_names
24
+ @slave_connection_registory
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ require "pickup"
2
+
3
+ module ActiveRecord
4
+ module Slave
5
+ class ReplicationRouter
6
+ def initialize(replication_config)
7
+ fail "Not ActiveRecord::Slave::ReplicationConfig object." unless replication_config.is_a? ActiveRecord::Slave::ReplicationConfig
8
+ @replication_config = replication_config
9
+ end
10
+
11
+ def master_connection_name
12
+ @replication_config.master_connection_name
13
+ end
14
+
15
+ def slave_connection_name
16
+ slaves = Pickup.new(@replication_config.slave_connection_names)
17
+ slaves.pick(1)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ module Slave
3
+ VERSION = "0.0.2"
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ require "active_support/lazy_load_hooks"
2
+
3
+ require "active_record"
4
+
5
+ require "active_record/slave/version"
6
+ require "active_record/slave/config"
7
+ require "active_record/slave/replication_config"
8
+ require "active_record/slave/replication_router"
9
+ require "active_record/slave/model"
10
+ require "active_record/slave/database_tasks"
11
+
12
+ module ActiveRecord
13
+ module Slave
14
+ class << self
15
+ def config
16
+ @config ||= Config.new
17
+ end
18
+
19
+ def configure(&block)
20
+ config.instance_eval(&block)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ require "active_record/slave/railtie" if defined? Rails
@@ -0,0 +1,13 @@
1
+ namespace :active_record do
2
+ namespace :slave do
3
+ desc "Create database for replicaition master"
4
+ task :db_create, %i(replicaition) => %i(environment) do |_, args|
5
+ ActiveRecord::Slave::DatabaseTasks.create_database args
6
+ end
7
+
8
+ desc "Drop database for replicaition master"
9
+ task :db_drop, %i(replicaition) => %i(environment) do |_, args|
10
+ ActiveRecord::Slave::DatabaseTasks.drop_database args
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ describe ActiveRecord::Slave::Config do
2
+ let!(:config) do
3
+ config = ActiveRecord::Slave::Config.new
4
+ config.define_replication(:test) do |replication|
5
+ replication.register_master :test_master
6
+ replication.register_slave :test_slave_001, 70
7
+ replication.register_slave :test_slave_002, 30
8
+ end
9
+ config
10
+ end
11
+
12
+ describe "#fetch_replication_config" do
13
+ it "returns :test replication config" do
14
+ expect(config.fetch_replication_config :test).to be_a ActiveRecord::Slave::ReplicationConfig
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ describe ActiveRecord::Slave::DatabaseTasks do
2
+ let(:args) do
3
+ { replicaition: "task" }
4
+ end
5
+
6
+ after(:each) do
7
+ ActiveRecord::Base.establish_connection(:test)
8
+ end
9
+
10
+ it "Create database" do
11
+ ActiveRecord::Slave::DatabaseTasks.create_database args
12
+ expect(ActiveRecord::Base.connection.execute("SHOW DATABASES").include? ["test_task"]).to be true
13
+ end
14
+
15
+ context "Created task replicaition database" do
16
+ it "Drop database" do
17
+ ActiveRecord::Slave::DatabaseTasks.drop_database args
18
+ expect(ActiveRecord::Base.connection.execute("SHOW DATABASES").include? ["test_task"]).to be false
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,98 @@
1
+ describe ActiveRecord::Slave::Model do
2
+ it "connect to master" do
3
+ expect(User.connection.pool.spec.config[:port]).to eq 21891
4
+ end
5
+
6
+ it "connect to slaves", retry: 3 do
7
+ slave_ports = 10.times.map { User.slave_for.connection.pool.spec.config[:port] }.uniq
8
+ expect(slave_ports.count).to eq 2
9
+ expect(slave_ports).to include 21892
10
+ expect(slave_ports).to include 21893
11
+ end
12
+
13
+ it "connect to default" do
14
+ expect(Item.connection.pool.spec.config[:port]).to eq 3306
15
+ end
16
+
17
+ describe "Write to master, Read from slave" do
18
+ it "returns user object from slave database" do
19
+ user_from_master = User.create name: "alice"
20
+
21
+ user_from_slave = User.slave_for.find user_from_master.id
22
+
23
+ expect(user_from_master.id).to eq user_from_slave.id
24
+ expect(user_from_master.name).to eq user_from_slave.name
25
+ end
26
+ end
27
+
28
+ describe "Assosiations" do
29
+ let(:user) { User.create name: "alice" }
30
+
31
+ context "has_many object" do
32
+ context "in default database" do
33
+ let(:item) { Item.create name: "foo", count: 1, user: user }
34
+
35
+ it "returns user object, belongs_to" do
36
+ expect(item.user).to be_a User
37
+ expect(item.user.id).to eq user.id
38
+ end
39
+
40
+ describe "model connect to default database" do
41
+ it "returns has_many objects" do
42
+ expect { item }.to change { user.items.count }.from(0).to(1)
43
+ expect { Item.create name: "bar", count: 1, user: user }.to change { user.items.count }.from(1).to(2)
44
+ end
45
+
46
+ it "returns replication model object" do
47
+ expect(Item.find(item.id).user).to eq user
48
+ end
49
+
50
+ it "returns default database service port" do
51
+ expect(Item.connection.pool.spec.config[:port]).to eq 3306
52
+ end
53
+ end
54
+ end
55
+
56
+ context "in replication database" do
57
+ let(:skill) { Skill.create name: "bar", user: user }
58
+
59
+ it "returns user object, belongs_to" do
60
+ expect(skill.user).to be_a User
61
+ expect(skill.user.id).to eq user.id
62
+ end
63
+
64
+ describe "model connect to raplication databases" do
65
+ it "returns has_many objects" do
66
+ expect { skill }.to change { user.skills.count }.from(0).to(1)
67
+ expect { Skill.create name: "foobar", user: user }.to change { user.skills.count }.from(1).to(2)
68
+ expect(user.skills.count).to eq Skill.slave_for.where(user_id: user.id).count
69
+ end
70
+
71
+ it "returns other model object from master" do
72
+ expect(Skill.find(skill.id).user).to eq user
73
+ end
74
+
75
+ it "returns other model object from slaves" do
76
+ expect(Skill.slave_for.find(skill.id).user).to eq user
77
+ end
78
+
79
+ it "returns master database service port" do
80
+ expect(Skill.connection.pool.spec.config[:port]).to eq 21891
81
+ end
82
+
83
+ it "returns slave database service port" do
84
+ expect(Skill.slave_for.connection.pool.spec.config[:port]).to eq(21892) | eq(21893)
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ context "Enable DatabaseRewinder, delete records at each after specs" do
91
+ it "No records in DataBases" do
92
+ expect(User.all.count).to eq 0
93
+ expect(Item.all.count).to eq 0
94
+ expect(Skill.all.count).to eq 0
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,46 @@
1
+ describe ActiveRecord::Slave::ReplicationConfig do
2
+ let!(:replication_config) do
3
+ config = ActiveRecord::Slave::ReplicationConfig.new :test_replication
4
+
5
+ config.register_master :test_master
6
+
7
+ config.register_slave :test_slave_001, 70
8
+ config.register_slave :test_slave_002, 30
9
+
10
+ config
11
+ end
12
+
13
+ context "not setup config for ReplicationConfig object" do
14
+ context "#validate_config!" do
15
+ let!(:not_setup_replication_config) { ActiveRecord::Slave::ReplicationConfig.new :test_replication }
16
+
17
+ it "returns raise, nothing register master connection" do
18
+ expect { not_setup_replication_config.validate_config! }.to raise_error "Nothing register master connection."
19
+ end
20
+
21
+ it "returns nil, config is valid" do
22
+ expect(replication_config.validate_config!).to be nil
23
+ end
24
+ end
25
+ end
26
+
27
+ context "#name" do
28
+ it "returns replication name" do
29
+ expect(replication_config.name).to eq :test_replication
30
+ end
31
+ end
32
+
33
+ context "#master_connection_name" do
34
+ it "returns master database connection name" do
35
+ expect(replication_config.master_connection_name).to eq :test_master
36
+ end
37
+ end
38
+
39
+ context "#slave_connection_names" do
40
+ it "returns slave database connection names" do
41
+ expect(replication_config.slave_connection_names).to be_a Hash
42
+ expect(replication_config.slave_connection_names).to include test_slave_001: 70
43
+ expect(replication_config.slave_connection_names).to include test_slave_002: 30
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,41 @@
1
+ describe ActiveRecord::Slave::ReplicationRouter do
2
+ let!(:replication_config) do
3
+ config = ActiveRecord::Slave::Config.new
4
+ config.define_replication(:test) do |replication|
5
+ replication.register_master :test_master
6
+ replication.register_slave :test_slave_001, 70
7
+ replication.register_slave :test_slave_002, 30
8
+ end
9
+ config.fetch_replication_config :test
10
+ end
11
+
12
+ let!(:replication_router) do
13
+ ActiveRecord::Slave::ReplicationRouter.new replication_config
14
+ end
15
+
16
+ describe "#new" do
17
+ it "Make instance object" do
18
+ expect(ActiveRecord::Slave::ReplicationRouter.new replication_config).to be_a ActiveRecord::Slave::ReplicationRouter
19
+ end
20
+
21
+ it "Raise error" do
22
+ expect { ActiveRecord::Slave::ReplicationRouter.new "test" }.to raise_error "Not ActiveRecord::Slave::ReplicationConfig object."
23
+ end
24
+ end
25
+
26
+ describe "#master_connection_name" do
27
+ it "returns master database conneciton name" do
28
+ expect(replication_router.master_connection_name).to eq :test_master
29
+ end
30
+ end
31
+
32
+ describe "#slave_connection_name" do
33
+ it "returns slave database conneciton name, by wait lottery confidence 99%", retry: 3 do
34
+ slave_connection_names = 10000.times.map { replication_router.slave_connection_name }
35
+ expect(slave_connection_names.count(:test_slave_001)).to be_between 7000 - 60, 7000 + 60
36
+ expect(slave_connection_names.count(:test_slave_002)).to be_between 3000 - 60, 3000 + 60
37
+ expect(slave_connection_names).to include :test_slave_001
38
+ expect(slave_connection_names).to include :test_slave_002
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ describe "ActiveRecord::Slave::VERSION" do
2
+ it "return version number" do
3
+ expect(ActiveRecord::Slave::VERSION).to be_a String
4
+ end
5
+ end
@@ -0,0 +1,49 @@
1
+ base = { "adapter" => "mysql2", "encoding" => "utf8", "pool" => 5, "username" => "root", "password" => "msandbox", "host" => "127.0.0.1" }
2
+
3
+ ActiveRecord::Base.configurations = {
4
+ "test_master" => base.merge("database" => "test_slave", "port" => 21891),
5
+ "test_slave_001" => base.merge("database" => "test_slave", "port" => 21892),
6
+ "test_slave_002" => base.merge("database" => "test_slave", "port" => 21893),
7
+ "test" => base.merge("database" => "test", "port" => 3306, "password" => ""),
8
+
9
+ "test_task_master" => base.merge("database" => "test_task", "port" => 21891),
10
+ "test_task_slave_001" => base.merge("database" => "test_task", "port" => 21892),
11
+ "test_task_slave_002" => base.merge("database" => "test_task", "port" => 21893)
12
+ }
13
+
14
+ ActiveRecord::Slave.configure do |config|
15
+ config.define_replication(:user) do |replication|
16
+ replication.register_master(:test_master)
17
+
18
+ replication.register_slave(:test_slave_001, 70)
19
+ replication.register_slave(:test_slave_002, 30)
20
+ end
21
+
22
+ config.define_replication(:task) do |replication|
23
+ replication.register_master(:test_task_master)
24
+
25
+ replication.register_slave(:test_task_slave_001, 1)
26
+ replication.register_slave(:test_task_slave_002, 1)
27
+ end
28
+ end
29
+
30
+ ActiveRecord::Base.establish_connection(:test)
31
+
32
+ class User < ActiveRecord::Base
33
+ has_many :items
34
+ has_many :skills
35
+
36
+ include ActiveRecord::Slave::Model
37
+ use_slave :user
38
+ end
39
+
40
+ class Item < ActiveRecord::Base
41
+ belongs_to :user
42
+ end
43
+
44
+ class Skill < ActiveRecord::Base
45
+ belongs_to :user
46
+
47
+ include ActiveRecord::Slave::Model
48
+ use_slave :user
49
+ end
@@ -0,0 +1,26 @@
1
+ ActiveRecord::Schema.define(version: 0) do
2
+ create_table "users", force: :cascade do |t|
3
+ t.string "name", null: false
4
+ t.datetime "created_at", null: false
5
+ t.datetime "updated_at", null: false
6
+ end
7
+ end
8
+
9
+ ActiveRecord::Schema.define(version: 1) do
10
+ create_table "items", force: :cascade do |t|
11
+ t.integer "user_id", null: false
12
+ t.string "name", null: false
13
+ t.integer "count", null: false
14
+ t.datetime "created_at", null: false
15
+ t.datetime "updated_at", null: false
16
+ end
17
+ end
18
+
19
+ ActiveRecord::Schema.define(version: 2) do
20
+ create_table "skills", force: :cascade do |t|
21
+ t.integer "user_id", null: false
22
+ t.string "name", null: false
23
+ t.datetime "created_at", null: false
24
+ t.datetime "updated_at", null: false
25
+ end
26
+ end