misha-ar-octopus 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +46 -0
  5. data/.rubocop_todo.yml +56 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +18 -0
  8. data/Appraisals +16 -0
  9. data/Gemfile +4 -0
  10. data/README.mkdn +242 -0
  11. data/Rakefile +172 -0
  12. data/TODO.txt +7 -0
  13. data/ar-octopus.gemspec +42 -0
  14. data/gemfiles/rails32.gemfile +7 -0
  15. data/gemfiles/rails4.gemfile +7 -0
  16. data/gemfiles/rails41.gemfile +7 -0
  17. data/gemfiles/rails42.gemfile +7 -0
  18. data/init.rb +1 -0
  19. data/lib/ar-octopus.rb +1 -0
  20. data/lib/octopus.rb +181 -0
  21. data/lib/octopus/abstract_adapter.rb +35 -0
  22. data/lib/octopus/association.rb +13 -0
  23. data/lib/octopus/association_shard_tracking.rb +114 -0
  24. data/lib/octopus/collection_association.rb +11 -0
  25. data/lib/octopus/collection_proxy.rb +16 -0
  26. data/lib/octopus/exception.rb +4 -0
  27. data/lib/octopus/has_and_belongs_to_many_association.rb +9 -0
  28. data/lib/octopus/load_balancing.rb +4 -0
  29. data/lib/octopus/load_balancing/round_robin.rb +20 -0
  30. data/lib/octopus/log_subscriber.rb +22 -0
  31. data/lib/octopus/migration.rb +168 -0
  32. data/lib/octopus/model.rb +210 -0
  33. data/lib/octopus/persistence.rb +39 -0
  34. data/lib/octopus/proxy.rb +534 -0
  35. data/lib/octopus/railtie.rb +11 -0
  36. data/lib/octopus/relation_proxy.rb +35 -0
  37. data/lib/octopus/scope_proxy.rb +61 -0
  38. data/lib/octopus/shard_tracking.rb +41 -0
  39. data/lib/octopus/shard_tracking/attribute.rb +22 -0
  40. data/lib/octopus/shard_tracking/dynamic.rb +11 -0
  41. data/lib/octopus/singular_association.rb +9 -0
  42. data/lib/octopus/slave_group.rb +13 -0
  43. data/lib/octopus/version.rb +3 -0
  44. data/lib/tasks/octopus.rake +16 -0
  45. data/rails/init.rb +1 -0
  46. data/sample_app/.gitignore +4 -0
  47. data/sample_app/.rspec +1 -0
  48. data/sample_app/Gemfile +20 -0
  49. data/sample_app/README +3 -0
  50. data/sample_app/README.rdoc +261 -0
  51. data/sample_app/Rakefile +7 -0
  52. data/sample_app/app/assets/images/rails.png +0 -0
  53. data/sample_app/app/assets/javascripts/application.js +15 -0
  54. data/sample_app/app/assets/stylesheets/application.css +13 -0
  55. data/sample_app/app/controllers/application_controller.rb +4 -0
  56. data/sample_app/app/helpers/application_helper.rb +2 -0
  57. data/sample_app/app/mailers/.gitkeep +0 -0
  58. data/sample_app/app/models/.gitkeep +0 -0
  59. data/sample_app/app/models/item.rb +3 -0
  60. data/sample_app/app/models/user.rb +3 -0
  61. data/sample_app/app/views/layouts/application.html.erb +14 -0
  62. data/sample_app/autotest/discover.rb +2 -0
  63. data/sample_app/config.ru +4 -0
  64. data/sample_app/config/application.rb +62 -0
  65. data/sample_app/config/boot.rb +6 -0
  66. data/sample_app/config/cucumber.yml +8 -0
  67. data/sample_app/config/database.yml +28 -0
  68. data/sample_app/config/environment.rb +5 -0
  69. data/sample_app/config/environments/development.rb +37 -0
  70. data/sample_app/config/environments/production.rb +67 -0
  71. data/sample_app/config/environments/test.rb +37 -0
  72. data/sample_app/config/initializers/backtrace_silencers.rb +7 -0
  73. data/sample_app/config/initializers/inflections.rb +15 -0
  74. data/sample_app/config/initializers/mime_types.rb +5 -0
  75. data/sample_app/config/initializers/secret_token.rb +7 -0
  76. data/sample_app/config/initializers/session_store.rb +8 -0
  77. data/sample_app/config/initializers/wrap_parameters.rb +14 -0
  78. data/sample_app/config/locales/en.yml +5 -0
  79. data/sample_app/config/routes.rb +58 -0
  80. data/sample_app/config/shards.yml +28 -0
  81. data/sample_app/db/migrate/20100720172715_create_users.rb +15 -0
  82. data/sample_app/db/migrate/20100720172730_create_items.rb +16 -0
  83. data/sample_app/db/migrate/20100720210335_create_sample_users.rb +11 -0
  84. data/sample_app/db/schema.rb +29 -0
  85. data/sample_app/db/seeds.rb +16 -0
  86. data/sample_app/doc/README_FOR_APP +2 -0
  87. data/sample_app/features/migrate.feature +45 -0
  88. data/sample_app/features/seed.feature +15 -0
  89. data/sample_app/features/step_definitions/seeds_steps.rb +13 -0
  90. data/sample_app/features/step_definitions/web_steps.rb +218 -0
  91. data/sample_app/features/support/database.rb +13 -0
  92. data/sample_app/features/support/env.rb +57 -0
  93. data/sample_app/features/support/paths.rb +33 -0
  94. data/sample_app/lib/assets/.gitkeep +0 -0
  95. data/sample_app/lib/tasks/.gitkeep +0 -0
  96. data/sample_app/lib/tasks/cucumber.rake +64 -0
  97. data/sample_app/log/.gitkeep +0 -0
  98. data/sample_app/public/404.html +26 -0
  99. data/sample_app/public/422.html +26 -0
  100. data/sample_app/public/500.html +26 -0
  101. data/sample_app/public/favicon.ico +0 -0
  102. data/sample_app/public/images/rails.png +0 -0
  103. data/sample_app/public/index.html +279 -0
  104. data/sample_app/public/javascripts/application.js +2 -0
  105. data/sample_app/public/javascripts/controls.js +965 -0
  106. data/sample_app/public/javascripts/dragdrop.js +974 -0
  107. data/sample_app/public/javascripts/effects.js +1123 -0
  108. data/sample_app/public/javascripts/prototype.js +4874 -0
  109. data/sample_app/public/javascripts/rails.js +118 -0
  110. data/sample_app/public/robots.txt +5 -0
  111. data/sample_app/public/stylesheets/.gitkeep +0 -0
  112. data/sample_app/script/cucumber +10 -0
  113. data/sample_app/script/rails +6 -0
  114. data/sample_app/spec/models/item_spec.rb +5 -0
  115. data/sample_app/spec/models/user_spec.rb +5 -0
  116. data/sample_app/spec/spec_helper.rb +27 -0
  117. data/sample_app/vendor/assets/javascripts/.gitkeep +0 -0
  118. data/sample_app/vendor/assets/stylesheets/.gitkeep +0 -0
  119. data/sample_app/vendor/plugins/.gitkeep +0 -0
  120. data/spec/config/shards.yml +217 -0
  121. data/spec/migrations/10_create_users_using_replication.rb +9 -0
  122. data/spec/migrations/11_add_field_in_all_slaves.rb +11 -0
  123. data/spec/migrations/12_create_users_using_block.rb +23 -0
  124. data/spec/migrations/13_create_users_using_block_and_using.rb +15 -0
  125. data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +11 -0
  126. data/spec/migrations/15_create_user_on_shards_of_default_group_with_versions.rb +9 -0
  127. data/spec/migrations/1_create_users_on_master.rb +9 -0
  128. data/spec/migrations/2_create_users_on_canada.rb +11 -0
  129. data/spec/migrations/3_create_users_on_both_shards.rb +11 -0
  130. data/spec/migrations/4_create_users_on_shards_of_a_group.rb +11 -0
  131. data/spec/migrations/5_create_users_on_multiples_groups.rb +11 -0
  132. data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +11 -0
  133. data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +11 -0
  134. data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +11 -0
  135. data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +11 -0
  136. data/spec/octopus/association_shard_tracking_spec.rb +714 -0
  137. data/spec/octopus/collection_proxy_spec.rb +16 -0
  138. data/spec/octopus/log_subscriber_spec.rb +19 -0
  139. data/spec/octopus/migration_spec.rb +115 -0
  140. data/spec/octopus/model_spec.rb +693 -0
  141. data/spec/octopus/octopus_spec.rb +123 -0
  142. data/spec/octopus/proxy_spec.rb +307 -0
  143. data/spec/octopus/relation_proxy_spec.rb +93 -0
  144. data/spec/octopus/replicated_slave_grouped_spec.rb +91 -0
  145. data/spec/octopus/replication_spec.rb +137 -0
  146. data/spec/octopus/scope_proxy_spec.rb +63 -0
  147. data/spec/octopus/sharded_replicated_slave_grouped_spec.rb +55 -0
  148. data/spec/octopus/sharded_spec.rb +33 -0
  149. data/spec/spec_helper.rb +16 -0
  150. data/spec/support/active_record/connection_adapters/modify_config_adapter.rb +15 -0
  151. data/spec/support/database_connection.rb +4 -0
  152. data/spec/support/database_models.rb +118 -0
  153. data/spec/support/octopus_helper.rb +55 -0
  154. data/spec/support/query_count.rb +17 -0
  155. data/spec/support/shared_contexts.rb +18 -0
  156. data/spec/tasks/octopus.rake_spec.rb +32 -0
  157. metadata +388 -0
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octopus::CollectionProxy do
4
+ describe 'method dispatch' do
5
+ before :each do
6
+ @client = Client.using(:canada).create!
7
+ @client.items << Item.using(:canada).create!
8
+ end
9
+
10
+ it 'computes the size of the collection without loading it' do
11
+ expect(@client.items.size).to eq(1)
12
+
13
+ expect(@client.items.loaded?).to be false
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octopus::LogSubscriber, :shards => [:canada] do
4
+ before :each do
5
+ @out = StringIO.new
6
+ @log = Logger.new(@out)
7
+ ActiveRecord::Base.logger = @log
8
+ ActiveRecord::Base.logger.level = Logger::DEBUG
9
+ end
10
+
11
+ after :each do
12
+ ActiveRecord::Base.logger = nil
13
+ end
14
+
15
+ it 'should add to the default logger the shard name the query was sent to' do
16
+ User.using(:canada).create!(:name => 'test')
17
+ expect(@out.string).to match(/Shard: canada/)
18
+ end
19
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octopus::Migration do
4
+ it 'should run just in the master shard' do
5
+ OctopusHelper.migrating_to_version 1 do
6
+ expect(User.using(:master).find_by_name('Master')).not_to be_nil
7
+ expect(User.using(:canada).find_by_name('Master')).to be_nil
8
+ end
9
+ end
10
+
11
+ it 'should run on specific shard' do
12
+ OctopusHelper.migrating_to_version 2 do
13
+ expect(User.using(:master).find_by_name('Sharding')).to be_nil
14
+ expect(User.using(:canada).find_by_name('Sharding')).not_to be_nil
15
+ end
16
+ end
17
+
18
+ it 'should run on specifieds shards' do
19
+ OctopusHelper.migrating_to_version 3 do
20
+ expect(User.using(:brazil).find_by_name('Both')).not_to be_nil
21
+ expect(User.using(:canada).find_by_name('Both')).not_to be_nil
22
+ end
23
+ end
24
+
25
+ it 'should run on specified group' do
26
+ OctopusHelper.migrating_to_version 4 do
27
+ expect(User.using(:canada).find_by_name('Group')).not_to be_nil
28
+ expect(User.using(:brazil).find_by_name('Group')).not_to be_nil
29
+ expect(User.using(:russia).find_by_name('Group')).not_to be_nil
30
+ end
31
+ end
32
+
33
+ it 'should run once per shard' do
34
+ OctopusHelper.migrating_to_version 5 do
35
+ expect(User.using(:canada).where(:name => 'MultipleGroup').size).to eq(1)
36
+ expect(User.using(:brazil).where(:name => 'MultipleGroup').size).to eq(1)
37
+ expect(User.using(:russia).where(:name => 'MultipleGroup').size).to eq(1)
38
+ end
39
+ end
40
+
41
+ it 'should create users inside block' do
42
+ OctopusHelper.migrating_to_version 12 do
43
+ expect(User.using(:brazil).where(:name => 'UsingBlock1').size).to eq(1)
44
+ expect(User.using(:brazil).where(:name => 'UsingBlock2').size).to eq(1)
45
+ expect(User.using(:canada).where(:name => 'UsingCanada').size).to eq(1)
46
+ expect(User.using(:canada).where(:name => 'UsingCanada2').size).to eq(1)
47
+ end
48
+ end
49
+
50
+ it 'should send the query to the correct shard' do
51
+ OctopusHelper.migrating_to_version 13 do
52
+ expect(User.using(:brazil).where(:name => 'Brazil').size).to eq(1)
53
+ expect(User.using(:brazil).where(:name => 'Canada').size).to eq(0)
54
+ expect(User.using(:canada).where(:name => 'Brazil').size).to eq(0)
55
+ expect(User.using(:canada).where(:name => 'Canada').size).to eq(1)
56
+ end
57
+ end
58
+
59
+ describe 'when using replication' do
60
+ it 'should run writes on master when you use replication' do
61
+ OctopusHelper.using_environment :production_replicated do
62
+ OctopusHelper.migrating_to_version 10 do
63
+ expect(Cat.find_by_name('Replication')).to be_nil
64
+ end
65
+ end
66
+ end
67
+
68
+ it 'should run in all shards, master or another shards' do
69
+ OctopusHelper.using_environment :production_replicated do
70
+ OctopusHelper.migrating_to_version 11 do
71
+ [:slave4, :slave1, :slave2, :slave3].each do |_sym|
72
+ expect(Cat.find_by_name('Slaves')).not_to be_nil
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ it 'should store the migration versions in each shard' do
80
+ class SchemaMigration < ActiveRecord::Base; end
81
+
82
+ OctopusHelper.migrating_to_version 14 do
83
+ expect(Octopus.using(:canada) { ActiveRecord::Migrator.get_all_versions }).to include(14)
84
+ expect(Octopus.using(:brazil) { ActiveRecord::Migrator.get_all_versions }).to include(14)
85
+ expect(Octopus.using(:russia) { ActiveRecord::Migrator.get_all_versions }).to include(14)
86
+ end
87
+ end
88
+
89
+ it 'should run the migrations on shards that are missing them' do
90
+ class SchemaMigration < ActiveRecord::Base; end
91
+
92
+ Octopus.using(:master) { SchemaMigration.create(:version => 14) }
93
+ Octopus.using(:canada) { SchemaMigration.create(:version => 14) }
94
+
95
+ OctopusHelper.migrating_to_version 14 do
96
+ expect(Octopus.using(:canada) { ActiveRecord::Migrator.get_all_versions }).to include(14)
97
+ expect(Octopus.using(:brazil) { ActiveRecord::Migrator.get_all_versions }).to include(14)
98
+ expect(Octopus.using(:russia) { ActiveRecord::Migrator.get_all_versions }).to include(14)
99
+ end
100
+ end
101
+
102
+ describe 'when using a default_migration_group' do
103
+ it 'should run migrations on all shards in the default_migration_group' do
104
+ OctopusHelper.using_environment :octopus_with_default_migration_group do
105
+ OctopusHelper.migrating_to_version 15 do
106
+ expect(Octopus.using(:master) { ActiveRecord::Migrator.get_all_versions }).not_to include(15)
107
+ expect(Octopus.using(:canada) { ActiveRecord::Migrator.get_all_versions }).to include(15)
108
+ expect(Octopus.using(:brazil) { ActiveRecord::Migrator.get_all_versions }).to include(15)
109
+ expect(Octopus.using(:russia) { ActiveRecord::Migrator.get_all_versions }).to include(15)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ end
@@ -0,0 +1,693 @@
1
+ require 'spec_helper'
2
+
3
+ describe Octopus::Model do
4
+ describe '#using method' do
5
+ it 'raise when Model#using receives a block' do
6
+ expect { User.using(:master) { true } }.to raise_error(Octopus::Exception, /User\.using is not allowed to receive a block/)
7
+ end
8
+
9
+ it 'should allow to send a block to the master shard' do
10
+ Octopus.using(:master) do
11
+ User.create!(:name => 'Block test')
12
+ end
13
+
14
+ expect(User.using(:master).find_by_name('Block test')).not_to be_nil
15
+ end
16
+
17
+ it 'should allow to pass a string as the shard name to a AR subclass' do
18
+ User.using('canada').create!(:name => 'Rafael Pilha')
19
+
20
+ expect(User.using('canada').find_by_name('Rafael Pilha')).not_to be_nil
21
+ end
22
+
23
+ it 'should allow comparison of a string shard name with symbol shard name' do
24
+ u = User.using('canada').create!(:name => 'Rafael Pilha')
25
+ expect(u).to eq(User.using(:canada).find_by_name('Rafael Pilha'))
26
+ end
27
+
28
+ it 'should allow comparison of a symbol shard name with string shard name' do
29
+ u = User.using(:canada).create!(:name => 'Rafael Pilha')
30
+ expect(u).to eq(User.using('canada').find_by_name('Rafael Pilha'))
31
+ end
32
+
33
+ it 'should allow to pass a string as the shard name to a block' do
34
+ Octopus.using('canada') do
35
+ User.create!(:name => 'Rafael Pilha')
36
+ end
37
+
38
+ expect(User.using('canada').find_by_name('Rafael Pilha')).not_to be_nil
39
+ end
40
+
41
+ it 'should allow selecting the shards on scope' do
42
+ User.using(:canada).create!(:name => 'oi')
43
+ expect(User.using(:canada).count).to eq(1)
44
+ expect(User.count).to eq(0)
45
+ end
46
+
47
+ it 'should allow selecting the shard using #new' do
48
+ u = User.using(:canada).new
49
+ u.name = 'Thiago'
50
+ u.save
51
+
52
+ expect(User.using(:canada).count).to eq(1)
53
+ expect(User.using(:brazil).count).to eq(0)
54
+
55
+ u1 = User.new
56
+ u1.name = 'Joaquim'
57
+ u2 = User.using(:canada).new
58
+ u2.name = 'Manuel'
59
+ u1.save
60
+ u2.save
61
+
62
+ expect(User.using(:canada).all).to eq([u, u2])
63
+ expect(User.all).to eq([u1])
64
+ end
65
+
66
+ describe 'multiple calls to the same scope' do
67
+ it 'works with nil response' do
68
+ scope = User.using(:canada)
69
+ expect(scope.count).to eq(0)
70
+ expect(scope.first).to be_nil
71
+ end
72
+
73
+ it 'works with non-nil response' do
74
+ user = User.using(:canada).create!(:name => 'oi')
75
+ scope = User.using(:canada)
76
+ expect(scope.count).to eq(1)
77
+ expect(scope.first).to eq(user)
78
+ end
79
+ end
80
+
81
+ it 'should select the correct shard' do
82
+ User.using(:canada)
83
+ User.create!(:name => 'oi')
84
+ expect(User.count).to eq(1)
85
+ end
86
+
87
+ it 'should ensure that the connection will be cleaned' do
88
+ expect(ActiveRecord::Base.connection.current_shard).to eq(:master)
89
+ expect do
90
+ Octopus.using(:canada) do
91
+ fail 'Some Exception'
92
+ end
93
+ end.to raise_error
94
+
95
+ expect(ActiveRecord::Base.connection.current_shard).to eq(:master)
96
+ end
97
+
98
+ it 'should ensure that the connection will be cleaned with custom master' do
99
+ OctopusHelper.using_environment :octopus do
100
+ Octopus.config[:master_shard] = :brazil
101
+ expect(ActiveRecord::Base.connection.current_shard).to eq(:brazil)
102
+ expect do
103
+ Octopus.using(:canada) do
104
+ fail 'Some Exception'
105
+ end
106
+ end.to raise_error
107
+
108
+ expect(ActiveRecord::Base.connection.current_shard).to eq(:brazil)
109
+ Octopus.config[:master_shard] = nil
110
+ end
111
+ end
112
+
113
+ it 'should allow creating more than one user' do
114
+ User.using(:canada).create([{ :name => 'America User 1' }, { :name => 'America User 2' }])
115
+ User.create!(:name => 'Thiago')
116
+ expect(User.using(:canada).find_by_name('America User 1')).not_to be_nil
117
+ expect(User.using(:canada).find_by_name('America User 2')).not_to be_nil
118
+ expect(User.using(:master).find_by_name('Thiago')).not_to be_nil
119
+ end
120
+
121
+ it 'should work when you have a SQLite3 shard' do
122
+ u = User.using(:sqlite_shard).create!(:name => 'Sqlite3')
123
+ expect(User.using(:sqlite_shard).where(name: 'Sqlite3').first).to eq(u)
124
+ end
125
+
126
+ it 'should clean #current_shard from proxy when using execute' do
127
+ User.using(:canada).connection.execute('select * from users limit 1;')
128
+ expect(User.connection.current_shard).to eq(:master)
129
+ end
130
+
131
+ it 'should clean #current_shard from proxy when using execute' do
132
+ OctopusHelper.using_environment :octopus do
133
+ Octopus.config[:master_shard] = :brazil
134
+ User.using(:canada).connection.execute('select * from users limit 1;')
135
+ expect(User.connection.current_shard).to eq(:brazil)
136
+ Octopus.config[:master_shard] = nil
137
+ end
138
+ end
139
+
140
+ it 'should allow scoping dynamically' do
141
+ User.using(:canada).using(:master).using(:canada).create!(:name => 'oi')
142
+ expect(User.using(:canada).using(:master).count).to eq(0)
143
+ expect(User.using(:master).using(:canada).count).to eq(1)
144
+ end
145
+
146
+ it 'should allow find inside blocks' do
147
+ @user = User.using(:brazil).create!(:name => 'Thiago')
148
+
149
+ Octopus.using(:brazil) do
150
+ expect(User.first).to eq(@user)
151
+ end
152
+
153
+ expect(User.using(:brazil).find_by_name('Thiago')).to eq(@user)
154
+ end
155
+
156
+ it 'should clean the current_shard after executing the current query' do
157
+ User.using(:canada).create!(:name => 'oi')
158
+ expect(User.count).to eq(0)
159
+ end
160
+
161
+ it 'should support both groups and alone shards' do
162
+ _u = User.using(:alone_shard).create!(:name => 'Alone')
163
+ expect(User.using(:alone_shard).count).to eq(1)
164
+ expect(User.using(:canada).count).to eq(0)
165
+ expect(User.using(:brazil).count).to eq(0)
166
+ expect(User.count).to eq(0)
167
+ end
168
+
169
+ it 'should work with named scopes' do
170
+ u = User.using(:brazil).create!(:name => 'Thiago')
171
+
172
+ expect(User.thiago.using(:brazil).first).to eq(u)
173
+ expect(User.using(:brazil).thiago.first).to eq(u)
174
+
175
+ Octopus.using(:brazil) do
176
+ expect(User.thiago.first).to eq(u)
177
+ end
178
+ end
179
+
180
+ describe '#current_shard attribute' do
181
+ it 'should store the attribute when you create or find an object' do
182
+ u = User.using(:alone_shard).create!(:name => 'Alone')
183
+ expect(u.current_shard).to eq(:alone_shard)
184
+ User.using(:canada).create!(:name => 'oi')
185
+ u = User.using(:canada).find_by_name('oi')
186
+ expect(u.current_shard).to eq(:canada)
187
+ end
188
+
189
+ it 'should store the attribute when you find multiple instances' do
190
+ 5.times { User.using(:alone_shard).create!(:name => 'Alone') }
191
+
192
+ User.using(:alone_shard).all.each do |u|
193
+ expect(u.current_shard).to eq(:alone_shard)
194
+ end
195
+ end
196
+
197
+ it 'should works when you find, and after that, alter that object' do
198
+ alone_user = User.using(:alone_shard).create!(:name => 'Alone')
199
+ _mstr_user = User.using(:master).create!(:name => 'Master')
200
+ alone_user.name = 'teste'
201
+ alone_user.save
202
+ expect(User.using(:master).first.name).to eq('Master')
203
+ expect(User.using(:alone_shard).first.name).to eq('teste')
204
+ end
205
+
206
+ it 'should work for the reload method' do
207
+ User.using(:alone_shard).create!(:name => 'Alone')
208
+ u = User.using(:alone_shard).find_by_name('Alone')
209
+ u.reload
210
+ expect(u.name).to eq('Alone')
211
+ end
212
+
213
+ it 'should work passing some arguments to reload method' do
214
+ User.using(:alone_shard).create!(:name => 'Alone')
215
+ u = User.using(:alone_shard).find_by_name('Alone')
216
+ u.reload(:lock => true)
217
+ expect(u.name).to eq('Alone')
218
+ end
219
+ end
220
+
221
+ describe 'passing a block' do
222
+ it 'should allow queries be executed inside the block, ponting to a specific shard' do
223
+ Octopus.using(:canada) do
224
+ User.create(:name => 'oi')
225
+ end
226
+
227
+ expect(User.using(:canada).count).to eq(1)
228
+ expect(User.using(:master).count).to eq(0)
229
+ expect(User.count).to eq(0)
230
+ end
231
+
232
+ it 'should allow execute queries inside a model' do
233
+ u = User.new
234
+ u.awesome_queries
235
+ expect(User.using(:canada).count).to eq(1)
236
+ expect(User.count).to eq(0)
237
+ end
238
+ end
239
+
240
+ describe 'raising errors' do
241
+ it "should raise a error when you specify a shard that doesn't exist" do
242
+ expect { User.using(:crazy_shard).create!(:name => 'Thiago') }.to raise_error('Nonexistent Shard Name: crazy_shard')
243
+ end
244
+ end
245
+
246
+ describe 'equality' do
247
+ let(:canada1) do
248
+ u = User.new
249
+ u.id = 1
250
+ u.current_shard = :canada
251
+ u
252
+ end
253
+
254
+ let(:canada1_dup) do
255
+ u = User.new
256
+ u.id = 1
257
+ u.current_shard = :canada
258
+ u
259
+ end
260
+
261
+ let(:brazil1) do
262
+ u = User.new
263
+ u.id = 1
264
+ u.current_shard = :brazil
265
+ u
266
+ end
267
+
268
+ it 'should work with persisted objects' do
269
+ u = User.using(:brazil).create(:name => 'Mike')
270
+ expect(User.using(:brazil).find_by_name('Mike')).to eq(u)
271
+ end
272
+
273
+ it 'should check current_shard when determining equality' do
274
+ expect(canada1).not_to eq(brazil1)
275
+ expect(canada1).to eq(canada1_dup)
276
+ end
277
+
278
+ it 'delegates equality check on scopes' do
279
+ u = User.using(:brazil).create!(:name => 'Mike')
280
+ expect(User.using(:brazil).where(:name => 'Mike')).to eq([u])
281
+ end
282
+ end
283
+ end
284
+
285
+ describe 'using a postgresql shard' do
286
+ it 'should update the Arel Engine' do
287
+ expect(User.using(:postgresql_shard).arel_engine.connection.adapter_name).to eq('PostgreSQL')
288
+ expect(User.using(:alone_shard).arel_engine.connection.adapter_name).to eq('Mysql2')
289
+ end
290
+
291
+ it 'should works with writes and reads' do
292
+ u = User.using(:postgresql_shard).create!(:name => 'PostgreSQL User')
293
+ expect(User.using(:postgresql_shard).all).to eq([u])
294
+ expect(User.using(:alone_shard).all).to eq([])
295
+ User.connection_handler.connection_pools['ActiveRecord::Base'] = User.connection.instance_variable_get(:@shards)[:master]
296
+ end
297
+ end
298
+
299
+ describe 'AR basic methods' do
300
+ it 'establish_connection' do
301
+ expect(CustomConnection.connection.current_database).to eq('octopus_shard_2')
302
+ end
303
+
304
+ it 'reuses parent model connection' do
305
+ klass = Class.new(CustomConnection)
306
+
307
+ expect(klass.connection).to be klass.connection
308
+ end
309
+
310
+ it 'should not mess with custom connection table names' do
311
+ expect(Advert.connection.current_database).to eq('octopus_shard_1')
312
+ Advert.create!(:name => 'Teste')
313
+ end
314
+
315
+ it 'increment' do
316
+ _ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
317
+ u = User.using(:brazil).find_by_number(10)
318
+ u.increment(:number)
319
+ u.save
320
+ expect(User.using(:brazil).find_by_number(11)).not_to be_nil
321
+ end
322
+
323
+ it 'increment!' do
324
+ _ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
325
+ u = User.using(:brazil).find_by_number(10)
326
+ u.increment!(:number)
327
+ expect(User.using(:brazil).find_by_number(11)).not_to be_nil
328
+ end
329
+
330
+ it 'decrement' do
331
+ _ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
332
+ u = User.using(:brazil).find_by_number(10)
333
+ u.decrement(:number)
334
+ u.save
335
+ expect(User.using(:brazil).find_by_number(9)).not_to be_nil
336
+ end
337
+
338
+ it 'decrement!' do
339
+ _ = User.using(:brazil).create!(:name => 'Teste', :number => 10)
340
+ u = User.using(:brazil).find_by_number(10)
341
+ u.decrement!(:number)
342
+ expect(User.using(:brazil).find_by_number(9)).not_to be_nil
343
+ end
344
+
345
+ it 'toggle' do
346
+ _ = User.using(:brazil).create!(:name => 'Teste', :admin => false)
347
+ u = User.using(:brazil).find_by_name('Teste')
348
+ u.toggle(:admin)
349
+ u.save
350
+ expect(User.using(:brazil).find_by_name('Teste').admin).to be true
351
+ end
352
+
353
+ it 'toggle!' do
354
+ _ = User.using(:brazil).create!(:name => 'Teste', :admin => false)
355
+ u = User.using(:brazil).find_by_name('Teste')
356
+ u.toggle!(:admin)
357
+ expect(User.using(:brazil).find_by_name('Teste').admin).to be true
358
+ end
359
+
360
+ it 'count' do
361
+ _u = User.using(:brazil).create!(:name => 'User1')
362
+ _v = User.using(:brazil).create!(:name => 'User2')
363
+ _w = User.using(:brazil).create!(:name => 'User3')
364
+ expect(User.using(:brazil).where(:name => 'User2').all.count).to eq(1)
365
+ end
366
+
367
+ it 'maximum' do
368
+ _u = User.using(:brazil).create!(:name => 'Teste', :number => 11)
369
+ _v = User.using(:master).create!(:name => 'Teste', :number => 12)
370
+
371
+ expect(User.using(:brazil).maximum(:number)).to eq(11)
372
+ expect(User.using(:master).maximum(:number)).to eq(12)
373
+ end
374
+
375
+ describe 'any?' do
376
+ before { User.using(:brazil).create!(:name => 'User1') }
377
+
378
+ it 'works when true' do
379
+ scope = User.using(:brazil).where(:name => 'User1')
380
+ expect(scope.any?).to be true
381
+ end
382
+
383
+ it 'works when false' do
384
+ scope = User.using(:brazil).where(:name => 'User2')
385
+ expect(scope.any?).to be false
386
+ end
387
+ end
388
+
389
+ it 'exists?' do
390
+ @user = User.using(:brazil).create!(:name => 'User1')
391
+
392
+ expect(User.using(:brazil).where(:name => 'User1').exists?).to be true
393
+ expect(User.using(:brazil).where(:name => 'User2').exists?).to be false
394
+ end
395
+
396
+ describe 'touch' do
397
+ it 'updates updated_at by default' do
398
+ @user = User.using(:brazil).create!(:name => 'User1')
399
+ User.using(:brazil).where(:id => @user.id).update_all(:updated_at => Time.now - 3.months)
400
+ @user.touch
401
+ expect(@user.reload.updated_at.in_time_zone('GMT').to_date).to eq(Time.now.in_time_zone('GMT').to_date)
402
+ end
403
+
404
+ it 'updates passed in attribute name' do
405
+ @user = User.using(:brazil).create!(:name => 'User1')
406
+ User.using(:brazil).where(:id => @user.id).update_all(:created_at => Time.now - 3.months)
407
+ @user.touch(:created_at)
408
+ expect(@user.reload.created_at.in_time_zone('GMT').to_date).to eq(Time.now.in_time_zone('GMT').to_date)
409
+ end
410
+ end
411
+
412
+ describe '#pluck' do
413
+ before { User.using(:brazil).create!(:name => 'User1') }
414
+
415
+ it 'should works from scope proxy' do
416
+ names = User.using(:brazil).pluck(:name)
417
+ expect(names).to eq(['User1'])
418
+ expect(User.using(:master).pluck(:name)).to eq([])
419
+ end
420
+ end
421
+
422
+ it 'update_column' do
423
+ @user = User.using(:brazil).create!(:name => 'User1')
424
+ @user2 = User.using(:brazil).find(@user.id)
425
+ @user2.update_column(:name, 'Joaquim Shard Brazil')
426
+ expect(User.using(:brazil).find_by_name('Joaquim Shard Brazil')).not_to be_nil
427
+ end
428
+
429
+ it 'update_attributes' do
430
+ @user = User.using(:brazil).create!(:name => 'User1')
431
+ @user2 = User.using(:brazil).find(@user.id)
432
+ @user2.update_attributes(:name => 'Joaquim')
433
+ expect(User.using(:brazil).find_by_name('Joaquim')).not_to be_nil
434
+ end
435
+
436
+ it 'using update_attributes inside a block' do
437
+ Octopus.using(:brazil) do
438
+ @user = User.create!(:name => 'User1')
439
+ @user2 = User.find(@user.id)
440
+ @user2.update_attributes(:name => 'Joaquim')
441
+ end
442
+
443
+ expect(User.find_by_name('Joaquim')).to be_nil
444
+ expect(User.using(:brazil).find_by_name('Joaquim')).not_to be_nil
445
+ end
446
+
447
+ it 'update_attribute' do
448
+ @user = User.using(:brazil).create!(:name => 'User1')
449
+ @user2 = User.using(:brazil).find(@user.id)
450
+ @user2.update_attribute(:name, 'Joaquim')
451
+ expect(User.using(:brazil).find_by_name('Joaquim')).not_to be_nil
452
+ end
453
+
454
+ it 'as_json' do
455
+ ActiveRecord::Base.include_root_in_json = false
456
+
457
+ Octopus.using(:brazil) do
458
+ User.create!(:name => 'User1')
459
+ end
460
+
461
+ user = User.using(:brazil).where(:name => 'User1').first
462
+ expect(user.as_json(:except => [:created_at, :updated_at, :id])).to eq('admin' => nil, 'name' => 'User1', 'number' => nil)
463
+ end
464
+
465
+ it 'transaction' do
466
+ _u = User.create!(:name => 'Thiago')
467
+
468
+ expect(User.using(:brazil).count).to eq(0)
469
+ expect(User.using(:master).count).to eq(1)
470
+
471
+ User.using(:brazil).transaction do
472
+ expect(User.find_by_name('Thiago')).to be_nil
473
+ User.create!(:name => 'Brazil')
474
+ end
475
+
476
+ expect(User.using(:brazil).count).to eq(1)
477
+ expect(User.using(:master).count).to eq(1)
478
+ end
479
+
480
+ describe 'deleting a record' do
481
+ before(:each) do
482
+ @user = User.using(:brazil).create!(:name => 'User1')
483
+ @user2 = User.using(:brazil).find(@user.id)
484
+ end
485
+
486
+ it 'delete' do
487
+ @user2.delete
488
+ expect { User.using(:brazil).find(@user2.id) }.to raise_error(ActiveRecord::RecordNotFound)
489
+ end
490
+
491
+ it "delete within block shouldn't lose shard" do
492
+ Octopus.using(:brazil) do
493
+ @user2.delete
494
+ @user3 = User.create(:name => 'User3')
495
+
496
+ expect(User.connection.current_shard).to eq(:brazil)
497
+ expect(User.find(@user3.id)).to eq(@user3)
498
+ end
499
+ end
500
+
501
+ it 'destroy' do
502
+ @user2.destroy
503
+ expect { User.using(:brazil).find(@user2.id) }.to raise_error(ActiveRecord::RecordNotFound)
504
+ end
505
+
506
+ it "destroy within block shouldn't lose shard" do
507
+ Octopus.using(:brazil) do
508
+ @user2.destroy
509
+ @user3 = User.create(:name => 'User3')
510
+
511
+ expect(User.connection.current_shard).to eq(:brazil)
512
+ expect(User.find(@user3.id)).to eq(@user3)
513
+ end
514
+ end
515
+ end
516
+ end
517
+
518
+ describe 'custom connection' do
519
+ context 'by default' do
520
+ it 'with plain call should use custom connection' do
521
+ expect(CustomConnection.connection.current_database).to eq('octopus_shard_2')
522
+ end
523
+
524
+ it 'should ignore using called on relation' do
525
+ expect(CustomConnection.using(:postgresql_shard).connection.current_database).to eq('octopus_shard_2')
526
+ end
527
+
528
+ it 'should ignore Octopus.using block' do
529
+ Octopus.using(:postgresql_shard) do
530
+ expect(CustomConnection.connection.current_database).to eq('octopus_shard_2')
531
+ end
532
+ end
533
+
534
+ it 'should save to correct shard' do
535
+ expect { CustomConnection.create(:value => 'custom value') }.to change {
536
+ CustomConnection
537
+ .connection
538
+ .execute("select count(*) as ct from custom where value = 'custom value'")
539
+ .to_a.first.first
540
+ }.by 1
541
+ end
542
+ end
543
+
544
+ context 'with allowed_shards configured' do
545
+ before do
546
+ CustomConnection.allow_shard :postgresql_shard
547
+ end
548
+
549
+ it 'with plain call should use custom connection' do
550
+ expect(CustomConnection.connection.current_database).to eq('octopus_shard_2')
551
+ end
552
+
553
+ it 'with using called on relation with allowed shard should use' do
554
+ expect(CustomConnection.using(:postgresql_shard).connection.current_database).to eq('octopus_shard_1')
555
+ end
556
+
557
+ it 'within Octopus.using block with allowed shard should use' do
558
+ Octopus.using(:postgresql_shard) do
559
+ expect(CustomConnection.connection.current_database).to eq('octopus_shard_1')
560
+ end
561
+ end
562
+
563
+ it 'with using called on relation with disallowed shard should not use' do
564
+ expect(CustomConnection.using(:brazil).connection.current_database).to eq('octopus_shard_2')
565
+ end
566
+
567
+ it 'within Octopus.using block with disallowed shard should not use' do
568
+ Octopus.using(:brazil) do
569
+ expect(CustomConnection.connection.current_database).to eq('octopus_shard_2')
570
+ end
571
+ end
572
+
573
+ it 'should save to correct shard' do
574
+ expect { CustomConnection.create(:value => 'custom value') }.to change {
575
+ CustomConnection
576
+ .connection
577
+ .execute("select count(*) as ct from custom where value = 'custom value'")
578
+ .to_a.first.first
579
+ }.by 1
580
+ end
581
+
582
+ it 'should clean up correctly' do
583
+ User.create!(:name => 'CleanUser')
584
+ CustomConnection.using(:postgresql_shard).first
585
+ expect(User.first).not_to be_nil
586
+ end
587
+
588
+ it 'should clean up correctly even inside block' do
589
+ User.create!(:name => 'CleanUser')
590
+
591
+ Octopus.using(:master) do
592
+ CustomConnection.using(:postgresql_shard).connection.execute('select count(*) from users')
593
+ expect(User.first).not_to be_nil
594
+ end
595
+ end
596
+ end
597
+
598
+ describe 'clear_active_connections!' do
599
+ it 'should not leak connection' do
600
+ CustomConnection.create(:value => 'custom value')
601
+
602
+ # This is what Rails, Sidekiq etc call--this normally handles all connection pools in the app
603
+ expect { ActiveRecord::Base.clear_active_connections! }
604
+ .to change { CustomConnection.connection_pool.active_connection? }
605
+
606
+ expect(CustomConnection.connection_pool.active_connection?).to be_falsey
607
+ end
608
+ end
609
+ end
610
+
611
+ describe 'when using set_table_name' do
612
+ it 'should work correctly' do
613
+ Bacon.using(:brazil).create!(:name => 'YUMMMYYYY')
614
+ end
615
+
616
+ it 'should work correctly with a block' do
617
+ Cheese.using(:brazil).create!(:name => 'YUMMMYYYY')
618
+ end
619
+ end
620
+
621
+ describe 'when using table_name=' do
622
+ it 'should work correctly' do
623
+ Ham.using(:brazil).create!(:name => 'YUMMMYYYY')
624
+ end
625
+ end
626
+
627
+ describe 'when using a environment with a single adapter' do
628
+ it 'should not clean the table name' do
629
+ OctopusHelper.using_environment :production_fully_replicated do
630
+ expect(Keyboard).not_to receive(:reset_table_name)
631
+ Keyboard.using(:master).create!(:name => 'Master Cat')
632
+ end
633
+ end
634
+ end
635
+
636
+ describe 'when you have joins/include' do
637
+ before(:each) do
638
+ @client1 = Client.using(:brazil).create(:name => 'Thiago')
639
+
640
+ Octopus.using(:canada) do
641
+ @client2 = Client.create(:name => 'Mike')
642
+ @client3 = Client.create(:name => 'Joao')
643
+ @item1 = Item.create(:client => @client2, :name => 'Item 1')
644
+ @item2 = Item.create(:client => @client2, :name => 'Item 2')
645
+ @item3 = Item.create(:client => @client3, :name => 'Item 3')
646
+ @part1 = Part.create(:item => @item1, :name => 'Part 1')
647
+ @part2 = Part.create(:item => @item1, :name => 'Part 2')
648
+ @part3 = Part.create(:item => @item2, :name => 'Part 3')
649
+ end
650
+
651
+ @item4 = Item.using(:brazil).create(:client => @client1, :name => 'Item 4')
652
+ end
653
+
654
+ it 'should work using the rails 3.x syntax' do
655
+ items = Item.using(:canada).joins(:client).where("clients.id = #{@client2.id}").all
656
+ expect(items).to eq([@item1, @item2])
657
+ end
658
+
659
+ it 'should work for include also, rails 3.x syntax' do
660
+ items = Item.using(:canada).includes(:client).where(:clients => { :id => @client2.id }).all
661
+ expect(items).to eq([@item1, @item2])
662
+ end
663
+ end
664
+
665
+ describe 'ActiveRecord::Base Validations' do
666
+ it 'should work correctly when using validations' do
667
+ @key = Keyboard.create!(:name => 'Key')
668
+ expect { Keyboard.using(:brazil).create!(:name => 'Key') }.not_to raise_error
669
+ expect { Keyboard.create!(:name => 'Key') }.to raise_error
670
+ end
671
+
672
+ it 'should work correctly when using validations with using syntax' do
673
+ @key = Keyboard.using(:brazil).create!(:name => 'Key')
674
+ expect { Keyboard.create!(:name => 'Key') }.not_to raise_error
675
+ expect { Keyboard.using(:brazil).create!(:name => 'Key') }.to raise_error
676
+ end
677
+ end
678
+
679
+ describe '#replicated_model method' do
680
+ it 'should be replicated' do
681
+ OctopusHelper.using_environment :production_replicated do
682
+ expect(ActiveRecord::Base.connection_proxy.instance_variable_get(:@replicated)).to be true
683
+ end
684
+ end
685
+
686
+ it 'should mark the Cat model as replicated' do
687
+ OctopusHelper.using_environment :production_replicated do
688
+ expect(User.replicated).to be_falsey
689
+ expect(Cat.replicated).to be true
690
+ end
691
+ end
692
+ end
693
+ end