ripple 0.9.5 → 1.0.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. data/.gitignore +32 -0
  2. data/Gemfile +10 -9
  3. data/Guardfile +15 -0
  4. data/Rakefile +11 -40
  5. data/lib/rails/generators/ripple/configuration/configuration_generator.rb +0 -13
  6. data/lib/rails/generators/ripple/configuration/templates/ripple.yml +4 -4
  7. data/lib/rails/generators/ripple/js/js_generator.rb +0 -13
  8. data/lib/rails/generators/ripple/js/templates/js/contrib.js +0 -17
  9. data/lib/rails/generators/ripple/js/templates/js/ripple.js +0 -13
  10. data/lib/rails/generators/ripple/model/model_generator.rb +0 -14
  11. data/lib/rails/generators/ripple/model/templates/model.rb +1 -1
  12. data/lib/rails/generators/ripple/observer/observer_generator.rb +0 -14
  13. data/lib/rails/generators/ripple/test/test_generator.rb +7 -19
  14. data/lib/rails/generators/ripple_generator.rb +1 -14
  15. data/lib/ripple.rb +7 -14
  16. data/lib/ripple/associations.rb +129 -26
  17. data/lib/ripple/associations/embedded.rb +1 -15
  18. data/lib/ripple/associations/instantiators.rb +0 -13
  19. data/lib/ripple/associations/linked.rb +41 -19
  20. data/lib/ripple/associations/many.rb +0 -14
  21. data/lib/ripple/associations/many_embedded_proxy.rb +0 -14
  22. data/lib/ripple/associations/many_linked_proxy.rb +39 -18
  23. data/lib/ripple/associations/many_reference_proxy.rb +93 -0
  24. data/lib/ripple/associations/many_stored_key_proxy.rb +76 -0
  25. data/lib/ripple/associations/one.rb +1 -15
  26. data/lib/ripple/associations/one_embedded_proxy.rb +0 -14
  27. data/lib/ripple/associations/one_key_proxy.rb +58 -0
  28. data/lib/ripple/associations/one_linked_proxy.rb +4 -14
  29. data/lib/ripple/associations/one_stored_key_proxy.rb +43 -0
  30. data/lib/ripple/associations/proxy.rb +8 -14
  31. data/lib/ripple/attribute_methods.rb +19 -17
  32. data/lib/ripple/attribute_methods/dirty.rb +19 -15
  33. data/lib/ripple/attribute_methods/query.rb +0 -14
  34. data/lib/ripple/attribute_methods/read.rb +0 -14
  35. data/lib/ripple/attribute_methods/write.rb +0 -14
  36. data/lib/ripple/callbacks.rb +10 -16
  37. data/lib/ripple/conflict/basic_resolver.rb +82 -0
  38. data/lib/ripple/conflict/document_hooks.rb +20 -0
  39. data/lib/ripple/conflict/resolver.rb +71 -0
  40. data/lib/ripple/conflict/test_helper.rb +33 -0
  41. data/lib/ripple/conversion.rb +0 -14
  42. data/lib/ripple/core_ext.rb +1 -14
  43. data/lib/ripple/core_ext/casting.rb +19 -19
  44. data/lib/ripple/core_ext/object.rb +8 -0
  45. data/lib/ripple/document.rb +21 -16
  46. data/lib/ripple/document/bucket_access.rb +0 -14
  47. data/lib/ripple/document/finders.rb +17 -23
  48. data/lib/ripple/document/key.rb +8 -28
  49. data/lib/ripple/document/link.rb +30 -0
  50. data/lib/ripple/document/persistence.rb +12 -20
  51. data/lib/ripple/embedded_document.rb +10 -15
  52. data/lib/ripple/embedded_document/around_callbacks.rb +18 -0
  53. data/lib/ripple/embedded_document/finders.rb +7 -18
  54. data/lib/ripple/embedded_document/persistence.rb +5 -17
  55. data/lib/ripple/i18n.rb +0 -14
  56. data/lib/ripple/inspection.rb +21 -15
  57. data/lib/ripple/locale/en.yml +9 -13
  58. data/lib/ripple/nested_attributes.rb +33 -39
  59. data/lib/ripple/observable.rb +0 -13
  60. data/lib/ripple/properties.rb +1 -14
  61. data/lib/ripple/property_type_mismatch.rb +0 -14
  62. data/lib/ripple/railtie.rb +4 -14
  63. data/lib/ripple/railties/ripple.rake +86 -0
  64. data/lib/ripple/serialization.rb +11 -11
  65. data/lib/ripple/test_server.rb +36 -0
  66. data/lib/ripple/timestamps.rb +0 -13
  67. data/lib/ripple/translation.rb +4 -14
  68. data/lib/ripple/validations.rb +1 -15
  69. data/lib/ripple/validations/associated_validator.rb +26 -17
  70. data/lib/ripple/version.rb +3 -0
  71. data/ripple.gemspec +24 -35
  72. data/spec/integration/ripple/associations_spec.rb +89 -58
  73. data/spec/integration/ripple/conflict_resolution_spec.rb +298 -0
  74. data/spec/integration/ripple/nested_attributes_spec.rb +19 -19
  75. data/spec/integration/ripple/persistence_spec.rb +15 -34
  76. data/spec/integration/ripple/search_associations_spec.rb +31 -0
  77. data/spec/ripple/associations/many_embedded_proxy_spec.rb +23 -36
  78. data/spec/ripple/associations/many_linked_proxy_spec.rb +133 -45
  79. data/spec/ripple/associations/many_reference_proxy_spec.rb +170 -0
  80. data/spec/ripple/associations/many_stored_key_proxy_spec.rb +158 -0
  81. data/spec/ripple/associations/one_embedded_proxy_spec.rb +24 -37
  82. data/spec/ripple/associations/one_key_proxy_spec.rb +82 -0
  83. data/spec/ripple/associations/one_linked_proxy_spec.rb +22 -23
  84. data/spec/ripple/associations/one_stored_key_proxy_spec.rb +72 -0
  85. data/spec/ripple/associations/proxy_spec.rb +21 -15
  86. data/spec/ripple/associations_spec.rb +54 -23
  87. data/spec/ripple/attribute_methods/dirty_spec.rb +56 -5
  88. data/spec/ripple/attribute_methods_spec.rb +47 -21
  89. data/spec/ripple/bucket_access_spec.rb +4 -17
  90. data/spec/ripple/callbacks_spec.rb +52 -15
  91. data/spec/ripple/conflict/resolver_spec.rb +42 -0
  92. data/spec/ripple/conversion_spec.rb +2 -15
  93. data/spec/ripple/core_ext_spec.rb +12 -15
  94. data/spec/ripple/document/link_spec.rb +67 -0
  95. data/spec/ripple/document_spec.rb +34 -19
  96. data/spec/ripple/embedded_document/finders_spec.rb +12 -19
  97. data/spec/ripple/embedded_document/persistence_spec.rb +20 -26
  98. data/spec/ripple/embedded_document_spec.rb +44 -34
  99. data/spec/ripple/finders_spec.rb +58 -29
  100. data/spec/ripple/inspection_spec.rb +40 -37
  101. data/spec/ripple/key_spec.rb +5 -17
  102. data/spec/ripple/observable_spec.rb +3 -16
  103. data/spec/ripple/persistence_spec.rb +134 -18
  104. data/spec/ripple/properties_spec.rb +21 -15
  105. data/spec/ripple/ripple_spec.rb +1 -14
  106. data/spec/ripple/serialization_spec.rb +16 -17
  107. data/spec/ripple/timestamps_spec.rb +73 -52
  108. data/spec/ripple/validations/associated_validator_spec.rb +69 -25
  109. data/spec/ripple/validations_spec.rb +3 -16
  110. data/spec/spec_helper.rb +17 -18
  111. data/spec/support/associations.rb +1 -1
  112. data/spec/support/associations/proxies.rb +0 -13
  113. data/spec/support/integration_setup.rb +11 -0
  114. data/spec/support/mocks.rb +0 -13
  115. data/spec/support/models.rb +33 -2
  116. data/spec/support/models/address.rb +1 -16
  117. data/spec/support/models/box.rb +7 -14
  118. data/spec/support/models/car.rb +27 -1
  119. data/spec/support/models/cardboard_box.rb +0 -14
  120. data/spec/support/models/clock.rb +6 -15
  121. data/spec/support/models/clock_observer.rb +0 -14
  122. data/spec/support/models/credit_card.rb +5 -0
  123. data/spec/support/models/customer.rb +0 -14
  124. data/spec/support/models/email.rb +0 -14
  125. data/spec/support/models/family.rb +1 -13
  126. data/spec/support/models/favorite.rb +0 -14
  127. data/spec/support/models/invoice.rb +0 -14
  128. data/spec/support/models/late_invoice.rb +0 -14
  129. data/spec/support/models/nested.rb +12 -0
  130. data/spec/support/models/ninja.rb +7 -0
  131. data/spec/support/models/note.rb +0 -14
  132. data/spec/support/models/page.rb +1 -15
  133. data/spec/support/models/paid_invoice.rb +0 -14
  134. data/spec/support/models/post.rb +12 -0
  135. data/spec/support/models/profile.rb +7 -0
  136. data/spec/support/models/subscription.rb +26 -0
  137. data/spec/support/models/tasks.rb +0 -18
  138. data/spec/support/models/team.rb +11 -0
  139. data/spec/support/models/transactions.rb +17 -0
  140. data/spec/support/models/tree.rb +2 -16
  141. data/spec/support/models/user.rb +14 -16
  142. data/spec/support/models/widget.rb +8 -14
  143. data/spec/support/search.rb +14 -0
  144. data/spec/support/test_server.rb +22 -12
  145. data/spec/support/test_server.yml.example +14 -2
  146. metadata +223 -59
  147. data/lib/rails/generators/ripple/test/templates/test_server.rb +0 -46
  148. data/spec/support/models/driver.rb +0 -5
  149. data/spec/support/models/engine.rb +0 -4
  150. data/spec/support/models/passenger.rb +0 -5
  151. data/spec/support/models/seat.rb +0 -4
  152. data/spec/support/models/wheel.rb +0 -5
