seomoz-ripple 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. data/Gemfile +20 -0
  2. data/Guardfile +15 -0
  3. data/Rakefile +88 -0
  4. data/lib/rails/generators/ripple/configuration/configuration_generator.rb +13 -0
  5. data/lib/rails/generators/ripple/configuration/templates/ripple.yml +24 -0
  6. data/lib/rails/generators/ripple/js/js_generator.rb +13 -0
  7. data/lib/rails/generators/ripple/js/templates/js/contrib.js +63 -0
  8. data/lib/rails/generators/ripple/js/templates/js/iso8601.js +76 -0
  9. data/lib/rails/generators/ripple/js/templates/js/ripple.js +132 -0
  10. data/lib/rails/generators/ripple/model/model_generator.rb +20 -0
  11. data/lib/rails/generators/ripple/model/templates/model.rb +10 -0
  12. data/lib/rails/generators/ripple/observer/observer_generator.rb +16 -0
  13. data/lib/rails/generators/ripple/observer/templates/observer.rb +4 -0
  14. data/lib/rails/generators/ripple/test/templates/test_server.rb +46 -0
  15. data/lib/rails/generators/ripple/test/test_generator.rb +39 -0
  16. data/lib/rails/generators/ripple_generator.rb +78 -0
  17. data/lib/ripple.rb +79 -0
  18. data/lib/ripple/associations.rb +356 -0
  19. data/lib/ripple/associations/embedded.rb +35 -0
  20. data/lib/ripple/associations/instantiators.rb +26 -0
  21. data/lib/ripple/associations/linked.rb +65 -0
  22. data/lib/ripple/associations/many.rb +38 -0
  23. data/lib/ripple/associations/many_embedded_proxy.rb +38 -0
  24. data/lib/ripple/associations/many_linked_proxy.rb +66 -0
  25. data/lib/ripple/associations/many_reference_proxy.rb +93 -0
  26. data/lib/ripple/associations/many_stored_key_proxy.rb +76 -0
  27. data/lib/ripple/associations/one.rb +20 -0
  28. data/lib/ripple/associations/one_embedded_proxy.rb +35 -0
  29. data/lib/ripple/associations/one_key_proxy.rb +58 -0
  30. data/lib/ripple/associations/one_linked_proxy.rb +22 -0
  31. data/lib/ripple/associations/one_stored_key_proxy.rb +43 -0
  32. data/lib/ripple/associations/proxy.rb +118 -0
  33. data/lib/ripple/attribute_methods.rb +118 -0
  34. data/lib/ripple/attribute_methods/dirty.rb +50 -0
  35. data/lib/ripple/attribute_methods/query.rb +34 -0
  36. data/lib/ripple/attribute_methods/read.rb +26 -0
  37. data/lib/ripple/attribute_methods/write.rb +25 -0
  38. data/lib/ripple/callbacks.rb +74 -0
  39. data/lib/ripple/conflict/basic_resolver.rb +82 -0
  40. data/lib/ripple/conflict/document_hooks.rb +20 -0
  41. data/lib/ripple/conflict/resolver.rb +71 -0
  42. data/lib/ripple/conflict/test_helper.rb +33 -0
  43. data/lib/ripple/conversion.rb +28 -0
  44. data/lib/ripple/core_ext.rb +2 -0
  45. data/lib/ripple/core_ext/casting.rb +148 -0
  46. data/lib/ripple/core_ext/object.rb +8 -0
  47. data/lib/ripple/document.rb +104 -0
  48. data/lib/ripple/document/bucket_access.rb +25 -0
  49. data/lib/ripple/document/finders.rb +131 -0
  50. data/lib/ripple/document/key.rb +43 -0
  51. data/lib/ripple/document/link.rb +30 -0
  52. data/lib/ripple/document/persistence.rb +115 -0
  53. data/lib/ripple/embedded_document.rb +64 -0
  54. data/lib/ripple/embedded_document/around_callbacks.rb +18 -0
  55. data/lib/ripple/embedded_document/finders.rb +26 -0
  56. data/lib/ripple/embedded_document/persistence.rb +77 -0
  57. data/lib/ripple/i18n.rb +2 -0
  58. data/lib/ripple/inspection.rb +32 -0
  59. data/lib/ripple/locale/en.yml +21 -0
  60. data/lib/ripple/nested_attributes.rb +265 -0
  61. data/lib/ripple/observable.rb +28 -0
  62. data/lib/ripple/properties.rb +73 -0
  63. data/lib/ripple/property_type_mismatch.rb +12 -0
  64. data/lib/ripple/railtie.rb +13 -0
  65. data/lib/ripple/serialization.rb +84 -0
  66. data/lib/ripple/timestamps.rb +27 -0
  67. data/lib/ripple/translation.rb +14 -0
  68. data/lib/ripple/validations.rb +67 -0
  69. data/lib/ripple/validations/associated_validator.rb +43 -0
  70. data/ripple.gemspec +46 -0
  71. data/seomoz-ripple.gemspec +46 -0
  72. data/spec/fixtures/config.yml +8 -0
  73. data/spec/integration/ripple/associations_spec.rb +220 -0
  74. data/spec/integration/ripple/conflict_resolution_spec.rb +293 -0
  75. data/spec/integration/ripple/nested_attributes_spec.rb +264 -0
  76. data/spec/integration/ripple/persistence_spec.rb +57 -0
  77. data/spec/integration/ripple/search_associations_spec.rb +42 -0
  78. data/spec/ripple/associations/many_embedded_proxy_spec.rb +122 -0
  79. data/spec/ripple/associations/many_linked_proxy_spec.rb +191 -0
  80. data/spec/ripple/associations/many_reference_proxy_spec.rb +170 -0
  81. data/spec/ripple/associations/many_stored_key_proxy_spec.rb +158 -0
  82. data/spec/ripple/associations/one_embedded_proxy_spec.rb +125 -0
  83. data/spec/ripple/associations/one_key_proxy_spec.rb +82 -0
  84. data/spec/ripple/associations/one_linked_proxy_spec.rb +91 -0
  85. data/spec/ripple/associations/one_stored_key_proxy_spec.rb +72 -0
  86. data/spec/ripple/associations/proxy_spec.rb +84 -0
  87. data/spec/ripple/associations_spec.rb +129 -0
  88. data/spec/ripple/attribute_methods/dirty_spec.rb +80 -0
  89. data/spec/ripple/attribute_methods_spec.rb +230 -0
  90. data/spec/ripple/bucket_access_spec.rb +25 -0
  91. data/spec/ripple/callbacks_spec.rb +176 -0
  92. data/spec/ripple/conflict/resolver_spec.rb +42 -0
  93. data/spec/ripple/conversion_spec.rb +22 -0
  94. data/spec/ripple/core_ext_spec.rb +103 -0
  95. data/spec/ripple/document/link_spec.rb +67 -0
  96. data/spec/ripple/document_spec.rb +96 -0
  97. data/spec/ripple/embedded_document/finders_spec.rb +29 -0
  98. data/spec/ripple/embedded_document/persistence_spec.rb +80 -0
  99. data/spec/ripple/embedded_document_spec.rb +84 -0
  100. data/spec/ripple/finders_spec.rb +217 -0
  101. data/spec/ripple/inspection_spec.rb +51 -0
  102. data/spec/ripple/key_spec.rb +30 -0
  103. data/spec/ripple/observable_spec.rb +121 -0
  104. data/spec/ripple/persistence_spec.rb +326 -0
  105. data/spec/ripple/properties_spec.rb +262 -0
  106. data/spec/ripple/ripple_spec.rb +71 -0
  107. data/spec/ripple/serialization_spec.rb +51 -0
  108. data/spec/ripple/timestamps_spec.rb +76 -0
  109. data/spec/ripple/validations/associated_validator_spec.rb +77 -0
  110. data/spec/ripple/validations_spec.rb +104 -0
  111. data/spec/spec_helper.rb +26 -0
  112. data/spec/support/associations.rb +1 -0
  113. data/spec/support/associations/proxies.rb +17 -0
  114. data/spec/support/integration_setup.rb +11 -0
  115. data/spec/support/mocks.rb +4 -0
  116. data/spec/support/models.rb +4 -0
  117. data/spec/support/models/address.rb +12 -0
  118. data/spec/support/models/box.rb +13 -0
  119. data/spec/support/models/car.rb +16 -0
  120. data/spec/support/models/cardboard_box.rb +3 -0
  121. data/spec/support/models/clock.rb +12 -0
  122. data/spec/support/models/clock_observer.rb +3 -0
  123. data/spec/support/models/company.rb +23 -0
  124. data/spec/support/models/customer.rb +4 -0
  125. data/spec/support/models/driver.rb +6 -0
  126. data/spec/support/models/email.rb +4 -0
  127. data/spec/support/models/engine.rb +5 -0
  128. data/spec/support/models/family.rb +16 -0
  129. data/spec/support/models/favorite.rb +4 -0
  130. data/spec/support/models/invoice.rb +7 -0
  131. data/spec/support/models/late_invoice.rb +3 -0
  132. data/spec/support/models/ninja.rb +9 -0
  133. data/spec/support/models/note.rb +5 -0
  134. data/spec/support/models/page.rb +4 -0
  135. data/spec/support/models/paid_invoice.rb +4 -0
  136. data/spec/support/models/passenger.rb +6 -0
  137. data/spec/support/models/profile.rb +10 -0
  138. data/spec/support/models/seat.rb +5 -0
  139. data/spec/support/models/subscription.rb +27 -0
  140. data/spec/support/models/tasks.rb +14 -0
  141. data/spec/support/models/team.rb +11 -0
  142. data/spec/support/models/transactions.rb +17 -0
  143. data/spec/support/models/tree.rb +4 -0
  144. data/spec/support/models/user.rb +10 -0
  145. data/spec/support/models/wheel.rb +6 -0
  146. data/spec/support/models/widget.rb +22 -0
  147. data/spec/support/test_server.rb +18 -0
  148. data/spec/support/test_server.yml.example +2 -0
  149. metadata +362 -0
