save_queue 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +13 -4
  2. data/.travis.yml +4 -1
  3. data/CONTRIBUTING.md +120 -0
  4. data/HISTORY.md +11 -0
  5. data/LICENSE +13 -4
  6. data/README.md +282 -55
  7. data/Rakefile +10 -3
  8. data/lib/save_queue/exceptions.rb +13 -0
  9. data/lib/save_queue/object.rb +41 -18
  10. data/lib/save_queue/object_queue.rb +87 -0
  11. data/lib/save_queue/plugins/notification/object.rb +35 -0
  12. data/lib/save_queue/plugins/notification/queue.rb +25 -0
  13. data/lib/save_queue/plugins/notification.rb +15 -0
  14. data/lib/save_queue/plugins/validation/exceptions.rb +12 -0
  15. data/lib/save_queue/plugins/validation/queue.rb +16 -17
  16. data/lib/save_queue/plugins/validation.rb +4 -17
  17. data/lib/save_queue/ruby1.9/observer.rb +204 -0
  18. data/lib/save_queue/uniq_queue.rb +38 -0
  19. data/lib/save_queue/version.rb +1 -1
  20. data/lib/save_queue.rb +1 -0
  21. data/save_queue.gemspec +4 -3
  22. data/spec/notification/notification_spec.rb +45 -0
  23. data/spec/notification/object_spec.rb +54 -0
  24. data/spec/notification/queue_spec.rb +28 -0
  25. data/spec/object_queue_spec.rb +155 -0
  26. data/spec/object_spec.rb +208 -0
  27. data/spec/save_queue_spec.rb +75 -0
  28. data/spec/support/object_helpers.rb +10 -0
  29. data/spec/support/queue_helpers.rb +26 -0
  30. data/spec/uniq_queue_spec.rb +132 -0
  31. data/spec/validation/queue_spec.rb +139 -0
  32. data/spec/validation/validation_spec.rb +42 -0
  33. metadata +35 -20
  34. data/lib/save_queue/plugins/validation/object.rb +0 -25
  35. data/lib/save_queue/queue.rb +0 -45
  36. data/spec/save_queue_usage_spec.rb +0 -311
  37. data/spec/support/mock_helpers.rb +0 -17
  38. data/spec/validation_spec.rb +0 -126
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+ require "save_queue/plugins/notification/object"
3
+
4
+ class NotifyObject
5
+ include SaveQueue
6
+ include SaveQueue::Plugins::Notification::Object
7
+ end
8
+
9
+ describe SaveQueue::Plugins::Notification::Object do
10
+
11
+ it "should register observer on queue to #queue_changed_event method" do
12
+ NotifyObject.queue_class.any_instance.should_receive(:add_observer).with do |observer, method|
13
+ observer.is_a?(NotifyObject) and method == :queue_changed_event
14
+ end
15
+ NotifyObject.new
16
+ end
17
+
18
+ let(:queue) do
19
+ queue_class = Class.new
20
+ queue_class.stub(:include?).with(Hooks).and_return(true)
21
+
22
+ queue_class
23
+ end
24
+
25
+ it "should raise an exception if queue does not respond to #add_observer" do
26
+ queue.any_instance.stub(:respond_to?).with(:add_observer).and_return(false)
27
+ NotifyObject.queue_class = queue
28
+ expect { NotifyObject.new }.to raise_error(RuntimeError, /add_observer/)
29
+ end
30
+
31
+ it "should not raise an exception if queue respond to #add_observer" do
32
+ queue.any_instance.stub(:respond_to?).with(:add_observer).and_return(true)
33
+ queue.any_instance.stub(:add_observer)
34
+ NotifyObject.queue_class = queue
35
+ expect { NotifyObject.new }.to_not raise_error
36
+ end
37
+
38
+ describe "#queue_changed_event" do
39
+ let(:object) do
40
+ NotifyObject.any_instance.stub(:create_queue)
41
+ NotifyObject.new
42
+ end
43
+
44
+ it "should mark self as changed" do
45
+ object.should_receive(:mark_as_changed)
46
+ object.send :queue_changed_event, true, stub(:some_object)
47
+ end
48
+
49
+ it "should not mark self as changed if result of adding element to a queue was false" do
50
+ object.should_not_receive(:mark_as_changed)
51
+ object.send :queue_changed_event, false, stub(:some_object)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,28 @@
1
+ require "spec_helper"
2
+ require "save_queue/plugins/notification/queue"
3
+
4
+ class NotifyQueue < SaveQueue::ObjectQueue
5
+ include SaveQueue::Plugins::Notification::Queue
6
+ end
7
+
8
+ describe SaveQueue::Plugins::Notification::Queue do
9
+ let(:queue) { NotifyQueue.new }
10
+
11
+ [:add, :<<, :push].each do |method|
12
+ describe "##{method}" do
13
+ let(:element) { new_element }
14
+
15
+ it "should notify observers about change" do
16
+ queue.should_receive(:changed)
17
+ queue.should_receive(:notify_observers)
18
+ queue.send method, element
19
+ end
20
+
21
+ it "should notify observer, provided result of a method call and input object" do
22
+ queue.should_receive(:changed)
23
+ queue.should_receive(:notify_observers).with(true, element)
24
+ queue.send method, element
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,155 @@
1
+ require "spec_helper"
2
+ require "save_queue/object_queue"
3
+
4
+ describe SaveQueue::ObjectQueue do
5
+ let(:queue) { SaveQueue::ObjectQueue.new }
6
+ let(:element) { new_element(:element) }
7
+
8
+ [:add, :<<, :push].each do |method|
9
+ describe "##{method}" do
10
+ #it "should add only objects that implement SaveQueue::Object interface" do
11
+ it "should accept objects that respond to #save and #has_unsaved_changes?" do
12
+ element = stub(:element, :save => true, :has_unsaved_changes? => true)
13
+ expect{ queue.send method, element }.not_to raise_error
14
+ end
15
+
16
+ it "should not accept objects that does not respond to #save" do
17
+ element.unstub(:save)
18
+ expect{ queue.send method, element }.to raise_error ArgumentError, "#{element.inspect} does not respond to #save"
19
+ end
20
+
21
+ it "should not accept objects that does not respond to #has_unsaved_changes?" do
22
+ element.unstub(:has_unsaved_changes?)
23
+ expect{ queue.send method, element }.to raise_error ArgumentError, "#{element.inspect} does not respond to #has_unsaved_changes?"
24
+ end
25
+ end
26
+ end
27
+
28
+ it "#<< method should be able to add objects in chain" do
29
+ queue << new_element << new_element
30
+ queue.should have(2).elements
31
+ end
32
+
33
+ describe "#save" do
34
+ it "should save all object in queue" do
35
+ 5.times do
36
+ element = stub(:element, :has_unsaved_changes? => true)
37
+ element.should_receive(:save).once
38
+ queue << element
39
+ end
40
+
41
+ queue.save
42
+ end
43
+
44
+ it "should save an object if it has unsaved changes" do
45
+ element = stub(:element)
46
+ element.stub(:has_unsaved_changes?).and_return(true)
47
+ element.should_receive(:save).once
48
+
49
+ queue << element
50
+ queue.save
51
+ end
52
+
53
+ it "should not save an object if it has not been changed" do
54
+ element = stub(:element)
55
+ element.stub(:has_unsaved_changes?).and_return(false)
56
+ element.should_not_receive(:save)
57
+
58
+ queue << element
59
+ queue.save
60
+ end
61
+
62
+ it "should save an object if it has changed state after been added to a queue" do
63
+ element = new_element(:element, :changed => false, :saved => true)
64
+
65
+ queue << element
66
+
67
+ element.stub(:has_unsaved_changes?).and_return(true)
68
+
69
+ element.should_receive(:save).once.and_return(true)
70
+ queue.save
71
+ end
72
+ end
73
+
74
+ context "at least one object in queue was not saved" do
75
+ before(:each) do
76
+ @objects ={}
77
+ queue << @objects[:valid1] = new_element(:valid1)
78
+ queue << @objects[:valid2] = new_element(:valid2)
79
+ queue << @objects[:not_changed] = new_element(:not_changed, :changed => false, :saved => true)
80
+ queue << @objects[:unsaved_but_changed] = new_element(:unsaved_but_changed, :changed => true, :saved => false)
81
+ queue << @objects[:saved] = new_element(:saved, :changed => true, :saved => true)
82
+ queue << @objects[:valid3] = new_element(:valid3)
83
+ end
84
+
85
+ describe "#save!" do
86
+ it "should set errors" do
87
+ expect {queue.save!}.to raise_error
88
+
89
+ queue.errors[:save].should_not be_empty
90
+ queue.errors.should eq :save => { :processed => @objects.values_at(:valid1, :valid2, :not_changed),
91
+ :saved => @objects.values_at(:valid1, :valid2),
92
+ :failed => @objects[:unsaved_but_changed],
93
+ :pending => @objects.values_at(:not_changed, :saved, :valid3) }
94
+ end
95
+
96
+ it "should raise SaveQueue::FailedSaveError" do
97
+ expect{ queue.save! }.to raise_error(SaveQueue::FailedSaveError) {|error| \
98
+ error.context.should == { :processed => @objects.values_at(:valid1, :valid2, :not_changed),
99
+ :saved => @objects.values_at(:valid1, :valid2),
100
+ :failed => @objects[:unsaved_but_changed],
101
+ :pending => @objects.values_at(:not_changed, :saved, :valid3) }
102
+ }
103
+ end
104
+ end
105
+
106
+ describe "#save" do
107
+ it "should return false" do
108
+ queue.save.should be_false
109
+ end
110
+
111
+ it "should set errors" do
112
+ queue.save
113
+
114
+ queue.errors[:save].should_not be_empty
115
+ queue.errors.should eq :save => { :processed => @objects.values_at(:valid1, :valid2, :not_changed),
116
+ :saved => @objects.values_at(:valid1, :valid2),
117
+ :failed => @objects[:unsaved_but_changed],
118
+ :pending => @objects.values_at(:not_changed, :saved, :valid3) }
119
+ end
120
+
121
+ it "should not raise SaveQueue::FailedSaveError" do
122
+ expect{ queue.save }.not_to raise_error(SaveQueue::FailedSaveError)
123
+ end
124
+ end
125
+
126
+ context "after fixing all failed objects" do
127
+ before(:each) do
128
+ queue.save.should be_false
129
+ @objects[:unsaved_but_changed].stub(:save).and_return(true)
130
+ end
131
+
132
+ it "should save all pending objects" do
133
+ @objects.values_at(:saved, :valid3).each do |object|
134
+ object.should_receive(:save).once
135
+ end
136
+
137
+ queue.save.should be_true
138
+ end
139
+
140
+ it "should not save already saved objects" do
141
+ pending "rewrite this test as integration"
142
+ @objects.values_at(:valid1, :valid2, :not_changed).each do |object|
143
+ object.should_not_receive(:save)
144
+ end
145
+
146
+ queue.save.should be_true
147
+ end
148
+
149
+ it "should not have any errors" do
150
+ queue.save
151
+ queue.errors.should be_empty
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,208 @@
1
+ require "spec_helper"
2
+ require "save_queue/object"
3
+
4
+ describe SaveQueue::Object do
5
+ let(:object) { new_object }
6
+
7
+ describe "#has_unsaved_changes?" do
8
+ it "should return true for changed object" do
9
+ object.mark_as_changed
10
+ object.should have_unsaved_changes
11
+ end
12
+
13
+ it "should return false for unchanged object" do
14
+ object.mark_as_saved
15
+ object.should_not have_unsaved_changes
16
+ end
17
+
18
+ it "should return false for new object" do
19
+ klass = Class.new
20
+ klass.send :include, SaveQueue::Object
21
+
22
+ klass.new.should_not have_unsaved_changes
23
+ end
24
+ end
25
+
26
+ describe "marks" do
27
+ it "should change state of an object" do
28
+ object.mark_as_saved
29
+ object.should_not have_unsaved_changes
30
+ object.mark_as_changed
31
+ object.should have_unsaved_changes
32
+ object.mark_as_saved
33
+ object.should_not have_unsaved_changes
34
+ end
35
+ end
36
+
37
+ describe "#save!" do
38
+ it "should delegate to save" do
39
+ object.save_queue.should_receive(:save!).once
40
+ object.save!
41
+ end
42
+ end
43
+
44
+ describe "#save" do
45
+ it "should save queue" do
46
+ object.save_queue.should_receive(:save).once
47
+ object.save
48
+ end
49
+
50
+ it "should save itself" do
51
+ klass = Class.new do
52
+ def save
53
+ "saved!"
54
+ end
55
+ end
56
+
57
+ klass.send :include, SaveQueue::Object
58
+ klass.new.save.should == "saved!"
59
+ end
60
+
61
+ context "object could not be saved" do
62
+ let(:object) do
63
+ klass = Class.new do
64
+ def save
65
+ false
66
+ end
67
+ end
68
+ klass.send :include, SaveQueue::Object
69
+ klass.new
70
+ end
71
+
72
+ it "should return false" do
73
+ object.save.should == false
74
+ end
75
+
76
+ it "should not save queue" do
77
+ object.save_queue.should_not_receive(:save)
78
+ object.save
79
+ end
80
+ end
81
+
82
+
83
+ it "should not circle" do
84
+ other_object = new_object
85
+
86
+ object.mark_as_changed
87
+ other_object.mark_as_changed
88
+
89
+
90
+ object .save_queue.add other_object
91
+ other_object.save_queue.add object
92
+
93
+ $object_counter = mock :counter
94
+ $object_counter.should_receive(:increment).once
95
+
96
+ def object.save
97
+ result = super
98
+ $object_counter.increment
99
+
100
+ result
101
+ end
102
+
103
+ $other_object_counter = mock :counter
104
+ $other_object_counter.should_receive(:increment).once
105
+
106
+ def other_object.save
107
+ result = super
108
+ $other_object_counter.increment
109
+
110
+ result
111
+ end
112
+
113
+ #object.should_receive(:save).once.and_return(true)
114
+ #@base.should_receive(:save).once.and_return(true)
115
+ other_object.save.should be_true
116
+ end
117
+
118
+ describe "multiple queues" do
119
+ let(:other_object) { new_object }
120
+
121
+ it "should save object only once" do
122
+ target = new_object
123
+ target.mark_as_changed
124
+
125
+ object .save_queue.add target
126
+ other_object.save_queue.add target
127
+
128
+ $counter = mock :counter
129
+ $counter.should_receive(:increment).once
130
+
131
+ def target.save
132
+ result = super
133
+ $counter.increment
134
+
135
+ result
136
+ end
137
+
138
+ #target.should_receive(:save).once.and_return(true)
139
+ object.save.should be_true
140
+ other_object.save.should be_true
141
+ end
142
+ end
143
+ end
144
+
145
+ describe "queue" do
146
+ let(:queue_class) { Class.new(SaveQueue::ObjectQueue) }
147
+ let(:other_queue_class) { Class.new(SaveQueue::ObjectQueue) }
148
+
149
+ it "should mapped to SaveQueue::ObjectQueue by default" do
150
+ klass = new_class
151
+ klass.queue_class.should be SaveQueue::ObjectQueue
152
+ end
153
+
154
+ describe "queue class change" do
155
+ it "before initialization should be possible" do
156
+ klass = new_class
157
+ klass.queue_class = other_queue_class
158
+ klass.new.save_queue.should be_kind_of other_queue_class
159
+ end
160
+
161
+ it "after initialization should not affect already created queue" do
162
+ klass = new_class
163
+ klass.queue_class = queue_class
164
+ object = klass.new
165
+
166
+ object.save_queue.should be_a queue_class
167
+ object.class.queue_class = other_queue_class
168
+ object.save_queue.should_not be_kind_of other_queue_class
169
+ object.save_queue.should be_a queue_class
170
+ end
171
+
172
+ it "should check dependencies for Hooks module" do
173
+ klass = new_class
174
+ expect{ klass.queue_class = Class.new }.to raise_error(RuntimeError, /Hooks/)
175
+ end
176
+ end
177
+
178
+ describe "inheritance" do
179
+ let(:klass) { new_class }
180
+
181
+ it "should inherit settings of parent class" do
182
+ klass.queue_class = queue_class
183
+
184
+ child = Class.new(klass)
185
+ child.queue_class.should == queue_class
186
+ end
187
+
188
+ it "should not override settings of parent class" do
189
+ klass.queue_class = queue_class
190
+
191
+ child = Class.new(klass)
192
+ child.queue_class = other_queue_class
193
+ child.queue_class.should == other_queue_class
194
+
195
+ klass.queue_class.should == queue_class
196
+ end
197
+
198
+
199
+ it "include SaveQueue::Object should not override settings of parent class" do
200
+ klass.queue_class = queue_class
201
+
202
+ child = Class.new(klass)
203
+ child.send :include, SaveQueue::Object
204
+ child.queue_class.should == queue_class
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,75 @@
1
+ require "spec_helper"
2
+ require "save_queue/object"
3
+
4
+ describe SaveQueue do
5
+ describe "#include" do
6
+ it "should include SaveQueue::Object" do
7
+ klass = Class.new
8
+ klass.send :include, SaveQueue
9
+
10
+ klass.should include SaveQueue::Object
11
+ end
12
+ end
13
+
14
+ describe "functional" do
15
+ it "README example should work" do
16
+ article = Article.new
17
+
18
+ tag_objects = []
19
+ 3.times do
20
+ tag = Tag.new
21
+ tag.change_attribute :title, "new tag"
22
+ tag.should_receive(:save).once
23
+ tag_objects << tag
24
+ end
25
+ article.tags = tag_objects
26
+
27
+ tag = Tag.new
28
+ tag.change_attribute :title, "single tag"
29
+ tag.should_receive(:save).once
30
+ article.add_tag tag
31
+
32
+ # that will save article and all tags in this article if article
33
+ # and tags are valid, and if article.save and all tag.save returns true
34
+ # You may also use #save! method, that will trigger save_queue.save! and
35
+ # raise SaveQueue::FailedSaveError on fail
36
+ article.save.should be_true
37
+ end
38
+ end
39
+ end
40
+
41
+
42
+
43
+ require 'save_queue'
44
+ class Article
45
+ include SaveQueue
46
+
47
+ def change_attribute attr, value
48
+ @attributes ||= {}
49
+ @attributes[attr] = value
50
+ mark_as_changed # call this and object will be marked for save
51
+ end
52
+
53
+ def tags= tag_objects
54
+ @tags = tag_objects
55
+ mark_as_changed
56
+ save_queue.add_all tag_objects
57
+ end
58
+
59
+ def add_tag tag
60
+ @tags ||= []
61
+ @tags << tag
62
+ save_queue.add tag # or use <<, push methods
63
+ end
64
+ end
65
+
66
+
67
+ class Tag
68
+ include SaveQueue
69
+
70
+ def change_attribute attr, value
71
+ @attributes ||= {}
72
+ @attributes[attr] = value
73
+ mark_as_changed # call this and object will be marked for save
74
+ end
75
+ end
@@ -0,0 +1,10 @@
1
+ def new_object
2
+ new_class.new
3
+ end
4
+
5
+ def new_class
6
+ klass = Class.new
7
+ klass.send :include, SaveQueue::Object
8
+
9
+ klass
10
+ end
@@ -0,0 +1,26 @@
1
+ def fill_queue
2
+ 5.times do
3
+ queue << new_element
4
+ end
5
+ end
6
+
7
+ def new_element(name = :element, options = {})
8
+ element = stub(name)
9
+ element.stub(:save).and_return(options.has_key?(:saved) ? options[:saved] : true)
10
+ element.stub(:has_unsaved_changes?).and_return(options.has_key?(:changed) ? options[:changed] : true)
11
+
12
+ element
13
+ end
14
+
15
+ def new_velement(options = {:valid => true})
16
+ object = new_element
17
+ object.stub(:valid?).and_return(options[:valid])
18
+ object
19
+ end
20
+
21
+ def new_queue_class(options = {})
22
+ queue_class = Class.new
23
+ queue_class.stub(:include?).with(Hooks).and_return(true)
24
+
25
+ queue_class
26
+ end
@@ -0,0 +1,132 @@
1
+ require "spec_helper"
2
+ require "save_queue/uniq_queue"
3
+
4
+ describe SaveQueue::UniqQueue do
5
+ let(:queue) { SaveQueue::UniqQueue.new }
6
+ #let(:element) { new_element(:element) }
7
+
8
+ [:size, :count].each do |method|
9
+ describe "##{method}" do
10
+ it "should return 0 for empty queue" do
11
+ queue.send(method).should be_zero
12
+ end
13
+
14
+ it "should count elements in queue" do
15
+ 3.times do
16
+ queue << new_element
17
+ end
18
+
19
+ queue.send(method).should == 3
20
+ end
21
+ end
22
+ end
23
+
24
+ describe "#new" do
25
+ it "should be empty queue" do
26
+ queue.should have(0).elements
27
+ queue.should be_empty
28
+ end
29
+ end
30
+
31
+ [:add, :<<, :push].each do |method|
32
+ describe "##{method}" do
33
+ let(:element) { new_element }
34
+ it "should add object to a queue" do
35
+ queue.should be_empty
36
+
37
+ queue.send(method, new_element)
38
+ queue.should_not be_empty
39
+ queue.should have(1).elements
40
+
41
+ queue.send(method, new_element)
42
+ queue.should_not be_empty
43
+ queue.should have(2).elements
44
+ end
45
+
46
+ it "should add object to a queue once" do
47
+ queue.should be_empty
48
+
49
+ queue.send(method, element)
50
+ queue.should have(1).elements
51
+
52
+ queue.send(method, element)
53
+ queue.should have(1).elements
54
+ end
55
+
56
+ it "should return true" do
57
+ queue.send(method, element).should === true
58
+ end
59
+
60
+ it "should return false if element was not added" do
61
+ queue.send(method, element)
62
+ queue.should have(1).elements
63
+
64
+ queue.send(method, element).should === false
65
+ end
66
+ end
67
+ end
68
+
69
+ describe "#add_all" do
70
+ it "should delegate to #add" do
71
+ queue.should_receive(:add).exactly(3).times
72
+ queue.add_all [1,2,3]
73
+ end
74
+
75
+ it "should act as #add if single argument passed" do
76
+ queue.should_receive(:add).once
77
+ queue.add_all 1
78
+ end
79
+ end
80
+
81
+
82
+ describe "#clear" do
83
+ it "should clear the queue" do
84
+ fill_queue
85
+ #expect{ queue.clear }.to change{ queue.count }.from(5).to(0)
86
+ queue.should_not be_empty
87
+ queue.clear
88
+ queue.should be_empty
89
+ end
90
+ end
91
+
92
+ describe "#pop" do
93
+ it "should remove last element from queue and return it back" do
94
+ queue << new_element(:first)
95
+ queue << new_element
96
+ queue << new_element
97
+ last = new_element(:last)
98
+ queue << last
99
+
100
+ result = nil
101
+ expect{ result = queue.pop }.to change{ queue.count }.by(-1)
102
+ result.should_not be_nil
103
+ result.should be last
104
+ end
105
+ end
106
+
107
+ describe "#shift" do
108
+ it "should remove first element from queue and return it back" do
109
+ first = new_element(:first)
110
+ queue << first
111
+ queue << new_element
112
+ queue << new_element
113
+ queue << new_element(:last)
114
+
115
+ result = nil
116
+ expect{ result = queue.shift }.to change{ queue.count }.by(-1)
117
+ result.should_not be_nil
118
+ result.should be first
119
+ end
120
+ end
121
+
122
+ describe "delegates" do
123
+ let(:queue_var) { queue.instance_variable_get("@queue") }
124
+
125
+ SaveQueue::UniqQueue::DELEGATED_METHODS.each do |method|
126
+ it "##{method}\tshould delegate to @queue##{method}" do
127
+ queue_var.should_receive(method).once
128
+ queue.send method
129
+ end
130
+ end
131
+ end
132
+ end