save_queue 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,21 +14,22 @@ module SaveQueue
14
14
  define_hook :before_add
15
15
  define_hook :after_add
16
16
 
17
+ before_add :check_requirements
18
+
17
19
  # @return [Hash] save
18
- # @option save [Array<Object>] :processed
19
20
  # @option save [Array<Object>] :saved
20
21
  # @option save [Object] :failed
21
22
  # @option save [Array<Object>] :pending
22
23
  attr_reader :errors
24
+
23
25
  def initialize(*args)
24
26
  super
25
27
  @errors = {}
26
28
  end
27
29
 
28
30
  def add object
29
- run_hook :before_add
31
+ run_hook :before_add, object
30
32
 
31
- check_requirements_for object
32
33
  result = super object
33
34
 
34
35
  run_hook :after_add, result, object
@@ -36,13 +37,6 @@ module SaveQueue
36
37
  result
37
38
  end
38
39
 
39
- def << object
40
- add object
41
- self
42
- end
43
-
44
- alias_method :push, :add
45
-
46
40
  def save
47
41
  save!
48
42
  true
@@ -52,34 +46,28 @@ module SaveQueue
52
46
 
53
47
  def save!
54
48
  run_hook :before_save
49
+
55
50
  @errors = {}
56
51
  saved = []
57
- processed = []
58
52
 
59
53
  @queue.each do |object|
60
- if object.has_unsaved_changes?
61
-
62
- result = object.save
63
- if false == result
64
- @errors[:save] = {:processed => processed, :saved => saved, :failed => object, :pending => @queue - (saved + [object])}
65
- raise FailedSaveError, errors[:save]
66
- end
67
-
68
- saved << object
54
+ if false == object.save
55
+ @errors[:save] = {:saved => saved, :failed => object, :pending => @queue - (saved + [object])}
56
+ raise FailedSaveError, errors[:save]
69
57
  end
70
- processed << object
71
- end
72
58
 
73
- @queue.clear
59
+ saved << object
60
+ end
61
+ clear
74
62
 
75
63
  run_hook :after_save
64
+
76
65
  true
77
66
  end
78
67
 
79
-
80
68
  private
81
- def check_requirements_for object
82
- [:save, :has_unsaved_changes?].each do |method|
69
+ def check_requirements(object)
70
+ [:save].each do |method|
83
71
  raise ArgumentError, "#{object.inspect} does not respond to ##{method}" unless object.respond_to? method
84
72
  end
85
73
  end
@@ -0,0 +1,31 @@
1
+ module SaveQueue
2
+ module Plugins
3
+ module Dirty
4
+ module Object
5
+ def mark_as_changed
6
+ @_changed_mark = true
7
+ end
8
+
9
+ def mark_as_saved
10
+ @_changed_mark = false
11
+ end
12
+
13
+ # @returns [Boolean] true if object has been modified
14
+ def has_unsaved_changes?
15
+ @_changed_mark ||= false
16
+ end
17
+
18
+ private
19
+ def _sq_around_original_save
20
+ result = nil
21
+ if has_unsaved_changes?
22
+ result = yield
23
+ mark_as_saved
24
+ end
25
+
26
+ result
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ require "save_queue/plugins/dirty/object"
2
+
3
+ module SaveQueue
4
+ module Plugins
5
+ module Dirty
6
+ def self.included base
7
+ base.send :include, Dirty::Object unless base.include? Dirty::Object
8
+ end
9
+ end
10
+ end
11
+ end
@@ -2,32 +2,14 @@ module SaveQueue
2
2
  module Plugins
3
3
  module Notification
4
4
  module Object
5
- module AddObserverToQueue
6
- def create_queue
7
- super
8
- queue = instance_variable_get("@_save_queue")
9
- raise "save queue should respond to add_observer in order to work correctly" unless queue.respond_to? :add_observer
10
- queue.add_observer(self, :queue_changed_event)
11
- end
12
- end
13
-
14
- def self.included base
15
-
16
- #queue_creator = Module.new do
17
- # def create_queue
18
- # super
19
- # queue = instance_variable_get("@_save_queue")
20
- # raise "save queue should respond to add_observer in order to work correctly" unless queue.respond_to? :add_observer
21
- # queue.add_observer(self, :queue_changed_event)
22
- # end
23
- #end
5
+ def queue_changed_event(result, object, *args); end
24
6
 
25
- #base.send :include, queue_creator
26
- base.send :include, AddObserverToQueue unless base.include?(AddObserverToQueue)
27
- end
7
+ def create_queue
8
+ super
28
9
 
29
- def queue_changed_event(result, object)
30
- mark_as_changed if result
10
+ queue = instance_variable_get("@_save_queue")
11
+ raise "save queue should respond to add_observer in order to work correctly" unless queue.respond_to? :add_observer
12
+ queue.add_observer(self, :queue_changed_event)
31
13
  end
32
14
  end
33
15
  end
@@ -9,8 +9,8 @@ module SaveQueue
9
9
  module Notification
10
10
  module Queue
11
11
  def self.included base
12
- base.send :include, Observable unless base.include? Observable
13
- base.after_add :change_and_notify # if base.respond_to? :after_add
12
+ base.send :include, Observable
13
+ base.after_add :change_and_notify
14
14
  end
15
15
 
16
16
  private
@@ -18,7 +18,6 @@ module SaveQueue
18
18
  changed
19
19
  notify_observers(*args)
20
20
  end
21
-
22
21
  end
23
22
  end
24
23
  end
@@ -6,8 +6,8 @@ module SaveQueue
6
6
  module Notification
7
7
  def self.included base
8
8
  klass = Class.new(base.queue_class)
9
- klass.send :include, Notification::Queue unless klass.include? Notification::Queue
10
- base.send :include, Notification::Object unless base.include? Notification::Object
9
+ klass.send :include, Notification::Queue unless klass.include? Notification::Queue
10
+ base.send :include, Notification::Object unless base.include? Notification::Object
11
11
  base.queue_class = klass
12
12
  end
13
13
  end
@@ -1,12 +1,20 @@
1
1
  module SaveQueue
2
2
  class FailedValidationError < Error
3
3
  attr_reader :failed_objects
4
+
4
5
  def initialize(failed_objects)
5
6
  @failed_objects = Array(failed_objects)
6
7
  end
7
8
 
8
- def to_s # Some default way to display errors
9
- "#{super}: " + @failed_objects.map{|object| "\"#{object.to_s}\": " + object.errors.full_messages.join(', ')}.join("\n")
9
+
10
+ # Overwrite to your needs
11
+ def to_s
12
+ "#{super}: " + @failed_objects.join("\n")
10
13
  end
14
+
15
+ # danger if object not respond to errors or full_messages
16
+ #def to_s
17
+ # "#{super}: " + @failed_objects.map{|object| "\"#{object.to_s}\": " + object.errors.full_messages.join(', ')}.join("\n")
18
+ #end
11
19
  end
12
20
  end
@@ -5,7 +5,7 @@ module SaveQueue
5
5
  module Validation
6
6
  module Queue
7
7
  def self.included base
8
- base.before_save :validate! if base.respond_to? :before_save
8
+ base.before_save :validate!
9
9
  end
10
10
 
11
11
  def valid?
@@ -7,7 +7,7 @@ module SaveQueue
7
7
  module Validation
8
8
  def self.included base
9
9
  klass = Class.new(base.queue_class)
10
- klass.send :include, Validation::Queue
10
+ klass.send :include, Validation::Queue unless klass.include? Validation::Queue
11
11
  base.queue_class = klass
12
12
  end
13
13
  end
@@ -32,7 +32,15 @@ module SaveQueue
32
32
 
33
33
  true
34
34
  end
35
- alias_method :push, :add
36
- alias_method :<<, :add
35
+
36
+ #alias_method :push, :add # Not working as expected: does not save inheritance
37
+ def push(*args)
38
+ add *args
39
+ end
40
+
41
+ def << *args
42
+ add *args
43
+ self
44
+ end
37
45
  end
38
46
  end
