save_queue 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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