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.
- 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
|