@@ -1,3 +1,3 @@
1
1
  module SaveQueue
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -0,0 +1,74 @@
1
+ require "spec_helper"
2
+ require "save_queue/plugins/dirty"
3
+
4
+ describe SaveQueue::Plugins::Dirty do
5
+ describe "#integration" do
6
+ it "should mix Object to object class" do
7
+ klass = new_class
8
+ klass.send :include, SaveQueue::Plugins::Dirty
9
+
10
+ klass.should include SaveQueue::Plugins::Dirty::Object
11
+ end
12
+ describe "functional" do
13
+ it "README example should work" do
14
+ article = Article.new
15
+
16
+ tag_objects = []
17
+ 3.times do
18
+ tag = Tag.new
19
+ tag.change_attribute :title, "new tag"
20
+ tag.should_receive(:save).once
21
+ tag_objects << tag
22
+ end
23
+ article.tags = tag_objects
24
+
25
+ tag = Tag.new
26
+ tag.change_attribute :title, "single tag"
27
+ tag.should_receive(:save).once
28
+ article.add_tag tag
29
+
30
+ # that will save article and all tags in this article if article
31
+ # and tags are valid, and if article.save and all tag.save returns true
32
+ # You may also use #save! method, that will trigger save_queue.save! and
33
+ # raise SaveQueue::FailedSaveError on fail
34
+ article.save.should be_true
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ require 'save_queue'
41
+ class Article
42
+ include SaveQueue
43
+ include SaveQueue::Plugins::Dirty
44
+
45
+ def change_attribute attr, value
46
+ @attributes ||= {}
47
+ @attributes[attr] = value
48
+ mark_as_changed # call this and object will be marked for save
49
+ end
50
+
51
+ def tags= tag_objects
52
+ @tags = tag_objects
53
+ mark_as_changed
54
+ save_queue.add_all tag_objects
55
+ end
56
+
57
+ def add_tag tag
58
+ @tags ||= []
59
+ @tags << tag
60
+ save_queue.add tag # or use <<, push methods
61
+ end
62
+ end
63
+
64
+
65
+ class Tag
66
+ include SaveQueue
67
+ include SaveQueue::Plugins::Dirty
68
+
69
+ def change_attribute attr, value
70
+ @attributes ||= {}
71
+ @attributes[attr] = value
72
+ mark_as_changed # call this and object will be marked for save
73
+ end
74
+ end
@@ -0,0 +1,200 @@
1
+ require "spec_helper"
2
+ require "save_queue/plugins/dirty/object"
3
+
4
+ class DirtyObject
5
+ include SaveQueue
6
+ include SaveQueue::Plugins::Dirty::Object
7
+ end
8
+
9
+ describe SaveQueue::Plugins::Dirty::Object do
10
+ let(:object) { DirtyObject.new }
11
+
12
+ describe "#has_unsaved_changes?" do
13
+ it "should return true for changed object" do
14
+ object.mark_as_changed
15
+ object.has_unsaved_changes?.should eq true
16
+ end
17
+
18
+ it "should return false for unchanged object" do
19
+ object.mark_as_saved
20
+ object.has_unsaved_changes?.should eq false
21
+ end
22
+
23
+ it "should return false for new object" do
24
+ object.has_unsaved_changes?.should eq false
25
+ end
26
+ end
27
+
28
+ describe "marks" do
29
+ it "should change state of an object" do
30
+ object.mark_as_saved
31
+ object.should_not have_unsaved_changes
32
+ object.mark_as_changed
33
+ object.should have_unsaved_changes
34
+ object.mark_as_saved
35
+ object.should_not have_unsaved_changes
36
+ end
37
+ end
38
+
39
+ describe "#save!" do
40
+ context "changed object" do
41
+ let(:changed_object) {object.mark_as_changed; object}
42
+
43
+ it "should call #save! on queue" do
44
+ changed_object.save_queue.should_receive(:save!).once
45
+ changed_object.save!
46
+ end
47
+
48
+ it "should mark object as saved" do
49
+ changed_object.should_receive(:mark_as_saved).once
50
+ changed_object.save!
51
+ end
52
+
53
+ context "original class has #save! method defined" do
54
+ it "should call that method" do
55
+ changed_object =
56
+ Class.new(DirtyObject) do
57
+ attr_reader :save_called
58
+ def save!
59
+ @save_called = true
60
+ end
61
+ end.new
62
+
63
+ changed_object.mark_as_changed
64
+ expect{ changed_object.save! }.to change { changed_object.save_called }.from(nil).to(true)
65
+ end
66
+ end
67
+ end
68
+
69
+ context "not changed object" do
70
+ let(:not_changed_object) {object.mark_as_saved; object}
71
+
72
+ it "should not mark object as saved" do
73
+ not_changed_object.should_not_receive(:mark_as_saved)
74
+ not_changed_object.save!
75
+ end
76
+
77
+ it "should call #save! on queue" do
78
+ not_changed_object.save_queue.should_receive(:save!).once
79
+ not_changed_object.save!
80
+ end
81
+
82
+ context "original class has #save! method defined" do
83
+ it "should not call that method" do
84
+ not_changed_object =
85
+ Class.new(DirtyObject) do
86
+ attr_reader :save_called
87
+ def save!
88
+ @save_called = true
89
+ end
90
+ end.new
91
+
92
+ not_changed_object.mark_as_saved
93
+ expect{ not_changed_object.save! }.to_not change { not_changed_object.save_called }
94
+ end
95
+
96
+ context "multiple save with changing state" do
97
+ it "should call that method if state was changed after 1st save" do
98
+ not_changed_object =
99
+ Class.new(DirtyObject) do
100
+ attr_reader :save_called
101
+ def save!
102
+ @save_called = true
103
+ end
104
+ end.new
105
+ not_changed_object.save!
106
+ not_changed_object.mark_as_changed
107
+ changed_object = not_changed_object
108
+
109
+ expect{ changed_object.save! }.to change { changed_object.save_called }.from(nil).to(true)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ describe "#save" do
117
+ context "changed object" do
118
+ let(:changed_object) {object.mark_as_changed; object}
119
+
120
+ it "should save queue" do
121
+ changed_object.save_queue.should_receive(:save).once
122
+ changed_object.save
123
+ end
124
+
125
+ context "original class has #save method defined" do
126
+ it "should call that method (save object)" do
127
+ changed_object =
128
+ Class.new(DirtyObject) do
129
+ attr_reader :save_called
130
+ def save
131
+ @save_called = true
132
+ end
133
+ end.new
134
+
135
+ changed_object.mark_as_changed
136
+ expect{ changed_object.save }.to change { changed_object.save_called }.from(nil).to(true)
137
+ end
138
+
139
+ #it "should save object only once in multiple queues" do
140
+ # other_object = new_object
141
+ # target = new_count_object
142
+ #
143
+ # object .save_queue.add target
144
+ # other_object.save_queue.add target
145
+ #
146
+ # other_object.save.should be_true
147
+ # object.save.should be_true
148
+ #
149
+ # target.save_call_count.should == 1
150
+ #end
151
+ end
152
+ end
153
+
154
+ context "not changed object" do
155
+ let(:not_changed_object) {object.mark_as_saved; object}
156
+
157
+ it "should not mark object as saved" do
158
+ not_changed_object.should_not_receive(:mark_as_saved)
159
+ not_changed_object.save
160
+ end
161
+
162
+ it "should save queue" do
163
+ not_changed_object.save_queue.should_receive(:save).once
164
+ not_changed_object.save
165
+ end
166
+
167
+ context "original class has #save method defined" do
168
+ it "should not call that method (dont save an object)" do
169
+ not_changed_object =
170
+ Class.new(DirtyObject) do
171
+ attr_reader :save_called
172
+ def save
173
+ @save_called = true
174
+ end
175
+ end.new
176
+
177
+ not_changed_object.mark_as_saved
178
+ expect{ not_changed_object.save }.to_not change { not_changed_object.save_called }
179
+ end
180
+
181
+ context "multiple save with changing state" do
182
+ it "should call that method if state was changed after 1st save" do
183
+ not_changed_object =
184
+ Class.new(DirtyObject) do
185
+ attr_reader :save_called
186
+ def save
187
+ @save_called = true
188
+ end
189
+ end.new
190
+ not_changed_object.save
191
+ not_changed_object.mark_as_changed
192
+ changed_object = not_changed_object
193
+
194
+ expect{ changed_object.save }.to change { changed_object.save_called }.from(nil).to(true)
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
@@ -34,11 +34,10 @@ describe SaveQueue::Plugins::Notification do
34
34
  klass.new
