activerecord-slave 0.0.2

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