save_queue 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: