ripple 1.0.0.beta → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/.gitignore +10 -6
  2. data/Gemfile +6 -15
  3. data/Gemfile.rails30 +3 -0
  4. data/Gemfile.rails31 +3 -0
  5. data/Gemfile.rails32 +3 -0
  6. data/Guardfile +3 -1
  7. data/LICENSE +16 -0
  8. data/README.markdown +173 -0
  9. data/RELEASE_NOTES.textile +286 -0
  10. data/Rakefile +19 -0
  11. data/lib/rails/generators/ripple/configuration/templates/ripple.yml +1 -0
  12. data/lib/rails/generators/ripple/model/model_generator.rb +1 -1
  13. data/lib/rails/generators/ripple/model/templates/{model.rb → model.rb.erb} +0 -0
  14. data/lib/rails/generators/ripple/observer/observer_generator.rb +1 -1
  15. data/lib/rails/generators/ripple/observer/templates/{observer.rb → observer.rb.erb} +0 -2
  16. data/lib/rails/generators/ripple/test/templates/cucumber.rb.erb +7 -0
  17. data/lib/rails/generators/ripple/test/test_generator.rb +17 -13
  18. data/lib/rails/generators/ripple_generator.rb +1 -0
  19. data/lib/ripple/associations.rb +65 -55
  20. data/lib/ripple/associations/embedded.rb +1 -1
  21. data/lib/ripple/associations/linked.rb +1 -1
  22. data/lib/ripple/associations/many.rb +1 -1
  23. data/lib/ripple/associations/many_embedded_proxy.rb +3 -2
  24. data/lib/ripple/associations/many_linked_proxy.rb +1 -1
  25. data/lib/ripple/associations/many_reference_proxy.rb +7 -5
  26. data/lib/ripple/associations/proxy.rb +2 -2
  27. data/lib/ripple/attribute_methods.rb +69 -61
  28. data/lib/ripple/attribute_methods/dirty.rb +2 -2
  29. data/lib/ripple/attribute_methods/read.rb +4 -2
  30. data/lib/ripple/callbacks.rb +23 -26
  31. data/lib/ripple/conflict/basic_resolver.rb +6 -2
  32. data/lib/ripple/conflict/document_hooks.rb +26 -0
  33. data/lib/ripple/conflict/resolver.rb +10 -2
  34. data/lib/ripple/conflict/test_helper.rb +3 -2
  35. data/lib/ripple/conversion.rb +1 -0
  36. data/lib/ripple/core_ext.rb +1 -0
  37. data/lib/ripple/core_ext/casting.rb +2 -0
  38. data/lib/ripple/core_ext/indexes.rb +89 -0
  39. data/lib/ripple/document.rb +23 -22
  40. data/lib/ripple/document/key.rb +12 -14
  41. data/lib/ripple/document/persistence.rb +99 -84
  42. data/lib/ripple/embedded_document.rb +9 -10
  43. data/lib/ripple/embedded_document/persistence.rb +42 -44
  44. data/lib/ripple/i18n.rb +4 -1
  45. data/lib/ripple/indexes.rb +151 -0
  46. data/lib/ripple/locale/en.yml +4 -0
  47. data/lib/ripple/locale/fr.yml +24 -0
  48. data/lib/ripple/nested_attributes.rb +92 -90
  49. data/lib/ripple/properties.rb +2 -1
  50. data/lib/ripple/railtie.rb +9 -0
  51. data/lib/ripple/railties/ripple.rake +32 -15
  52. data/lib/ripple/serialization.rb +50 -52
  53. data/lib/ripple/test_server.rb +1 -2
  54. data/lib/ripple/timestamps.rb +6 -8
  55. data/lib/ripple/validations.rb +19 -21
  56. data/lib/ripple/version.rb +1 -1
  57. data/ripple.gemspec +6 -5
  58. data/spec/generators/ripple/configuration_generator_spec.rb +9 -0
  59. data/spec/generators/ripple/js_generator_spec.rb +14 -0
  60. data/spec/generators/ripple/model_generator_spec.rb +64 -0
  61. data/spec/generators/ripple/observer_generator_spec.rb +20 -0
  62. data/spec/generators/ripple/test_generator_spec.rb +116 -0
  63. data/spec/generators/ripple_generator_spec.rb +11 -0
  64. data/spec/integration/ripple/conflict_resolution_spec.rb +35 -4
  65. data/spec/integration/ripple/indexes_spec.rb +47 -0
  66. data/spec/ripple/associations/many_embedded_proxy_spec.rb +50 -60
  67. data/spec/ripple/associations/many_linked_proxy_spec.rb +2 -2
  68. data/spec/ripple/associations/many_reference_proxy_spec.rb +1 -1
  69. data/spec/ripple/associations_spec.rb +16 -7
  70. data/spec/ripple/attribute_methods_spec.rb +43 -2
  71. data/spec/ripple/callbacks_spec.rb +120 -101
  72. data/spec/ripple/conversion_spec.rb +5 -13
  73. data/spec/ripple/core_ext_spec.rb +93 -15
  74. data/spec/ripple/finders_spec.rb +0 -2
  75. data/spec/ripple/indexes_spec.rb +111 -0
  76. data/spec/ripple/observable_spec.rb +1 -2
  77. data/spec/ripple/persistence_spec.rb +55 -32
  78. data/spec/ripple/properties_spec.rb +1 -1
  79. data/spec/ripple/ripple_spec.rb +5 -5
  80. data/spec/ripple/timestamps_spec.rb +9 -2
  81. data/spec/ripple/validations_spec.rb +50 -52
  82. data/spec/spec_helper.rb +9 -2
  83. data/spec/support/generator_setup.rb +26 -0
  84. data/spec/support/models.rb +1 -0
  85. data/spec/support/models/box.rb +1 -0
  86. data/spec/support/models/clock.rb +1 -1
  87. data/spec/support/models/indexer.rb +26 -0
  88. data/spec/support/models/post.rb +3 -2
  89. data/spec/support/models/widget.rb +2 -0
  90. data/spec/support/search.rb +2 -2
  91. data/spec/support/test_server.rb +23 -11
  92. data/spec/support/test_server.yml.example +1 -1
  93. metadata +159 -135
  94. data/spec/support/mocks.rb +0 -4
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+ require 'rails/generators/ripple_generator'
3
+
4
+ describe RippleGenerator do
5
+ it "should invoke the sub-generators" do
6
+ %w{configuration js test}.each do |gen|
7
+ generator.should_receive(:invoke).with("ripple:#{gen}")
8
+ end
9
+ run_generator
10
+ end
11
+ end
@@ -37,11 +37,8 @@ describe "Ripple conflict resolution", :integration => true do
37
37
  property :title, String
