save_queue 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +13 -4
- data/.travis.yml +4 -1
- data/CONTRIBUTING.md +120 -0
- data/HISTORY.md +11 -0
- data/LICENSE +13 -4
- data/README.md +282 -55
- data/Rakefile +10 -3
- data/lib/save_queue/exceptions.rb +13 -0
- data/lib/save_queue/object.rb +41 -18
- data/lib/save_queue/object_queue.rb +87 -0
- data/lib/save_queue/plugins/notification/object.rb +35 -0
- data/lib/save_queue/plugins/notification/queue.rb +25 -0
- data/lib/save_queue/plugins/notification.rb +15 -0
- data/lib/save_queue/plugins/validation/exceptions.rb +12 -0
- data/lib/save_queue/plugins/validation/queue.rb +16 -17
- data/lib/save_queue/plugins/validation.rb +4 -17
- data/lib/save_queue/ruby1.9/observer.rb +204 -0
- data/lib/save_queue/uniq_queue.rb +38 -0
- data/lib/save_queue/version.rb +1 -1
- data/lib/save_queue.rb +1 -0
- data/save_queue.gemspec +4 -3
- data/spec/notification/notification_spec.rb +45 -0
- data/spec/notification/object_spec.rb +54 -0
- data/spec/notification/queue_spec.rb +28 -0
- data/spec/object_queue_spec.rb +155 -0
- data/spec/object_spec.rb +208 -0
- data/spec/save_queue_spec.rb +75 -0
- data/spec/support/object_helpers.rb +10 -0
- data/spec/support/queue_helpers.rb +26 -0
- data/spec/uniq_queue_spec.rb +132 -0
- data/spec/validation/queue_spec.rb +139 -0
- data/spec/validation/validation_spec.rb +42 -0
- metadata +35 -20
- data/lib/save_queue/plugins/validation/object.rb +0 -25
- data/lib/save_queue/queue.rb +0 -45
- data/spec/save_queue_usage_spec.rb +0 -311
- data/spec/support/mock_helpers.rb +0 -17
- data/spec/validation_spec.rb +0 -126
data/lib/save_queue/object.rb
CHANGED
@@ -1,39 +1,54 @@
|
|
1
|
-
require
|
2
|
-
require 'active_support/core_ext/class/inheritable_attributes'
|
1
|
+
require 'save_queue/object_queue'
|
3
2
|
|
4
3
|
module SaveQueue
|
5
4
|
module Object
|
6
|
-
#class_inheritable_accessor :queue_class
|
7
5
|
def self.included base
|
8
6
|
base.class_eval do
|
9
|
-
class_inheritable_accessor :queue_class
|
10
|
-
#class<<self
|
11
|
-
# attr_accessor :queue_class
|
12
|
-
#end
|
13
7
|
|
14
|
-
self
|
8
|
+
class<<self
|
9
|
+
attr_reader :queue_class
|
10
|
+
|
11
|
+
def queue_class=(klass)
|
12
|
+
raise "Your Queue implementation: #{klass} should include Hooks module!" unless klass.include? Hooks
|
13
|
+
@queue_class = klass
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.inherited base
|
18
|
+
base.queue_class = self.queue_class
|
19
|
+
end
|
20
|
+
|
21
|
+
self.queue_class ||= ObjectQueue
|
15
22
|
end
|
16
23
|
end
|
17
24
|
|
25
|
+
|
18
26
|
module RunAlwaysFirst
|
19
|
-
#
|
27
|
+
# can not reilly on save! here, because client may not define it at all
|
20
28
|
def save(*args)
|
21
|
-
|
29
|
+
super_result = true
|
30
|
+
super_result = super if defined?(super)
|
31
|
+
|
32
|
+
return false unless !!super_result
|
22
33
|
|
23
|
-
super_saved = true
|
24
|
-
super_saved = super if defined?(super)
|
25
|
-
# object is saved here
|
26
34
|
mark_as_saved
|
27
|
-
|
35
|
+
if save_queue.save
|
36
|
+
true == super_result ? true : super_result # super_result may be not boolean, String for ex
|
37
|
+
else
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
28
41
|
|
42
|
+
# Suppose,that save! raise an Exception if failed to save an object
|
43
|
+
def save!
|
44
|
+
super if defined?(super)
|
45
|
+
mark_as_saved
|
46
|
+
save_queue.save!
|
29
47
|
end
|
30
48
|
end
|
31
49
|
|
32
|
-
|
33
50
|
def initialize(*args)
|
34
|
-
|
35
|
-
instance_variable_set "@_save_queue", queue
|
36
|
-
|
51
|
+
create_queue
|
37
52
|
super if defined?(super)
|
38
53
|
|
39
54
|
# this will make RunAlwaysFirst methods triggered first in inheritance tree
|
@@ -44,6 +59,7 @@ module SaveQueue
|
|
44
59
|
instance_variable_set "@_changed_mark", true
|
45
60
|
end
|
46
61
|
|
62
|
+
# @returns [Boolean] true if object has been modified
|
47
63
|
def has_unsaved_changes?
|
48
64
|
status = instance_variable_get("@_changed_mark")
|
49
65
|
status.nil? ? false : status
|
@@ -56,5 +72,12 @@ module SaveQueue
|
|
56
72
|
def mark_as_saved
|
57
73
|
instance_variable_set "@_changed_mark", false
|
58
74
|
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def create_queue
|
78
|
+
klass = self.class.queue_class
|
79
|
+
queue = klass.new
|
80
|
+
instance_variable_set "@_save_queue", queue
|
81
|
+
end
|
59
82
|
end
|
60
83
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
# TODO remove hooks or extract to a module
|
3
|
+
require 'hooks'
|
4
|
+
require 'save_queue/uniq_queue'
|
5
|
+
|
6
|
+
module SaveQueue
|
7
|
+
class ObjectQueue < UniqQueue
|
8
|
+
include Hooks
|
9
|
+
|
10
|
+
define_hook :before_save
|
11
|
+
# triggered only after successful save
|
12
|
+
define_hook :after_save
|
13
|
+
|
14
|
+
define_hook :before_add
|
15
|
+
define_hook :after_add
|
16
|
+
|
17
|
+
# @return [Hash] save
|
18
|
+
# @option save [Array<Object>] :processed
|
19
|
+
# @option save [Array<Object>] :saved
|
20
|
+
# @option save [Object] :failed
|
21
|
+
# @option save [Array<Object>] :pending
|
22
|
+
attr_reader :errors
|
23
|
+
def initialize(*args)
|
24
|
+
super
|
25
|
+
@errors = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def add object
|
29
|
+
run_hook :before_add
|
30
|
+
|
31
|
+
check_requirements_for object
|
32
|
+
result = super object
|
33
|
+
|
34
|
+
run_hook :after_add, result, object
|
35
|
+
|
36
|
+
result
|
37
|
+
end
|
38
|
+
|
39
|
+
def << object
|
40
|
+
add object
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
alias_method :push, :add
|
45
|
+
|
46
|
+
def save
|
47
|
+
save!
|
48
|
+
true
|
49
|
+
rescue SaveQueue::Error
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
def save!
|
54
|
+
run_hook :before_save
|
55
|
+
@errors = {}
|
56
|
+
saved = []
|
57
|
+
processed = []
|
58
|
+
|
59
|
+
@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
|
69
|
+
end
|
70
|
+
processed << object
|
71
|
+
end
|
72
|
+
|
73
|
+
@queue.clear
|
74
|
+
|
75
|
+
run_hook :after_save
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
private
|
81
|
+
def check_requirements_for object
|
82
|
+
[:save, :has_unsaved_changes?].each do |method|
|
83
|
+
raise ArgumentError, "#{object.inspect} does not respond to ##{method}" unless object.respond_to? method
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SaveQueue
|
2
|
+
module Plugins
|
3
|
+
module Notification
|
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
|
24
|
+
|
25
|
+
#base.send :include, queue_creator
|
26
|
+
base.send :include, AddObserverToQueue unless base.include?(AddObserverToQueue)
|
27
|
+
end
|
28
|
+
|
29
|
+
def queue_changed_event(result, object)
|
30
|
+
mark_as_changed if result
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
if RUBY_VERSION < "1.9"
|
2
|
+
require "save_queue/ruby1.9/observer"
|
3
|
+
else
|
4
|
+
require 'observer'
|
5
|
+
end
|
6
|
+
|
7
|
+
module SaveQueue
|
8
|
+
module Plugins
|
9
|
+
module Notification
|
10
|
+
module Queue
|
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
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def change_and_notify(*args)
|
18
|
+
changed
|
19
|
+
notify_observers(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "save_queue/plugins/notification/queue"
|
2
|
+
require "save_queue/plugins/notification/object"
|
3
|
+
|
4
|
+
module SaveQueue
|
5
|
+
module Plugins
|
6
|
+
module Notification
|
7
|
+
def self.included base
|
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
|
11
|
+
base.queue_class = klass
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module SaveQueue
|
2
|
+
class FailedValidationError < Error
|
3
|
+
attr_reader :failed_objects
|
4
|
+
def initialize(failed_objects)
|
5
|
+
@failed_objects = Array(failed_objects)
|
6
|
+
end
|
7
|
+
|
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")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -1,33 +1,32 @@
|
|
1
|
-
require "save_queue/
|
1
|
+
require "save_queue/plugins/validation/exceptions"
|
2
|
+
|
2
3
|
module SaveQueue
|
3
4
|
module Plugins
|
4
5
|
module Validation
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def initialize(*args)
|
9
|
-
@objects_with_errors = []
|
10
|
-
super
|
6
|
+
module Queue
|
7
|
+
def self.included base
|
8
|
+
base.before_save :validate! if base.respond_to? :before_save
|
11
9
|
end
|
12
10
|
|
13
11
|
def valid?
|
14
|
-
|
12
|
+
validate
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate
|
15
16
|
@queue.each do |object|
|
16
|
-
|
17
|
+
unless object.valid?
|
18
|
+
@errors[:validation] ||= []
|
19
|
+
@errors[:validation].push(object)
|
20
|
+
end
|
17
21
|
end
|
18
|
-
|
19
|
-
@
|
22
|
+
|
23
|
+
@errors.empty?
|
20
24
|
end
|
21
25
|
|
22
26
|
def validate!
|
23
|
-
raise FailedValidationError, @
|
24
|
-
|
27
|
+
raise FailedValidationError, @errors[:validation] unless valid?
|
25
28
|
true
|
26
29
|
end
|
27
|
-
|
28
|
-
def errors
|
29
|
-
@objects_with_errors.map(&:errors).reduce(:+)
|
30
|
-
end
|
31
30
|
end
|
32
31
|
end
|
33
32
|
end
|
@@ -1,27 +1,14 @@
|
|
1
|
-
require "save_queue/queue"
|
2
|
-
|
3
|
-
require "save_queue/plugins/validation/object"
|
4
1
|
require "save_queue/plugins/validation/queue"
|
2
|
+
require "save_queue/plugins/validation/exceptions"
|
5
3
|
|
6
4
|
|
7
5
|
module SaveQueue
|
8
6
|
module Plugins
|
9
7
|
module Validation
|
10
8
|
def self.included base
|
11
|
-
|
12
|
-
|
13
|
-
base.queue_class =
|
14
|
-
end
|
15
|
-
|
16
|
-
class FailedValidationError < RuntimeError
|
17
|
-
attr_reader :failed_objects
|
18
|
-
def initialize(failed_objects)
|
19
|
-
@failed_objects = Array(failed_objects)
|
20
|
-
end
|
21
|
-
|
22
|
-
def to_s # Some default way to display errors
|
23
|
-
"#{super}: " + @failed_objects.map{|object| "\"#{object.to_s}\": " + object.errors.full_messages.join(', ')}.join("\n")
|
24
|
-
end
|
9
|
+
klass = Class.new(base.queue_class)
|
10
|
+
klass.send :include, Validation::Queue
|
11
|
+
base.queue_class = klass
|
25
12
|
end
|
26
13
|
end
|
27
14
|
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
module SaveQueue
|
2
|
+
#
|
3
|
+
# Implementation of the _Observer_ object-oriented design pattern. The
|
4
|
+
# following documentation is copied, with modifications, from "Programming
|
5
|
+
# Ruby", by Hunt and Thomas; http://www.rubycentral.com/book/lib_patterns.html.
|
6
|
+
#
|
7
|
+
# See Observable for more info.
|
8
|
+
|
9
|
+
# The Observer pattern (also known as publish/subscribe) provides a simple
|
10
|
+
# mechanism for one object to inform a set of interested third-party objects
|
11
|
+
# when its state changes.
|
12
|
+
#
|
13
|
+
# == Mechanism
|
14
|
+
#
|
15
|
+
# The notifying class mixes in the +Observable+
|
16
|
+
# module, which provides the methods for managing the associated observer
|
17
|
+
# objects.
|
18
|
+
#
|
19
|
+
# The observers must implement a method called +update+ to receive
|
20
|
+
# notifications.
|
21
|
+
#
|
22
|
+
# The observable object must:
|
23
|
+
# * assert that it has +#changed+
|
24
|
+
# * call +#notify_observers+
|
25
|
+
#
|
26
|
+
# === Example
|
27
|
+
#
|
28
|
+
# The following example demonstrates this nicely. A +Ticker+, when run,
|
29
|
+
# continually receives the stock +Price+ for its <tt>@symbol</tt>. A +Warner+
|
30
|
+
# is a general observer of the price, and two warners are demonstrated, a
|
31
|
+
# +WarnLow+ and a +WarnHigh+, which print a warning if the price is below or
|
32
|
+
# above their set limits, respectively.
|
33
|
+
#
|
34
|
+
# The +update+ callback allows the warners to run without being explicitly
|
35
|
+
# called. The system is set up with the +Ticker+ and several observers, and the
|
36
|
+
# observers do their duty without the top-level code having to interfere.
|
37
|
+
#
|
38
|
+
# Note that the contract between publisher and subscriber (observable and
|
39
|
+
# observer) is not declared or enforced. The +Ticker+ publishes a time and a
|
40
|
+
# price, and the warners receive that. But if you don't ensure that your
|
41
|
+
# contracts are correct, nothing else can warn you.
|
42
|
+
#
|
43
|
+
# require "observer"
|
44
|
+
#
|
45
|
+
# class Ticker ### Periodically fetch a stock price.
|
46
|
+
# include Observable
|
47
|
+
#
|
48
|
+
# def initialize(symbol)
|
49
|
+
# @symbol = symbol
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# def run
|
53
|
+
# lastPrice = nil
|
54
|
+
# loop do
|
55
|
+
# price = Price.fetch(@symbol)
|
56
|
+
# print "Current price: #{price}\n"
|
57
|
+
# if price != lastPrice
|
58
|
+
# changed # notify observers
|
59
|
+
# lastPrice = price
|
60
|
+
# notify_observers(Time.now, price)
|
61
|
+
# end
|
62
|
+
# sleep 1
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# class Price ### A mock class to fetch a stock price (60 - 140).
|
68
|
+
# def Price.fetch(symbol)
|
69
|
+
# 60 + rand(80)
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# class Warner ### An abstract observer of Ticker objects.
|
74
|
+
# def initialize(ticker, limit)
|
75
|
+
# @limit = limit
|
76
|
+
# ticker.add_observer(self)
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# class WarnLow < Warner
|
81
|
+
# def update(time, price) # callback for observer
|
82
|
+
# if price < @limit
|
83
|
+
# print "--- #{time.to_s}: Price below #@limit: #{price}\n"
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# class WarnHigh < Warner
|
89
|
+
# def update(time, price) # callback for observer
|
90
|
+
# if price > @limit
|
91
|
+
# print "+++ #{time.to_s}: Price above #@limit: #{price}\n"
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# ticker = Ticker.new("MSFT")
|
97
|
+
# WarnLow.new(ticker, 80)
|
98
|
+
# WarnHigh.new(ticker, 120)
|
99
|
+
# ticker.run
|
100
|
+
#
|
101
|
+
# Produces:
|
102
|
+
#
|
103
|
+
# Current price: 83
|
104
|
+
# Current price: 75
|
105
|
+
# --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 75
|
106
|
+
# Current price: 90
|
107
|
+
# Current price: 134
|
108
|
+
# +++ Sun Jun 09 00:10:25 CDT 2002: Price above 120: 134
|
109
|
+
# Current price: 134
|
110
|
+
# Current price: 112
|
111
|
+
# Current price: 79
|
112
|
+
# --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 79
|
113
|
+
module Observable
|
114
|
+
|
115
|
+
#
|
116
|
+
# Add +observer+ as an observer on this object. so that it will receive
|
117
|
+
# notifications.
|
118
|
+
#
|
119
|
+
# +observer+:: the object that will be notified of changes.
|
120
|
+
# +func+:: Symbol naming the method that will be called when this Observable
|
121
|
+
# has changes.
|
122
|
+
#
|
123
|
+
# This method must return true for +observer.respond_to?+ and will
|
124
|
+
# receive <tt>*arg</tt> when #notify_observers is called, where
|
125
|
+
# <tt>*arg</tt> is the value passed to #notify_observers by this
|
126
|
+
# Observable
|
127
|
+
def add_observer(observer, func=:update)
|
128
|
+
@observer_peers = {} unless defined? @observer_peers
|
129
|
+
unless observer.respond_to? func
|
130
|
+
raise NoMethodError, "observer does not respond to `#{func.to_s}'"
|
131
|
+
end
|
132
|
+
@observer_peers[observer] = func
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# Remove +observer+ as an observer on this object so that it will no longer
|
137
|
+
# receive notifications.
|
138
|
+
#
|
139
|
+
# +observer+:: An observer of this Observable
|
140
|
+
def delete_observer(observer)
|
141
|
+
@observer_peers.delete observer if defined? @observer_peers
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Remove all observers associated with this object.
|
146
|
+
#
|
147
|
+
def delete_observers
|
148
|
+
@observer_peers.clear if defined? @observer_peers
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# Return the number of observers associated with this object.
|
153
|
+
#
|
154
|
+
def count_observers
|
155
|
+
if defined? @observer_peers
|
156
|
+
@observer_peers.size
|
157
|
+
else
|
158
|
+
0
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Set the changed state of this object. Notifications will be sent only if
|
164
|
+
# the changed +state+ is +true+.
|
165
|
+
#
|
166
|
+
# +state+:: Boolean indicating the changed state of this Observable.
|
167
|
+
#
|
168
|
+
def changed(state=true)
|
169
|
+
@observer_state = state
|
170
|
+
end
|
171
|
+
|
172
|
+
#
|
173
|
+
# Returns true if this object's state has been changed since the last
|
174
|
+
# #notify_observers call.
|
175
|
+
#
|
176
|
+
def changed?
|
177
|
+
if defined? @observer_state and @observer_state
|
178
|
+
true
|
179
|
+
else
|
180
|
+
false
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# Notify observers of a change in state *if* this object's changed state is
|
186
|
+
# +true+.
|
187
|
+
#
|
188
|
+
# This will invoke the method named in #add_observer, pasing <tt>*arg</tt>.
|
189
|
+
# The changed state is then set to +false+.
|
190
|
+
#
|
191
|
+
# <tt>*arg</tt>:: Any arguments to pass to the observers.
|
192
|
+
def notify_observers(*arg)
|
193
|
+
if defined? @observer_state and @observer_state
|
194
|
+
if defined? @observer_peers
|
195
|
+
@observer_peers.each do |k, v|
|
196
|
+
k.send v, *arg
|
197
|
+
end
|
198
|
+
end
|
199
|
+
@observer_state = false
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
module SaveQueue
|
3
|
+
class UniqQueue
|
4
|
+
extend ::Forwardable
|
5
|
+
DELEGATED_METHODS = [:empty?,
|
6
|
+
:any?,
|
7
|
+
:size,
|
8
|
+
:count,
|
9
|
+
:clear,
|
10
|
+
:inspect,
|
11
|
+
:to_s,
|
12
|
+
:first,
|
13
|
+
:last,
|
14
|
+
:pop,
|
15
|
+
:shift]
|
16
|
+
|
17
|
+
def_delegators :@queue, *DELEGATED_METHODS
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@queue = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_all objects
|
24
|
+
Array(objects).each do |object|
|
25
|
+
add object
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def add object
|
30
|
+
return false if @queue.include? object
|
31
|
+
@queue << object
|
32
|
+
|
33
|
+
true
|
34
|
+
end
|
35
|
+
alias_method :push, :add
|
36
|
+
alias_method :<<, :add
|
37
|
+
end
|
38
|
+
end
|
data/lib/save_queue/version.rb
CHANGED
data/lib/save_queue.rb
CHANGED
data/save_queue.gemspec
CHANGED
@@ -8,8 +8,9 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.authors = ["Alexander Paramonov"]
|
9
9
|
s.email = ["alexander.n.paramonov@gmail.com"]
|
10
10
|
s.homepage = "http://github.com/AlexParamonov/save_queue"
|
11
|
-
s.summary = %q{Push related objects to a queue for delayed save}
|
12
|
-
s.description = %q{Save Queue allows to push
|
11
|
+
s.summary = %q{Push related objects to a queue for a delayed save}
|
12
|
+
s.description = %q{Save Queue allows to push objects to other object's queue for a delayed save.
|
13
|
+
Queue save will be triggered by object#save.}
|
13
14
|
|
14
15
|
s.rubyforge_project = "save_queue"
|
15
16
|
|
@@ -21,5 +22,5 @@ Gem::Specification.new do |s|
|
|
21
22
|
# specify any dependencies here; for example:
|
22
23
|
s.add_development_dependency "rspec", ">= 2.6"
|
23
24
|
s.add_development_dependency "rake"
|
24
|
-
s.add_runtime_dependency "
|
25
|
+
s.add_runtime_dependency "hooks"
|
25
26
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "save_queue/plugins/notification"
|
3
|
+
|
4
|
+
describe SaveQueue::Plugins::Notification do
|
5
|
+
describe "#integration" do
|
6
|
+
it "should mix Queue to object's save_queue" do
|
7
|
+
klass = new_class
|
8
|
+
klass.send :include, SaveQueue::Plugins::Notification
|
9
|
+
|
10
|
+
klass.queue_class.should include SaveQueue::Plugins::Notification::Queue
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should not change original SaveQueue::*Queue class" do
|
14
|
+
klass = new_class
|
15
|
+
old_queue = klass.queue_class
|
16
|
+
klass.queue_class = Class.new(old_queue)
|
17
|
+
|
18
|
+
klass.send :include, SaveQueue::Plugins::Notification
|
19
|
+
old_queue.should_not include SaveQueue::Plugins::Notification::Queue
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should mix Object to object class" do
|
23
|
+
klass = new_class
|
24
|
+
klass.send :include, SaveQueue::Plugins::Notification
|
25
|
+
|
26
|
+
klass.should include SaveQueue::Plugins::Notification::Object
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "workflow" do
|
31
|
+
let(:object) do
|
32
|
+
klass = new_class
|
33
|
+
klass.send :include, SaveQueue::Plugins::Notification
|
34
|
+
klass.new
|
35
|
+
end
|
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
|
40
|
+
object.save_queue.send method, new_object
|
41
|
+
object.should have_unsaved_changes
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|