seomoz-ripple 1.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +20 -0
- data/Guardfile +15 -0
- data/Rakefile +88 -0
- data/lib/rails/generators/ripple/configuration/configuration_generator.rb +13 -0
- data/lib/rails/generators/ripple/configuration/templates/ripple.yml +24 -0
- data/lib/rails/generators/ripple/js/js_generator.rb +13 -0
- data/lib/rails/generators/ripple/js/templates/js/contrib.js +63 -0
- data/lib/rails/generators/ripple/js/templates/js/iso8601.js +76 -0
- data/lib/rails/generators/ripple/js/templates/js/ripple.js +132 -0
- data/lib/rails/generators/ripple/model/model_generator.rb +20 -0
- data/lib/rails/generators/ripple/model/templates/model.rb +10 -0
- data/lib/rails/generators/ripple/observer/observer_generator.rb +16 -0
- data/lib/rails/generators/ripple/observer/templates/observer.rb +4 -0
- data/lib/rails/generators/ripple/test/templates/test_server.rb +46 -0
- data/lib/rails/generators/ripple/test/test_generator.rb +39 -0
- data/lib/rails/generators/ripple_generator.rb +78 -0
- data/lib/ripple.rb +79 -0
- data/lib/ripple/associations.rb +356 -0
- data/lib/ripple/associations/embedded.rb +35 -0
- data/lib/ripple/associations/instantiators.rb +26 -0
- data/lib/ripple/associations/linked.rb +65 -0
- data/lib/ripple/associations/many.rb +38 -0
- data/lib/ripple/associations/many_embedded_proxy.rb +38 -0
- data/lib/ripple/associations/many_linked_proxy.rb +66 -0
- data/lib/ripple/associations/many_reference_proxy.rb +93 -0
- data/lib/ripple/associations/many_stored_key_proxy.rb +76 -0
- data/lib/ripple/associations/one.rb +20 -0
- data/lib/ripple/associations/one_embedded_proxy.rb +35 -0
- data/lib/ripple/associations/one_key_proxy.rb +58 -0
- data/lib/ripple/associations/one_linked_proxy.rb +22 -0
- data/lib/ripple/associations/one_stored_key_proxy.rb +43 -0
- data/lib/ripple/associations/proxy.rb +118 -0
- data/lib/ripple/attribute_methods.rb +118 -0
- data/lib/ripple/attribute_methods/dirty.rb +50 -0
- data/lib/ripple/attribute_methods/query.rb +34 -0
- data/lib/ripple/attribute_methods/read.rb +26 -0
- data/lib/ripple/attribute_methods/write.rb +25 -0
- data/lib/ripple/callbacks.rb +74 -0
- data/lib/ripple/conflict/basic_resolver.rb +82 -0
- data/lib/ripple/conflict/document_hooks.rb +20 -0
- data/lib/ripple/conflict/resolver.rb +71 -0
- data/lib/ripple/conflict/test_helper.rb +33 -0
- data/lib/ripple/conversion.rb +28 -0
- data/lib/ripple/core_ext.rb +2 -0
- data/lib/ripple/core_ext/casting.rb +148 -0
- data/lib/ripple/core_ext/object.rb +8 -0
- data/lib/ripple/document.rb +104 -0
- data/lib/ripple/document/bucket_access.rb +25 -0
- data/lib/ripple/document/finders.rb +131 -0
- data/lib/ripple/document/key.rb +43 -0
- data/lib/ripple/document/link.rb +30 -0
- data/lib/ripple/document/persistence.rb +115 -0
- data/lib/ripple/embedded_document.rb +64 -0
- data/lib/ripple/embedded_document/around_callbacks.rb +18 -0
- data/lib/ripple/embedded_document/finders.rb +26 -0
- data/lib/ripple/embedded_document/persistence.rb +77 -0
- data/lib/ripple/i18n.rb +2 -0
- data/lib/ripple/inspection.rb +32 -0
- data/lib/ripple/locale/en.yml +21 -0
- data/lib/ripple/nested_attributes.rb +265 -0
- data/lib/ripple/observable.rb +28 -0
- data/lib/ripple/properties.rb +73 -0
- data/lib/ripple/property_type_mismatch.rb +12 -0
- data/lib/ripple/railtie.rb +13 -0
- data/lib/ripple/serialization.rb +84 -0
- data/lib/ripple/timestamps.rb +27 -0
- data/lib/ripple/translation.rb +14 -0
- data/lib/ripple/validations.rb +67 -0
- data/lib/ripple/validations/associated_validator.rb +43 -0
- data/ripple.gemspec +46 -0
- data/seomoz-ripple.gemspec +46 -0
- data/spec/fixtures/config.yml +8 -0
- data/spec/integration/ripple/associations_spec.rb +220 -0
- data/spec/integration/ripple/conflict_resolution_spec.rb +293 -0
- data/spec/integration/ripple/nested_attributes_spec.rb +264 -0
- data/spec/integration/ripple/persistence_spec.rb +57 -0
- data/spec/integration/ripple/search_associations_spec.rb +42 -0
- data/spec/ripple/associations/many_embedded_proxy_spec.rb +122 -0
- data/spec/ripple/associations/many_linked_proxy_spec.rb +191 -0
- data/spec/ripple/associations/many_reference_proxy_spec.rb +170 -0
- data/spec/ripple/associations/many_stored_key_proxy_spec.rb +158 -0
- data/spec/ripple/associations/one_embedded_proxy_spec.rb +125 -0
- data/spec/ripple/associations/one_key_proxy_spec.rb +82 -0
- data/spec/ripple/associations/one_linked_proxy_spec.rb +91 -0
- data/spec/ripple/associations/one_stored_key_proxy_spec.rb +72 -0
- data/spec/ripple/associations/proxy_spec.rb +84 -0
- data/spec/ripple/associations_spec.rb +129 -0
- data/spec/ripple/attribute_methods/dirty_spec.rb +80 -0
- data/spec/ripple/attribute_methods_spec.rb +230 -0
- data/spec/ripple/bucket_access_spec.rb +25 -0
- data/spec/ripple/callbacks_spec.rb +176 -0
- data/spec/ripple/conflict/resolver_spec.rb +42 -0
- data/spec/ripple/conversion_spec.rb +22 -0
- data/spec/ripple/core_ext_spec.rb +103 -0
- data/spec/ripple/document/link_spec.rb +67 -0
- data/spec/ripple/document_spec.rb +96 -0
- data/spec/ripple/embedded_document/finders_spec.rb +29 -0
- data/spec/ripple/embedded_document/persistence_spec.rb +80 -0
- data/spec/ripple/embedded_document_spec.rb +84 -0
- data/spec/ripple/finders_spec.rb +217 -0
- data/spec/ripple/inspection_spec.rb +51 -0
- data/spec/ripple/key_spec.rb +30 -0
- data/spec/ripple/observable_spec.rb +121 -0
- data/spec/ripple/persistence_spec.rb +326 -0
- data/spec/ripple/properties_spec.rb +262 -0
- data/spec/ripple/ripple_spec.rb +71 -0
- data/spec/ripple/serialization_spec.rb +51 -0
- data/spec/ripple/timestamps_spec.rb +76 -0
- data/spec/ripple/validations/associated_validator_spec.rb +77 -0
- data/spec/ripple/validations_spec.rb +104 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/associations.rb +1 -0
- data/spec/support/associations/proxies.rb +17 -0
- data/spec/support/integration_setup.rb +11 -0
- data/spec/support/mocks.rb +4 -0
- data/spec/support/models.rb +4 -0
- data/spec/support/models/address.rb +12 -0
- data/spec/support/models/box.rb +13 -0
- data/spec/support/models/car.rb +16 -0
- data/spec/support/models/cardboard_box.rb +3 -0
- data/spec/support/models/clock.rb +12 -0
- data/spec/support/models/clock_observer.rb +3 -0
- data/spec/support/models/company.rb +23 -0
- data/spec/support/models/customer.rb +4 -0
- data/spec/support/models/driver.rb +6 -0
- data/spec/support/models/email.rb +4 -0
- data/spec/support/models/engine.rb +5 -0
- data/spec/support/models/family.rb +16 -0
- data/spec/support/models/favorite.rb +4 -0
- data/spec/support/models/invoice.rb +7 -0
- data/spec/support/models/late_invoice.rb +3 -0
- data/spec/support/models/ninja.rb +9 -0
- data/spec/support/models/note.rb +5 -0
- data/spec/support/models/page.rb +4 -0
- data/spec/support/models/paid_invoice.rb +4 -0
- data/spec/support/models/passenger.rb +6 -0
- data/spec/support/models/profile.rb +10 -0
- data/spec/support/models/seat.rb +5 -0
- data/spec/support/models/subscription.rb +27 -0
- data/spec/support/models/tasks.rb +14 -0
- data/spec/support/models/team.rb +11 -0
- data/spec/support/models/transactions.rb +17 -0
- data/spec/support/models/tree.rb +4 -0
- data/spec/support/models/user.rb +10 -0
- data/spec/support/models/wheel.rb +6 -0
- data/spec/support/models/widget.rb +22 -0
- data/spec/support/test_server.rb +18 -0
- data/spec/support/test_server.yml.example +2 -0
- metadata +362 -0
@@ -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
|