ripple 0.9.5 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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