@@ -1,14 +1,13 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
1
+ require 'spec_helper'
2
2
  require 'active_support/core_ext/array'
3
3
 
4
- describe Ripple::NestedAttributes do
5
- require 'support/models/car'
6
- require 'support/models/driver'
7
- require 'support/models/passenger'
8
- require 'support/models/engine'
9
- require 'support/models/seat'
10
- require 'support/models/wheel'
11
- require 'support/test_server'
4
+ describe Ripple::NestedAttributes, :integration => true do
5
+ # require 'support/models/car'
6
+ # require 'support/models/driver'
7
+ # require 'support/models/passenger'
8
+ # require 'support/models/engine'
9
+ # require 'support/models/seat'
10
+ # require 'support/models/wheel'
12
11
 
13
12
  context "one :driver (link)" do
14
13
  subject { Car.new }
@@ -93,10 +92,9 @@ describe Ripple::NestedAttributes do
93
92
  end
94
93
 
95
94
  it "should save the children when saving the parent" do
96
- subject.passengers.each do |passenger|
97
- passenger.should_receive(:save)
98
- end
99
95
  subject.save
96
+ found_subject = Car.find(subject.key)
97
+ found_subject.passengers.map(&:name).should =~ %w[ Joe Sue Pat ]
100
98
  end