@@ -0,0 +1,8 @@
1
+ ripple:
2
+ http_port: 9000
3
+ host: localhost
4
+
5
+ ripple_rails:
6
+ development:
7
+ http_port: 9001
8
+ host: 127.0.0.1
@@ -0,0 +1,220 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Ripple Associations" do
4
+ require 'support/test_server'
5
+
6
+ before :all do
7
+ Object.module_eval do
8
+ class User
9
+ include Ripple::Document
10
+ one :profile
11
+ many :addresses
12
+ property :email, String, :presence => true
13
+ many :friends, :class_name => "User"
14
+ one :emergency_contact, :class_name => "User"
15
+ one :credit_card, :using => :key
16
+ end
17
+ class Profile
18
+ include Ripple::EmbeddedDocument
19
+ property :name, String, :presence => true
20
+ embedded_in :user
21
+ end
22
+ class Address
23
+ include Ripple::EmbeddedDocument
24
+ property :street, String, :presence => true
25
+ property :kind, String, :presence => true
26
+ embedded_in :user
27
+ end
28
+ class CreditCard
29
+ include Ripple::Document
30
+ one :user, :using => :key
31
+ property :number, Integer
32
+ end
33
+ class Post
34
+ include Ripple::Document
35
+ one :user, :using => :stored_key
36
+ many :comments, :using => :stored_key
37
+ property :comment_keys, Array
38
+ property :user_key, String
39
+ property :title, String
40
+ end
41
+ class Comment
42
+ include Ripple::Document
43
+ end
44
+ end
45
+ end
46
+
47
+ before :each do
48
+ @user = User.new(:email => 'riak@ripple.com')
49
+ @profile = Profile.new(:name => 'Ripple')
50
+ @billing = Address.new(:street => '123 Somewhere Dr', :kind => 'billing')
51
+ @shipping = Address.new(:street => '321 Anywhere Pl', :kind => 'shipping')
52
+ @friend1 = User.create(:email => "friend@ripple.com")
53
+ @friend2 = User.create(:email => "friend2@ripple.com")
54
+ @cc = CreditCard.new(:number => '12345')
55
+ @post = Post.new(:title => "Hello, world!")
56
+ @comment_one = Comment.new.tap{|c| c.key = "one"; c.save! }
57
+ @comment_two = Comment.new.tap{|c| c.key = "two"; c.save! }
58
+ end
59
+
60
+ it "should save and restore a many stored key association" do
61
+ @post.comments << @comment_one << @comment_two
62
+ @post.save!
63
+
64
+ post = Post.find(@post.key)
65
+ post.comment_keys.should == [ 'one', 'two' ]
66
+ post.comments.keys.should == [ 'one', 'two' ]
67
+ post.comments.should == [ @comment_one, @comment_two ]
68
+ end
69
+
70
+ it "should remove a document from a many stored key association" do
71
+ @post.comments << @comment_one
72
+ @post.comments << @comment_two
73
+ @post.save!
74
+ @post.comments.delete(@comment_one)
75
+ @post.save!
76
+
77
+ @post = Post.find(@post.key)
78
+ @post.comment_keys.should == [ @comment_two.key ]
79
+ @post.comments.should == [ @comment_two ]
80
+ end
81
+
82
+ it "should save one embedded associations" do
83
+ @user.profile = @profile
84
+ @user.save
85
+ @found = User.find(@user.key)
86
+ @found.profile.name.should == 'Ripple'
87
+ @found.profile.should be_a(Profile)
88
+ @found.profile.user.should == @found
89
+ end
90
+
91
+ it "should not raise an error when a one linked associated record has been deleted" do
92
+ @user.emergency_contact = @friend1
93
+ @user.save
94
+
95
+ @friend1.destroy
96
+ @found = User.find(@user.key)
97
+ @found.emergency_contact.should be_nil
98
+ end
99
+
100
+ it "should allow a many linked record to be deleted from the association but kept in the datastore" do
101
+ @user.friends << @friend1
102
+ @user.save!
103
+
104
+ @user.friends.delete(@friend1)
105
+ @user.save!
106
+
107
+ found_user = User.find(@user.key)
108
+ found_user.friends.should be_empty
109
+ User.find(@friend1.key).should be
110
+ end
111
+
112
+ it "should allow a many embedded record to be deleted from the association" do
113
+ @user.addresses << @billing << @shipping
114
+ @user.save!
115
+
116
+ @user.addresses.delete(@billing)
117
+ @user.save!
118
+ User.find(@user.key).addresses.should == [@shipping]
119
+ end
120
+
121
+ it "should save many embedded associations" do
122
+ @user.addresses << @billing << @shipping
123
+ @user.save
124
+ @found = User.find(@user.key)
125
+ @found.addresses.count.should == 2
126
+ @bill = @found.addresses.detect {|a| a.kind == 'billing'}
127
+ @ship = @found.addresses.detect {|a| a.kind == 'shipping'}
128
+ @bill.street.should == '123 Somewhere Dr'
129
+ @ship.street.should == '321 Anywhere Pl'
130
+ @bill.user.should == @found
131
+ @ship.user.should == @found
132
+ @bill.should be_a(Address)
133
+ @ship.should be_a(Address)
134
+ end
135
+
136
+ it "should save a many linked association" do
137
+ @user.friends << @friend1 << @friend2
138
+ @user.save
139
+ @user.should_not be_new_record
140
+ @found = User.find(@user.key)
141
+ @found.friends.map(&:key).should include(@friend1.key)
142
+ @found.friends.map(&:key).should include(@friend2.key)
143
+ end
144
+
145
+ it "should save a one linked association" do
146
+ @user.emergency_contact = @friend1
147
+ @user.save
148
+ @user.should_not be_new_record
149
+ @found = User.find(@user.key)
150
+ @found.emergency_contact.key.should == @friend1.key
151
+ end
152
+
153
+ it "should reload associations" do
154
+ @user.friends << @friend1
155
+ @user.save!
156
+
157
+ friend1_new_instance = User.find(@friend1.key)
158
+ friend1_new_instance.email = 'new-address@ripple.com'
159
+ friend1_new_instance.save!
160
+
161
+ @user.reload
162
+ @user.friends.map(&:email).should == ['new-address@ripple.com']
163
+ end
164
+
165
+ it "allows and autosaves transitive linked associations" do
166
+ friend = User.new(:email => 'user-friend@example.com')
167
+ friend.key = 'main-user-friend'
168
+ @user.key = 'main-user'
169
+ @user.friends << friend
170
+ friend.friends << @user
171
+
172
+ @user.save! # should save both since friend is new
173
+
174
+ found_user = User.find!(@user.key)
175
+ found_friend = User.find!(friend.key)
176
+
177
+ found_user.friends.should == [found_friend]
178
+ found_friend.friends.should == [found_user]
179
+ end
180
+
181
+ it "should find the object associated by key after saving" do
182
+ @user.key = 'paying-user'
183
+ @user.credit_card = @cc
184
+ @user.save && @cc.save
185
+ @found = User.find(@user.key)
186
+ @found.reload
187
+ @found.credit_card.should eq(@cc)
188
+ end
189
+
190
+ it "should assign the generated riak key to the associated object using key" do
191
+ @user.key.should be_nil
192
+ @user.credit_card = @cc
193
+ @user.save
194
+ @cc.key.should_not be_blank
195
+ @cc.key.should eq(@user.key)
196
+ end
197
+
198
+ it "should save one association by storing key" do
199
+ @user.save!
200
+ @post.user = @user
201
+ @post.save!
202
+ @post.user_key.should == @user.key
203
+ @found = Post.find(@post.key)
204
+ @found.user.email.should == 'riak@ripple.com'
205
+ @found.user.should be_a(User)
206
+ end
207
+
208
+ after :each do
209
+ User.destroy_all
210
+ end
211
+
212
+ after :all do
213
+ Object.send(:remove_const, :User)
214
+ Object.send(:remove_const, :Profile)
215
+ Object.send(:remove_const, :Address)
216
+ Object.send(:remove_const, :CreditCard)
217
+ Object.send(:remove_const, :Post)
218
+ end
219
+
220
+ end
@@ -0,0 +1,293 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Ripple conflict resolution" do
4
+ class ConflictedPerson
5
+ include Ripple::Document
6
+
7
+ property :name, String
8
+ property :age, Integer, :numericality => { :greater_than => 0, :allow_nil => true }
9
+ property :gender, String
10
+ property :favorite_colors, Set, :default => lambda { Set.new }
11
+ property :created_at, DateTime
12
+ property :updated_at, DateTime
13
+ property :coworker_keys, Array
14
+ property :mother_key, String
15
+ key_on :name
16
+
17
+ # embedded
18
+ one :address, :class_name => 'ConflictedAddress'
19
+ many :jobs, :class_name => 'ConflictedJob'
20
+
21
+ # linked
22
+ one :spouse, :class_name => 'ConflictedPerson'
23
+ many :friends, :class_name => 'ConflictedPerson'
24
+
25
+ #stored_key
26
+ one :mother, :using => :stored_key, :class_name => 'ConflictedPerson'
27
+ many :coworkers, :using => :stored_key, :class_name => 'ConflictedPerson'
28
+
29
+ bucket.allow_mult = true
30
+ end
31
+
32
+ class ConflictedAddress
33
+ include Ripple::EmbeddedDocument
34
+ property :city, String
35
+ end
36
+
37
+ class ConflictedJob
38
+ include Ripple::EmbeddedDocument
39
+ property :title, String
40
+ end
41
+
42
+
43
+ before(:each) do
44
+ ConflictedPerson.on_conflict { } # reset to no-op
45
+ end
46
+
47
+ context 'when there is no conflict' do
48
+ it 'does not invoke the on_conflict hook' do
49
+ ConflictedPerson.on_conflict { raise "This conflict hook should not be invoked" }
50
+
51
+ # no errors should be raised by the hook above
52
+ ConflictedPerson.find('Noone')
53
+ ConflictedPerson.create!(:name => 'John')
54
+ ConflictedPerson.find('John').should_not be_nil
55
+ end
56
+ end
57
+
58
+ let(:created_at) { DateTime.new(2011, 5, 12, 8, 30, 0) }
59
+ let(:updated_at) { DateTime.new(2011, 5, 12, 8, 30, 0) }
60
+
61
+ let(:original_person) do
62
+ ConflictedPerson.create!(
63
+ :name => 'John',
64
+ :age => 25,
65
+ :gender => 'male',
66
+ :favorite_colors => ['green'],
67
+ :address => ConflictedAddress.new(:city => 'Seattle'),
68
+ :jobs => [ConflictedJob.new(:title => 'Engineer')],
69
+ :spouse => ConflictedPerson.create!(:name => 'Jill', :gender => 'female'),
70
+ :friends => [ConflictedPerson.create!(:name => 'Quinn', :gender => 'male')],
71
+ :coworkers => [ConflictedPerson.create!(:name => 'Horace', :gender => 'male')],
72
+ :mother => ConflictedPerson.create!(:name => 'Serena', :gender => 'female'),
73
+ :created_at => created_at,
74
+ :updated_at => updated_at
75
+ )
76
+ end
77
+
78
+ context 'for a document that has conflicted attributes' do
79
+ let(:most_recent_updated_at) { DateTime.new(2011, 6, 4, 12, 30) }
80
+ let(:earliest_created_at) { DateTime.new(2010, 5, 3, 12, 30) }
81
+
82
+ before(:each) do
83
+ create_conflict original_person,
84
+ lambda { |p| p.age = 20; p.created_at = earliest_created_at },
85
+ lambda { |p| p.age = 30; p.updated_at = most_recent_updated_at },
86
+ lambda { |p| p.favorite_colors << 'red' }
87
+ end
88
+
89
+ it 'raises a NotImplementedError when there is no on_conflict handler' do
90
+ ConflictedPerson.instance_variable_get(:@on_conflict_block).should_not be_nil
91
+ ConflictedPerson.instance_variable_set(:@on_conflict_block, nil)
92
+
93
+ expect {
94
+ ConflictedPerson.find('John')
95
+ }.to raise_error(NotImplementedError)
96
+ end
97
+
98
+ it 'invokes the on_conflict block with the siblings and the list of conflicted attributes' do
99
+ siblings = conflicts = nil
100
+ ConflictedPerson.on_conflict { |s, c| siblings = s; conflicts = c }
101
+ ConflictedPerson.find('John')
102
+
103
+ siblings.should have(3).sibling_records
104
+ siblings.map(&:class).uniq.should == [ConflictedPerson]
105
+ siblings.map(&:age).should =~ [20, 25, 30]
106
+
107
+ conflicts.should =~ [:age, :favorite_colors]
108
+ end
109
+
110
+ it 'automatically resolves any attributes that are in agreement among all siblings' do
111
+ record = nil
112
+ ConflictedPerson.on_conflict { record = self }
113
+ ConflictedPerson.find('John')
114
+ record.name.should == 'John'
115
+ record.gender.should == 'male'
116
+ end
117
+
118
+ it 'automatically resolves updated_at to the most recent timestamp' do
119
+ record = nil
120
+ ConflictedPerson.on_conflict { record = self }
121
+ ConflictedPerson.find('John')
122
+ record.updated_at.should == most_recent_updated_at
123
+ end
124
+
125
+ it 'automatically resolves created_at to the earliest timestamp' do
126
+ record = nil
127
+ ConflictedPerson.on_conflict { record = self }
128
+ ConflictedPerson.find('John')
129
+ record.created_at.should == earliest_created_at
130
+ end
131
+
132
+ it 'automatically sets conflicted attributes to their default values' do
133
+ record = nil
134
+ ConflictedPerson.on_conflict { record = self }
135
+ ConflictedPerson.find('John')
136
+ record.age.should be_nil
137
+ record.favorite_colors.should == Set.new
138
+ end
139
+
140
+ it 'returns the resolved record with the changes made by the on_conflict hook' do
141
+ ConflictedPerson.on_conflict do |siblings, _|
142
+ self.age = siblings.map(&:age).inject(&:+)
143
+ end
144
+
145
+ person = ConflictedPerson.find('John')
146
+ person.age.should == (20 + 25 + 30)
147
+ end
148
+
149
+ context 'when .on_conflict is given a list of attributes' do
150
+ it 'raises an error if attributes not mentioned in the list are in conflict' do
151
+ ConflictedPerson.on_conflict(:age) { }
152
+ expect {
153
+ ConflictedPerson.find('John')
154
+ }.to raise_error(NotImplementedError) # since favorite_colors is also in conflict
155
+ end
156
+
157
+ it 'does not raise an error if all conflicted attributes are in the list' do
158
+ ConflictedPerson.on_conflict(:age, :favorite_colors) { }
159
+ ConflictedPerson.find('John')
160
+ end
161
+ end
162
+ end
163
+
164
+ context 'when there are conflicts on a one embedded association' do
165
+ before(:each) do
166
+ create_conflict original_person,
167
+ lambda { |p| p.address.city = 'San Francisco' },
168
+ lambda { |p| p.address.city = 'Portland' }
169
+ end
170
+
171
+ it 'sets the association to nil and includes its name in the list of conflicts passed to the on_conflict block' do
172
+ siblings = conflicts = record = nil
173
+ ConflictedPerson.on_conflict { |*a| siblings, conflicts = *a; record = self }
174
+ ConflictedPerson.find('John')
175
+ record.address.should be_nil
176
+ conflicts.should == [:address]
177
+ siblings.map { |s| s.address.city }.should =~ ['Portland', 'San Francisco']
178
+ end
179
+ end
180
+
181
+ context 'when there are conflicts on a many embedded association' do
182
+ before(:each) do
183
+ create_conflict original_person,
184
+ lambda { |p| p.jobs << ConflictedJob.new(:title => 'CEO') },
185
+ lambda { |p| p.jobs << ConflictedJob.new(:title => 'CTO') }
186
+ end
187
+
188
+ it 'sets the association to an empty array and includes its name in the list of conflicts passed to the on_conflict block' do
189
+ siblings = conflicts = record = nil
190
+ ConflictedPerson.on_conflict { |*a| siblings, conflicts = *a; record = self }
191
+ ConflictedPerson.find('John')
192
+ record.jobs.should == []
193
+ conflicts.should == [:jobs]
194
+ siblings.map { |s| s.jobs.map(&:title) }.should =~ [["Engineer", "CEO"], ["Engineer", "CTO"]]
195
+ end
196
+ end
197
+
198
+ context 'when there are conflicts on a one linked association' do
199
+ before(:each) do
200
+ create_conflict original_person,
201
+ lambda { |p| p.spouse = ConflictedPerson.create!(:name => 'Renee', :gender => 'female') },
202
+ lambda { |p| p.spouse = ConflictedPerson.create!(:name => 'Sharon', :gender => 'female') }
203
+ end
204
+
205
+ it 'sets the association to nil and includes its name in the list of conflicts passed to the on_conflict block' do
206
+ record_spouse = conflicts = sibling_spouse_names = nil
207
+
208
+ ConflictedPerson.on_conflict do |siblings, c|
209
+ record_spouse = spouse
210
+ conflicts = c
211
+ sibling_spouse_names = siblings.map { |s| s.spouse.name }
212
+ end
213
+
214
+ ConflictedPerson.find('John')
215
+ record_spouse.should be_nil
216
+ conflicts.should == [:spouse]
217
+ sibling_spouse_names.should =~ %w[ Sharon Renee ]
218
+ end
219
+ end
220
+
221
+ context 'when there are conflicts on a many linked association' do
222
+ before(:each) do
223
+ create_conflict original_person,
224
+ lambda { |p| p.friends << ConflictedPerson.new(:name => 'Luna', :gender => 'female') },
225
+ lambda { |p| p.friends << ConflictedPerson.new(:name => 'Molly', :gender => 'female') }
226
+ end
227
+
228
+ it 'sets the association to a blank array and includes its name in the list of conflicts passed to the on_conflict block' do
229
+ record_friends = conflicts = sibling_friend_names = nil
230
+
231
+ ConflictedPerson.on_conflict do |siblings, c|
232
+ record_friends = friends
233
+ conflicts = c
234
+ sibling_friend_names = siblings.map { |s| s.friends.map(&:name) }
235
+ end
236
+
237
+ ConflictedPerson.find('John')
238
+ record_friends.should == []
239
+ conflicts.should == [:friends]
240
+ sibling_friend_names.map(&:sort).should =~ [['Luna', 'Quinn'], ['Molly', 'Quinn']]
241
+ end
242
+ end
243
+
244
+ context 'when there are conflicts on a many stored_key association' do
245
+ before(:each) do
246
+ create_conflict original_person,
247
+ lambda { |p| p.coworkers << ConflictedPerson.create!(:name => 'Colleen', :gender => 'female') },
248
+ lambda { |p| p.coworkers = [ ConflictedPerson.create!(:name => 'Russ', :gender => 'male'),
249
+ ConflictedPerson.create!(:name => 'Denise', :gender => 'female') ] }
250
+ end
251
+
252
+ it 'sets the association to a blank array and includes the owner_keys in the list of conflicts passed to the on_conflict block' do
253
+ record_coworkers = record_coworker_keys = conflicts = sibling_coworker_keys = nil
254
+
255
+ ConflictedPerson.on_conflict do |siblings, c|
256
+ record_coworkers = coworkers
257
+ record_coworker_keys = coworker_keys
258
+ conflicts = c
259
+ sibling_coworker_keys = siblings.map { |s| s.coworker_keys }
260
+ end
261
+
262
+ ConflictedPerson.find('John')
263
+ record_coworker_keys.should == []
264
+ record_coworkers.should == []
265
+ conflicts.should == [:coworker_keys]
266
+ sibling_coworker_keys.map(&:sort).should =~ [['Colleen', 'Horace'], ['Denise', 'Russ']]
267
+ end
268
+ end
269
+
270
+ context 'when there are conflicts on a one stored_key association' do
271
+ before(:each) do
272
+ create_conflict original_person,
273
+ lambda { |p| p.mother = ConflictedPerson.new(:name => 'Nancy', :gender => 'female') },
274
+ lambda { |p| p.mother = ConflictedPerson.new(:name => 'Sherry', :gender => 'male') }
275
+ end
276
+
277
+ it 'sets the association to nil and includes its name in the list of conflicts passed to the on_conflict block' do
278
+ record_mother = conflicts = sibling_mother_keys = nil
279
+
280
+ ConflictedPerson.on_conflict do |siblings, c|
281
+ record_mother = mother
282
+ conflicts = c
283
+ sibling_mother_keys = siblings.map { |s| s.mother_key }
284
+ end
285
+
286
+ ConflictedPerson.find('John')
287
+ record_mother.should be_nil
288
+ conflicts.should == [:mother_key]
289
+ sibling_mother_keys.sort.should == %w(Nancy Sherry)
290
+ end
291
+ end
292
+
293
+ end