switchman 0.0.1

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