101
99
  end
102
100
 
@@ -134,12 +132,16 @@ describe Ripple::NestedAttributes do
134
132
 
135
133
  it "should save the child when saving the parent" do
136
134
  subject.passengers_attributes = [ { :key => passenger1.key, :name => 'UPDATED One' },
137
- { :key => passenger1.key, :name => 'UPDATED Two' },
138
- { :key => passenger1.key, :name => 'UPDATED Three' } ]
139
- subject.passengers.each do |passenger|
140
- passenger.should_receive(:save)
141
- end
135
+ { :key => passenger2.key, :name => 'UPDATED Two' },
136
+ { :key => passenger3.key, :name => 'UPDATED Three' } ]
142
137
  subject.save
138
+
139
+ found_subject = Car.find(subject.key)
140
+ found_subject.passengers.map(&:name).should =~ [
141
+ 'UPDATED One',
142
+ 'UPDATED Two',
143
+ 'UPDATED Three'
144
+ ]
143
145
  end
144
146
  end
145
147
  end
@@ -255,7 +257,5 @@ describe Ripple::NestedAttributes do
255
257
  subject.save
256
258
  subject.wheels.should == []
257
259
  end
258
-
259
260
  end
260
-
261
261
  end
@@ -1,41 +1,16 @@
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 Persistence" do
17
- require 'support/test_server'
18
-
19
- before :all do
20
- Object.module_eval do
21
- class Widget
22
- include Ripple::Document
23
- property :name, String
24
- property :size, Integer
25
- end
26
- end
27
- end
28
-
29
4
  before :each do
