switchman 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +30 -0
  3. data/app/models/switchman/shard.rb +502 -0
  4. data/db/migrate/20130328212039_create_switchman_shards.rb +9 -0
  5. data/db/migrate/20130328224244_create_default_shard.rb +9 -0
  6. data/lib/switchman.rb +9 -0
  7. data/lib/switchman/active_record/abstract_adapter.rb +11 -0
  8. data/lib/switchman/active_record/association.rb +108 -0
  9. data/lib/switchman/active_record/attribute_methods.rb +104 -0
  10. data/lib/switchman/active_record/base.rb +95 -0
  11. data/lib/switchman/active_record/calculations.rb +63 -0
  12. data/lib/switchman/active_record/connection_handler.rb +147 -0
  13. data/lib/switchman/active_record/connection_pool.rb +117 -0
  14. data/lib/switchman/active_record/finder_methods.rb +25 -0
  15. data/lib/switchman/active_record/log_subscriber.rb +43 -0
  16. data/lib/switchman/active_record/postgresql_adapter.rb +13 -0
  17. data/lib/switchman/active_record/query_cache.rb +12 -0
  18. data/lib/switchman/active_record/query_methods.rb +184 -0
  19. data/lib/switchman/active_record/relation.rb +69 -0
  20. data/lib/switchman/cache_extensions.rb +12 -0
  21. data/lib/switchman/connection_pool_proxy.rb +62 -0
  22. data/lib/switchman/database_server.rb +197 -0
  23. data/lib/switchman/default_shard.rb +28 -0
  24. data/lib/switchman/engine.rb +91 -0
  25. data/lib/switchman/r_spec_helper.rb +124 -0
  26. data/lib/switchman/shackles.rb +34 -0
  27. data/lib/switchman/test_helper.rb +65 -0
  28. data/lib/switchman/version.rb +3 -0
  29. data/spec/dummy/Rakefile +7 -0
  30. data/spec/dummy/app/models/appendage.rb +24 -0
  31. data/spec/dummy/app/models/digit.rb +9 -0
  32. data/spec/dummy/app/models/feature.rb +5 -0
  33. data/spec/dummy/app/models/mirror_user.rb +5 -0
  34. data/spec/dummy/app/models/user.rb +23 -0
  35. data/spec/dummy/config.ru +4 -0
  36. data/spec/dummy/config/application.rb +59 -0
  37. data/spec/dummy/config/boot.rb +10 -0
  38. data/spec/dummy/config/database.yml +17 -0
  39. data/spec/dummy/config/database.yml.example +25 -0
  40. data/spec/dummy/config/environment.rb +5 -0
  41. data/spec/dummy/config/environments/development.rb +37 -0
  42. data/spec/dummy/config/environments/production.rb +67 -0
  43. data/spec/dummy/config/environments/test.rb +37 -0
  44. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  45. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  46. data/spec/dummy/config/initializers/session_store.rb +8 -0
  47. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  48. data/spec/dummy/config/routes.rb +8 -0
  49. data/spec/dummy/db/migrate/20130403132607_create_users.rb +10 -0
  50. data/spec/dummy/db/migrate/20130411202442_create_appendages.rb +10 -0
  51. data/spec/dummy/db/migrate/20130411202551_create_mirror_users.rb +9 -0
  52. data/spec/dummy/db/migrate/20131022202028_create_digits.rb +10 -0
  53. data/spec/dummy/db/migrate/20131206172923_create_features.rb +12 -0
  54. data/spec/dummy/db/schema.rb +57 -0
  55. data/spec/dummy/log/development.log +504 -0
  56. data/spec/dummy/log/test.log +29907 -0
  57. data/spec/dummy/script/rails +6 -0
  58. data/spec/dummy/tmp/cache/2E2/830/shard%2F2 +0 -0
  59. data/spec/dummy/tmp/cache/2E3/840/shard%2F3 +0 -0
  60. data/spec/dummy/tmp/cache/313/970/shard%2F30 +0 -0
  61. data/spec/dummy/tmp/cache/314/980/shard%2F31 +0 -0
  62. data/spec/dummy/tmp/cache/316/980/shard%2F15 +1 -0
  63. data/spec/dummy/tmp/cache/316/9D0/shard%2F60 +0 -0
  64. data/spec/dummy/tmp/cache/317/990/shard%2F16 +0 -0
  65. data/spec/dummy/tmp/cache/317/9C0/shard%2F43 +1 -0
  66. data/spec/dummy/tmp/cache/317/9E0/shard%2F61 +0 -0
  67. data/spec/dummy/tmp/cache/318/9A0/shard%2F17 +0 -0
  68. data/spec/dummy/tmp/cache/318/9D0/shard%2F44 +0 -0
  69. data/spec/dummy/tmp/cache/318/9F0/shard%2F62 +1 -0
  70. data/spec/dummy/tmp/cache/319/9E0/shard%2F45 +0 -0
  71. data/spec/dummy/tmp/cache/319/9F0/shard%2F54 +1 -0
  72. data/spec/dummy/tmp/cache/319/A10/shard%2F72 +1 -0
  73. data/spec/dummy/tmp/cache/319/A30/shard%2F90 +0 -0
  74. data/spec/dummy/tmp/cache/31B/9E0/shard%2F29 +1 -0
  75. data/spec/dummy/tmp/cache/321/AA0/shard%2F89 +0 -0
  76. data/spec/dummy/tmp/cache/322/AC0/shard%2F99 +1 -0
  77. data/spec/dummy/tmp/cache/344/D70/shard%2F103 +1 -0
  78. data/spec/dummy/tmp/cache/345/D80/shard%2F104 +0 -0
  79. data/spec/dummy/tmp/cache/345/DB0/shard%2F131 +1 -0
  80. data/spec/dummy/tmp/cache/345/DC0/shard%2F140 +0 -0
  81. data/spec/dummy/tmp/cache/346/D90/shard%2F105 +0 -0
  82. data/spec/dummy/tmp/cache/346/DB0/shard%2F123 +0 -0
  83. data/spec/dummy/tmp/cache/346/DD0/shard%2F222 +1 -0
  84. data/spec/dummy/tmp/cache/346/DE0/shard%2F150 +0 -0
  85. data/spec/dummy/tmp/cache/346/DF0/shard%2F240 +1 -0
  86. data/spec/dummy/tmp/cache/347/DA0/shard%2F106 +1 -0
  87. data/spec/dummy/tmp/cache/347/DC0/shard%2F124 +0 -0
  88. data/spec/dummy/tmp/cache/347/DC0/shard%2F205 +1 -0
  89. data/spec/dummy/tmp/cache/347/E10/shard%2F250 +1 -0
  90. data/spec/dummy/tmp/cache/348/DF0/shard%2F143 +1 -0
  91. data/spec/dummy/tmp/cache/348/DF0/shard%2F224 +1 -0
  92. data/spec/dummy/tmp/cache/348/E10/shard%2F161 +1 -0
  93. data/spec/dummy/tmp/cache/349/DD0/shard%2F117 +1 -0
  94. data/spec/dummy/tmp/cache/349/E40/shard%2F180 +1 -0
  95. data/spec/dummy/tmp/cache/34A/DF0/shard%2F127 +1 -0
  96. data/spec/dummy/tmp/cache/34A/DF0/shard%2F208 +1 -0
  97. data/spec/dummy/tmp/cache/34A/E10/shard%2F145 +1 -0
  98. data/spec/dummy/tmp/cache/34A/E60/shard%2F190 +1 -0
  99. data/spec/dummy/tmp/cache/34B/E30/shard%2F155 +1 -0
  100. data/spec/dummy/tmp/cache/34D/E30/shard%2F139 +0 -0
  101. data/spec/dummy/tmp/cache/34E/E50/shard%2F149 +0 -0
  102. data/spec/dummy/tmp/cache/353/EF0/shard%2F199 +1 -0
  103. data/spec/dummy/tmp/cache/3A4/E90/shard%2F10003 +1 -0
  104. data/spec/dummy/tmp/cache/3A5/ED0/shard%2F10031 +1 -0
  105. data/spec/dummy/tmp/cache/3A9/EF0/shard%2F10017 +1 -0
  106. data/spec/lib/active_record/association_spec.rb +305 -0
  107. data/spec/lib/active_record/attribute_methods_spec.rb +108 -0
  108. data/spec/lib/active_record/base_spec.rb +66 -0
  109. data/spec/lib/active_record/calculations_spec.rb +119 -0
  110. data/spec/lib/active_record/connection_handler_spec.rb +45 -0
  111. data/spec/lib/active_record/connection_pool_spec.rb +23 -0
  112. data/spec/lib/active_record/finder_methods_spec.rb +29 -0
  113. data/spec/lib/active_record/query_cache_spec.rb +20 -0
  114. data/spec/lib/active_record/query_methods_spec.rb +130 -0
  115. data/spec/lib/active_record/relation_spec.rb +38 -0
  116. data/spec/lib/cache_extensions_spec.rb +27 -0
  117. data/spec/lib/connection_pool_proxy_spec.rb +13 -0
  118. data/spec/lib/database_server_spec.rb +154 -0
  119. data/spec/lib/shackles_spec.rb +147 -0
  120. data/spec/models/shard_spec.rb +382 -0
  121. data/spec/spec_helper.rb +32 -0
  122. metadata +344 -0
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1387233834.229409: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1387233783.55399: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376347234.081155: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376347205.332012: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376346079.7480378: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1387233860.152838: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376346810.801615: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376347125.1334531: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376347176.218136: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376345790.522966: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376346962.726431: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376346876.272081: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376346876.201909: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376347007.3382888: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376346962.848526: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376346920.458379: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376346079.693532: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376347153.642796: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376345698.2460349: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376346920.485046: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376345721.1751559: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376347205.233504: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376346810.651356: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376347007.318975: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1376345708.931933: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1384546201.9292722: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1387233783.433878: @value" :nil
@@ -0,0 +1 @@
1
+ o: ActiveSupport::Cache::Entry :@compressedF:@expires_in0:@created_atf1384546433.2994628: @value" :nil
@@ -0,0 +1,305 @@
1
+ require "spec_helper"
2
+
3
+ module Switchman
4
+ module ActiveRecord
5
+ describe Association do
6
+ include RSpecHelper
7
+
8
+ before do
9
+ @shard1.activate do
10
+ @user1 = User.create!
11
+ end
12
+ @shard2.activate do
13
+ @user2 = User.create!
14
+ end
15
+ end
16
+
17
+ it "should associate built objects with parent shard" do
18
+ a1 = @user1.appendages.build
19
+ a1.shard.should == @shard1
20
+ end
21
+
22
+ it "should associate created objects with parent shard" do
23
+ a1 = @user1.appendages.create!
24
+ a1.shard.should == @shard1
25
+ end
26
+
27
+ it "should set shard value to parent for association scope" do
28
+ scope = @user1.appendages.scoped
29
+ scope.shard_value.should == @user1
30
+ scope.shard_source_value.should == :association
31
+ end
32
+
33
+ it "should find by id through association" do
34
+ a1 = @user1.appendages.create!
35
+
36
+ @user1.appendages.find(a1.id).should == a1
37
+ lambda { @user2.appendages.find(a1.id) }.should raise_exception(::ActiveRecord::RecordNotFound)
38
+ end
39
+
40
+ describe "transaction" do
41
+ it "should activate the owner's shard and start the transaction on that shard" do
42
+ @user1.appendages.transaction(:requires_new => true) do
43
+ Shard.current.should == @shard1
44
+ User.connection.open_transactions.should == 2
45
+ end
46
+ end
47
+ end
48
+
49
+ it "should get the record size" do
50
+ a1 = @user1.appendages.create!
51
+ a2 = @user1.appendages.build
52
+ @user1.appendages.size.should == 2
53
+ @user1.reload
54
+ @user1.appendages.size.should == 1
55
+ end
56
+
57
+ it "should reverse the association" do
58
+ a1 = @user1.appendages.create!
59
+ a1.reload
60
+ a1.user.shard.should == @shard1
61
+ a1.user.should == @user1
62
+ end
63
+
64
+ it "should work with has_many through associations" do
65
+ a1 = @user1.appendages.create!
66
+ d1 = a1.digits.create!
67
+ d1.shard.should == @shard1
68
+
69
+ @user1.digits.scoped.shard_value.should == @user1
70
+ @user1.digits.find(d1.id).should == d1
71
+ end
72
+
73
+ it "shard should be changeable, and change conditions when it is changed" do
74
+ a1 = @user1.appendages.create!
75
+ relation = @user1.appendages.where(:id => a1).shard(@shard1)
76
+ relation.shard_value.should == @shard1
77
+ relation.shard_source_value.should == :explicit
78
+ relation.where_values.detect{|v| v.left.name == "id"}.right.should == a1.local_id
79
+
80
+ relation = @user1.appendages.where(:id => a1).shard(@shard2)
81
+ relation.shard_value.should == @shard2
82
+ relation.shard_source_value.should == :explicit
83
+ relation.where_values.detect{|v| v.left.name == "id"}.right.should == a1.global_id
84
+ end
85
+
86
+ it "should transpose predicates correctly" do
87
+ a1 = @user1.appendages.create!
88
+ a2 = @user2.appendages.create!
89
+
90
+ relation = @user1.appendages.where(:id => a2)
91
+ relation.shard_value.should == @user1
92
+ relation.where_values.detect{|v| v.left.name == "id"}.right.should == a2.global_id
93
+
94
+ relation = @user1.appendages.where(:id => [a1, a2])
95
+ relation.shard_value.should == @user1
96
+ relation.where_values.detect{|v| v.left.name == "id"}.right.should == [a1.local_id, a2.global_id]
97
+ end
98
+
99
+ describe "multishard associations" do
100
+ it "should group has_many associations over associated_shards" do
101
+ @shard1.activate{ Appendage.create!(:user_id => @user1, :value => 1) }
102
+ @shard2.activate{ Appendage.create!(:user => @user1, :value => 2) }
103
+
104
+ @user1.appendages.to_a.map(&:value).should == [1]
105
+
106
+ @user1.reload
107
+ @user1.associated_shards = [@shard1, @shard2]
108
+ @user1.appendages.to_a.map(&:value).sort.should == [1, 2]
109
+ end
110
+
111
+ it "follow shards for has_many :through" do
112
+ @shard1.activate{ a1 = Appendage.create!(:user_id => @user1); a1.digits.create!(:value => 1) }
113
+ @shard2.activate{ a2 = Appendage.create!(:user_id => @user1); a2.digits.create!(:value => 2) }
114
+
115
+ @user1.digits.to_a.map(&:value).should == [1]
116
+
117
+ @user1.reload
118
+ @user1.associated_shards = [@shard1, @shard2]
119
+ @user1.digits.to_a.map(&:value).sort.should == [1, 2]
120
+ end
121
+
122
+ it "should include the shard in scopes created by associations" do
123
+ @user1.associated_shards = [@shard1, @shard2]
124
+
125
+ @shard1.activate{ Appendage.create!(:user_id => @user1, :value => 1) }
126
+ @shard2.activate{ Appendage.create!(:user => @user1) }
127
+
128
+ @user1.appendages.has_no_value.to_a.count.should == 1
129
+
130
+ @user1.reload
131
+ @shard2.activate {@user1.appendages.has_no_value.to_a.count.should == 1}
132
+ end
133
+
134
+ it "should include the shard in scopes created by has_many :through associations" do
135
+ @user1.associated_shards = [@shard1, @shard2]
136
+
137
+ @shard1.activate{ a1 = Appendage.create!(:user_id => @user1); a1.digits.create! }
138
+ @shard2.activate{ a2 = Appendage.create!(:user_id => @user1); a2.digits.create!(:value => 2) }
139
+
140
+ @user1.digits.has_no_value.count.should == 1
141
+
142
+ @user1.reload
143
+ @shard2.activate {@user1.digits.has_no_value.to_a.count.should == 1}
144
+ end
145
+
146
+ it "should work with calculations in scopes created by associations" do
147
+ @user1.associated_shards = [@shard1, @shard2]
148
+
149
+ @shard1.activate{ Appendage.create!(:user_id => @user1, :value => 1) }
150
+ @shard2.activate{ Appendage.create!(:user => @user1); @user1.appendages.create!(:value => 2) }
151
+
152
+ @user1.reload
153
+ @user1.appendages.has_value.sum(:value).should == 3
154
+
155
+ @user1.reload
156
+ @shard2.activate {@user1.appendages.has_value.sum(:value).should == 3}
157
+ end
158
+
159
+ it "should work with calculations in scopes created by has_many :through associations" do
160
+ @user1.associated_shards = [@shard1, @shard2]
161
+ @shard1.activate{ a1 = Appendage.create!(:user_id => @user1); a1.digits.create!; a1.digits.create!(:value => 1) }
162
+ @shard2.activate{ a2 = Appendage.create!(:user_id => @user1); a2.digits.create!(:value => 2) }
163
+
164
+ @user1.digits.has_value.sum(:value).should == 3
165
+ @user1.reload
166
+ @shard2.activate {@user1.digits.has_value.sum(:value).should == 3}
167
+ end
168
+
169
+ it "should be able to explicitly set the shard and still work with named scopes" do
170
+ @user1.associated_shards = [@shard1, @shard2]
171
+
172
+ @shard1.activate{ a1 = Appendage.create!(:user_id => @user1); a1.digits.create! }
173
+ @shard2.activate{ a2 = Appendage.create!(:user_id => @user1); a2.digits.create!(:value => 2) }
174
+
175
+ @user1.digits.shard(@shard1).has_no_value.to_a.count.should == 1
176
+ @user1.digits.shard(@shard2).has_no_value.to_a.count.should == 0
177
+
178
+ @user1.reload
179
+
180
+ @user1.digits.has_no_value.shard(@shard1).to_a.count.should == 1
181
+ @user1.digits.has_no_value.shard(@shard2).to_a.count.should == 0
182
+ end
183
+
184
+ describe "preloading" do
185
+ it "should preload belongs_to associations across shards" do
186
+ a1 = Appendage.create!(:user => @user1)
187
+ a2 = Appendage.create!(:user => @user2)
188
+ user3 = User.create!
189
+ user3.appendages.create!
190
+
191
+ appendages = Appendage.all(:include => :user)
192
+ appendages2 = Appendage.includes(:user).all
193
+ @user1.delete
194
+
195
+ appendages.map(&:user).sort.should == [@user1, @user2, user3].sort
196
+ appendages2.map(&:user).sort.should == [@user1, @user2, user3].sort
197
+ end
198
+
199
+ it "should preload belongs_to :through associations across shards" do
200
+ a1 = Appendage.create!(:user => @user1)
201
+ d1 = a1.digits.create!
202
+
203
+ a2 = @shard1.activate {Appendage.create!(:user => @user2) }
204
+ d2 = Digit.create!(:appendage => a2)
205
+
206
+ digits = Digit.includes(:user).all
207
+ @user1.delete
208
+
209
+ digits.map(&:user).sort.should == [@user1, @user2].sort
210
+ end
211
+
212
+ it "should preload has_many associations across associated shards" do
213
+ a1 = @user1.appendages.create!
214
+ a2 = @shard2.activate { Appendage.create!(:user_id => @user1) } # a2 will be in @user1's associated shards
215
+ a3 = @shard1.activate { Appendage.create!(:user_id => @user2) } # a3 is not on @user2's associated shard
216
+
217
+ User.associated_shards_map = { @user1.global_id => [@shard1, @shard2] }
218
+
219
+ begin
220
+ users = User.where(:id => [@user1, @user2]).includes(:appendages).all
221
+ users.each {|u| u.appendages.loaded?.should be_true}
222
+
223
+ u1 = users.detect {|u| u.id == @user1.id}
224
+ u2 = users.detect {|u| u.id == @user2.id}
225
+
226
+ a1.delete
227
+ u1.appendages.sort.should == [a1, a2].sort
228
+ u2.appendages.should be_empty
229
+ ensure
230
+ User.associated_shards_map = nil
231
+ end
232
+ end
233
+
234
+ it "should preload has_many :through associations across associated shards" do
235
+ a1 = @user1.appendages.create!
236
+ a2 = @shard2.activate { Appendage.create!(:user_id => @user1) }
237
+ a3 = @shard2.activate { Appendage.create!(:user_id => @user1) }
238
+
239
+ d1 = a1.digits.create!
240
+ d2 = a2.digits.create! # a2 will be in @user1's associated shards
241
+ d3 = @shard1.activate { Digit.create!(:appendage_id => a2) } # d3 will be in a2's associated shards
242
+ d4 = @shard1.activate { Digit.create!(:appendage_id => a3) } # d4 is not on a3's shard
243
+
244
+ a4 = @shard1.activate { Appendage.create!(:user_id => @user2) }
245
+ a5 = @user2.appendages.create!
246
+ a6 = @user2.appendages.create!
247
+
248
+ d5 = @shard2.activate { Digit.create!(:appendage_id => a4) } # d5 is on @user2's shard but a4 is not
249
+ d6 = @shard1.activate { Digit.create!(:appendage_id => a5) } # a5 is on @user2's shard but d6 is not
250
+ d7 = @shard1.activate { Digit.create!(:appendage_id => a6) } # d7 will be in a6's associated shards
251
+
252
+ User.associated_shards_map = { @user1.global_id => [@shard1, @shard2] }
253
+ Appendage.associated_shards_map = { a2.global_id => [@shard1, @shard2], a6.global_id => [@shard1] }
254
+
255
+ begin
256
+ users = User.where(:id => [@user1, @user2]).includes(:digits).all
257
+ users.each {|u| u.digits.loaded?.should be_true}
258
+
259
+ u1 = users.detect {|u| u.id == @user1.id}
260
+ u2 = users.detect {|u| u.id == @user2.id}
261
+
262
+ d1.delete
263
+
264
+ u1.digits.sort.should == [d1, d2, d3].sort
265
+ u2.digits.should == [d7]
266
+ ensure
267
+ User.associated_shards_map = nil
268
+ Appendage.associated_shards_map = nil
269
+ end
270
+ end
271
+ end
272
+
273
+ describe "polymorphic associations" do
274
+ it "should work normally" do
275
+ appendage = Appendage.create!
276
+ feature = Feature.create!(:owner => appendage)
277
+
278
+ feature.reload
279
+ feature.owner.should == appendage
280
+ feature.owner_id.should == appendage.id
281
+ feature.owner_type.should == "Appendage"
282
+
283
+ feature.owner = @user1
284
+ feature.save!
285
+
286
+ feature.reload
287
+ feature.owner_id.should == @user1.global_id
288
+ feature.owner_type.should == "User"
289
+ end
290
+
291
+ it "should work with multi-shard associations" do
292
+ @shard1.activate{ Feature.create!(:owner => @user1, :value => 1) }
293
+ @shard2.activate{ Feature.create!(:owner => @user1, :value => 2) }
294
+
295
+ @user1.features.to_a.map(&:value).should == [1]
296
+
297
+ @user1.reload
298
+ @user1.associated_shards = [@shard1, @shard2]
299
+ @user1.features.to_a.map(&:value).sort.should == [1, 2]
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
305
+ end