38
38
  end
39
39
 
40
- before :all do
41
- ConflictedPerson.bucket.allow_mult = true
42
- end
43
-
44
40
  before(:each) do
41
+ ConflictedPerson.bucket.allow_mult ||= true
45
42
  ConflictedPerson.on_conflict { } # reset to no-op
46
43
  end
47
44
 
@@ -76,6 +73,40 @@ describe "Ripple conflict resolution", :integration => true do
76
73
  )
77
74
  end
78
75
 
76
+ context 'for a document that has a deleted sibling' do
77
+ before(:each) do
78
+ create_conflict original_person,
79
+ lambda { |p| p.destroy! },
80
+ lambda { |p| p.age = 20 },
81
+ lambda { |p| p.age = 30 }
82
+ end
83
+
84
+ it 'indicates that one of the siblings was deleted' do
85
+ siblings = nil
86
+ ConflictedPerson.on_conflict { |s, c| siblings = s }
87
+ ConflictedPerson.find('John')
88
+
89
+ siblings.should have(3).sibling_records
90
+ deleted, undeleted = siblings.partition(&:deleted?)
91
+ deleted.should have(1).record
92
+ undeleted.should have(2).records
93
+ deleted = deleted.first
94
+
95
+ # the deleted record should be totally blank except for the name (since it is the key)
96
+ deleted.attributes.reject { |k, v| v.blank? }.should == {"name" => "John"}
97
+ end
98
+
99
+ it 'does not consider the deleted sibling when trying basic resolution of attributes that siblings are in agreement about' do
100
+ record = conflicts = nil
101
+ ConflictedPerson.on_conflict { |s, c| conflicts = c; record = self }
102
+ ConflictedPerson.find('John')
103
+
104
+ conflicts.should == [:age]
105
+ record.gender.should == 'male'
106
+ record.favorite_colors.should == ['green'].to_set
107
+ end
108
+ end
109
+
79
110
  context 'for a document that has conflicted attributes' do
80
111
  let(:most_recent_updated_at) { DateTime.new(2011, 6, 4, 12, 30) }