30
5
  @widget = Widget.new
31
6
  end
32
-
7
+
33
8
  it "should save an object to the riak database" do
34
9
  @widget.save
35
10
  @found = Widget.find(@widget.key)
36
11
  @found.should be_a(Widget)
37
12
  end
38
-
13
+
39
14
  it "should save attributes properly to riak" do
40
15
  @widget.attributes = {:name => 'Sprocket', :size => 10}
41
16
  @widget.save
@@ -43,13 +18,19 @@ describe "Ripple Persistence" do
43
18
  @found.name.should == 'Sprocket'
44
19
  @found.size.should == 10
45
20
  end
46
-
47
- after :each do
48
- Widget.destroy_all
21
+ end
22
+
23
+ describe Ripple::Document do
24
+ let(:custom_data) { Subscription::MyCustomType.new('bar') }
25
+ let(:days_of_month) { Set.new([1, 7, 15, 23]) }
26
+ let(:subscription) { Subscription.create!(:custom_data => custom_data, :days_of_month => days_of_month) }
27
+ let(:found_subscription) { Subscription.find(subscription.key) }
28
+
29
+ it 'allows properties with custom types to be saved and restored from riak' do
30
+ found_subscription.custom_data.should == custom_data
49
31
  end
50
32
 
51
- after :all do
52
- Object.send(:remove_const, :Widget)
33
+ it 'allows Set properties to be saved and restored from riak' do
34
+ found_subscription.days_of_month.should == days_of_month
53
35
  end
54
-
55
36
  end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Ripple Search Associations", :integration => true, :search => true do
4
+ class SearchTransaction
5
+ include Ripple::Document
6
+ property :search_account_key, String
7
+ property :name, String
8
+ end
9
+
10
+ class SearchAccount
11
+ include Ripple::Document
12
+ many :search_transactions, :using => :reference
13
+ property :email, String
14
+ end
15
+
16
+ before :each do
17
+ @account = SearchAccount.new(:email => 'riak@ripple.com')
18
+ @transaction1 = SearchTransaction.new(:name => 'One')
19
+ @transaction2 = SearchTransaction.new(:name => 'Two')
20
+ end
21
+
22
+ it "should save a many referenced association" do
23
+ @account.save!
24
+ @account.search_transactions << @transaction1 << @transaction2
25
+ @transaction1.save!
26
+ @transaction2.save!
27
+ @found = SearchAccount.find(@account.key)
28
+ @found.search_transactions.map(&:key).should include(@transaction1.key)
29
+ @found.search_transactions.map(&:key).should include(@transaction2.key)
30
+ end
31
+ end
@@ -1,81 +1,68 @@
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::ManyEmbeddedProxy do
17
- require 'support/models/user'
18
- require 'support/models/address'
19
- require 'support/models/note'
20
-
4
+ # require 'support/models/user'
5
+ # require 'support/models/address'
6
+ # require 'support/models/note'
7
+
21
8
  before :each do
22
- @user = User.new
9
+ @user = User.new(:email => "riak@ripple.com")
23
10
  @address = Address.new
24
11
  @addr = Address.new(:street => '123 Somewhere')
25
12
  @note = Note.new
26
13
  end
27
-
14
+
28
15
  it "should not have children before any are set" do
29
16
  @user.addresses.should == []
30
17
  end
31
-
18
+
32
19
  it "should be able to set and get its children" do
33
20
  Address.stub!(:instantiate).and_return(@address)
34
21
  @user.addresses = [@address]
35
22
  @user.addresses.should == [@address]
36
23
  end
