seomoz-ripple 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. data/Gemfile +20 -0
  2. data/Guardfile +15 -0
  3. data/Rakefile +88 -0
  4. data/lib/rails/generators/ripple/configuration/configuration_generator.rb +13 -0
  5. data/lib/rails/generators/ripple/configuration/templates/ripple.yml +24 -0
  6. data/lib/rails/generators/ripple/js/js_generator.rb +13 -0
  7. data/lib/rails/generators/ripple/js/templates/js/contrib.js +63 -0
  8. data/lib/rails/generators/ripple/js/templates/js/iso8601.js +76 -0
  9. data/lib/rails/generators/ripple/js/templates/js/ripple.js +132 -0
  10. data/lib/rails/generators/ripple/model/model_generator.rb +20 -0
  11. data/lib/rails/generators/ripple/model/templates/model.rb +10 -0
  12. data/lib/rails/generators/ripple/observer/observer_generator.rb +16 -0
  13. data/lib/rails/generators/ripple/observer/templates/observer.rb +4 -0
  14. data/lib/rails/generators/ripple/test/templates/test_server.rb +46 -0
  15. data/lib/rails/generators/ripple/test/test_generator.rb +39 -0
  16. data/lib/rails/generators/ripple_generator.rb +78 -0
  17. data/lib/ripple.rb +79 -0
  18. data/lib/ripple/associations.rb +356 -0
  19. data/lib/ripple/associations/embedded.rb +35 -0
  20. data/lib/ripple/associations/instantiators.rb +26 -0
  21. data/lib/ripple/associations/linked.rb +65 -0
  22. data/lib/ripple/associations/many.rb +38 -0
  23. data/lib/ripple/associations/many_embedded_proxy.rb +38 -0
  24. data/lib/ripple/associations/many_linked_proxy.rb +66 -0
  25. data/lib/ripple/associations/many_reference_proxy.rb +93 -0
  26. data/lib/ripple/associations/many_stored_key_proxy.rb +76 -0
  27. data/lib/ripple/associations/one.rb +20 -0
  28. data/lib/ripple/associations/one_embedded_proxy.rb +35 -0
  29. data/lib/ripple/associations/one_key_proxy.rb +58 -0
  30. data/lib/ripple/associations/one_linked_proxy.rb +22 -0
  31. data/lib/ripple/associations/one_stored_key_proxy.rb +43 -0
  32. data/lib/ripple/associations/proxy.rb +118 -0
  33. data/lib/ripple/attribute_methods.rb +118 -0
  34. data/lib/ripple/attribute_methods/dirty.rb +50 -0
  35. data/lib/ripple/attribute_methods/query.rb +34 -0
  36. data/lib/ripple/attribute_methods/read.rb +26 -0
  37. data/lib/ripple/attribute_methods/write.rb +25 -0
  38. data/lib/ripple/callbacks.rb +74 -0
  39. data/lib/ripple/conflict/basic_resolver.rb +82 -0
  40. data/lib/ripple/conflict/document_hooks.rb +20 -0
  41. data/lib/ripple/conflict/resolver.rb +71 -0
  42. data/lib/ripple/conflict/test_helper.rb +33 -0
  43. data/lib/ripple/conversion.rb +28 -0
  44. data/lib/ripple/core_ext.rb +2 -0
  45. data/lib/ripple/core_ext/casting.rb +148 -0
  46. data/lib/ripple/core_ext/object.rb +8 -0
  47. data/lib/ripple/document.rb +104 -0
  48. data/lib/ripple/document/bucket_access.rb +25 -0
  49. data/lib/ripple/document/finders.rb +131 -0
  50. data/lib/ripple/document/key.rb +43 -0
  51. data/lib/ripple/document/link.rb +30 -0
  52. data/lib/ripple/document/persistence.rb +115 -0
  53. data/lib/ripple/embedded_document.rb +64 -0
  54. data/lib/ripple/embedded_document/around_callbacks.rb +18 -0
  55. data/lib/ripple/embedded_document/finders.rb +26 -0
  56. data/lib/ripple/embedded_document/persistence.rb +77 -0
  57. data/lib/ripple/i18n.rb +2 -0
  58. data/lib/ripple/inspection.rb +32 -0
  59. data/lib/ripple/locale/en.yml +21 -0
  60. data/lib/ripple/nested_attributes.rb +265 -0
  61. data/lib/ripple/observable.rb +28 -0
  62. data/lib/ripple/properties.rb +73 -0
  63. data/lib/ripple/property_type_mismatch.rb +12 -0
  64. data/lib/ripple/railtie.rb +13 -0
  65. data/lib/ripple/serialization.rb +84 -0
  66. data/lib/ripple/timestamps.rb +27 -0
  67. data/lib/ripple/translation.rb +14 -0
  68. data/lib/ripple/validations.rb +67 -0
  69. data/lib/ripple/validations/associated_validator.rb +43 -0
  70. data/ripple.gemspec +46 -0
  71. data/seomoz-ripple.gemspec +46 -0
  72. data/spec/fixtures/config.yml +8 -0
  73. data/spec/integration/ripple/associations_spec.rb +220 -0
  74. data/spec/integration/ripple/conflict_resolution_spec.rb +293 -0
  75. data/spec/integration/ripple/nested_attributes_spec.rb +264 -0
  76. data/spec/integration/ripple/persistence_spec.rb +57 -0
  77. data/spec/integration/ripple/search_associations_spec.rb +42 -0
  78. data/spec/ripple/associations/many_embedded_proxy_spec.rb +122 -0
  79. data/spec/ripple/associations/many_linked_proxy_spec.rb +191 -0
  80. data/spec/ripple/associations/many_reference_proxy_spec.rb +170 -0
  81. data/spec/ripple/associations/many_stored_key_proxy_spec.rb +158 -0
  82. data/spec/ripple/associations/one_embedded_proxy_spec.rb +125 -0
  83. data/spec/ripple/associations/one_key_proxy_spec.rb +82 -0
  84. data/spec/ripple/associations/one_linked_proxy_spec.rb +91 -0
  85. data/spec/ripple/associations/one_stored_key_proxy_spec.rb +72 -0
  86. data/spec/ripple/associations/proxy_spec.rb +84 -0
  87. data/spec/ripple/associations_spec.rb +129 -0
  88. data/spec/ripple/attribute_methods/dirty_spec.rb +80 -0
  89. data/spec/ripple/attribute_methods_spec.rb +230 -0
  90. data/spec/ripple/bucket_access_spec.rb +25 -0
  91. data/spec/ripple/callbacks_spec.rb +176 -0
  92. data/spec/ripple/conflict/resolver_spec.rb +42 -0
  93. data/spec/ripple/conversion_spec.rb +22 -0
  94. data/spec/ripple/core_ext_spec.rb +103 -0
  95. data/spec/ripple/document/link_spec.rb +67 -0
  96. data/spec/ripple/document_spec.rb +96 -0
  97. data/spec/ripple/embedded_document/finders_spec.rb +29 -0
  98. data/spec/ripple/embedded_document/persistence_spec.rb +80 -0
  99. data/spec/ripple/embedded_document_spec.rb +84 -0
  100. data/spec/ripple/finders_spec.rb +217 -0
  101. data/spec/ripple/inspection_spec.rb +51 -0
  102. data/spec/ripple/key_spec.rb +30 -0
  103. data/spec/ripple/observable_spec.rb +121 -0
  104. data/spec/ripple/persistence_spec.rb +326 -0
  105. data/spec/ripple/properties_spec.rb +262 -0
  106. data/spec/ripple/ripple_spec.rb +71 -0
  107. data/spec/ripple/serialization_spec.rb +51 -0
  108. data/spec/ripple/timestamps_spec.rb +76 -0
  109. data/spec/ripple/validations/associated_validator_spec.rb +77 -0
  110. data/spec/ripple/validations_spec.rb +104 -0
  111. data/spec/spec_helper.rb +26 -0
  112. data/spec/support/associations.rb +1 -0
  113. data/spec/support/associations/proxies.rb +17 -0
  114. data/spec/support/integration_setup.rb +11 -0
  115. data/spec/support/mocks.rb +4 -0
  116. data/spec/support/models.rb +4 -0
  117. data/spec/support/models/address.rb +12 -0
  118. data/spec/support/models/box.rb +13 -0
  119. data/spec/support/models/car.rb +16 -0
  120. data/spec/support/models/cardboard_box.rb +3 -0
  121. data/spec/support/models/clock.rb +12 -0
  122. data/spec/support/models/clock_observer.rb +3 -0
  123. data/spec/support/models/company.rb +23 -0
  124. data/spec/support/models/customer.rb +4 -0
  125. data/spec/support/models/driver.rb +6 -0
  126. data/spec/support/models/email.rb +4 -0
  127. data/spec/support/models/engine.rb +5 -0
  128. data/spec/support/models/family.rb +16 -0
  129. data/spec/support/models/favorite.rb +4 -0
  130. data/spec/support/models/invoice.rb +7 -0
  131. data/spec/support/models/late_invoice.rb +3 -0
  132. data/spec/support/models/ninja.rb +9 -0
  133. data/spec/support/models/note.rb +5 -0
  134. data/spec/support/models/page.rb +4 -0
  135. data/spec/support/models/paid_invoice.rb +4 -0
  136. data/spec/support/models/passenger.rb +6 -0
  137. data/spec/support/models/profile.rb +10 -0
  138. data/spec/support/models/seat.rb +5 -0
  139. data/spec/support/models/subscription.rb +27 -0
  140. data/spec/support/models/tasks.rb +14 -0
  141. data/spec/support/models/team.rb +11 -0
  142. data/spec/support/models/transactions.rb +17 -0
  143. data/spec/support/models/tree.rb +4 -0
  144. data/spec/support/models/user.rb +10 -0
  145. data/spec/support/models/wheel.rb +6 -0
  146. data/spec/support/models/widget.rb +22 -0
  147. data/spec/support/test_server.rb +18 -0
  148. data/spec/support/test_server.yml.example +2 -0
  149. metadata +362 -0
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ripple::Associations::OneEmbeddedProxy do
4
+ require 'support/models/family'
5
+ require 'support/models/user'
6
+ require 'support/models/address'
7
+
8
+ before :each do
9
+ @parent = Parent.new
10
+ @child = Child.new
11
+ @gchild = Grandchild.new
12
+ end
13
+
14
+ it "should not have a child before one is set" do
15
+ @parent.child.should be_nil
16
+ end
17
+
18
+ it "should raise NoMethodError when an undefined method is called on the unset child" do
19
+ expect { @parent.child.some_undefined_method }.to raise_error(NoMethodError)
20
+ end
21
+
22
+ it "should be able to set and get its child" do
23
+ @parent.child = @child
24
+ @parent.child.should equal(@child)
25
+ end
26
+
27
+ it "should set the parent document on the child when assigning" do
28
+ @parent.child = @child
29
+ @child._parent_document.should == @parent
30
+ end
31
+
32
+ it "should return the assignment when assigning" do
33
+ rtn = @parent.child = @child
34
+ rtn.should == @child
35
+ end
36
+
37
+ it "should set the parent document on the child when accessing" do
38
+ @parent.child = @child
39
+ @parent.child._parent_document.should == @parent
40
+ end
41
+
42
+ it "should be able to replace its child with a different child" do
43
+ @son = Child.new(:name => 'Son')
44
+ @parent.child = @child
45
+ @parent.child.name.should be_blank
46
+ @parent.child = @son
47
+ @parent.child.name.should == 'Son'
48
+ end
49
+
50
+ it "should be able to build a new child" do
51
+ Child.stub!(:new).and_return(@child)
52
+ @parent.child.build.should == @child
53
+ end
54
+
55
+ it "should assign a parent to the child created with instantiate_target" do
56
+ Child.stub!(:new).and_return(@child)
57
+ @child._parent_document.should be_nil
58
+ @parent.child.build._parent_document.should == @parent
59
+ end
60
+
61
+ it "should validate the child when saving the parent" do
62
+ @parent.valid?.should be_true
63
+ @child.name = ''
64
+ @parent.child = @child
65
+ @child.valid?.should be_false
66
+ @parent.valid?.should be_false
67
+ end
68
+
69
+ it "should not save the root document when a child is invalid" do
70
+ @parent.child = @child
71
+ @parent.save.should be_false
72
+ end
73
+
74
+ it "should allow embedding documents in embedded documents" do
75
+ @parent.child = @child
76
+ @child.gchild = @gchild
77
+ @gchild._root_document.should == @parent
78
+ @gchild._parent_document.should == @child
79
+ end
80
+
81
+ it "should refuse assigning a document of the wrong type" do
82
+ lambda { @parent.child = @gchild }.should raise_error
83
+ lambda { @child.gchild = [] }.should raise_error
84
+ end
85
+
86
+ describe "callbacks" do
87
+ before :each do
88
+ $pinger = mock("callback verifier")
89
+ end
90
+
91
+ it "should run callbacks for the child and documents" do
92
+ $pinger.should_receive(:ping).once
93
+ Child.before_validation { $pinger.ping }
94
+ @child = Child.new
95
+ @child.valid?
96
+ end
97
+
98
+ # this will work using parent and child classes, but only run by itself
99
+ # it also works using different classes, but only run in this file
100
+ # IDK why that is, but my Yakshaver 2000 just ran out of juice
101
+
102
+ # does this even matter? we call valid? all over the place and that
103
+ # will trigger the callback anyway.
104
+ # you probably shouldn't use validation callbacks and expect them to
105
+ # *only* run once
106
+
107
+ # it "should run callbacks for the parent and child and documents respectivly" do
108
+ # $pinger = mock("callback verifier")
109
+ # $pinger.should_receive(:ping).once
110
+ # $pinger.should_receive(:pong).once
111
+ # Child.before_validation { $pinger.ping }
112
+ # Parent.before_validation { $pinger.pong }
113
+ # @child = Child.new
114
+ # @parent = Parent.new
115
+ # @parent.child = @child
116
+ # @parent.valid?
117
+ # end
118
+
119
+ after :each do
120
+ Child.reset_callbacks(:validation)
121
+ Parent.reset_callbacks(:validation)
122
+ end
123
+ end
124
+
125
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ripple::Associations::OneKeyProxy do
4
+
5
+ before :each do
6
+ @user = User.new
7
+ @profile = Profile.new(:color => 'blue')
8
+ end
9
+
10
+ describe "User with a key corresponded profile" do
11
+ it "should not have a profile before it is set" do
12
+ @user.profile.should be_nil
13
+ end
14
+
15
+ it "should be able to get and set its profile" do
16
+ @user.profile = @profile
17
+ @user.profile.should eq(@profile)
18
+ end
19
+
20
+ it "should return the assignment when assigning" do
21
+ rtn = @user.profile = @profile
22
+ rtn.should eq(@profile)
23
+ end
24
+
25
+ it "should be able to replace its profile with a new one" do
26
+ @user.profile = @profile
27
+ @user.profile = Profile.new(:color => 'red')
28
+ @user.profile.color.should eq('red')
29
+ end
30
+
31
+ it "should be able to build a new profile" do
32
+ Profile.stub(:new).and_return(@profile)
33
+ @user.profile.build.should eq(@profile)
34
+ end
35
+
36
+ it "should assign its key to the associated profile when assigning" do
37
+ @user.key = "foo"
38
+ @user.profile = @profile
39
+ @profile.key.should eq("foo")
40
+ end
41
+
42
+ it "should assign its key to the built profile" do
43
+ @user.key = "foo"
44
+ @user.profile.build.key.should eq("foo")
45
+ end
46
+
47
+ it "should update the key on the profile when updating it on the user" do
48
+ @user.profile = @profile
49
+ @user.key = "foo"
50
+ @profile.key.should eq("foo")
51
+ end
52
+
53
+ it "should not update the key of a previous profile" do
54
+ @profile2 = Profile.new
55
+ @user.profile = @profile
56
+ @user.profile = @profile2
57
+ @user.key = "foo"
58
+ @profile.key.should_not eq("foo")
59
+ end
60
+
61
+ it "should not infinitely loop when assigning a user to the profile" do
62
+ @user2 = User.new
63
+ @user.profile = @profile
64
+ @profile.user = @user2
65
+ @profile.key = "foo"
66
+ @user2.key.should eq("foo")
67
+ end
68
+
69
+ it "should update the user's key when being updated on the profile" do
70
+ @user.profile = @profile
71
+ @profile.key = "foo"
72
+ @user.key.should eq("foo")
73
+ end
74
+
75
+ it "should work properly with custom key methods" do
76
+ @user = Ninja.new(:name => 'Naruto')
77
+ @user.profile = @profile
78
+ @profile.key.should eq("ninja-naruto")
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ripple::Associations::OneLinkedProxy do
4
+ require 'support/models/tasks'
5
+ require 'support/models/family'
6
+
7
+ before :each do
8
+ @person = Person.new {|p| p.key = "riak-user" }
9
+ @profile = Profile.new {|t| t.key = "one" }
10
+ @other_profile = Profile.new {|t| t.key = "two" }
11
+ [@person, @profile, @other_profile].each do |doc|
12
+ doc.stub!(:new?).and_return(false)
13
+ end
14
+ end
15
+
16
+ it "should be blank before the associated document is set" do
17
+ @person.profile.should_not be_present
18
+ end
19
+
20
+ it "should accept a single document" do
21
+ lambda { @person.profile = @profile }.should_not raise_error
22
+ end
23
+
24
+ it "should set the link on the RObject when assigning" do
25
+ @person.profile = @profile
26
+ @person.robject.links.should include(@profile.to_link("profile"))
27
+ end
28
+
29
+ it "should return the assigned document when assigning" do
30
+ ret = (@person.profile = @profile)
31
+ ret.should == @profile
32
+ end
33
+
34
+ it "should link-walk to the associated document when accessing" do
35
+ @person.robject.links << @profile.robject.to_link("profile")
36
+ @person.robject.should_receive(:walk).with(Riak::WalkSpec.new(:bucket => "profiles", :tag => "profile")).and_return([[@profile.robject]])
37
+ @person.profile.should be_present
38
+ end
39
+
40
+ it "handles conflict appropriately by selecting the linked-walk robject that matches the link" do
41
+ @person.robject.links << @profile.robject.to_link("profile")
42
+ @person.robject.
43
+ should_receive(:walk).
44
+ with(Riak::WalkSpec.new(:bucket => "profiles", :tag => "profile")).
45
+ and_return([[@other_profile.robject, @profile.robject]])
46
+
47
+ @person.profile.should == @profile
48
+ end
49
+
50
+ it "allows the links to be replaced directly" do
51
+ @person.profile = @profile
52
+ @person.profile.__send__(:should_receive, :reset)
53
+ @person.profile.__send__(:links).should == [@profile.robject.to_link("profile")]
54
+ @person.profile.replace_links(@other_profile.robject.to_link("profile"))
55
+ @person.profile.__send__(:links).should == [@other_profile.robject.to_link("profile")]
56
+ end
57
+
58
+ it "should return nil immediately if the association link is missing" do
59
+ @person.robject.links.should be_empty
60
+ @person.profile.should be_nil
61
+ end
62
+
63
+ it "should replace associated document with a new one" do
64
+ @person.profile = @profile
65
+ @person.profile = @other_profile
66
+ @person.profile.should == @other_profile
67
+ end
68
+
69
+ it "replaces the associated document with the target of the proxy" do
70
+ @other_person = Person.new {|p| p.key = "another-riak-user" }
71
+ @other_person.profile = @other_profile
72
+
73
+ @person.profile = @other_person.profile
74
+ @person.profile.should == @other_profile
75
+ end
76
+
77
+ it "refuses assigning a proxy if its target is the wrong type" do
78
+ parent = Parent.new
79
+ parent.child = Child.new
80
+
81
+ lambda { @person.profile = parent.child }.should raise_error
82
+ end
83
+
84
+ # it "should be able to build a new associated document" do
85
+ # @person.profile.build
86
+ # end
87
+
88
+ it "should refuse assigning a document of the wrong type" do
89
+ lambda { @person.profile = @person }.should raise_error
90
+ end
91
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ripple::Associations::OneStoredKeyProxy do
4
+ require 'support/models/transactions'
5
+ require 'support/models/family'
6
+
7
+ before :each do
8
+ @account = Account.new {|e| e.key = "accounty" }
9
+ @other_account = Account.new{|e| e.key = "ickycount" }
10
+ @transaction = Transaction.new{|e| e.key = "transacty" }
11
+ end
12
+
13
+ it "should be blank before the associated document is set" do
14
+ @transaction.account.should_not be_present
15
+ end
16
+
17
+ it "should accept a single document" do
18
+ lambda { @transaction.account = @account }.should_not raise_error
19
+ end
20
+
21
+ it "should set the key when assigning" do
22
+ @transaction.account = @account
23
+ @transaction.account_key.should == "accounty"
24
+ end
25
+
26
+ it "should return the assigned document when assigning" do
27
+ ret = (@transaction.account = @account)
28
+ ret.should == @account
29
+ end
30
+
31
+ it "should find the associated document when accessing" do
32
+ @transaction.account_key = "accounty"
33
+ Account.should_receive(:find).with("accounty").and_return(@account)
34
+ @transaction.account.should be_present
35
+ end
36
+
37
+ it "should return nil immediately if the association link is missing" do
38
+ @transaction.account_key.should be_nil
39
+ @transaction.account.should be_nil
40
+ end
41
+
42
+ it "should replace associated document with a new one" do
43
+ @transaction.account = @account
44
+ @transaction.account = @other_account
45
+ @transaction.account.should == @other_account
46
+ @transaction.account_key.should == "ickycount"
47
+ end
48
+
49
+ it "should replace the associated document with the target of the proxy" do
50
+ other_transaction = Transaction.new {|e| e.key = "ickytrans" }
51
+ other_transaction.account = @other_account
52
+
53
+ @transaction.account = other_transaction.account
54
+ @transaction.account.should == @other_account
55
+ end
56
+
57
+ it "refuses assigning a proxy if its target is the wrong type" do
58
+ parent = Parent.new{|e| e.child = Child.new}
59
+ lambda { @transaction.account = parent.child }.should raise_error
60
+ end
61
+
62
+ it "should refuse assigning a document of the wrong type" do
63
+ lambda { @transaction.account = @transaction }.should raise_error
64
+ end
65
+
66
+ it "should nil out the association if nil is assigned" do
67
+ @transaction.account = @account
68
+ @transaction.account = nil
69
+ @transaction.account.should be_nil
70
+ @transaction.account_key.should be_nil
71
+ end
72
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ripple::Associations::Proxy do
4
+ require 'support/associations/proxies'
5
+
6
+ before :each do
7
+ @owner = mock('owner')
8
+ @owner.stub!(:new?).and_return(false)
9
+ @association = mock('association')
10
+ @association.stub!(:options).and_return({:extend => []})
11
+
12
+ @proxy = FakeProxy.new(@owner, @association)
13
+ @nil_proxy = FakeNilProxy.new(@owner, @association)
14
+ @blank_proxy = FakeBlankProxy.new(@owner, @association)
15
+ end
16
+
17
+ it "should pretend to be the target class" do
18
+ @proxy.should be_kind_of(Array)
19
+ end
20
+
21
+ it "should set the target to nil when reset" do
22
+ @proxy.reset
23
+ @proxy.target.should be_nil
24
+ end
25
+
26
+ describe "delegation" do
27
+ it "should inspect the target" do
28
+ @proxy.inspect.should == "[1, 2]"
29
+ end
30
+
31
+ it "should respond to the same methods as the target" do
32
+ [:each, :size].each do |m|
33
+ @proxy.should respond_to(m)
34
+ end
35
+ @proxy.should_not respond_to(:gsub)
36
+ end
37
+
38
+ it "should send to the proxy if it responds to the method" do
39
+ @proxy.send(:reset)
40
+ @proxy.target.should be_nil
41
+ end
42
+
43
+ it "should send to the target if target responds to the method" do
44
+ @proxy.send(:size).should == 2
45
+ end
46
+
47
+ it "should send resulting in a method missing if neither the proxy nor the target respond to the method" do
48
+ lambda { @proxy.send(:explode) }.should raise_error(NoMethodError)
49
+ end
50
+ end
51
+
52
+ describe "when target is nil" do
53
+ subject { @nil_proxy }
54
+ it { should be_nil }
55
+ it { should be_blank }
56
+ it { should_not be_present }
57
+ end
58
+
59
+ describe "when the target is blank" do
60
+ subject { @blank_proxy }
61
+ it { should_not be_nil }
62
+ it { should be_blank }
63
+ it { should_not be_present }
64
+ end
65
+
66
+ describe "#has_changed_documents?" do
67
+ before(:each) { @proxy.respond_to?(:loaded_documents).should be_true }
68
+
69
+ it "returns true if any of the loaded documents return true from #changed?" do
70
+ @proxy.stub(:loaded_documents => [stub(:changed? => false), stub(:changed? => true)])
71
+ @proxy.has_changed_documents?.should be_true
72
+ end
73
+
74
+ it "returns false if none of the loaded documents return true from #changed?" do
75
+ @proxy.stub(:loaded_documents => [stub(:changed? => false), stub(:changed? => false)])
76
+ @proxy.has_changed_documents?.should be_false
77
+ end
78
+
79
+ it "returns false if it has no loaded documents" do
80
+ @proxy.stub(:loaded_documents => [])
81
+ @proxy.has_changed_documents?.should be_false
82
+ end
83
+ end
84
+ end