81
112
  let(:earliest_created_at) { DateTime.new(2010, 5, 3, 12, 30) }
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ripple::Indexes do
4
+ context "finding documents by an index" do
5
+ before do
6
+ @bob = Indexer.create(:name => "Bob", :age => 28)
7
+ @sally = Indexer.create(:name => "Sally", :age => 28)
8
+ @mary = Indexer.create(:name => "Mary", :age => 25)
9
+ end
10
+
11
+ it "should find a document by equality" do
12
+ Indexer.find_by_index(:name, 'Bob').should == [@bob]
13
+ end
14
+
15
+ it "should find many documents by equality" do
16
+ Indexer.find_by_index(:age, 28).should =~ [@bob, @sally]
17
+ end
18
+
19
+ it "should find nothing by equality" do
20
+ Indexer.find_by_index(:age, 30).should == []
21
+ end
22
+
23
+ it "should find a document by range" do
24
+ Indexer.find_by_index(:name, "B".."C").should == [@bob]
25
+ end
26
+
27
+ it "should find many documents by range" do
28
+ Indexer.find_by_index(:age, 27..29).should =~ [@bob, @sally]
29
+ end
30
+
31
+ it "should find nothing by range" do
32
+ Indexer.find_by_index(:age, 10..20).should == []
33
+ end
34
+
35
+ it "should find by the special $bucket index" do
36
+ Indexer.find_by_index('$bucket', Indexer.bucket.name).should =~ [@bob, @sally, @mary]
37
+ end
38
+
39
+ it "should find by the special $key index" do
40
+ Indexer.find_by_index('$key', @bob.key..@bob.key.succ).should =~ [@bob]
41
+ end
42
+
43
+ it "should raise an error when the requested index doesn't exist" do
44
+ lambda { Indexer.find_by_index(:hair, 'blonde') }.should raise_error(ArgumentError)
45
+ end
46
+ end
47
+ end
@@ -1,122 +1,112 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Ripple::Associations::ManyEmbeddedProxy do
4
- # require 'support/models/user'
5
- # require 'support/models/address'
6
- # require 'support/models/note'
7
-
8
- before :each do
9
- @user = User.new(:email => "riak@ripple.com")
10
- @address = Address.new
11
- @addr = Address.new(:street => '123 Somewhere')
12
- @note = Note.new
13
- end
4
+ let(:user){ User.new(:email => "riak@ripple.com") }
5
+ let(:address){ Address.new }
6
+ let(:addr){ Address.new(:street => '123 Somewhere') }
7
+ let(:note){ Note.new }
14
8
 
15
9
  it "should not have children before any are set" do
16
- @user.addresses.should == []
10
+ user.addresses.should == []
17
11
  end
18
12
 
19
13
  it "should be able to set and get its children" do
20
- Address.stub!(:instantiate).and_return(@address)
21
- @user.addresses = [@address]
22
- @user.addresses.should == [@address]
14
+ user.addresses = [address]
15
+ user.addresses.should == [address]
23
16
  end
24
17
 
25
18
  it "should set the parent document on the children when assigning" do
26
- @user.addresses = [@address]
27
- @address._parent_document.should == @user
19
+ user.addresses = [address]
20
+ address._parent_document.should == user
28
21
  end
29
22
 
30
23
  it "should return the assignment when assigning" do
31
- rtn = @user.addresses = [@address]
32
- rtn.should == [@address]
24
+ rtn = user.addresses = [address]
25
+ rtn.should == [address]
33
26
  end
34
27
 
35
28
  it "should set the parent document on the children when accessing" do
36
- @user.addresses = [@address]
37
- @user.addresses.first._parent_document.should == @user
29
+ user.addresses = [address]
30
+ user.addresses.first._parent_document.should == user
38
31
  end
39
32
 
40
33
  it "should be able to replace its children with different children" do
41
- @user.addresses = [@address]
42
- @user.addresses.first.street.should be_blank
43
- @user.addresses = [@addr]
44
- @user.addresses.first.street.should == '123 Somewhere'
34
+ user.addresses = [address]
35
+ user.addresses.first.street.should be_blank
36
+ user.addresses = [addr]
37
+ user.addresses.first.street.should == '123 Somewhere'
45
38
  end
46
39
 
47
40
  it "should be able to add to its children" do
48
- Address.stub!(:instantiate).and_return(@address)
49
- @user.addresses = [@address]
50
- @user.addresses << @address
51
- @user.addresses.should == [@address, @address]
41
+ user.addresses = [address]
42
+ user.addresses << addr
43
+ user.addresses.should == [address, addr]
52
44
  end
53
45
 
54
46
  it "should be able to chain calls to adding children" do
55
- Address.stub!(:instantiate).and_return(@address)
56
- @user.addresses = [@address]
57
- @user.addresses << @address << @address << @address
58
- @user.addresses.should == [@address, @address, @address, @address]
47
+ user.addresses = [address]
48
+ user.addresses << address << address << address
49
+ user.addresses.should == [address, address, address, address]
59
50
  end
60
51
 
61
52
  it "should set the parent document when adding to its children" do
62
- @user.addresses << @address
63
- @user.addresses.first._parent_document.should == @user
53
+ user.addresses << address
54
+ user.addresses.first._parent_document.should == user
64
55
  end
65
56
 
66
57
  it "should be able to count its children" do
67
- @user.addresses = [@address, @address]
68
- @user.addresses.count.should == 2
58
+ user.addresses = [address, address]
59
+ user.addresses.count.should == 2
69
60
  end
70
61
 
71
62
  it "should be able to build a new child" do
72
- Address.stub!(:new).and_return(@address)
73
- @user.addresses.build.should == @address
63
+ user.addresses.build.should be_kind_of(Address)
74
64
  end
75
65
 
76
66
  it "should assign a parent to the children created with instantiate_target" do
77
- Address.stub!(:new).and_return(@address)
78
- @address._parent_document.should be_nil
79
- @user.addresses.build._parent_document.should == @user
67
+ address._parent_document.should be_nil
68
+ user.addresses.build._parent_document.should == user
80
69
  end
81
70
 
82
71
  it "should validate the children when saving the parent" do
83
- @user.valid?.should be_true
84
- @user.addresses << @address
85
- @address.valid?.should be_false
86
- @user.valid?.should be_false
72
+ user.valid?.should be_true
73
+ user.addresses << address
74
+ address.valid?.should be_false
75
+ user.valid?.should be_false
87
76
  end
88
77
 
89
78
  it "should not save the root document when a child is invalid" do
90
- @user.addresses << @address
91
- @user.save.should be_false
79
+ user.addresses << address
80
+ user.save.should be_false
92
81
  end
93
82
 
94
83
  it "should allow embedding documents in embedded documents" do
95
- @user.addresses << @address
96
- @address.notes << @note
97
- @note._root_document.should == @user
98
- @note._parent_document.should == @address
84
+ user.addresses << address
85
+ address.notes << note
86
+ note._root_document.should == user
87
+ note._parent_document.should == address
99
88
  end
100
89
 
101
90
  it "should allow assiging child documents as an array of hashes" do
102
- @user.attributes = {'addresses' => [{'street' => '123 Somewhere'}]}
103
- @user.addresses.first.street.should == '123 Somewhere'
91
+ user.attributes = {'addresses' => [{'street' => '123 Somewhere'}]}
92
+ user.addresses.first.street.should == '123 Somewhere'
104
93
  end
105
94
 
106
95
  it "should return an array from to_ary" do
107
- Address.stub!(:instantiate).and_return(@address)
108
- @user.addresses << @address
109
- @user.addresses.to_ary.should == [@address]
96
+ user.addresses << address
97
+ user.addresses.to_ary.should == [address]
110
98
  end
111
99
 
112
100
  it "should refuse assigning documents of the wrong type" do
113
- lambda { @user.addresses = nil }.should raise_error
114
- lambda { @user.addresses = @address }.should raise_error
115
- lambda { @user.addresses = [@note] }.should raise_error
101
+ lambda { user.addresses = nil }.should raise_error
102
+ lambda { user.addresses = address }.should raise_error
103
+ lambda { user.addresses = [note] }.should raise_error
116
104
  end
117
105
 
118
106
  it "should not add the associated validator multiple times" do
119
- #$stderr.puts User.validators.collect(&:inspect)
107
+ # TODO: the validator is added lazily on first instantiation of
108
+ # the proxy. This is a code smell!
109
+ user.addresses
120
110
  User.validators_on(:addresses).count.should eq(1)
121
111
  end
122
112
  end
@@ -58,7 +58,7 @@ describe Ripple::Associations::ManyLinkedProxy do
58
58
  @person.tasks.__send__(:should_receive, :reset)
59
59
  @person.tasks.__send__(:links).should == [@task.robject.to_link("tasks")]
60
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") }
61
+ @person.tasks.__send__(:links).should =~ [@other_task, @third_task].map { |t| t.robject.to_link("tasks") }
62
62
  end
63
63
 
64
64
  it "should replace associated documents with a new set" do
@@ -151,7 +151,7 @@ describe Ripple::Associations::ManyLinkedProxy do
151
151
 
152
152
  it "returns a set of keys" do
153
153
  @person.tasks.keys.should be_a(Set)
154
- @person.tasks.keys.to_a.should == link_keys
154
+ @person.tasks.keys.to_a.should =~ link_keys
155
155
  end
156
156
 
157
157
  it "is memoized between calls" do
@@ -123,7 +123,7 @@ describe Ripple::Associations::ManyReferenceProxy do
123
123
 
124
124
  it "returns a set of keys" do
125
125
  @account.payment_methods.keys.should be_a(Set)
126
- @account.payment_methods.keys.to_a.should == ze_keys
126
+ @account.payment_methods.keys.to_a.should =~ ze_keys
127
127
  end
128
128
 
129
129
  it "is memoized between calls" do
@@ -11,22 +11,23 @@ describe Ripple::Associations do
11
11
  end
12
12
 
13
13
  it "should collect the embedded associations" do
14
- Invoice.embedded_associations.should == Array(Invoice.associations[:note])
14
+ Invoice.embedded_associations.should == [ Invoice.associations[:note] ]
15
15
  end
16
16
 
17
17
  it "should collect the linked associations" do
18
- Invoice.linked_associations.should == Array(Invoice.associations[:customer])
18
+ Invoice.linked_associations.should == [ Invoice.associations[:customer] ]
19
19
  end
20
20
 
21
21
  it "should collect the stored_key associations" do
22
- Account.stored_key_associations.should == Array(Account.associations[:transactions])
23
- Transaction.stored_key_associations.should == Array(Transaction.associations[:account])
22
+ Account.stored_key_associations.should == [ Account.associations[:transactions] ]
23
+ Transaction.stored_key_associations.should == [ Transaction.associations[:account] ]
24
24
  end
25
25
 
26
26
  it "should copy associations to a subclass" do
27
27
  Invoice.associations[:foo] = "bar"
28
28
  class SubInvoice < Invoice; end
29
29
  SubInvoice.associations[:foo].should == "bar"
30
+ Invoice.associations.delete :foo
30
31
  end
31
32
 
32
33
  describe "when adding a :many association" do
@@ -128,9 +129,6 @@ describe Ripple::Association do
128
129
  end
129
130
 
130
131
  describe "key correspondence" do
131
- require 'support/models/profile'
132
- require 'support/models/user'
133
-
134
132
  it "should raise an exception if trying to use a many association when :using => :key" do
135
133
  expect { Profile.many :user, :using => :key }.to raise_error(ArgumentError, "You cannot use a many association using :key")
136
134
  end
@@ -141,4 +139,15 @@ describe Ripple::Association do
141
139
  Profile.new.should respond_to(:key_delegate=)
142
140
  end
143
141
  end
142
+
143
+ describe "stored key" do
144
+ it "should raise an error when the target property does not exist" do
145
+ expect {
146
+ Class.new do
147
+ include Ripple::Document
148
+ many :transactions, :using => :stored_key
149
+ end
150
+ }.to raise_error(ArgumentError)
151
+ end
152
+ end
144
153
  end
@@ -140,7 +140,7 @@ describe Ripple::AttributeMethods do
140
140
 
141
141
  describe "#attributes" do
142
142
  it "it returns a hash representation of all of the attributes" do
143
- @widget.attributes.should == {"name" => "widget", "size" => nil, "manufactured" => false, "shipped_at" => nil}
143
+ @widget.attributes.should == {"name" => "widget", "size" => nil, "manufactured" => false, "shipped_at" => nil, "restricted" => false}
144
144
  end
145
145
 
146
146
  it "does not include ghost attributes (attributes that do not have a defined property)" do
@@ -157,7 +157,8 @@ describe Ripple::AttributeMethods do
157
157
  'size' => nil,
158
158
  'manufactured' => false,
159
159
  'shipped_at' => nil,
160
- 'some_undefined_prop' => 17
160
+ 'some_undefined_prop' => 17,
161
+ 'restricted' => false,
161
162
  }
162
163
  end
163
164
  end
@@ -227,4 +228,44 @@ describe Ripple::AttributeMethods do
227
228
  lambda { @widget = Widget.new(:explode => '?BOOM') }.should raise_error(ArgumentError, %q[Undefined property :explode for class 'Widget'])
228
229
  end
229
230
 
231
+ it "should allow mass assigning arbitrary attributes when without_protection is specified" do
232
+ @widget = Widget.new({:manufactured => true}, :without_protection => true)
233
+ @widget[:manufactured].should be_true
234
+
235
+ @client = Ripple.client
236
+ @client.stub(:store_object => true)
237
+
238
+ @widget = Widget.create({:manufactured => true}, :without_protection => true)
239
+ @widget[:manufactured].should be_true
240
+
241
+ @widget = Widget.create!({:manufactured => true}, :without_protection => true)
242
+ @widget[:manufactured].should be_true
243
+ end
244
+
245
+ it "default assign_attributes should respect mass attribute assignment security" do
246
+ @widget = Widget.new
247
+ @widget.assign_attributes(:manufactured => true)
248
+ @widget.manufactured.should be_false
249
+ end
250
+
251
+ if(ActiveModel::VERSION::MAJOR == 3 && ActiveModel::VERSION::MINOR == 0)
252
+ it "assigning attributes should respect roles" do
253
+ @widget = Widget.new
254
+ @widget.assign_attributes(:restricted => true)
255
+ @widget.restricted.should be_false
256
+
257
+ lambda do
258
+ @widget.assign_attributes({:restricted => true}, :as => :admin)
259
+ end.should raise_error(ArgumentError, %q[Roles for mass assignment are not supported for Rails 3.0])
260
+ end
261
+ else
262
+ it "assigning attributes with a role should raise an error" do
263
+ @widget = Widget.new
264
+ @widget.assign_attributes(:restricted => true)
265
+ @widget.restricted.should be_false
266
+
267
+ @widget.assign_attributes({:restricted => true}, :as => :admin)
268
+ @widget.restricted.should be_true
269
+ end
270
+ end
230
271
  end
@@ -1,175 +1,194 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Ripple::Callbacks do
4
- # require 'support/models/box'
4
+ let(:doc) do
5
+ _e = embedded
6
+ Class.new do
7
+ include Ripple::Document
8
+ self.bucket_name = "docs"
9
+ many :embeddeds, :class => _e
10
+ end
11
+ end
12
+
13
+ let(:embedded) do
14
+ Class.new do
15
+ include Ripple::EmbeddedDocument
16
+ end
17
+ end
18
+
19
+ subject { doc.new }
5
20
 
6
21
  it "should add create, update, save, and destroy callback declarations" do
7
22
  [:save, :create, :update, :destroy].each do |event|
8
- Box.private_instance_methods.map(&:to_s).should include("_run_#{event}_callbacks")
23
+ doc.private_instance_methods.map(&:to_s).should include("_run_#{event}_callbacks")
9
24
  [:before, :after, :around].each do |time|
10
- Box.should respond_to("#{time}_#{event}")
25
+ doc.should respond_to("#{time}_#{event}")
11
26
  end
12
27
  end
13
28
  end
14
29
 
15
30
  it "should validate callback declarations" do
16
- Box.private_instance_methods.map(&:to_s).should include("_run_validation_callbacks")
17
- Box.should respond_to("before_validation")
18
- Box.should respond_to("after_validation")
31
+ doc.private_instance_methods.map(&:to_s).should include("_run_validation_callbacks")
32
+ doc.should respond_to("before_validation")
33
+ doc.should respond_to("after_validation")
19
34
  end
20
35
 
21
36
  describe "invoking callbacks" do
22
37
  before :each do
23
- response = {:headers => {"content-type" => ["application/json"]}, :body => "{}"}
24
38
  @client = Ripple.client
25
- @backend = mock("Backend", :store_object => true)
26
- @client.stub!(:backend).and_return(@backend)
27
- $pinger = mock("callback verifier")
39
+ @client.stub(:store_object => true)
28
40
  end
29
41
 
30
42
  it "should call save callbacks on save" do
31
- Box.before_save { $pinger.ping }
32
- Box.after_save { $pinger.ping }
33
- Box.around_save(lambda { $pinger.ping })
34
- $pinger.should_receive(:ping).exactly(3).times
35
- @box = Box.new
36
- @box.save
43
+ callbacks = []
44
+ doc.before_save { callbacks << :before }
45
+ doc.after_save { callbacks << :after }
46
+ doc.around_save(lambda { callbacks << :around })
47
+ subject.save
48
+ callbacks.should == [ :before, :around, :after ]
37
49
  end
38
50
 
39
51
  it "propagates callbacks to embedded associated documents" do