35
35
  end
36
36
 
37
- [:add, :<<, :push].each do |method|
38
- it "should mark object as changed if save_queue was changed by ##{method}" do
39
- object.mark_as_saved
37
+ ADD_METHODS.each do |method|
38
+ it "should call queue_changed_event if save_queue was changed by ##{method} method" do
39
+ object.should_receive(:queue_changed_event)
40
40
  object.save_queue.send method, new_object
41
- object.should have_unsaved_changes
42
41
  end
43
42
  end
44
43
  end
@@ -34,21 +34,4 @@ describe SaveQueue::Plugins::Notification::Object do
34
34
  NotifyObject.queue_class = queue
35
35
  expect { NotifyObject.new }.to_not raise_error
36
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
37
  end
@@ -1,14 +1,14 @@
1
1
  require "spec_helper"
2
2
  require "save_queue/plugins/notification/queue"
3
3
 
4
- class NotifyQueue < SaveQueue::ObjectQueue
4
+ class QueueWithNotification < SaveQueue::ObjectQueue
5
5
  include SaveQueue::Plugins::Notification::Queue
6
6
  end
7
7
 
8
8
  describe SaveQueue::Plugins::Notification::Queue do
9
- let(:queue) { NotifyQueue.new }
9
+ let(:queue) { QueueWithNotification.new }
10
10
 