37
-
24
+
38
25
  it "should set the parent document on the children when assigning" do
39
26
  @user.addresses = [@address]
40
27
  @address._parent_document.should == @user
41
28
  end
42
-
29
+
43
30
  it "should return the assignment when assigning" do
44
31
  rtn = @user.addresses = [@address]
45
32
  rtn.should == [@address]
46
33
  end
47
-
34
+
48
35
  it "should set the parent document on the children when accessing" do
49
36
  @user.addresses = [@address]
50
37
  @user.addresses.first._parent_document.should == @user
51
38
  end
52
-
39
+
53
40
  it "should be able to replace its children with different children" do
54
41
  @user.addresses = [@address]
55
42
  @user.addresses.first.street.should be_blank
56
43
  @user.addresses = [@addr]
57
44
  @user.addresses.first.street.should == '123 Somewhere'
58
45
  end
59
-
46
+
60
47
  it "should be able to add to its children" do
61
48
  Address.stub!(:instantiate).and_return(@address)
62
49
  @user.addresses = [@address]
63
50
  @user.addresses << @address
64
51
  @user.addresses.should == [@address, @address]
65
52
  end
66
-
53
+
67
54
  it "should be able to chain calls to adding children" do
68
55
  Address.stub!(:instantiate).and_return(@address)
69
56
  @user.addresses = [@address]
70
57
  @user.addresses << @address << @address << @address
71
58
  @user.addresses.should == [@address, @address, @address, @address]
72
59
  end
73
-
60
+
74
61
  it "should set the parent document when adding to its children" do
75
62
  @user.addresses << @address
76
63
  @user.addresses.first._parent_document.should == @user
77
64
  end
78
-
65
+
79
66
  it "should be able to count its children" do
80
67
  @user.addresses = [@address, @address]
81
68
  @user.addresses.count.should == 2
@@ -85,37 +72,37 @@ describe Ripple::Associations::ManyEmbeddedProxy do
85
72
  Address.stub!(:new).and_return(@address)
86
73
  @user.addresses.build.should == @address
87
74
  end
88
-
75
+
89
76
  it "should assign a parent to the children created with instantiate_target" do
90
77
  Address.stub!(:new).and_return(@address)
91
78
  @address._parent_document.should be_nil
92
79
  @user.addresses.build._parent_document.should == @user
93
80
  end
94
-
81
+
95
82
  it "should validate the children when saving the parent" do
96
83
  @user.valid?.should be_true
97
84
  @user.addresses << @address
98
85
  @address.valid?.should be_false
99
86
  @user.valid?.should be_false
100
87
  end
101
-
88
+
102
89
  it "should not save the root document when a child is invalid" do
103
90
  @user.addresses << @address
104
91
  @user.save.should be_false
105
92
  end
106
-
93
+
107
94
  it "should allow embedding documents in embedded documents" do
108
95
  @user.addresses << @address
109
96
  @address.notes << @note
110
97
  @note._root_document.should == @user
111
98
  @note._parent_document.should == @address
112
99
  end
113
-
100
+
114
101
  it "should allow assiging child documents as an array of hashes" do
115
102
  @user.attributes = {'addresses' => [{'street' => '123 Somewhere'}]}
116
103
  @user.addresses.first.street.should == '123 Somewhere'
117
104
  end
118
-
105
+
119
106
  it "should return an array from to_ary" do
120
107
  Address.stub!(:instantiate).and_return(@address)
121
108
  @user.addresses << @address
@@ -127,7 +114,7 @@ describe Ripple::Associations::ManyEmbeddedProxy do
127
114
  lambda { @user.addresses = @address }.should raise_error
128
115
  lambda { @user.addresses = [@note] }.should raise_error
129
116
  end
130
-
117
+
131
118
  it "should not add the associated validator multiple times" do
132
119
  #$stderr.puts User.validators.collect(&:inspect)
133
120
  User.validators_on(:addresses).count.should eq(1)
@@ -1,25 +1,13 @@
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::ManyLinkedProxy do
17
- require 'support/models/tasks'
4
+ # require 'support/models/tasks'
18
5
 
19
6
  before :each do
20
7
  @person = Person.new {|p| p.key = "riak-user" }
