ripple 0.9.5 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/.gitignore +32 -0
  2. data/Gemfile +10 -9
  3. data/Guardfile +15 -0
  4. data/Rakefile +11 -40
  5. data/lib/rails/generators/ripple/configuration/configuration_generator.rb +0 -13
  6. data/lib/rails/generators/ripple/configuration/templates/ripple.yml +4 -4
  7. data/lib/rails/generators/ripple/js/js_generator.rb +0 -13
  8. data/lib/rails/generators/ripple/js/templates/js/contrib.js +0 -17
  9. data/lib/rails/generators/ripple/js/templates/js/ripple.js +0 -13
  10. data/lib/rails/generators/ripple/model/model_generator.rb +0 -14
  11. data/lib/rails/generators/ripple/model/templates/model.rb +1 -1
  12. data/lib/rails/generators/ripple/observer/observer_generator.rb +0 -14
  13. data/lib/rails/generators/ripple/test/test_generator.rb +7 -19
  14. data/lib/rails/generators/ripple_generator.rb +1 -14
  15. data/lib/ripple.rb +7 -14
  16. data/lib/ripple/associations.rb +129 -26
  17. data/lib/ripple/associations/embedded.rb +1 -15
  18. data/lib/ripple/associations/instantiators.rb +0 -13
  19. data/lib/ripple/associations/linked.rb +41 -19
  20. data/lib/ripple/associations/many.rb +0 -14
  21. data/lib/ripple/associations/many_embedded_proxy.rb +0 -14
  22. data/lib/ripple/associations/many_linked_proxy.rb +39 -18
  23. data/lib/ripple/associations/many_reference_proxy.rb +93 -0
  24. data/lib/ripple/associations/many_stored_key_proxy.rb +76 -0
  25. data/lib/ripple/associations/one.rb +1 -15
  26. data/lib/ripple/associations/one_embedded_proxy.rb +0 -14
  27. data/lib/ripple/associations/one_key_proxy.rb +58 -0
  28. data/lib/ripple/associations/one_linked_proxy.rb +4 -14
  29. data/lib/ripple/associations/one_stored_key_proxy.rb +43 -0
  30. data/lib/ripple/associations/proxy.rb +8 -14
  31. data/lib/ripple/attribute_methods.rb +19 -17
  32. data/lib/ripple/attribute_methods/dirty.rb +19 -15
  33. data/lib/ripple/attribute_methods/query.rb +0 -14
  34. data/lib/ripple/attribute_methods/read.rb +0 -14
  35. data/lib/ripple/attribute_methods/write.rb +0 -14
  36. data/lib/ripple/callbacks.rb +10 -16
  37. data/lib/ripple/conflict/basic_resolver.rb +82 -0
  38. data/lib/ripple/conflict/document_hooks.rb +20 -0
  39. data/lib/ripple/conflict/resolver.rb +71 -0
  40. data/lib/ripple/conflict/test_helper.rb +33 -0
  41. data/lib/ripple/conversion.rb +0 -14
  42. data/lib/ripple/core_ext.rb +1 -14
  43. data/lib/ripple/core_ext/casting.rb +19 -19
  44. data/lib/ripple/core_ext/object.rb +8 -0
  45. data/lib/ripple/document.rb +21 -16
  46. data/lib/ripple/document/bucket_access.rb +0 -14
  47. data/lib/ripple/document/finders.rb +17 -23
  48. data/lib/ripple/document/key.rb +8 -28
  49. data/lib/ripple/document/link.rb +30 -0
  50. data/lib/ripple/document/persistence.rb +12 -20
  51. data/lib/ripple/embedded_document.rb +10 -15
  52. data/lib/ripple/embedded_document/around_callbacks.rb +18 -0
  53. data/lib/ripple/embedded_document/finders.rb +7 -18
  54. data/lib/ripple/embedded_document/persistence.rb +5 -17
  55. data/lib/ripple/i18n.rb +0 -14
  56. data/lib/ripple/inspection.rb +21 -15
  57. data/lib/ripple/locale/en.yml +9 -13
  58. data/lib/ripple/nested_attributes.rb +33 -39
  59. data/lib/ripple/observable.rb +0 -13
  60. data/lib/ripple/properties.rb +1 -14
  61. data/lib/ripple/property_type_mismatch.rb +0 -14
  62. data/lib/ripple/railtie.rb +4 -14
  63. data/lib/ripple/railties/ripple.rake +86 -0
  64. data/lib/ripple/serialization.rb +11 -11
  65. data/lib/ripple/test_server.rb +36 -0
  66. data/lib/ripple/timestamps.rb +0 -13
  67. data/lib/ripple/translation.rb +4 -14
  68. data/lib/ripple/validations.rb +1 -15
  69. data/lib/ripple/validations/associated_validator.rb +26 -17
  70. data/lib/ripple/version.rb +3 -0
  71. data/ripple.gemspec +24 -35
  72. data/spec/integration/ripple/associations_spec.rb +89 -58
  73. data/spec/integration/ripple/conflict_resolution_spec.rb +298 -0
  74. data/spec/integration/ripple/nested_attributes_spec.rb +19 -19
  75. data/spec/integration/ripple/persistence_spec.rb +15 -34
  76. data/spec/integration/ripple/search_associations_spec.rb +31 -0
  77. data/spec/ripple/associations/many_embedded_proxy_spec.rb +23 -36
  78. data/spec/ripple/associations/many_linked_proxy_spec.rb +133 -45
  79. data/spec/ripple/associations/many_reference_proxy_spec.rb +170 -0
  80. data/spec/ripple/associations/many_stored_key_proxy_spec.rb +158 -0
  81. data/spec/ripple/associations/one_embedded_proxy_spec.rb +24 -37
  82. data/spec/ripple/associations/one_key_proxy_spec.rb +82 -0
  83. data/spec/ripple/associations/one_linked_proxy_spec.rb +22 -23
  84. data/spec/ripple/associations/one_stored_key_proxy_spec.rb +72 -0
  85. data/spec/ripple/associations/proxy_spec.rb +21 -15
  86. data/spec/ripple/associations_spec.rb +54 -23
  87. data/spec/ripple/attribute_methods/dirty_spec.rb +56 -5
  88. data/spec/ripple/attribute_methods_spec.rb +47 -21
  89. data/spec/ripple/bucket_access_spec.rb +4 -17
  90. data/spec/ripple/callbacks_spec.rb +52 -15
  91. data/spec/ripple/conflict/resolver_spec.rb +42 -0
  92. data/spec/ripple/conversion_spec.rb +2 -15
  93. data/spec/ripple/core_ext_spec.rb +12 -15
  94. data/spec/ripple/document/link_spec.rb +67 -0
  95. data/spec/ripple/document_spec.rb +34 -19
  96. data/spec/ripple/embedded_document/finders_spec.rb +12 -19
  97. data/spec/ripple/embedded_document/persistence_spec.rb +20 -26
  98. data/spec/ripple/embedded_document_spec.rb +44 -34
  99. data/spec/ripple/finders_spec.rb +58 -29
  100. data/spec/ripple/inspection_spec.rb +40 -37
  101. data/spec/ripple/key_spec.rb +5 -17
  102. data/spec/ripple/observable_spec.rb +3 -16
  103. data/spec/ripple/persistence_spec.rb +134 -18
  104. data/spec/ripple/properties_spec.rb +21 -15
  105. data/spec/ripple/ripple_spec.rb +1 -14
  106. data/spec/ripple/serialization_spec.rb +16 -17
  107. data/spec/ripple/timestamps_spec.rb +73 -52
  108. data/spec/ripple/validations/associated_validator_spec.rb +69 -25
  109. data/spec/ripple/validations_spec.rb +3 -16
  110. data/spec/spec_helper.rb +17 -18
  111. data/spec/support/associations.rb +1 -1
  112. data/spec/support/associations/proxies.rb +0 -13
  113. data/spec/support/integration_setup.rb +11 -0
  114. data/spec/support/mocks.rb +0 -13
  115. data/spec/support/models.rb +33 -2
  116. data/spec/support/models/address.rb +1 -16
  117. data/spec/support/models/box.rb +7 -14
  118. data/spec/support/models/car.rb +27 -1
  119. data/spec/support/models/cardboard_box.rb +0 -14
  120. data/spec/support/models/clock.rb +6 -15
  121. data/spec/support/models/clock_observer.rb +0 -14
  122. data/spec/support/models/credit_card.rb +5 -0
  123. data/spec/support/models/customer.rb +0 -14
  124. data/spec/support/models/email.rb +0 -14
  125. data/spec/support/models/family.rb +1 -13
  126. data/spec/support/models/favorite.rb +0 -14
  127. data/spec/support/models/invoice.rb +0 -14
  128. data/spec/support/models/late_invoice.rb +0 -14
  129. data/spec/support/models/nested.rb +12 -0
  130. data/spec/support/models/ninja.rb +7 -0
  131. data/spec/support/models/note.rb +0 -14
  132. data/spec/support/models/page.rb +1 -15
  133. data/spec/support/models/paid_invoice.rb +0 -14
  134. data/spec/support/models/post.rb +12 -0
  135. data/spec/support/models/profile.rb +7 -0
  136. data/spec/support/models/subscription.rb +26 -0
  137. data/spec/support/models/tasks.rb +0 -18
  138. data/spec/support/models/team.rb +11 -0
  139. data/spec/support/models/transactions.rb +17 -0
  140. data/spec/support/models/tree.rb +2 -16
  141. data/spec/support/models/user.rb +14 -16
  142. data/spec/support/models/widget.rb +8 -14
  143. data/spec/support/search.rb +14 -0
  144. data/spec/support/test_server.rb +22 -12
  145. data/spec/support/test_server.yml.example +14 -2
  146. metadata +223 -59
  147. data/lib/rails/generators/ripple/test/templates/test_server.rb +0 -46
  148. data/spec/support/models/driver.rb +0 -5
  149. data/spec/support/models/engine.rb +0 -4
  150. data/spec/support/models/passenger.rb +0 -5
  151. data/spec/support/models/seat.rb +0 -4
  152. data/spec/support/models/wheel.rb +0 -5
@@ -1,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