11
- [:add, :<<, :push].each do |method|
11
+ ADD_METHODS.each do |method|
12
12
  describe "##{method}" do
13
13
  let(:element) { new_element }
14
14
 
@@ -18,7 +18,7 @@ describe SaveQueue::Plugins::Notification::Queue do
18
18
  queue.send method, element
19
19
  end
20
20
 
21
- it "should notify observer, provided result of a method call and input object" do
21
+ it "should provide input params and result of ##{method} method call to observers" do
22
22
  queue.should_receive(:changed)
23
23
  queue.should_receive(:notify_observers).with(true, element)
24
24
  queue.send method, element
@@ -0,0 +1,53 @@
1
+ require 'save_queue/object/queue_class_management'
2
+
3
+ describe SaveQueue::Object::QueueClassManagement do
4
+ let(:klass) do
5
+ Class.new do
6
+ extend SaveQueue::Object::QueueClassManagement
7
+ end
8
+ end
9
+ let(:default_queue_class) { SaveQueue::ObjectQueue }
10
+ let(:first_queue_class) { Class.new(default_queue_class) }
11
+ let(:second_queue_class) { Class.new(default_queue_class) }
12
+
13
+ it "should map to SaveQueue::ObjectQueue by default" do
14
+ klass.queue_class.should == default_queue_class
15
+ end
16
+
17
+ describe "changing" do
18
+ it "should be possible" do
19
+ klass.queue_class = second_queue_class
20
+ klass.queue_class.should eq second_queue_class
21
+ end
22
+
23
+ it "should check inclusion of Hooks module" do
24
+ expect{ klass.queue_class = Class.new }.to raise_error(RuntimeError, /Hooks/)
25
+ end
26
+ end
27
+
28
+ it "inclusion of SaveQueue::Object should not override settings" do
29
+ klass.queue_class = second_queue_class
30
+ expect { klass.send :include, SaveQueue::Object }.to_not change { klass.queue_class }
31
+ end
32
+
33
+ describe "inheritance" do
34
+ let(:parent) { klass }
35
+ let(:child) { Class.new(parent) }
36
+ before(:each) do
37
+ parent.queue_class = first_queue_class
38
+ end
39
+
40
+ it "should inherit settings of parent class" do
41
+ child.queue_class.should == first_queue_class
42
+ end
43
+
44
+ it "should not override settings of parent class" do
45
+ expect { child.queue_class = second_queue_class }.to_not change { parent.queue_class }
46
+ end
47
+
48
+ it "inclusion of SaveQueue::Object should not override settings of child class" do
49
+ child.queue_class = second_queue_class
50
+ expect { child.send :include, SaveQueue::Object }.to_not change { child.queue_class }
51
+ end
52
+ end
53
+ end