21
8
  @task = Task.new {|t| t.key = "one" }
22
9
  @other_task = Task.new {|t| t.key = "two" }
10
+ @third_task = Task.new {|t| t.key = "three" }
23
11
  [@person, @task, @other_task].each do |doc|
24
12
  doc.stub!(:new?).and_return(false)
25
13
  end
@@ -35,7 +23,13 @@ describe Ripple::Associations::ManyLinkedProxy do
35
23
 
36
24
  it "should set the links on the RObject when assigning" do
37
25
  @person.tasks = [@task]
38
- @person.robject.links.should include(@task.robject.to_link("tasks"))
26
+ @person.robject.links.should include(@task.to_link("tasks"))
27
+ end
28
+
29
+ it "should be able to replace the entire collection of documents (even appended ones)" do
30
+ @person.tasks << @task
31
+ @person.tasks = [@other_task]
32
+ @person.tasks.should == [@other_task]
39
33
  end
40
34
 
41
35
  it "should return the assigned documents when assigning" do
@@ -43,47 +37,39 @@ describe Ripple::Associations::ManyLinkedProxy do
43
37
  t.should == [@task]
44
38
  end
45
39
 
46
- it "should save unsaved documents when assigning" do
47
- @task.should_receive(:new?).and_return(true)
48
- @task.should_receive(:save).and_return(true)
49
- @person.tasks = [@task]
50
- end
51
-
52
40
  it "should link-walk to the associated documents when accessing" do
53
- @person.robject.links << @task.robject.to_link("tasks")
41
+ @person.robject.links << @task.to_link("tasks")
54
42
  @person.robject.should_receive(:walk).with(Riak::WalkSpec.new(:bucket => "tasks", :tag => "tasks")).and_return([])
55
43
  @person.tasks.should == []
56
44
  end
57
45
 
58
- it "should replace associated documents with a new set" do
59
- @person.tasks = [@task]
60
- @person.tasks = [@other_task]
61
- @person.tasks.should == [@other_task]
62
- end
46
+ it "handles conflict appropriately by selecting the linked-walk robjects that match the links" do
47
+ @person.robject.links << @task.to_link("tasks") << @other_task.to_link("tasks")
48
+ @person.robject.
49
+ should_receive(:walk).
50
+ with(Riak::WalkSpec.new(:bucket => "tasks", :tag => "tasks")).
51
+ and_return([[@task.robject, @other_task.robject, @third_task.robject]])
63
52
 
64
- it "should be able to append documents to the associated set" do
65
- @person.tasks << @task
66
- @person.tasks << @other_task
67
- @person.should have(2).tasks
53
+ @person.tasks.should == [@task, @other_task]
68
54
  end
69
55
 
70
- it "should be able to chain calls to adding documents" do
71
- @person.tasks << @task << @other_task
72
- @person.should have(2).tasks
56
+ it "allows the links to be replaced directly" do
57
+ @person.tasks = [@task]
58
+ @person.tasks.__send__(:should_receive, :reset)
59
+ @person.tasks.__send__(:links).should == [@task.robject.to_link("tasks")]
60
+ @person.tasks.replace_links([@other_task, @third_task].map { |t| t.robject.to_link("tasks") })
61
+ @person.tasks.__send__(:links).should == [@other_task, @third_task].map { |t| t.robject.to_link("tasks") }
73
62
  end
74
63
 
75
- it "should set the links on the RObject when appending" do
76
- @person.tasks << @task << @other_task
77
- [@task, @other_task].each do |t|
78
- @person.robject.links.should include(t.robject.to_link("tasks"))
79
- end
64
+ it "should replace associated documents with a new set" do
65
+ @person.tasks = [@task]
66
+ @person.tasks = [@other_task]
67
+ @person.tasks.should == [@other_task]
80
68
  end
81
69
 
82
- it "should be able to count the associated documents" do
83
- @person.tasks << @task
84
- @person.tasks.count.should == 1
85
- @person.tasks << @other_task
86
- @person.tasks.count.should == 2
70
+ it "asks the keys set for the count to avoid having to unnecessarily load all documents" do
71
+ @person.tasks.keys.stub(:size => 17)
72
+ @person.tasks.count.should == 17
87
73
  end
88
74
 
89
75
  # it "should be able to build a new associated document" do
@@ -100,4 +86,106 @@ describe Ripple::Associations::ManyLinkedProxy do
100
86
  lambda { @person.tasks = @task }.should raise_error
101
87
  lambda { @person.tasks = [@person] }.should raise_error
102
88
  end
89
+
90
+ describe "#<< (when the target has not already been loaded)" do
91
+ it "avoids link-walking when adding a record to an unloaded association" do
92
+ @person.robject.should_not_receive(:walk)
93
+ @person.tasks << @task
94
+ end
95
+
96
+ it "should be able to count the associated documents" do
97
+ @person.tasks << @task
98
+ @person.tasks.count.should == 1
99
+ @person.tasks << @other_task
100
+ @person.tasks.count.should == 2
101
+ end
102
+
103
+ it "maintains the list of keys properly as new documents are appended" do
104
+ @person.tasks << @task
105
+ @person.tasks.should have(1).key
106
+ @person.tasks << @other_task
107
+ @person.tasks.should have(2).keys
108
+ end
109
+
110
+ it "should be able to append documents to the associated set" do
111
+ @person.tasks << @task
112
+ @person.tasks << @other_task
113
+ @person.should have(2).tasks
114
+ end
115
+
116
+ it "should be able to chain calls to adding documents" do
117
+ @person.tasks << @task << @other_task
118
+ @person.should have(2).tasks
119
+ end
120
+
121
+ it "should set the links on the RObject when appending" do
122
+ @person.tasks << @task << @other_task
123
+ [@task, @other_task].each do |t|
124
+ @person.robject.links.should include(t.to_link("tasks"))
125
+ end
126
+ end
127
+
128
+ it "does not return duplicates (for when the object has been appended and it's robject is found while walking the links)" do
129
+ @person.tasks.stub(:robjects => [@task.robject])
130
+ @person.tasks.reset
131
+ @person.tasks << @task
132
+ @person.tasks.should == [@task]
133
+ end
134
+ end
135
+
136
+ describe "#reset" do
137
+ it "clears appended documents" do
138
+ @person.tasks << @task
139
+ @person.tasks.reset
140
+ @person.tasks.should == []
141
+ end
142
+ end
143
+
144
+ describe "#keys" do
145
+ let(:link_keys) { %w[ 1 2 3 ] }
146
+ let(:links) { link_keys.map { |k| Riak::Link.new('tasks', k, 'task') } }
147
+
148
+ before(:each) do
149
+ @person.tasks.stub(:links => links)
150
+ end
151
+
152
+ it "returns a set of keys" do
153
+ @person.tasks.keys.should be_a(Set)
154
+ @person.tasks.keys.to_a.should == link_keys
155
+ end
156
+
157
+ it "is memoized between calls" do
158
+ @person.tasks.keys.should equal(@person.tasks.keys)
159
+ end
160
+
161
+ it "is cleared when the association is reset" do
162
+ orig_set = @person.tasks.keys
163
+ @person.tasks.reset
164
+ @person.tasks.keys.should_not equal(orig_set)
165
+ end
166
+
167
+ it "is cleared when the association is replaced" do
168
+ orig_set = @person.tasks.keys
169
+ @person.tasks.replace([@task])
170
+ @person.tasks.keys.should_not equal(orig_set)
171
+ end
172
+ end
173
+
174
+ describe "#include?" do
175
+ it "delegates to the set of keys so as not to unnecessarily load the associated documents" do
176
+ @person.tasks.keys.should_receive(:include?).with(@task.key).and_return(true)
177
+ @person.tasks.include?(@task).should be_true
178
+ end
179
+
180
+ it "short-circuits and returns false if the given object is not a ripple document" do
181
+ @person.tasks.keys.should_not_receive(:include?)
182
+ @person.tasks.include?(Object.new).should be_false
183
+ end
184
+
185
+ it "returns false if the document's bucket is different from the associations bucket, even if the keys are the same" do
186
+ @person.tasks << @task
187
+ other_person = Person.new { |p| p.key = @task.key }
188
+ @person.tasks.include?(other_person).should be_false
189
+ end
190
+ end
103
191
  end