ripple 0.9.5 → 1.0.0.beta

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