40
- Box.before_save { $pinger.ping }
41
- BoxSide.before_save { $pinger.ping }
42
- $pinger.should_receive(:ping).exactly(2).times
43
- @box = Box.new
44
- @box.sides << BoxSide.new
45
- @box.save
52
+ callbacks = []
53
+ doc.before_save { callbacks << :box }
54
+ embedded.before_save { callbacks << :side }
55
+ subject.embeddeds << embedded.new
56
+ subject.save
57
+ callbacks.should == [:side, :box]
46
58
  end
47
59
 
48
60
  it 'does not persist the object to riak multiple times when propagating callbacks' do
49
- Box.before_save { }
50
- BoxSide.before_save { }
51
- @box = Box.new
52
- @box.sides << BoxSide.new << BoxSide.new
61
+ doc.before_save { }
62
+ embedded.before_save { }
63
+ subject.embeddeds << embedded.new << embedded.new
53
64
 
54
- @box.robject.should_receive(:store).once
55
- @box.save
65
+ subject.robject.should_receive(:store).once
66
+ subject.save
56
67
  end
57
68
 
58
69
  it 'invokes the before/after callbacks in the correct order on embedded associated documents' do
59
70
  callbacks = []
60
- BoxSide.before_save { callbacks << :before_save }
61
- BoxSide.after_save { callbacks << :after_save }
71
+ embedded.before_save { callbacks << :before_save }
72
+ embedded.after_save { callbacks << :after_save }
62
73
 
63
- @box = Box.new
64
- @box.sides << BoxSide.new
65
- @box.robject.stub(:store) do
74
+ subject.embeddeds << embedded.new
75
+ subject.robject.stub(:store) do
66
76
  callbacks << :save
67
77
  end
68
- @box.save
78
+ subject.save
69
79
 
70
80
  callbacks.should == [:before_save, :save, :after_save]
71
81
  end
72
82
 
73
83
  it 'does not allow around callbacks on embedded associated documents' do
74
84
  expect {
75
- BoxSide.around_save { }
85
+ embedded.around_save { }
76
86
  }.to raise_error(/around_save callbacks are not supported/)
77
87
  end
78
88
 
79
89
  it 'does not propagate validation callbacks multiple times' do
80
- Box.before_validation { $pinger.ping }
81
- BoxSide.before_validation { $pinger.ping }
82
- $pinger.should_receive(:ping).exactly(2).times
83
- @box = Box.new
84
- @box.sides << BoxSide.new
85
- @box.valid?
90
+ callbacks = []
91
+ doc.before_validation { callbacks << :box }
92
+ embedded.before_validation { callbacks << :side }
93
+
94
+ subject.embeddeds << embedded.new
95
+ subject.valid?
96
+ callbacks.should == [:box, :side]
86
97
  end
87
98
 
88
99
  it "should call create callbacks on save when the document is new" do
89
- Box.before_create { $pinger.ping }
90
- Box.after_create { $pinger.ping }
91
- Box.around_create(lambda { $pinger.ping })
92
- $pinger.should_receive(:ping).exactly(3).times
93
- @box = Box.new
94
- @box.save
100
+ callbacks = []
101
+ doc.before_create { callbacks << :before }
102
+ doc.after_create { callbacks << :after }
103
+ doc.around_create(lambda { callbacks << :around })
104
+
105
+ subject.save
106
+ callbacks.should == [:before, :around, :after ]
95
107
  end
96
108
 
97
109
  it "should call update callbacks on save when the document is not new" do
98
- Box.before_update { $pinger.ping }
99
- Box.after_update { $pinger.ping }
100
- Box.around_update(lambda { $pinger.ping })
101
- $pinger.should_receive(:ping).exactly(3).times
102
- @box = Box.new
103
- @box.stub!(:new?).and_return(false)
104
- @box.save
110
+ callbacks = []
111
+ doc.before_update { callbacks << :before }
112
+ doc.after_update { callbacks << :after }
113
+ doc.around_update(lambda { callbacks << :around })
114
+
115
+ subject.stub!(:new?).and_return(false)
116
+ subject.save
117
+ callbacks.should == [:before, :around, :after ]
105
118
  end
106
119
 
