libis-workflow 2.0.24 → 2.0.25

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -1
  3. data/.gitignore +36 -36
  4. data/.travis.yml +32 -32
  5. data/Gemfile +4 -4
  6. data/LICENSE +20 -20
  7. data/README.md +380 -380
  8. data/Rakefile +6 -6
  9. data/lib/libis/exceptions.rb +6 -6
  10. data/lib/libis/workflow.rb +41 -41
  11. data/lib/libis/workflow/action.rb +24 -24
  12. data/lib/libis/workflow/base/dir_item.rb +13 -13
  13. data/lib/libis/workflow/base/file_item.rb +80 -80
  14. data/lib/libis/workflow/base/job.rb +83 -83
  15. data/lib/libis/workflow/base/logging.rb +66 -66
  16. data/lib/libis/workflow/base/run.rb +95 -95
  17. data/lib/libis/workflow/base/work_item.rb +173 -173
  18. data/lib/libis/workflow/base/workflow.rb +149 -149
  19. data/lib/libis/workflow/config.rb +22 -22
  20. data/lib/libis/workflow/dir_item.rb +10 -10
  21. data/lib/libis/workflow/file_item.rb +15 -15
  22. data/lib/libis/workflow/job.rb +28 -28
  23. data/lib/libis/workflow/message_registry.rb +30 -30
  24. data/lib/libis/workflow/run.rb +34 -34
  25. data/lib/libis/workflow/status.rb +133 -133
  26. data/lib/libis/workflow/task.rb +316 -316
  27. data/lib/libis/workflow/task_group.rb +71 -71
  28. data/lib/libis/workflow/task_runner.rb +34 -34
  29. data/lib/libis/workflow/version.rb +5 -5
  30. data/lib/libis/workflow/work_item.rb +37 -37
  31. data/lib/libis/workflow/worker.rb +42 -42
  32. data/lib/libis/workflow/workflow.rb +20 -20
  33. data/libis-workflow.gemspec +38 -38
  34. data/spec/items.rb +2 -2
  35. data/spec/items/test_dir_item.rb +13 -13
  36. data/spec/items/test_file_item.rb +16 -16
  37. data/spec/items/test_run.rb +8 -8
  38. data/spec/spec_helper.rb +8 -8
  39. data/spec/task_spec.rb +15 -15
  40. data/spec/tasks/camelize_name.rb +12 -12
  41. data/spec/tasks/checksum_tester.rb +32 -32
  42. data/spec/tasks/collect_files.rb +47 -47
  43. data/spec/workflow_spec.rb +154 -154
  44. metadata +3 -3
@@ -1,67 +1,67 @@
1
- module Libis
2
- module Workflow
3
- module Base
4
- module Logging
5
-
6
- # Add a structured message to the log history. The message text can be submitted as an integer or text. If an
7
- # integer is submitted, it will be used to look up the text in the MessageRegistry. The message text will be
8
- # passed to the % operator with the args parameter. If that failes (e.g. because the format string is not correct)
9
- # the args value is appended to the message.
10
- #
11
- # @param [Symbol] severity
12
- # @param [Hash] msg should contain message text as :id or :text and the hierarchical name of the task as :task
13
- # @param [Array] args string format values
14
- def log_message(severity, msg, *args)
15
- # Prepare info from msg struct for use with string substitution
16
- message_id, message_text = if msg[:id]
17
- [msg[:id], MessageRegistry.instance.get_message(msg[:id])]
18
- elsif msg[:text]
19
- [0, msg[:text]]
20
- else
21
- [0, '']
22
- end
23
- task = msg[:task] || ''
24
- message_text = (message_text % args rescue "#{message_text} - #{args}")
25
-
26
- run_id = self.get_run.id rescue nil
27
-
28
- self.add_log severity: severity, id: message_id.to_i, text: message_text, task: task, run_id: run_id
29
- end
30
-
31
- # Helper function for the WorkItems to add a log entry to the log_history.
32
- #
33
- # The supplied message structure is expected to contain the following fields:
34
- # - :severity : ::Logger::Severity value
35
- # - :id : optional message id
36
- # - :text : message text
37
- # - :task : list of tasks names (task hierarchy) that submits the message
38
- #
39
- # @param [Hash] message
40
- def add_log(message = {})
41
- msg = message_struct(message)
42
- add_log_entry(msg)
43
- self.save!
44
- end
45
-
46
- def <=(message = {})
47
- self.add_log(message)
48
- end
49
-
50
- protected
51
-
52
- # create and return a proper message structure
53
- # @param [Hash] opts
54
- def message_struct(opts = {})
55
- opts.reverse_merge!(severity: :info, code: nil, text: '')
56
- {
57
- severity: ::Logging::levelify(opts[:severity]).upcase,
58
- task: opts[:task],
59
- code: opts[:code],
60
- message: opts[:text]
61
- }.cleanup
62
- end
63
-
64
- end
65
- end
66
- end
1
+ module Libis
2
+ module Workflow
3
+ module Base
4
+ module Logging
5
+
6
+ # Add a structured message to the log history. The message text can be submitted as an integer or text. If an
7
+ # integer is submitted, it will be used to look up the text in the MessageRegistry. The message text will be
8
+ # passed to the % operator with the args parameter. If that failes (e.g. because the format string is not correct)
9
+ # the args value is appended to the message.
10
+ #
11
+ # @param [Symbol] severity
12
+ # @param [Hash] msg should contain message text as :id or :text and the hierarchical name of the task as :task
13
+ # @param [Array] args string format values
14
+ def log_message(severity, msg, *args)
15
+ # Prepare info from msg struct for use with string substitution
16
+ message_id, message_text = if msg[:id]
17
+ [msg[:id], MessageRegistry.instance.get_message(msg[:id])]
18
+ elsif msg[:text]
19
+ [0, msg[:text]]
20
+ else
21
+ [0, '']
22
+ end
23
+ task = msg[:task] || ''
24
+ message_text = (message_text % args rescue "#{message_text} - #{args}")
25
+
26
+ run_id = self.get_run.id rescue nil
27
+
28
+ self.add_log severity: severity, id: message_id.to_i, text: message_text, task: task, run_id: run_id
29
+ end
30
+
31
+ # Helper function for the WorkItems to add a log entry to the log_history.
32
+ #
33
+ # The supplied message structure is expected to contain the following fields:
34
+ # - :severity : ::Logger::Severity value
35
+ # - :id : optional message id
36
+ # - :text : message text
37
+ # - :task : list of tasks names (task hierarchy) that submits the message
38
+ #
39
+ # @param [Hash] message
40
+ def add_log(message = {})
41
+ msg = message_struct(message)
42
+ add_log_entry(msg)
43
+ self.save!
44
+ end
45
+
46
+ def <=(message = {})
47
+ self.add_log(message)
48
+ end
49
+
50
+ protected
51
+
52
+ # create and return a proper message structure
53
+ # @param [Hash] opts
54
+ def message_struct(opts = {})
55
+ opts.reverse_merge!(severity: :info, code: nil, text: '')
56
+ {
57
+ severity: ::Logging::levelify(opts[:severity]).upcase,
58
+ task: opts[:task],
59
+ code: opts[:code],
60
+ message: opts[:text]
61
+ }.cleanup
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
67
  end
@@ -1,95 +1,95 @@
1
- require 'fileutils'
2
-
3
- require 'libis/workflow/base/work_item'
4
- require 'libis/workflow/task_runner'
5
-
6
- module Libis
7
- module Workflow
8
- module Base
9
-
10
- # Base module for all workflow runs. It is created by an associated workflow when the workflow is executed.
11
- #
12
- # This module lacks the implementation for the data attributes. It functions as an interface that describes the
13
- # common functionality regardless of the storage implementation. These attributes require some implementation:
14
- #
15
- # - start_date: [Time] the timestamp of the execution of the run
16
- # - job: [Object] a reference to the Job this Run belongs to
17
- # - id: [String] (Optional) a unique run number
18
- #
19
- # Note that ::Libis::Workflow::Base::WorkItem is a parent module and therefore requires implementation of the
20
- # attributes of that module too.
21
- #
22
- # A simple in-memory implementation can be found in ::Libis::Workflow::Run
23
- module Run
24
- include ::Libis::Workflow::Base::WorkItem
25
-
26
- attr_accessor :tasks, :action
27
-
28
- def work_dir
29
- # noinspection RubyResolve
30
- dir = File.join(Libis::Workflow::Config.workdir, self.name)
31
- FileUtils.mkpath dir unless Dir.exist?(dir)
32
- dir
33
- end
34
-
35
- def name
36
- self.job.run_name(self.start_date)
37
- end
38
-
39
- def names
40
- Array.new
41
- end
42
-
43
- def namepath
44
- self.name
45
- end
46
-
47
- def workflow
48
- self.job.workflow
49
- end
50
-
51
- def logger
52
- self.properties['logger'] || self.job.logger rescue ::Libis::Workflow::Config.logger
53
- end
54
-
55
- # Execute the workflow.
56
- #
57
- # The action parameter defines how the execution of the tasks will behave:
58
- # - With the default :run action each task will be executed regardsless how the task performed on the item
59
- # previously.
60
- # - When using the :retry action a task will not perform on an item if it was successful the last time. This
61
- # allows you to retry a run when an temporary error (e.g. asynchronous wait or halt) occured.
62
- #
63
- # @param [Symbol] action the type of action to take during this run. :run or :retry
64
- def run(action = :run)
65
- self.action = action
66
-
67
- self.start_date = Time.now unless action == :retry
68
-
69
- self.options = workflow.prepare_input(self.options)
70
-
71
- self.tasks = workflow.tasks
72
- configure_tasks self.options
73
-
74
- self.save!
75
-
76
- runner = Libis::Workflow::TaskRunner.new nil
77
-
78
- self.tasks.each do |task|
79
- runner << task
80
- end
81
-
82
- runner.run self
83
-
84
- end
85
-
86
- protected
87
-
88
- def configure_tasks(opts)
89
- self.tasks.each { |task| task.apply_options opts }
90
- end
91
-
92
- end
93
- end
94
- end
95
- end
1
+ require 'fileutils'
2
+
3
+ require 'libis/workflow/base/work_item'
4
+ require 'libis/workflow/task_runner'
5
+
6
+ module Libis
7
+ module Workflow
8
+ module Base
9
+
10
+ # Base module for all workflow runs. It is created by an associated workflow when the workflow is executed.
11
+ #
12
+ # This module lacks the implementation for the data attributes. It functions as an interface that describes the
13
+ # common functionality regardless of the storage implementation. These attributes require some implementation:
14
+ #
15
+ # - start_date: [Time] the timestamp of the execution of the run
16
+ # - job: [Object] a reference to the Job this Run belongs to
17
+ # - id: [String] (Optional) a unique run number
18
+ #
19
+ # Note that ::Libis::Workflow::Base::WorkItem is a parent module and therefore requires implementation of the
20
+ # attributes of that module too.
21
+ #
22
+ # A simple in-memory implementation can be found in ::Libis::Workflow::Run
23
+ module Run
24
+ include ::Libis::Workflow::Base::WorkItem
25
+
26
+ attr_accessor :tasks, :action
27
+
28
+ def work_dir
29
+ # noinspection RubyResolve
30
+ dir = File.join(Libis::Workflow::Config.workdir, self.name)
31
+ FileUtils.mkpath dir unless Dir.exist?(dir)
32
+ dir
33
+ end
34
+
35
+ def name
36
+ self.job.run_name(self.start_date)
37
+ end
38
+
39
+ def names
40
+ Array.new
41
+ end
42
+
43
+ def namepath
44
+ self.name
45
+ end
46
+
47
+ def workflow
48
+ self.job.workflow
49
+ end
50
+
51
+ def logger
52
+ self.properties['logger'] || self.job.logger rescue ::Libis::Workflow::Config.logger
53
+ end
54
+
55
+ # Execute the workflow.
56
+ #
57
+ # The action parameter defines how the execution of the tasks will behave:
58
+ # - With the default :run action each task will be executed regardsless how the task performed on the item
59
+ # previously.
60
+ # - When using the :retry action a task will not perform on an item if it was successful the last time. This
61
+ # allows you to retry a run when an temporary error (e.g. asynchronous wait or halt) occured.
62
+ #
63
+ # @param [Symbol] action the type of action to take during this run. :run or :retry
64
+ def run(action = :run)
65
+ self.action = action
66
+
67
+ self.start_date = Time.now unless action == :retry
68
+
69
+ self.options = workflow.prepare_input(self.options)
70
+
71
+ self.tasks = workflow.tasks
72
+ configure_tasks self.options
73
+
74
+ self.save!
75
+
76
+ runner = Libis::Workflow::TaskRunner.new nil
77
+
78
+ self.tasks.each do |task|
79
+ runner << task
80
+ end
81
+
82
+ runner.run self
83
+
84
+ end
85
+
86
+ protected
87
+
88
+ def configure_tasks(opts)
89
+ self.tasks.each { |task| task.apply_options opts }
90
+ end
91
+
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,173 +1,173 @@
1
- require 'backports/rails/hash'
2
- require 'libis/tools/extend/hash'
3
-
4
- require 'libis/workflow/config'
5
- require 'libis/workflow/status'
6
- require_relative 'logging'
7
-
8
- module Libis
9
- module Workflow
10
- module Base
11
-
12
- # Base module for all work items.
13
- #
14
- # This module lacks the implementation for the data attributes. It functions as an interface that describes the
15
- # common functionality regardless of the storage implementation. These attributes require some implementation:
16
- #
17
- # - parent: [Object|nil] a link to a parent work item. Work items can be organized in any hierarchy you think is
18
- # relevant for your workflow (e.g. directory[/directory...]/file/line or library/section/book/page). Of course
19
- # hierarchies are not mandatory.
20
- # - items: [Array] a list of child work items. see above.
21
- # - options: [Hash] a set of options for the task chain on how to deal with this work item. This attribute can be
22
- # used to fine-tune the behaviour of tasks for a particular work item.
23
- # - properties: [Hash] a set of properties, typically collected during the workflow processing and used to store
24
- # final or intermediate resulst of tasks. The ::Lias::Ingester::FileItem module uses this attribute to store the
25
- # properties (e.g. size, checksum, ...) of the file it represents.
26
- # - status_log: [Array] a list of all status changes the work item went through.
27
- # - summary: [Hash] collected statistics about the ingest for the work item and its children. This structure will
28
- # be filled in by the included task ::Lias::Ingester::Tasks::Analyzer wich is appended to the workflow by default.
29
- #
30
- # The module is created so that it is possible to implement an ActiveRecord/Datamapper/... implementation easily.
31
- # A simple in-memory implementation would require:
32
- #
33
- # attr_accessor :parent
34
- # attr_accessor :items
35
- # attr_accessor :options, :properties
36
- # attr_accessor :status_log
37
- # attr_accessor :summary
38
- #
39
- # def initialize
40
- # self.parent = nil
41
- # self.items = []
42
- # self.options = {}
43
- # self.properties = {}
44
- # self.status_log = []
45
- # self.summary = {}
46
- # end
47
- #
48
- # protected
49
- #
50
- # ## Method below should be adapted to match the implementation of the status array
51
- #
52
- # def add_status_log(info)
53
- # self.status_log << info
54
- # end
55
- #
56
- #
57
- module WorkItem
58
- include Enumerable
59
- include Libis::Workflow::Status
60
- include Libis::Workflow::Base::Logging
61
-
62
- # String representation of the identity of the work item.
63
- #
64
- # You may want to overwrite this method as it tries the :name property or whatever #inspect returns if that
65
- # failes. Typically this should return the key value, file name or id number. If that's what your :name property
66
- # contains, you're fine.
67
- #
68
- # @return [String] string identification for this work item.
69
- def name
70
- # noinspection RubyResolve
71
- self.properties['name'] || self.inspect
72
- end
73
-
74
- def to_s;
75
- self.name;
76
- end
77
-
78
- def names
79
- (self.parent.names rescue Array.new).push(name).compact
80
- end
81
-
82
- def namepath;
83
- self.names.join('/');
84
- end
85
-
86
- # File name safe version of the to_s output.
87
- #
88
- # The output should be safe to use as a file name to store work item
89
- # data. Typical use is when extra file items are created by a task and need to be stored on disk. The default
90
- # implementation URL-encodes (%xx) all characters except alphanumeric, '.' and '-'.
91
- #
92
- # @return [String] file name
93
- def to_filename
94
- self.to_s.gsub(/[^\w.-]/) { |s| '%%%02x' % s.ord }
95
- end
96
-
97
- # Iterates over the work item clients and invokes code on each of them.
98
- def each(&block)
99
- self.items.each(&block)
100
- end
101
-
102
- def size
103
- self.items.size
104
- end
105
-
106
- alias_method :count, :size
107
-
108
- # Add a child work item
109
- #
110
- # @param [WorkItem] item to be added to the child list :items
111
- def add_item(item)
112
- return self unless item and item.is_a?(Libis::Workflow::Base::WorkItem)
113
- self.items << item
114
- item.parent = self
115
- self.save!
116
- item.save!
117
- self
118
- end
119
-
120
- alias_method :<<, :add_item
121
-
122
- # Get list of items.
123
- #
124
- # This method should return a list of items that can be accessed during long processing times.
125
- def get_items
126
- self.items
127
- end
128
-
129
- # Get list of items.
130
- #
131
- # This method should return a list of items that is safe to iterate over while it is being altered.
132
- def get_item_list
133
- self.items.dup
134
- end
135
-
136
- # Return item's parent
137
- # @return [Libis::Workflow::Base::WorkItem]
138
- def get_parent
139
- self.parent
140
- end
141
-
142
- # go up the hierarchy and return the topmost work item
143
- #
144
- # @return [Libis::Workflow::Base::WorkItem]
145
- def get_root
146
- self.get_parent && self.get_parent.is_a?(Libis::Workflow::Base::WorkItem) && self.get_parent.get_root || self
147
- end
148
-
149
- # Get the top
150
- #
151
- # @return [Libis::Workflow::Base::Run]
152
- def get_run
153
- return self if self.is_a?(Libis::Workflow::Base::Run)
154
- self.get_parent && self.get_parent.get_run || nil
155
- end
156
-
157
- # Dummy method. It is a placeholder for DB backed implementations. Wherever appropriate WorkItem#save will be
158
- # called to save the current item's state. If state needs to persisted, you should override this method or make
159
- # sure your persistence layer implements it in your class.
160
- def save
161
- end
162
-
163
- # Dummy method. It is a placeholder for DB backed implementations. Wherever appropriate WorkItem#save will be
164
- # called to save the current item's state. If state needs to persisted, you should override this method or make
165
- # sure your persistence layer implements it in your class.
166
- def save!
167
- end
168
-
169
- end
170
-
171
- end
172
- end
173
- end
1
+ require 'backports/rails/hash'
2
+ require 'libis/tools/extend/hash'
3
+
4
+ require 'libis/workflow/config'
5
+ require 'libis/workflow/status'
6
+ require_relative 'logging'
7
+
8
+ module Libis
9
+ module Workflow
10
+ module Base
11
+
12
+ # Base module for all work items.
13
+ #
14
+ # This module lacks the implementation for the data attributes. It functions as an interface that describes the
15
+ # common functionality regardless of the storage implementation. These attributes require some implementation:
16
+ #
17
+ # - parent: [Object|nil] a link to a parent work item. Work items can be organized in any hierarchy you think is
18
+ # relevant for your workflow (e.g. directory[/directory...]/file/line or library/section/book/page). Of course
19
+ # hierarchies are not mandatory.
20
+ # - items: [Array] a list of child work items. see above.
21
+ # - options: [Hash] a set of options for the task chain on how to deal with this work item. This attribute can be
22
+ # used to fine-tune the behaviour of tasks for a particular work item.
23
+ # - properties: [Hash] a set of properties, typically collected during the workflow processing and used to store
24
+ # final or intermediate resulst of tasks. The ::Lias::Ingester::FileItem module uses this attribute to store the
25
+ # properties (e.g. size, checksum, ...) of the file it represents.
26
+ # - status_log: [Array] a list of all status changes the work item went through.
27
+ # - summary: [Hash] collected statistics about the ingest for the work item and its children. This structure will
28
+ # be filled in by the included task ::Lias::Ingester::Tasks::Analyzer wich is appended to the workflow by default.
29
+ #
30
+ # The module is created so that it is possible to implement an ActiveRecord/Datamapper/... implementation easily.
31
+ # A simple in-memory implementation would require:
32
+ #
33
+ # attr_accessor :parent
34
+ # attr_accessor :items
35
+ # attr_accessor :options, :properties
36
+ # attr_accessor :status_log
37
+ # attr_accessor :summary
38
+ #
39
+ # def initialize
40
+ # self.parent = nil
41
+ # self.items = []
42
+ # self.options = {}
43
+ # self.properties = {}
44
+ # self.status_log = []
45
+ # self.summary = {}
46
+ # end
47
+ #
48
+ # protected
49
+ #
50
+ # ## Method below should be adapted to match the implementation of the status array
51
+ #
52
+ # def add_status_log(info)
53
+ # self.status_log << info
54
+ # end
55
+ #
56
+ #
57
+ module WorkItem
58
+ include Enumerable
59
+ include Libis::Workflow::Status
60
+ include Libis::Workflow::Base::Logging
61
+
62
+ # String representation of the identity of the work item.
63
+ #
64
+ # You may want to overwrite this method as it tries the :name property or whatever #inspect returns if that
65
+ # failes. Typically this should return the key value, file name or id number. If that's what your :name property
66
+ # contains, you're fine.
67
+ #
68
+ # @return [String] string identification for this work item.
69
+ def name
70
+ # noinspection RubyResolve
71
+ self.properties['name'] || self.inspect
72
+ end
73
+
74
+ def to_s;
75
+ self.name;
76
+ end
77
+
78
+ def names
79
+ (self.parent.names rescue Array.new).push(name).compact
80
+ end
81
+
82
+ def namepath;
83
+ self.names.join('/');
84
+ end
85
+
86
+ # File name safe version of the to_s output.
87
+ #
88
+ # The output should be safe to use as a file name to store work item
89
+ # data. Typical use is when extra file items are created by a task and need to be stored on disk. The default
90
+ # implementation URL-encodes (%xx) all characters except alphanumeric, '.' and '-'.
91
+ #
92
+ # @return [String] file name
93
+ def to_filename
94
+ self.to_s.gsub(/[^\w.-]/) { |s| '%%%02x' % s.ord }
95
+ end
96
+
97
+ # Iterates over the work item clients and invokes code on each of them.
98
+ def each(&block)
99
+ self.items.each(&block)
100
+ end
101
+
102
+ def size
103
+ self.items.size
104
+ end
105
+
106
+ alias_method :count, :size
107
+
108
+ # Add a child work item
109
+ #
110
+ # @param [WorkItem] item to be added to the child list :items
111
+ def add_item(item)
112
+ return self unless item and item.is_a?(Libis::Workflow::Base::WorkItem)
113
+ self.items << item
114
+ item.parent = self
115
+ self.save!
116
+ item.save!
117
+ self
118
+ end
119
+
120
+ alias_method :<<, :add_item
121
+
122
+ # Get list of items.
123
+ #
124
+ # This method should return a list of items that can be accessed during long processing times.
125
+ def get_items
126
+ self.items
127
+ end
128
+
129
+ # Get list of items.
130
+ #
131
+ # This method should return a list of items that is safe to iterate over while it is being altered.
132
+ def get_item_list
133
+ self.items.dup
134
+ end
135
+
136
+ # Return item's parent
137
+ # @return [Libis::Workflow::Base::WorkItem]
138
+ def get_parent
139
+ self.parent
140
+ end
141
+
142
+ # go up the hierarchy and return the topmost work item
143
+ #
144
+ # @return [Libis::Workflow::Base::WorkItem]
145
+ def get_root
146
+ self.get_parent && self.get_parent.is_a?(Libis::Workflow::Base::WorkItem) && self.get_parent.get_root || self
147
+ end
148
+
149
+ # Get the top
150
+ #
151
+ # @return [Libis::Workflow::Base::Run]
152
+ def get_run
153
+ return self if self.is_a?(Libis::Workflow::Base::Run)
154
+ self.get_parent && self.get_parent.get_run || nil
155
+ end
156
+
157
+ # Dummy method. It is a placeholder for DB backed implementations. Wherever appropriate WorkItem#save will be
158
+ # called to save the current item's state. If state needs to persisted, you should override this method or make
159
+ # sure your persistence layer implements it in your class.
160
+ def save
161
+ end
162
+
163
+ # Dummy method. It is a placeholder for DB backed implementations. Wherever appropriate WorkItem#save will be
164
+ # called to save the current item's state. If state needs to persisted, you should override this method or make
165
+ # sure your persistence layer implements it in your class.
166
+ def save!
167
+ end
168
+
169
+ end
170
+
171
+ end
172
+ end
173
+ end