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.
- data/.gitignore +32 -0
- data/Gemfile +10 -9
- data/Guardfile +15 -0
- data/Rakefile +11 -40
- data/lib/rails/generators/ripple/configuration/configuration_generator.rb +0 -13
- data/lib/rails/generators/ripple/configuration/templates/ripple.yml +4 -4
- data/lib/rails/generators/ripple/js/js_generator.rb +0 -13
- data/lib/rails/generators/ripple/js/templates/js/contrib.js +0 -17
- data/lib/rails/generators/ripple/js/templates/js/ripple.js +0 -13
- data/lib/rails/generators/ripple/model/model_generator.rb +0 -14
- data/lib/rails/generators/ripple/model/templates/model.rb +1 -1
- data/lib/rails/generators/ripple/observer/observer_generator.rb +0 -14
- data/lib/rails/generators/ripple/test/test_generator.rb +7 -19
- data/lib/rails/generators/ripple_generator.rb +1 -14
- data/lib/ripple.rb +7 -14
- data/lib/ripple/associations.rb +129 -26
- data/lib/ripple/associations/embedded.rb +1 -15
- data/lib/ripple/associations/instantiators.rb +0 -13
- data/lib/ripple/associations/linked.rb +41 -19
- data/lib/ripple/associations/many.rb +0 -14
- data/lib/ripple/associations/many_embedded_proxy.rb +0 -14
- data/lib/ripple/associations/many_linked_proxy.rb +39 -18
- 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 +1 -15
- data/lib/ripple/associations/one_embedded_proxy.rb +0 -14
- data/lib/ripple/associations/one_key_proxy.rb +58 -0
- data/lib/ripple/associations/one_linked_proxy.rb +4 -14
- data/lib/ripple/associations/one_stored_key_proxy.rb +43 -0
- data/lib/ripple/associations/proxy.rb +8 -14
- data/lib/ripple/attribute_methods.rb +19 -17
- data/lib/ripple/attribute_methods/dirty.rb +19 -15
- data/lib/ripple/attribute_methods/query.rb +0 -14
- data/lib/ripple/attribute_methods/read.rb +0 -14
- data/lib/ripple/attribute_methods/write.rb +0 -14
- data/lib/ripple/callbacks.rb +10 -16
- 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 +0 -14
- data/lib/ripple/core_ext.rb +1 -14
- data/lib/ripple/core_ext/casting.rb +19 -19
- data/lib/ripple/core_ext/object.rb +8 -0
- data/lib/ripple/document.rb +21 -16
- data/lib/ripple/document/bucket_access.rb +0 -14
- data/lib/ripple/document/finders.rb +17 -23
- data/lib/ripple/document/key.rb +8 -28
- data/lib/ripple/document/link.rb +30 -0
- data/lib/ripple/document/persistence.rb +12 -20
- data/lib/ripple/embedded_document.rb +10 -15
- data/lib/ripple/embedded_document/around_callbacks.rb +18 -0
- data/lib/ripple/embedded_document/finders.rb +7 -18
- data/lib/ripple/embedded_document/persistence.rb +5 -17
- data/lib/ripple/i18n.rb +0 -14
- data/lib/ripple/inspection.rb +21 -15
- data/lib/ripple/locale/en.yml +9 -13
- data/lib/ripple/nested_attributes.rb +33 -39
- data/lib/ripple/observable.rb +0 -13
- data/lib/ripple/properties.rb +1 -14
- data/lib/ripple/property_type_mismatch.rb +0 -14
- data/lib/ripple/railtie.rb +4 -14
- data/lib/ripple/railties/ripple.rake +86 -0
- data/lib/ripple/serialization.rb +11 -11
- data/lib/ripple/test_server.rb +36 -0
- data/lib/ripple/timestamps.rb +0 -13
- data/lib/ripple/translation.rb +4 -14
- data/lib/ripple/validations.rb +1 -15
- data/lib/ripple/validations/associated_validator.rb +26 -17
- data/lib/ripple/version.rb +3 -0
- data/ripple.gemspec +24 -35
- data/spec/integration/ripple/associations_spec.rb +89 -58
- data/spec/integration/ripple/conflict_resolution_spec.rb +298 -0
- data/spec/integration/ripple/nested_attributes_spec.rb +19 -19
- data/spec/integration/ripple/persistence_spec.rb +15 -34
- data/spec/integration/ripple/search_associations_spec.rb +31 -0
- data/spec/ripple/associations/many_embedded_proxy_spec.rb +23 -36
- data/spec/ripple/associations/many_linked_proxy_spec.rb +133 -45
- 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 +24 -37
- data/spec/ripple/associations/one_key_proxy_spec.rb +82 -0
- data/spec/ripple/associations/one_linked_proxy_spec.rb +22 -23
- data/spec/ripple/associations/one_stored_key_proxy_spec.rb +72 -0
- data/spec/ripple/associations/proxy_spec.rb +21 -15
- data/spec/ripple/associations_spec.rb +54 -23
- data/spec/ripple/attribute_methods/dirty_spec.rb +56 -5
- data/spec/ripple/attribute_methods_spec.rb +47 -21
- data/spec/ripple/bucket_access_spec.rb +4 -17
- data/spec/ripple/callbacks_spec.rb +52 -15
- data/spec/ripple/conflict/resolver_spec.rb +42 -0
- data/spec/ripple/conversion_spec.rb +2 -15
- data/spec/ripple/core_ext_spec.rb +12 -15
- data/spec/ripple/document/link_spec.rb +67 -0
- data/spec/ripple/document_spec.rb +34 -19
- data/spec/ripple/embedded_document/finders_spec.rb +12 -19
- data/spec/ripple/embedded_document/persistence_spec.rb +20 -26
- data/spec/ripple/embedded_document_spec.rb +44 -34
- data/spec/ripple/finders_spec.rb +58 -29
- data/spec/ripple/inspection_spec.rb +40 -37
- data/spec/ripple/key_spec.rb +5 -17
- data/spec/ripple/observable_spec.rb +3 -16
- data/spec/ripple/persistence_spec.rb +134 -18
- data/spec/ripple/properties_spec.rb +21 -15
- data/spec/ripple/ripple_spec.rb +1 -14
- data/spec/ripple/serialization_spec.rb +16 -17
- data/spec/ripple/timestamps_spec.rb +73 -52
- data/spec/ripple/validations/associated_validator_spec.rb +69 -25
- data/spec/ripple/validations_spec.rb +3 -16
- data/spec/spec_helper.rb +17 -18
- data/spec/support/associations.rb +1 -1
- data/spec/support/associations/proxies.rb +0 -13
- data/spec/support/integration_setup.rb +11 -0
- data/spec/support/mocks.rb +0 -13
- data/spec/support/models.rb +33 -2
- data/spec/support/models/address.rb +1 -16
- data/spec/support/models/box.rb +7 -14
- data/spec/support/models/car.rb +27 -1
- data/spec/support/models/cardboard_box.rb +0 -14
- data/spec/support/models/clock.rb +6 -15
- data/spec/support/models/clock_observer.rb +0 -14
- data/spec/support/models/credit_card.rb +5 -0
- data/spec/support/models/customer.rb +0 -14
- data/spec/support/models/email.rb +0 -14
- data/spec/support/models/family.rb +1 -13
- data/spec/support/models/favorite.rb +0 -14
- data/spec/support/models/invoice.rb +0 -14
- data/spec/support/models/late_invoice.rb +0 -14
- data/spec/support/models/nested.rb +12 -0
- data/spec/support/models/ninja.rb +7 -0
- data/spec/support/models/note.rb +0 -14
- data/spec/support/models/page.rb +1 -15
- data/spec/support/models/paid_invoice.rb +0 -14
- data/spec/support/models/post.rb +12 -0
- data/spec/support/models/profile.rb +7 -0
- data/spec/support/models/subscription.rb +26 -0
- data/spec/support/models/tasks.rb +0 -18
- data/spec/support/models/team.rb +11 -0
- data/spec/support/models/transactions.rb +17 -0
- data/spec/support/models/tree.rb +2 -16
- data/spec/support/models/user.rb +14 -16
- data/spec/support/models/widget.rb +8 -14
- data/spec/support/search.rb +14 -0
- data/spec/support/test_server.rb +22 -12
- data/spec/support/test_server.yml.example +14 -2
- metadata +223 -59
- data/lib/rails/generators/ripple/test/templates/test_server.rb +0 -46
- data/spec/support/models/driver.rb +0 -5
- data/spec/support/models/engine.rb +0 -4
- data/spec/support/models/passenger.rb +0 -5
- data/spec/support/models/seat.rb +0 -4
- data/spec/support/models/wheel.rb +0 -5
|
@@ -1,61 +1,48 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
44
|
-
@
|
|
45
|
-
@
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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.
|
|
40
|
+
@user.user_profile = @profile
|
|
54
41
|
@user.save
|
|
55
42
|
@found = User.find(@user.key)
|
|
56
|
-
@found.
|
|
57
|
-
@found.
|
|
58
|
-
@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.
|
|
112
|
-
@found.friends.
|
|
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.
|
|
107
|
+
@found.emergency_contact.should == @friend1
|
|
121
108
|
end
|
|
122
109
|
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|