ar-octopus-ruby-3 0.11.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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +46 -0
- data/.rubocop_todo.yml +56 -0
- data/.travis.yml +18 -0
- data/Appraisals +16 -0
- data/Gemfile +4 -0
- data/README.mkdn +257 -0
- data/Rakefile +175 -0
- data/TODO.txt +7 -0
- data/ar-octopus.gemspec +44 -0
- data/gemfiles/rails42.gemfile +7 -0
- data/gemfiles/rails5.gemfile +7 -0
- data/gemfiles/rails51.gemfile +7 -0
- data/gemfiles/rails52.gemfile +7 -0
- data/lib/ar-octopus.rb +1 -0
- data/lib/octopus/abstract_adapter.rb +33 -0
- data/lib/octopus/association.rb +14 -0
- data/lib/octopus/association_shard_tracking.rb +74 -0
- data/lib/octopus/collection_association.rb +17 -0
- data/lib/octopus/collection_proxy.rb +16 -0
- data/lib/octopus/exception.rb +4 -0
- data/lib/octopus/finder_methods.rb +8 -0
- data/lib/octopus/load_balancing/round_robin.rb +20 -0
- data/lib/octopus/load_balancing.rb +4 -0
- data/lib/octopus/log_subscriber.rb +26 -0
- data/lib/octopus/migration.rb +236 -0
- data/lib/octopus/model.rb +216 -0
- data/lib/octopus/persistence.rb +45 -0
- data/lib/octopus/proxy.rb +399 -0
- data/lib/octopus/proxy_config.rb +251 -0
- data/lib/octopus/query_cache_for_shards.rb +24 -0
- data/lib/octopus/railtie.rb +11 -0
- data/lib/octopus/relation_proxy.rb +74 -0
- data/lib/octopus/result_patch.rb +19 -0
- data/lib/octopus/scope_proxy.rb +68 -0
- data/lib/octopus/shard_tracking/attribute.rb +22 -0
- data/lib/octopus/shard_tracking/dynamic.rb +11 -0
- data/lib/octopus/shard_tracking.rb +46 -0
- data/lib/octopus/singular_association.rb +9 -0
- data/lib/octopus/slave_group.rb +13 -0
- data/lib/octopus/version.rb +3 -0
- data/lib/octopus.rb +209 -0
- data/lib/tasks/octopus.rake +16 -0
- data/sample_app/.gitignore +4 -0
- data/sample_app/.rspec +1 -0
- data/sample_app/Gemfile +20 -0
- data/sample_app/Gemfile.lock +155 -0
- data/sample_app/README +3 -0
- data/sample_app/README.rdoc +261 -0
- data/sample_app/Rakefile +7 -0
- data/sample_app/app/assets/images/rails.png +0 -0
- data/sample_app/app/assets/javascripts/application.js +15 -0
- data/sample_app/app/assets/stylesheets/application.css +13 -0
- data/sample_app/app/controllers/application_controller.rb +4 -0
- data/sample_app/app/helpers/application_helper.rb +2 -0
- data/sample_app/app/mailers/.gitkeep +0 -0
- data/sample_app/app/models/.gitkeep +0 -0
- data/sample_app/app/models/item.rb +3 -0
- data/sample_app/app/models/user.rb +3 -0
- data/sample_app/app/views/layouts/application.html.erb +14 -0
- data/sample_app/autotest/discover.rb +2 -0
- data/sample_app/config/application.rb +62 -0
- data/sample_app/config/boot.rb +6 -0
- data/sample_app/config/cucumber.yml +8 -0
- data/sample_app/config/database.yml +28 -0
- data/sample_app/config/environment.rb +5 -0
- data/sample_app/config/environments/development.rb +37 -0
- data/sample_app/config/environments/production.rb +67 -0
- data/sample_app/config/environments/test.rb +37 -0
- data/sample_app/config/initializers/backtrace_silencers.rb +7 -0
- data/sample_app/config/initializers/inflections.rb +15 -0
- data/sample_app/config/initializers/mime_types.rb +5 -0
- data/sample_app/config/initializers/secret_token.rb +7 -0
- data/sample_app/config/initializers/session_store.rb +8 -0
- data/sample_app/config/initializers/wrap_parameters.rb +14 -0
- data/sample_app/config/locales/en.yml +5 -0
- data/sample_app/config/routes.rb +58 -0
- data/sample_app/config/shards.yml +28 -0
- data/sample_app/config.ru +4 -0
- data/sample_app/db/migrate/20100720172715_create_users.rb +15 -0
- data/sample_app/db/migrate/20100720172730_create_items.rb +16 -0
- data/sample_app/db/migrate/20100720210335_create_sample_users.rb +11 -0
- data/sample_app/db/schema.rb +29 -0
- data/sample_app/db/seeds.rb +16 -0
- data/sample_app/doc/README_FOR_APP +2 -0
- data/sample_app/features/migrate.feature +45 -0
- data/sample_app/features/seed.feature +15 -0
- data/sample_app/features/step_definitions/seeds_steps.rb +13 -0
- data/sample_app/features/step_definitions/web_steps.rb +218 -0
- data/sample_app/features/support/database.rb +13 -0
- data/sample_app/features/support/env.rb +57 -0
- data/sample_app/features/support/paths.rb +33 -0
- data/sample_app/lib/assets/.gitkeep +0 -0
- data/sample_app/lib/tasks/.gitkeep +0 -0
- data/sample_app/lib/tasks/cucumber.rake +64 -0
- data/sample_app/log/.gitkeep +0 -0
- data/sample_app/public/404.html +26 -0
- data/sample_app/public/422.html +26 -0
- data/sample_app/public/500.html +26 -0
- data/sample_app/public/favicon.ico +0 -0
- data/sample_app/public/images/rails.png +0 -0
- data/sample_app/public/index.html +279 -0
- data/sample_app/public/javascripts/application.js +2 -0
- data/sample_app/public/javascripts/controls.js +965 -0
- data/sample_app/public/javascripts/dragdrop.js +974 -0
- data/sample_app/public/javascripts/effects.js +1123 -0
- data/sample_app/public/javascripts/prototype.js +4874 -0
- data/sample_app/public/javascripts/rails.js +118 -0
- data/sample_app/public/robots.txt +5 -0
- data/sample_app/public/stylesheets/.gitkeep +0 -0
- data/sample_app/script/cucumber +10 -0
- data/sample_app/script/rails +6 -0
- data/sample_app/spec/models/item_spec.rb +5 -0
- data/sample_app/spec/models/user_spec.rb +5 -0
- data/sample_app/spec/spec_helper.rb +27 -0
- data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
- data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/sample_app/vendor/plugins/.gitkeep +0 -0
- data/spec/config/shards.yml +231 -0
- data/spec/migrations/10_create_users_using_replication.rb +9 -0
- data/spec/migrations/11_add_field_in_all_slaves.rb +11 -0
- data/spec/migrations/12_create_users_using_block.rb +23 -0
- data/spec/migrations/13_create_users_using_block_and_using.rb +15 -0
- data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +11 -0
- data/spec/migrations/15_create_user_on_shards_of_default_group_with_versions.rb +9 -0
- data/spec/migrations/1_create_users_on_master.rb +9 -0
- data/spec/migrations/2_create_users_on_canada.rb +11 -0
- data/spec/migrations/3_create_users_on_both_shards.rb +11 -0
- data/spec/migrations/4_create_users_on_shards_of_a_group.rb +11 -0
- data/spec/migrations/5_create_users_on_multiples_groups.rb +11 -0
- data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +11 -0
- data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +11 -0
- data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +11 -0
- data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +11 -0
- data/spec/octopus/association_shard_tracking_spec.rb +1036 -0
- data/spec/octopus/collection_proxy_spec.rb +16 -0
- data/spec/octopus/load_balancing/round_robin_spec.rb +15 -0
- data/spec/octopus/log_subscriber_spec.rb +19 -0
- data/spec/octopus/migration_spec.rb +151 -0
- data/spec/octopus/model_spec.rb +837 -0
- data/spec/octopus/octopus_spec.rb +123 -0
- data/spec/octopus/proxy_spec.rb +303 -0
- data/spec/octopus/query_cache_for_shards_spec.rb +40 -0
- data/spec/octopus/relation_proxy_spec.rb +132 -0
- data/spec/octopus/replicated_slave_grouped_spec.rb +91 -0
- data/spec/octopus/replication_spec.rb +196 -0
- data/spec/octopus/scope_proxy_spec.rb +97 -0
- data/spec/octopus/sharded_replicated_slave_grouped_spec.rb +55 -0
- data/spec/octopus/sharded_spec.rb +33 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/active_record/connection_adapters/modify_config_adapter.rb +15 -0
- data/spec/support/database_connection.rb +4 -0
- data/spec/support/database_models.rb +118 -0
- data/spec/support/octopus_helper.rb +66 -0
- data/spec/support/query_count.rb +17 -0
- data/spec/support/shared_contexts.rb +18 -0
- data/spec/tasks/octopus.rake_spec.rb +32 -0
- metadata +351 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Octopus, :shards => [] do
|
|
4
|
+
describe '#config' do
|
|
5
|
+
it 'should load shards.yml file to start working' do
|
|
6
|
+
expect(Octopus.config).to be_kind_of(HashWithIndifferentAccess)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
describe "when config file doesn't exist" do
|
|
10
|
+
before(:each) do
|
|
11
|
+
allow(Octopus).to receive(:directory).and_return('/tmp')
|
|
12
|
+
Octopus.instance_variable_set(:@config, nil)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'should return an empty HashWithIndifferentAccess' do
|
|
16
|
+
expect(Octopus.config).to eq(HashWithIndifferentAccess.new)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe '#directory' do
|
|
22
|
+
it 'should return the directory that contains the shards.yml file' do
|
|
23
|
+
expect(Octopus.directory).to eq(File.expand_path(File.dirname(__FILE__) + '/../'))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe '#env' do
|
|
28
|
+
it "should return 'production' when is outside of a rails application" do
|
|
29
|
+
expect(Octopus.env).to eq('octopus')
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '#shards=' do
|
|
34
|
+
after(:each) do
|
|
35
|
+
Octopus.instance_variable_set(:@config, nil)
|
|
36
|
+
Octopus::Model.send(:class_variable_set, :@@connection_proxy, nil)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'should permit users to configure shards on initializer files, instead of on a yml file.' do
|
|
40
|
+
expect { User.using(:crazy_shard).create!(:name => 'Joaquim') }.to raise_error(RuntimeError)
|
|
41
|
+
|
|
42
|
+
Octopus.setup do |config|
|
|
43
|
+
config.shards = { :crazy_shard => { :adapter => 'mysql2', :database => 'octopus_shard_5', :username => "#{ENV['MYSQL_USER'] || 'root'}", :password => ENV['MYSQL_PASSWORD'] } }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
expect { User.using(:crazy_shard).create!(:name => 'Joaquim') }.not_to raise_error
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe '#setup' do
|
|
51
|
+
it 'should have the default octopus environment as production' do
|
|
52
|
+
expect(Octopus.environments).to eq(['production'])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'should allow the user to configure the octopus environments' do
|
|
56
|
+
Octopus.setup do |config|
|
|
57
|
+
config.environments = [:production, :staging]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
expect(Octopus.environments).to eq(%w(production staging))
|
|
61
|
+
|
|
62
|
+
Octopus.setup do |config|
|
|
63
|
+
config.environments = [:production]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe '#enabled?' do
|
|
69
|
+
before do
|
|
70
|
+
Rails = double
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
after do
|
|
74
|
+
Object.send(:remove_const, :Rails)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'should be if octopus is configured and should hook into current environment' do
|
|
78
|
+
allow(Rails).to receive(:env).and_return('production')
|
|
79
|
+
|
|
80
|
+
expect(Octopus).to be_enabled
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'should not be if octopus should not hook into current environment' do
|
|
84
|
+
allow(Rails).to receive(:env).and_return('staging')
|
|
85
|
+
|
|
86
|
+
expect(Octopus).not_to be_enabled
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe '#fully_replicated' do
|
|
91
|
+
before do
|
|
92
|
+
OctopusHelper.using_environment :production_replicated do
|
|
93
|
+
OctopusHelper.clean_all_shards([:slave1, :slave2, :slave3, :slave4])
|
|
94
|
+
4.times { |i| User.using(:"slave#{i + 1}").create!(:name => 'Slave User') }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'sends queries to slaves' do
|
|
99
|
+
OctopusHelper.using_environment :production_replicated do
|
|
100
|
+
expect(User.count).to eq(0)
|
|
101
|
+
4.times do |_i|
|
|
102
|
+
Octopus.fully_replicated do
|
|
103
|
+
expect(User.count).to eq(1)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'allows nesting' do
|
|
110
|
+
OctopusHelper.using_environment :production_replicated do
|
|
111
|
+
Octopus.fully_replicated do
|
|
112
|
+
expect(User.count).to eq(1)
|
|
113
|
+
|
|
114
|
+
Octopus.fully_replicated do
|
|
115
|
+
expect(User.count).to eq(1)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
expect(User.count).to eq(1)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Octopus::Proxy do
|
|
4
|
+
let(:proxy) { subject }
|
|
5
|
+
|
|
6
|
+
describe 'creating a new instance', :shards => [] do
|
|
7
|
+
it 'should initialize all shards and groups' do
|
|
8
|
+
# FIXME: Don't test implementation details
|
|
9
|
+
expect(proxy.shards).to include('canada', 'brazil', 'master', 'sqlite_shard', 'russia', 'alone_shard',
|
|
10
|
+
'aug2009', 'postgresql_shard', 'aug2010', 'aug2011')
|
|
11
|
+
|
|
12
|
+
expect(proxy.shards).to include('protocol_shard')
|
|
13
|
+
|
|
14
|
+
expect(proxy.has_group?('country_shards')).to be true
|
|
15
|
+
expect(proxy.shards_for_group('country_shards')).to include(:canada, :brazil, :russia)
|
|
16
|
+
|
|
17
|
+
expect(proxy.has_group?('history_shards')).to be true
|
|
18
|
+
expect(proxy.shards_for_group('history_shards')).to include(:aug2009, :aug2010, :aug2011)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'should initialize the block attribute as false' do
|
|
22
|
+
expect(proxy.block).to be_falsey
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'should initialize replicated attribute as false' do
|
|
26
|
+
expect(proxy.replicated).to be_falsey
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'should work with thinking sphinx' do
|
|
30
|
+
config = proxy.config
|
|
31
|
+
expect(config[:adapter]).to eq('mysql2')
|
|
32
|
+
expect(config[:database]).to eq('octopus_shard_1')
|
|
33
|
+
expect(config[:username]).to eq("#{ENV['MYSQL_USER'] || 'root'}")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
unless Octopus.rails50? || Octopus.rails51?|| Octopus.rails52? || Octopus.rails60?
|
|
37
|
+
it 'should respond correctly to respond_to?(:pk_and_sequence_for)' do
|
|
38
|
+
expect(proxy.respond_to?(:pk_and_sequence_for)).to be true
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'should respond correctly to respond_to?(:primary_key)' do
|
|
43
|
+
expect(proxy.respond_to?(:primary_key)).to be true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context 'when an adapter that modifies the config' do
|
|
47
|
+
before { OctopusHelper.octopus_env = 'modify_config' }
|
|
48
|
+
after { OctopusHelper.octopus_env = 'octopus' }
|
|
49
|
+
|
|
50
|
+
it 'should not fail with missing adapter second time round' do
|
|
51
|
+
skip 'This test was actually failing because of a typo in the error message.'
|
|
52
|
+
Thread.current['octopus.current_shard'] = :modify_config_read
|
|
53
|
+
|
|
54
|
+
expect { Octopus::Proxy.new(Octopus.config) }.not_to raise_error
|
|
55
|
+
|
|
56
|
+
Thread.current['octopus.current_shard'] = nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe 'should raise error if you have duplicated shard names' do
|
|
61
|
+
before(:each) do
|
|
62
|
+
OctopusHelper.octopus_env = 'production_raise_error'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'should raise the error' do
|
|
66
|
+
expect { proxy }.to raise_error('You have duplicated shard names!')
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe "should initialize just the master when you don't have a shards.yml file" do
|
|
71
|
+
before(:each) do
|
|
72
|
+
OctopusHelper.octopus_env = 'crazy_environment'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'should initialize just the master shard' do
|
|
76
|
+
expect(proxy.shards.keys).to eq(['master'])
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'should not initialize replication' do
|
|
80
|
+
expect(proxy.replicated).to be_nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe 'when you have a replicated environment' do
|
|
86
|
+
before(:each) do
|
|
87
|
+
OctopusHelper.octopus_env = 'production_replicated'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'should have the replicated attribute as true' do
|
|
91
|
+
expect(proxy.replicated).to be true
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'should initialize the list of shards' do
|
|
95
|
+
expect(proxy.slaves_list).to eq(%w(slave1 slave2 slave3 slave4))
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe 'when you have a rails application' do
|
|
100
|
+
before(:each) do
|
|
101
|
+
Rails = double
|
|
102
|
+
OctopusHelper.octopus_env = 'octopus_rails'
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
after(:each) do
|
|
106
|
+
Object.send(:remove_const, :Rails)
|
|
107
|
+
Octopus.instance_variable_set(:@config, nil)
|
|
108
|
+
Octopus.instance_variable_set(:@rails_env, nil)
|
|
109
|
+
OctopusHelper.clean_connection_proxy
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'should initialize correctly octopus common variables for the environments' do
|
|
113
|
+
allow(Rails).to receive(:env).and_return('staging')
|
|
114
|
+
Octopus.instance_variable_set(:@rails_env, nil)
|
|
115
|
+
Octopus.instance_variable_set(:@environments, nil)
|
|
116
|
+
Octopus.config
|
|
117
|
+
|
|
118
|
+
expect(proxy.replicated).to be true
|
|
119
|
+
expect(Octopus.environments).to eq(%w(staging production))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'should initialize correctly the shards for the staging environment' do
|
|
123
|
+
allow(Rails).to receive(:env).and_return('staging')
|
|
124
|
+
Octopus.instance_variable_set(:@rails_env, nil)
|
|
125
|
+
Octopus.instance_variable_set(:@environments, nil)
|
|
126
|
+
Octopus.config
|
|
127
|
+
|
|
128
|
+
expect(proxy.shards.keys.to_set).to eq(Set.new(%w(slave1 slave2 master)))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'should initialize correctly the shard octopus_shard value for logging' do
|
|
132
|
+
allow(Rails).to receive(:env).and_return('staging')
|
|
133
|
+
Octopus.instance_variable_set(:@rails_env, nil)
|
|
134
|
+
Octopus.instance_variable_set(:@environments, nil)
|
|
135
|
+
Octopus.config
|
|
136
|
+
|
|
137
|
+
expect(proxy.shards['slave1'].spec.config).to have_key :octopus_shard
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'should initialize correctly the shards for the production environment' do
|
|
141
|
+
allow(Rails).to receive(:env).and_return('production')
|
|
142
|
+
Octopus.instance_variable_set(:@rails_env, nil)
|
|
143
|
+
Octopus.instance_variable_set(:@environments, nil)
|
|
144
|
+
Octopus.config
|
|
145
|
+
|
|
146
|
+
expect(proxy.shards.keys.to_set).to eq(Set.new(%w(slave3 slave4 master)))
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe 'using the master connection', :shards => [:russia, :master] do
|
|
150
|
+
before(:each) do
|
|
151
|
+
allow(Rails).to receive(:env).and_return('development')
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'should use the master connection' do
|
|
155
|
+
user = User.create!(:name => 'Thiago')
|
|
156
|
+
user.name = 'New Thiago'
|
|
157
|
+
user.save
|
|
158
|
+
expect(User.find_by_name('New Thiago')).not_to be_nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'should work when using using syntax' do
|
|
162
|
+
user = User.using(:russia).create!(:name => 'Thiago')
|
|
163
|
+
|
|
164
|
+
user.name = 'New Thiago'
|
|
165
|
+
user.save
|
|
166
|
+
|
|
167
|
+
expect(User.using(:russia).find_by_name('New Thiago')).to eq(user)
|
|
168
|
+
expect(User.find_by_name('New Thiago')).to eq(user)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'should work when using blocks' do
|
|
172
|
+
Octopus.using(:russia) do
|
|
173
|
+
@user = User.create!(:name => 'Thiago')
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
expect(User.find_by_name('Thiago')).to eq(@user)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'should work with associations' do
|
|
180
|
+
u = Client.create!(:name => 'Thiago')
|
|
181
|
+
i = Item.create(:name => 'Item')
|
|
182
|
+
u.items << i
|
|
183
|
+
u.save
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
describe 'returning the correct connection' do
|
|
189
|
+
describe 'should return the shard name' do
|
|
190
|
+
it 'when current_shard is empty' do
|
|
191
|
+
expect(proxy.shard_name).to eq(:master)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it 'when current_shard is empty with custom master' do
|
|
195
|
+
OctopusHelper.using_environment :octopus do
|
|
196
|
+
Octopus.config[:master_shard] = :brazil
|
|
197
|
+
expect(proxy.shard_name).to eq(:brazil)
|
|
198
|
+
Octopus.config[:master_shard] = nil
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it 'when current_shard is a single shard' do
|
|
203
|
+
proxy.current_shard = :canada
|
|
204
|
+
expect(proxy.shard_name).to eq(:canada)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it 'when current_shard is more than one shard' do
|
|
208
|
+
proxy.current_shard = [:russia, :brazil]
|
|
209
|
+
expect(proxy.shard_name).to eq(:russia)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
describe 'should return the connection based on shard_name' do
|
|
214
|
+
it 'when current_shard is empty' do
|
|
215
|
+
expect(proxy.select_connection).to eq(proxy.shards[:master].connection)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it 'when current_shard is a single shard' do
|
|
219
|
+
proxy.current_shard = :canada
|
|
220
|
+
expect(proxy.select_connection).to eq(proxy.shards[:canada].connection)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
describe 'saving multiple sharded objects at once' do
|
|
226
|
+
before :each do
|
|
227
|
+
@p = MmorpgPlayer.using(:alone_shard).create!(:player_name => 'Thiago')
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
subject { @p.save! }
|
|
231
|
+
|
|
232
|
+
context 'when the objects are created with #new and saved one at a time' do
|
|
233
|
+
before :each do
|
|
234
|
+
@p.weapons.create!(:name => 'battleaxe', :hand => 'right')
|
|
235
|
+
@p.skills.create!(:name => 'smiting', :weapon => @p.weapons[0])
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it 'should save all associated objects on the correct shard' do
|
|
239
|
+
expect { subject }.to_not raise_error
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
context 'when the objects are created with #new and saved at the same time' do
|
|
244
|
+
before :each do
|
|
245
|
+
@p.weapons.new(:name => 'battleaxe', :hand => 'right')
|
|
246
|
+
@p.skills.new(:name => 'smiting', :weapon => @p.weapons[0])
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it 'should save all associated objects on the correct shard' do
|
|
250
|
+
expect { subject }.to_not raise_error
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
describe 'cleaning the connection proxy' do
|
|
257
|
+
it 'should not clean #current_shard from proxy when using a block and calling #execute' do
|
|
258
|
+
Octopus.using(:canada) do
|
|
259
|
+
expect(User.connection.current_shard).to eq(:canada)
|
|
260
|
+
|
|
261
|
+
connection = User.connection
|
|
262
|
+
|
|
263
|
+
result = connection.execute('select * from users limit 1;')
|
|
264
|
+
result = connection.execute('select * from users limit 1;')
|
|
265
|
+
|
|
266
|
+
expect(User.connection.current_shard).to eq(:canada)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
describe 'connection reuse' do
|
|
272
|
+
before :each do
|
|
273
|
+
@item_brazil_conn = Item.using(:brazil).new(:name => 'Brazil Item').class.connection.select_connection
|
|
274
|
+
@item_canada_conn = Item.using(:canada).new(:name => 'Canada Item').class.connection.select_connection
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
it 'reuses connections' do
|
|
278
|
+
expect(Item.using(:brazil).new(:name => 'Another Brazil Item').class.connection.select_connection).to eq(@item_brazil_conn)
|
|
279
|
+
expect(Item.using(:canada).new(:name => 'Another Canada Item').class.connection.select_connection).to eq(@item_canada_conn)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it 'reuses connections after clear_active_connections! is called' do
|
|
283
|
+
expect(Item.using(:brazil).new(:name => 'Another Brazil Item').class.connection.select_connection).to eq(@item_brazil_conn)
|
|
284
|
+
expect(Item.using(:canada).new(:name => 'Another Canada Item').class.connection.select_connection).to eq(@item_canada_conn)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
it 'creates new connections after clear_all_connections! is called' do
|
|
288
|
+
Item.clear_all_connections!
|
|
289
|
+
expect(Item.using(:brazil).new(:name => 'Another Brazil Item').class.connection.select_connection).not_to eq(@item_brazil_conn)
|
|
290
|
+
expect(Item.using(:canada).new(:name => 'Another Canada Item').class.connection.select_connection).not_to eq(@item_canada_conn)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
it 'is consistent with connected?' do
|
|
294
|
+
expect(Item.connected?).to be true
|
|
295
|
+
expect(ActiveRecord::Base.connected?).to be true
|
|
296
|
+
|
|
297
|
+
Item.clear_all_connections!
|
|
298
|
+
|
|
299
|
+
expect(Item.connected?).to be false
|
|
300
|
+
expect(ActiveRecord::Base.connected?).to be false
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
unless Octopus.rails4? || Octopus.rails50?
|
|
4
|
+
describe Octopus::ConnectionPool::QueryCacheForShards do
|
|
5
|
+
subject(:query_cache_on_shard) { ActiveRecord::Base.using(:brazil).connection.query_cache_enabled }
|
|
6
|
+
|
|
7
|
+
context 'Octopus enabled' do
|
|
8
|
+
context 'when query cache is enabled on the primary connection_pool' do
|
|
9
|
+
before { ActiveRecord::Base.connection_pool.enable_query_cache! }
|
|
10
|
+
it { is_expected.to be true }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
context 'when query cache is disabled on the primary connection_pool' do
|
|
14
|
+
before { ActiveRecord::Base.connection_pool.disable_query_cache! }
|
|
15
|
+
it { is_expected.to be false }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context 'Octopus disabled' do
|
|
20
|
+
before do
|
|
21
|
+
Rails = double
|
|
22
|
+
allow(Rails).to receive(:env).and_return('staging')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
after do
|
|
26
|
+
Object.send(:remove_const, :Rails)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'when query cache is enabled on the primary connection_pool' do
|
|
30
|
+
before { ActiveRecord::Base.connection_pool.enable_query_cache! }
|
|
31
|
+
it { is_expected.to be true }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context 'when query cache is disabled on the primary connection_pool' do
|
|
35
|
+
before { ActiveRecord::Base.connection_pool.disable_query_cache! }
|
|
36
|
+
it { is_expected.to be false }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Octopus::RelationProxy do
|
|
4
|
+
describe 'shard tracking' do
|
|
5
|
+
before :each do
|
|
6
|
+
@client = Client.using(:canada).create!
|
|
7
|
+
@client.items << Item.using(:canada).create!
|
|
8
|
+
@relation = @client.items
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'remembers the shard on which a relation was created' do
|
|
12
|
+
expect(@relation.current_shard).to eq(:canada)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'can define collection association with the same name as ancestor private method' do
|
|
16
|
+
@client.comments << Comment.using(:canada).create!(open: true)
|
|
17
|
+
expect(@client.comments.open).to be_a_kind_of(ActiveRecord::Relation)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'can be dumped and loaded' do
|
|
21
|
+
expect(Marshal.load(Marshal.dump(@relation))).to eq @relation
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'maintains the current shard when using where.not(...)' do
|
|
25
|
+
where_chain = @relation.where
|
|
26
|
+
expect(where_chain.current_shard).to eq(@relation.current_shard)
|
|
27
|
+
not_relation = where_chain.not("1=0")
|
|
28
|
+
expect(not_relation.current_shard).to eq(@relation.current_shard)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context 'when a new relation is constructed from the original relation' do
|
|
32
|
+
context 'and a where(...) is used' do
|
|
33
|
+
it 'does not tamper with the original relation' do
|
|
34
|
+
relation = Item.using(:canada).where(id: 1)
|
|
35
|
+
original_sql = relation.to_sql
|
|
36
|
+
new_relation = relation.where(id: 2)
|
|
37
|
+
expect(relation.to_sql).to eq(original_sql)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context 'and a where.not(...) is used' do
|
|
42
|
+
it 'does not tamper with the original relation' do
|
|
43
|
+
relation = Item.using(:canada).where(id: 1)
|
|
44
|
+
original_sql = relation.to_sql
|
|
45
|
+
new_relation = relation.where.not(id: 2)
|
|
46
|
+
expect(relation.to_sql).to eq(original_sql)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context 'when comparing to other Relation objects' do
|
|
52
|
+
before :each do
|
|
53
|
+
@relation.reset
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'is equal to its clone' do
|
|
57
|
+
expect(@relation).to eq(@relation.clone)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "can deliver methods in ActiveRecord::Batches correctly when given a block" do
|
|
62
|
+
expect { @relation.find_each(&:inspect) }.not_to raise_error
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "can deliver methods in ActiveRecord::Batches correctly as an enumerator" do
|
|
66
|
+
expect { @relation.find_each.each(&:inspect) }.not_to raise_error
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "can deliver methods in ActiveRecord::Batches correctly as a lazy enumerator" do
|
|
70
|
+
expect { @relation.find_each.lazy.each(&:inspect) }.not_to raise_error
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context 'under Rails 4' do
|
|
74
|
+
it 'is an Octopus::RelationProxy' do
|
|
75
|
+
expect{@relation.ar_relation}.not_to raise_error
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'should be able to return its ActiveRecord::Relation' do
|
|
79
|
+
expect(@relation.ar_relation.is_a?(ActiveRecord::Relation)).to be true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'is equal to an identically-defined, but different, RelationProxy' do
|
|
83
|
+
i = @client.items
|
|
84
|
+
expect(@relation).to eq(i)
|
|
85
|
+
expect(@relation.__id__).not_to eq(i.__id__)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'is equal to its own underlying ActiveRecord::Relation' do
|
|
89
|
+
expect(@relation).to eq(@relation.ar_relation)
|
|
90
|
+
expect(@relation.ar_relation).to eq(@relation)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context 'when no explicit shard context is provided' do
|
|
95
|
+
it 'uses the correct shard' do
|
|
96
|
+
expect(@relation.count).to eq(1)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'lazily evaluates on the correct shard' do
|
|
100
|
+
# Do something to force Client.connection_proxy.current_shard to change
|
|
101
|
+
_some_count = Client.using(:brazil).count
|
|
102
|
+
expect(@relation.select(:client_id).count).to eq(1)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
context 'when an explicit, but different, shard context is provided' do
|
|
107
|
+
it 'uses the correct shard' do
|
|
108
|
+
expect(Item.using(:brazil).count).to eq(0)
|
|
109
|
+
_clients_on_brazil = Client.using(:brazil).all
|
|
110
|
+
Octopus.using(:brazil) do
|
|
111
|
+
expect(@relation.count).to eq(1)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'uses the correct shard in block when method_missing is triggered on CollectionProxy objects' do
|
|
116
|
+
Octopus.using(:brazil) do
|
|
117
|
+
@client.items.each do |item|
|
|
118
|
+
expect(item.current_shard).to eq(:canada)
|
|
119
|
+
expect(ActiveRecord::Base.connection.current_shard).to eq(:brazil)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it 'lazily evaluates on the correct shard' do
|
|
125
|
+
expect(Item.using(:brazil).count).to eq(0)
|
|
126
|
+
Octopus.using(:brazil) do
|
|
127
|
+
expect(@relation.select(:client_id).count).to eq(1)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'when the database is replicated and has slave groups' do
|
|
4
|
+
|
|
5
|
+
it 'should pick the slave group based on current_slave_grup when you have a replicated model' do
|
|
6
|
+
|
|
7
|
+
OctopusHelper.using_environment :replicated_slave_grouped do
|
|
8
|
+
# The following two calls of `create!` both creates cats in :master(The database `octopus_shard_1`)
|
|
9
|
+
# which is configured through RAILS_ENV and database.yml
|
|
10
|
+
Cat.create!(:name => 'Thiago1')
|
|
11
|
+
Cat.create!(:name => 'Thiago2')
|
|
12
|
+
|
|
13
|
+
# See "replicated_slave_grouped" defined in shards.yml
|
|
14
|
+
# We have:
|
|
15
|
+
# The database `octopus_shard_1` as :slave21 which is a member of the slave group :slaves2, and as :master
|
|
16
|
+
# The databse `octopus_shard_2` as :slave11 which is a member of the slave group :slaves1
|
|
17
|
+
# When a select-count query is sent to `octopus_shard_1`, it should return 2 because we have create two cats in :master .
|
|
18
|
+
# When a select-count query is sent to `octopus_shard_2`, it should return 0.
|
|
19
|
+
|
|
20
|
+
# The query goes to `octopus_shard_1`
|
|
21
|
+
expect(Cat.using(:master).count).to eq(2)
|
|
22
|
+
# The query goes to `octopus_shard_1`
|
|
23
|
+
expect(Cat.count).to eq(2)
|
|
24
|
+
# The query goes to `octopus_shard_2`
|
|
25
|
+
expect(Cat.using(:slave_group => :slaves1).count).to eq(0)
|
|
26
|
+
# The query goes to `octopus_shard_1`
|
|
27
|
+
expect(Cat.using(:slave_group => :slaves2).count).to eq(2)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'should distribute queries between slaves in a slave group in round-robin' do
|
|
32
|
+
OctopusHelper.using_environment :replicated_slave_grouped do
|
|
33
|
+
# The query goes to :master(`octopus_shard_1`)
|
|
34
|
+
Cat.create!(:name => 'Thiago1')
|
|
35
|
+
# The query goes to :master(`octopus_shard_1`)
|
|
36
|
+
Cat.create!(:name => 'Thiago2')
|
|
37
|
+
|
|
38
|
+
# The query goes to :slave32(`octopus_shard_2`)
|
|
39
|
+
expect(Cat.using(:slave_group => :slaves3).count).to eq(0)
|
|
40
|
+
# The query goes to :slave31(`octopus_shard_1`)
|
|
41
|
+
expect(Cat.using(:slave_group => :slaves3).count).to eq(2)
|
|
42
|
+
# The query goes to :slave32(`octopus_shard_2`)
|
|
43
|
+
expect(Cat.using(:slave_group => :slaves3).count).to eq(0)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'should make queries to master when slave groups are configured but not selected' do
|
|
48
|
+
OctopusHelper.using_environment :replicated_slave_grouped do
|
|
49
|
+
# All the queries go to :master(`octopus_shard_1`)
|
|
50
|
+
|
|
51
|
+
Cat.create!(:name => 'Thiago1')
|
|
52
|
+
Cat.create!(:name => 'Thiago2')
|
|
53
|
+
|
|
54
|
+
# In `database.yml` and `shards.yml`, we have configured 1 master and 4 slaves.
|
|
55
|
+
# So we can ensure Octopus is not distributing queries between them
|
|
56
|
+
# by asserting 1 + 4 = 5 queries go to :master(`octopus_shard_1`)
|
|
57
|
+
expect(Cat.count).to eq(2)
|
|
58
|
+
expect(Cat.count).to eq(2)
|
|
59
|
+
expect(Cat.count).to eq(2)
|
|
60
|
+
expect(Cat.count).to eq(2)
|
|
61
|
+
expect(Cat.count).to eq(2)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'should keep sending to slaves in a using block' do
|
|
66
|
+
OctopusHelper.using_environment :replicated_slave_grouped do
|
|
67
|
+
Cat.create!(:name => 'Thiago1')
|
|
68
|
+
Cat.create!(:name => 'Thiago2')
|
|
69
|
+
|
|
70
|
+
expect(Cat.count).to eq(2)
|
|
71
|
+
Octopus.using(:slave_group => :slaves1) do
|
|
72
|
+
expect(Cat.count).to eq(0)
|
|
73
|
+
expect(Cat.count).to eq(0)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'should restore previous slave group after a using block' do
|
|
79
|
+
OctopusHelper.using_environment :replicated_slave_grouped do
|
|
80
|
+
Cat.create!(:name => 'Thiago1')
|
|
81
|
+
Cat.create!(:name => 'Thiago2')
|
|
82
|
+
|
|
83
|
+
Octopus.using(:slave_group => :slaves1) do
|
|
84
|
+
Octopus.using(:slave_group => :slaves2) do
|
|
85
|
+
expect(Cat.count).to eq(2)
|
|
86
|
+
end
|
|
87
|
+
expect(Cat.count).to eq(0)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|