107
- it "should call destroy callbacks" do
108
- Box.before_destroy { $pinger.ping }
109
- Box.after_destroy { $pinger.ping }
110
- Box.around_destroy(lambda { $pinger.ping })
111
- $pinger.should_receive(:ping).exactly(3).times
112
- @box = Box.new
113
- @box.destroy
120
+ describe "destroy callbacks" do
121
+ let(:callbacks) { [] }
122
+
123
+ before(:each) do
124
+ _callbacks = callbacks
125
+ doc.before_destroy { _callbacks << :before }
126
+ doc.after_destroy { _callbacks << :after }
127
+ doc.around_destroy(lambda { _callbacks << :around })
128
+ end
129
+
130
+ after { callbacks.should == [ :before, :around, :after ] }
131
+
132
+ it "invokes them when #destroy is called" do
133
+ subject.destroy
134
+ end
135
+
136
+ it "invokes them when #destroy! is called" do
137
+ subject.destroy!
138
+ end
114
139
  end
115
140
 
116
141
  it "should call save and validate callbacks in the correct order" do
117
- Box.before_validation { $pinger.ping(:v) }
118
- Box.before_save { $pinger.ping(:s) }
119
- $pinger.should_receive(:ping).with(:v).ordered
120
- $pinger.should_receive(:ping).with(:s).ordered
121
- @box = Box.new
122
- @box.save
142
+ callbacks = []
143
+ doc.before_validation { callbacks << :validation }
144
+ doc.before_save { callbacks << :save }
145
+
146
+ subject.save
147
+ callbacks.should == [:validation, :save]
123
148
  end
124
149
 
125
150
  describe "validation callbacks" do
126
151
  it "should call validation callbacks" do
127
- Box.before_validation { $pinger.ping }
128
- Box.after_validation { $pinger.ping }
129
- $pinger.should_receive(:ping).twice
130
- @box = Box.new
131
- @box.valid?
152
+ callbacks = []
153
+ doc.before_validation { callbacks << :before }
154
+ doc.after_validation { callbacks << :after }
155
+ subject.valid?
156
+ callbacks.should == [:before, :after]
132
157
  end
133
158
 
134
159
  it "should call validation callbacks only if the document is new" do
135
- Box.before_validation(:on => :create) { $pinger.ping }
136
- Box.after_validation(:on => :create) { $pinger.ping }
137
- $pinger.should_receive(:ping).twice
138
- @box = Box.new
139
- @box.valid?
160
+ callbacks = []
161
+ doc.before_validation(:on => :create) { callbacks << :before }
162
+ doc.after_validation(:on => :create) { callbacks << :after }
163
+ subject.valid?
164
+ callbacks.should == [:before, :after]
140
165
  end
141
166
 
142
167
  it "should not call validation callbacks only if the document is new" do
143
- Box.before_validation(:on => :update) { $pinger.ping }
144
- Box.after_validation(:on => :update) { $pinger.ping }
145
- $pinger.should_not_receive(:ping)
146
- @box = Box.new
147
- @box.valid?
168
+ callbacks = []
169
+ doc.before_validation(:on => :update) { callbacks << :before }
170
+ doc.after_validation(:on => :update) { callbacks << :after }
171
+ subject.valid?
172
+ callbacks.should be_empty
148
173
  end
149
174
 
150
175
  it "should call validation callbacks only if the document is not new" do
151
- Box.before_validation(:on => :update) { $pinger.ping }
152
- Box.after_validation(:on => :update) { $pinger.ping }
153
- $pinger.should_receive(:ping).twice
154
- @box = Box.new
155
- @box.stub(:new?).and_return(false)
156
- @box.valid?
157
- end
176
+ callbacks = []
177
+ doc.before_validation(:on => :update) { callbacks << :before }
178
+ doc.after_validation(:on => :update) { callbacks << :after }
158
179
 
159
- it "should not call validation callbacks only if the document is not new" do
160
- Box.before_validation(:on => :create) { $pinger.ping }
161
- Box.after_validation(:on => :create) { $pinger.ping }
162
- $pinger.should_not_receive(:ping)
163
- @box = Box.new
164
- @box.stub!(:new?).and_return(false)
165
- @box.valid?
180
+ subject.stub(:new?).and_return(false)
181
+ subject.valid?
182
+ callbacks.should == [:before, :after]
166
183
  end
167
- end
168
184
 
169
- after :each do
170
- [:save, :create, :update, :destroy, :validation].each do |type|
171
- Box.reset_callbacks(type)
172
- BoxSide.reset_callbacks(type)
185
+ it "should not call validation callbacks only if the document is not new" do
186
+ callbacks = []
187
+ doc.before_validation(:on => :create) { callbacks << :validation }
188
+ doc.after_validation(:on => :create) { callbacks << :after }
189
+ subject.stub!(:new?).and_return(false)
190
+ subject.valid?
191
+ callbacks.should be_empty
173
192
  end
174
193
  end
175
194
  end