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 ADDED
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby
6
+ - ruby-head
7
+ - ree
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
@@ -1,2 +1,5 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rspec'
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
2
5
  task :default => [:spec]
@@ -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
@@ -0,0 +1,15 @@
1
+ module SaveQueue
2
+ module Plugins
3
+ module Validation
4
+ module Object
5
+
6
+ # @return [Boolean]
7
+ def save(*args)
8
+ # are objects in queue valid?
9
+ return false unless valid?
10
+ return false unless save_queue.valid?
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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
@@ -1,3 +1,3 @@
1
1
  module SaveQueue
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/save_queue.rb CHANGED
@@ -1,5 +1,10 @@
1
1
  require "save_queue/version"
2
+ require "save_queue/object"
2
3
 
3
4
  module SaveQueue
4
- # Your code goes here...
5
+ def self.included base
6
+ base.send :include, SaveQueue::Object
7
+ end
8
+
9
+
5
10
  end
data/save_queue.gemspec CHANGED
@@ -20,5 +20,6 @@ Gem::Specification.new do |s|
20
20
 
21
21
  # specify any dependencies here; for example:
22
22
  s.add_development_dependency "rspec", ">= 2.6"
23
+ s.add_development_dependency "rake"
23
24
  # s.add_runtime_dependency "rest-client"
24
25
  end
@@ -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.1
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 00:00:00.000000000Z
12
+ date: 2011-11-15 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &18435660 !ruby/object:Gem::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: *18435660
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.6
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: