save_queue 0.0.1 → 0.1.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.
- data/.travis.yml +7 -0
- data/README.md +117 -0
- data/Rakefile +3 -0
- data/lib/save_queue/object.rb +51 -0
- data/lib/save_queue/plugins/validation/object.rb +15 -0
- data/lib/save_queue/plugins/validation/queue.rb +0 -0
- data/lib/save_queue/queue.rb +51 -0
- data/lib/save_queue/version.rb +1 -1
- data/lib/save_queue.rb +6 -1
- data/save_queue.gemspec +1 -0
- data/spec/save_queue_usage_spec.rb +292 -0
- metadata +23 -5
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -6,6 +6,123 @@ Installation
|
|
6
6
|
------------
|
7
7
|
gem install save_queue
|
8
8
|
|
9
|
+
Usage
|
10
|
+
-----
|
11
|
+
|
12
|
+
How to start:
|
13
|
+
|
14
|
+
1. include SavedQueue:
|
15
|
+
|
16
|
+
require 'saved_queue'
|
17
|
+
class Artice
|
18
|
+
include SavedQueue
|
19
|
+
end
|
20
|
+
|
21
|
+
2. call \#mark_as_saved method when object gets dirty:
|
22
|
+
|
23
|
+
class Artice
|
24
|
+
def change_attribute attr, value
|
25
|
+
@attributes[attr] = value
|
26
|
+
mark_as_saved # call this and object will be marked for save
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
3. point save method to your save logic or dont care if you use #save already:
|
31
|
+
|
32
|
+
class Artice
|
33
|
+
# @return [boolean]
|
34
|
+
def save
|
35
|
+
write
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
4. Save Queue forced you to use valid? method, this should be changes in future and extracted to a module:
|
40
|
+
|
41
|
+
class Artice
|
42
|
+
# @return [boolean]
|
43
|
+
def valid?
|
44
|
+
true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
5. add SavedQueue to some other classes:
|
49
|
+
|
50
|
+
require 'saved_queue'
|
51
|
+
class Tag
|
52
|
+
include SavedQueue
|
53
|
+
end
|
54
|
+
|
55
|
+
class Artice
|
56
|
+
def tags= tag_objects
|
57
|
+
@tags = tag_objects
|
58
|
+
saved_queue.add_all tag_objects
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_tag tag
|
62
|
+
@tags ||= []
|
63
|
+
@tags << tag
|
64
|
+
saved_queue.add tag
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
6. Use it:
|
69
|
+
|
70
|
+
article = Article.new
|
71
|
+
tag_objects = [Tag.new, Tag.new, Tag.new]
|
72
|
+
article.tags = tag_object
|
73
|
+
article.add_tag Tag.new
|
74
|
+
|
75
|
+
# that will save article and all tags in this article if article
|
76
|
+
# and tags are valid, and if article.save and all tag.save returns true
|
77
|
+
article.save
|
78
|
+
|
79
|
+
7. Handle errors
|
80
|
+
|
81
|
+
begin
|
82
|
+
article.save
|
83
|
+
rescue SaveQueue::FailedSaveError => save_error
|
84
|
+
|
85
|
+
# @params [Hash] info
|
86
|
+
# @option info [Array<Object>] :saved
|
87
|
+
# @option info [Object] :failed
|
88
|
+
# @option info [Array<Object>] :pending
|
89
|
+
save_error.context
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
If you have custom logic for marking objects dirty then you may want to override
|
95
|
+
\#has_unsaved_changes? method in you class like this:
|
96
|
+
|
97
|
+
def has_unsaved_changes?
|
98
|
+
dirty? # dirty is you custom method to determine has object unsaved_changes or not
|
99
|
+
end
|
100
|
+
|
101
|
+
method \#mark_as_saved becomes useless in this case and you should mark objects by your self.
|
102
|
+
|
103
|
+
|
104
|
+
Note: Today Save Queue use only #save method to perform save actions on an objects, but later this should be changed to custom option.
|
105
|
+
|
106
|
+
Note: Save Queue forced you to use valid? method, this should be changes in future and extracted to a module
|
107
|
+
|
108
|
+
|
109
|
+
Requirements
|
110
|
+
------------
|
111
|
+
none, rspec2 for testing
|
112
|
+
|
113
|
+
Compatibility
|
114
|
+
-------------
|
115
|
+
tested with Ruby
|
116
|
+
|
117
|
+
* 1.8.7
|
118
|
+
* 1.9.2
|
119
|
+
* 1.9.3
|
120
|
+
* jruby
|
121
|
+
* ruby-head
|
122
|
+
* ree
|
123
|
+
|
124
|
+
see [build history](http://travis-ci.org/#!/AlexParamonov/save_queue/builds)
|
125
|
+
|
9
126
|
Copyright
|
10
127
|
---------
|
11
128
|
Copyright © 2011 Alexander N Paramonov. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -0,0 +1,51 @@
|
|
1
|
+
require "save_queue/queue"
|
2
|
+
|
3
|
+
module SaveQueue
|
4
|
+
module Object
|
5
|
+
|
6
|
+
module RunAlwaysFirst
|
7
|
+
# @return [Boolean]
|
8
|
+
def save(*args)
|
9
|
+
# are objects in queue valid?
|
10
|
+
return false unless valid?
|
11
|
+
return false unless save_queue.valid?
|
12
|
+
#return false if defined?(super) and false == super
|
13
|
+
|
14
|
+
super_saved = true
|
15
|
+
super_saved = super if defined?(super)
|
16
|
+
# object is saved here
|
17
|
+
mark_as_saved
|
18
|
+
return (super_saved and save_queue.save)
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def initialize(*args)
|
25
|
+
super if defined?(super)
|
26
|
+
queue = Queue.new
|
27
|
+
instance_variable_set "@_save_queue", queue
|
28
|
+
|
29
|
+
# this will make RunAlwaysFirst methods triggered first in inheritance tree
|
30
|
+
extend RunAlwaysFirst
|
31
|
+
end
|
32
|
+
|
33
|
+
def mark_as_changed
|
34
|
+
instance_variable_set "@_changed_mark", true
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_unsaved_changes?
|
38
|
+
status = instance_variable_get("@_changed_mark")
|
39
|
+
status.nil? ? false : status
|
40
|
+
end
|
41
|
+
|
42
|
+
def save_queue
|
43
|
+
instance_variable_get "@_save_queue"
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def mark_as_saved
|
48
|
+
instance_variable_set "@_changed_mark", false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
File without changes
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module SaveQueue
|
2
|
+
class Queue
|
3
|
+
def initialize
|
4
|
+
@queue = []
|
5
|
+
end
|
6
|
+
|
7
|
+
def add_all objects
|
8
|
+
Array(objects).each do |object|
|
9
|
+
add object
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def add object
|
14
|
+
raise ArgumentError, "#{object.inspect} does not include SaveQueue::Object" unless object.class.include? SaveQueue::Object
|
15
|
+
@queue << object unless @queue.include? object
|
16
|
+
end
|
17
|
+
|
18
|
+
def save
|
19
|
+
saved = []
|
20
|
+
@queue.each do |object|
|
21
|
+
if object.has_unsaved_changes?
|
22
|
+
|
23
|
+
result = object.save
|
24
|
+
raise FailedSaveError, {:saved => saved, :failed => object, :pending => @queue - (saved + [object])} if false == result
|
25
|
+
|
26
|
+
saved << object
|
27
|
+
end
|
28
|
+
end
|
29
|
+
@queue = []
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def valid?
|
35
|
+
@queue.all? do |object|
|
36
|
+
object.valid?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class FailedSaveError < RuntimeError
|
42
|
+
attr_reader :context
|
43
|
+
def initialize(context_hash)
|
44
|
+
@context = context_hash
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s # Some default way to display errors
|
48
|
+
"#{super}: " + @context.to_s
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/save_queue/version.rb
CHANGED
data/lib/save_queue.rb
CHANGED
data/save_queue.gemspec
CHANGED
@@ -0,0 +1,292 @@
|
|
1
|
+
require "save_queue"
|
2
|
+
module Tests
|
3
|
+
class Object
|
4
|
+
include SaveQueue
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
# TODO split by Object and Queue
|
9
|
+
describe "SaveQueue usage" do
|
10
|
+
before(:each) do
|
11
|
+
@base = new_object
|
12
|
+
@related_object = new_object
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#has_unsaved_changes?" do
|
16
|
+
it "should return true for changed object" do
|
17
|
+
@base.save
|
18
|
+
@base.should_not have_unsaved_changes
|
19
|
+
@base.mark_as_changed
|
20
|
+
@base.should have_unsaved_changes
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return false for saved object" do
|
24
|
+
@base.mark_as_changed
|
25
|
+
@base.should have_unsaved_changes
|
26
|
+
@base.save
|
27
|
+
@base.should_not have_unsaved_changes
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should return false for new object" do
|
31
|
+
Tests::Object.new.should_not have_unsaved_changes
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#add" do
|
36
|
+
it "should ignore same objects in queue" do
|
37
|
+
@base.save_queue.add @related_object
|
38
|
+
@base.save_queue.add @related_object
|
39
|
+
@base.save_queue.add @related_object
|
40
|
+
|
41
|
+
@related_object.should_receive(:save).once.and_return(true)
|
42
|
+
@base.save.should be_true
|
43
|
+
@base.save.should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should raise ArgumentError if object does not include SavedQueue" do
|
47
|
+
object = new_mock_object :without_include
|
48
|
+
object.stub_chain(:class, :include?).with(SaveQueue::Object).and_return(false)
|
49
|
+
|
50
|
+
expect{ @base.save_queue.add object }.to raise_error ArgumentError, "#{object.inspect} does not include SaveQueue::Object"
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should not raise ArgumentError if object includes SavedQueue" do
|
54
|
+
object = new_mock_object :with_include
|
55
|
+
object.stub_chain(:class, :include?).with(SaveQueue::Object).and_return(true)
|
56
|
+
|
57
|
+
expect{ @base.save_queue.add object }.to_not raise_error ArgumentError, "#{object.inspect} does not include SaveQueue::Object"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "add_all" do
|
62
|
+
it "should delegate to #add" do
|
63
|
+
@base.save_queue.should_receive(:add).exactly(3).times
|
64
|
+
@base.save_queue.add_all [1,2,3]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#save" do
|
69
|
+
it "should raise SaveQueue::FailedSaveError if at least one object in queue is not saved" do
|
70
|
+
@base.save_queue.add @related_object
|
71
|
+
bad_object = new_mock_object :bad_object
|
72
|
+
bad_object.stub(:save).and_return(false)
|
73
|
+
|
74
|
+
@base.save_queue.add bad_object
|
75
|
+
|
76
|
+
|
77
|
+
expect{ @base.save_queue.save }.to raise_error SaveQueue::FailedSaveError
|
78
|
+
#, {:saved => [@related_object],
|
79
|
+
# :failed => bad_object,
|
80
|
+
# :pending => []}
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should save assigned object" do
|
84
|
+
@base.save_queue.add @related_object
|
85
|
+
|
86
|
+
@related_object.should_receive(:save).once.and_return(true)
|
87
|
+
@base.save.should be_true
|
88
|
+
end
|
89
|
+
|
90
|
+
it "only 1st save will save the queue" do
|
91
|
+
@base.save_queue.add @related_object
|
92
|
+
|
93
|
+
@related_object.should_receive(:save).once.and_return(true)
|
94
|
+
@base.save.should be_true
|
95
|
+
@base.save.should be_true
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should not circle" do
|
99
|
+
object = new_object
|
100
|
+
|
101
|
+
@base.mark_as_changed
|
102
|
+
|
103
|
+
object.save_queue.add @base
|
104
|
+
@base.save_queue.add object
|
105
|
+
|
106
|
+
$object_counter = mock :counter
|
107
|
+
$object_counter.should_receive(:increment).once
|
108
|
+
|
109
|
+
def object.save
|
110
|
+
result = super
|
111
|
+
$object_counter.increment
|
112
|
+
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
$base_counter = mock :counter
|
117
|
+
$base_counter.should_receive(:increment).once
|
118
|
+
|
119
|
+
def @base.save
|
120
|
+
result = super
|
121
|
+
$base_counter.increment
|
122
|
+
|
123
|
+
result
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
|
128
|
+
#object.should_receive(:save).once.and_return(true)
|
129
|
+
#@base.should_receive(:save).once.and_return(true)
|
130
|
+
@base.save.should be_true
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should save only unsaved objects" do
|
134
|
+
object = new_object
|
135
|
+
|
136
|
+
@base.save_queue.add object
|
137
|
+
object.save
|
138
|
+
|
139
|
+
object.should_not_receive(:save)
|
140
|
+
@base.save_queue.save.should be_true
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should save and object if it had changed state" do
|
144
|
+
object = new_object
|
145
|
+
|
146
|
+
@base.save_queue.add object
|
147
|
+
object.save
|
148
|
+
object.mark_as_changed
|
149
|
+
|
150
|
+
object.should_receive(:save).once.and_return(true)
|
151
|
+
@base.save.should be_true
|
152
|
+
end
|
153
|
+
|
154
|
+
context "invalid caller" do
|
155
|
+
it "should not save queue" do
|
156
|
+
@base.save_queue.add @related_object
|
157
|
+
|
158
|
+
@base.stub(:valid?).and_return(false)
|
159
|
+
|
160
|
+
@related_object.should_not_receive(:save)
|
161
|
+
@base.save.should be_false
|
162
|
+
|
163
|
+
#@base.stub(:valid?).and_return(true)
|
164
|
+
#@related_object.should_receive(:save).once.and_return(true)
|
165
|
+
#@base.save.should be_true
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "invalid object in queue" do
|
170
|
+
it "should not save queue and caller" do
|
171
|
+
invalid = new_mock_object :invalid
|
172
|
+
invalid.stub(:valid?).and_return(false)
|
173
|
+
|
174
|
+
valid = new_mock_object :valid
|
175
|
+
valid.stub(:valid?).and_return(true)
|
176
|
+
|
177
|
+
@base.save_queue.add invalid
|
178
|
+
@base.save_queue.add valid
|
179
|
+
|
180
|
+
invalid.should_not_receive(:save)
|
181
|
+
valid.should_not_receive(:save)
|
182
|
+
@base.save.should be_false
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "multiple callers" do
|
187
|
+
before(:each) do
|
188
|
+
@base2 = new_object
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should save assigned object only once" do
|
192
|
+
object = new_object
|
193
|
+
|
194
|
+
@base.save_queue.add object
|
195
|
+
@base2.save_queue.add object
|
196
|
+
|
197
|
+
$counter = mock :counter
|
198
|
+
$counter.should_receive(:increment).once
|
199
|
+
|
200
|
+
def object.save
|
201
|
+
result = super
|
202
|
+
$counter.increment
|
203
|
+
|
204
|
+
result
|
205
|
+
end
|
206
|
+
|
207
|
+
#object.should_receive(:save).once.and_return(true)
|
208
|
+
@base.save.should be_true
|
209
|
+
@base2.save.should be_true
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should correctly save all objects once" do
|
213
|
+
|
214
|
+
$base_counters = []
|
215
|
+
3.times do |index|
|
216
|
+
object = new_object
|
217
|
+
$base_counters[index] = mock :counter
|
218
|
+
$base_counters[index].should_receive(:increment).once
|
219
|
+
eval %{
|
220
|
+
def object.save
|
221
|
+
result = super
|
222
|
+
$base_counters[#{index}].increment
|
223
|
+
|
224
|
+
result
|
225
|
+
end
|
226
|
+
}
|
227
|
+
|
228
|
+
@base.save_queue.add object
|
229
|
+
end
|
230
|
+
|
231
|
+
$base2_counters = []
|
232
|
+
2.times do |index|
|
233
|
+
object = new_object
|
234
|
+
$base2_counters[index] = mock :counter
|
235
|
+
$base2_counters[index].should_receive(:increment).once
|
236
|
+
eval %{
|
237
|
+
def object.save
|
238
|
+
result = super
|
239
|
+
$base2_counters[#{index}].increment
|
240
|
+
|
241
|
+
result
|
242
|
+
end
|
243
|
+
}
|
244
|
+
|
245
|
+
@base2.save_queue.add object
|
246
|
+
end
|
247
|
+
|
248
|
+
$shared_counters = []
|
249
|
+
2.times do |index|
|
250
|
+
object = new_object
|
251
|
+
$shared_counters[index] = mock :counter
|
252
|
+
$shared_counters[index].should_receive(:increment).once
|
253
|
+
eval %{
|
254
|
+
def object.save
|
255
|
+
result = super
|
256
|
+
$shared_counters[#{index}].increment
|
257
|
+
|
258
|
+
result
|
259
|
+
end
|
260
|
+
}
|
261
|
+
|
262
|
+
@base.save_queue.add object
|
263
|
+
@base2.save_queue.add object
|
264
|
+
end
|
265
|
+
|
266
|
+
@base.save.should be_true
|
267
|
+
@base2.save.should be_true
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
private
|
273
|
+
def new_mock_object name
|
274
|
+
bad_object = mock name
|
275
|
+
bad_object.stub_chain(:class, :include?).with(SaveQueue::Object).and_return(true)
|
276
|
+
bad_object.stub(:valid?).and_return(true)
|
277
|
+
bad_object.stub(:save).and_return(true)
|
278
|
+
bad_object.stub(:has_unsaved_changes?).and_return(true)
|
279
|
+
|
280
|
+
bad_object
|
281
|
+
end
|
282
|
+
|
283
|
+
def new_object
|
284
|
+
object = Tests::Object.new
|
285
|
+
object.stub(:valid?).and_return(true)
|
286
|
+
object.mark_as_changed
|
287
|
+
|
288
|
+
object
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: save_queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-11-
|
12
|
+
date: 2011-11-15 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &23937520 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,18 @@ dependencies:
|
|
21
21
|
version: '2.6'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *23937520
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &23936900 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *23936900
|
25
36
|
description: Save Queue allows to push related objects to an object's queue for delayed
|
26
37
|
save, that will triggered on object#save. In this case object wil store all related
|
27
38
|
information on its save.
|
@@ -33,14 +44,20 @@ extra_rdoc_files: []
|
|
33
44
|
files:
|
34
45
|
- .gitignore
|
35
46
|
- .rspec
|
47
|
+
- .travis.yml
|
36
48
|
- Gemfile
|
37
49
|
- HISTORY.md
|
38
50
|
- LICENSE
|
39
51
|
- README.md
|
40
52
|
- Rakefile
|
41
53
|
- lib/save_queue.rb
|
54
|
+
- lib/save_queue/object.rb
|
55
|
+
- lib/save_queue/plugins/validation/object.rb
|
56
|
+
- lib/save_queue/plugins/validation/queue.rb
|
57
|
+
- lib/save_queue/queue.rb
|
42
58
|
- lib/save_queue/version.rb
|
43
59
|
- save_queue.gemspec
|
60
|
+
- spec/save_queue_usage_spec.rb
|
44
61
|
homepage: http://github.com/AlexParamonov/save_queue
|
45
62
|
licenses: []
|
46
63
|
post_install_message:
|
@@ -61,8 +78,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
78
|
version: '0'
|
62
79
|
requirements: []
|
63
80
|
rubyforge_project: save_queue
|
64
|
-
rubygems_version: 1.8.
|
81
|
+
rubygems_version: 1.8.10
|
65
82
|
signing_key:
|
66
83
|
specification_version: 3
|
67
84
|
summary: Push related objects to a queue for delayed save
|
68
85
|
test_files: []
|
86
|
+
has_rdoc:
|