rworkflow 0.6.1

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 (61) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +117 -0
  4. data/lib/rworkflow/flow.rb +450 -0
  5. data/lib/rworkflow/flow_registry.rb +69 -0
  6. data/lib/rworkflow/lifecycle.rb +102 -0
  7. data/lib/rworkflow/minitest/test.rb +53 -0
  8. data/lib/rworkflow/minitest/worker.rb +17 -0
  9. data/lib/rworkflow/minitest.rb +8 -0
  10. data/lib/rworkflow/sidekiq_flow.rb +186 -0
  11. data/lib/rworkflow/sidekiq_helper.rb +84 -0
  12. data/lib/rworkflow/sidekiq_lifecycle.rb +8 -0
  13. data/lib/rworkflow/sidekiq_state.rb +42 -0
  14. data/lib/rworkflow/state.rb +104 -0
  15. data/lib/rworkflow/state_error.rb +13 -0
  16. data/lib/rworkflow/transition_error.rb +9 -0
  17. data/lib/rworkflow/version.rb +3 -0
  18. data/lib/rworkflow/worker.rb +62 -0
  19. data/lib/rworkflow.rb +15 -0
  20. data/lib/tasks/rworkflow_tasks.rake +4 -0
  21. data/test/dummy/README.rdoc +28 -0
  22. data/test/dummy/Rakefile +6 -0
  23. data/test/dummy/app/assets/javascripts/application.js +13 -0
  24. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  25. data/test/dummy/app/controllers/application_controller.rb +5 -0
  26. data/test/dummy/app/helpers/application_helper.rb +2 -0
  27. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  28. data/test/dummy/bin/bundle +3 -0
  29. data/test/dummy/bin/rails +4 -0
  30. data/test/dummy/bin/rake +4 -0
  31. data/test/dummy/config/application.rb +15 -0
  32. data/test/dummy/config/boot.rb +5 -0
  33. data/test/dummy/config/database.yml +25 -0
  34. data/test/dummy/config/environment.rb +5 -0
  35. data/test/dummy/config/environments/development.rb +37 -0
  36. data/test/dummy/config/environments/production.rb +83 -0
  37. data/test/dummy/config/environments/test.rb +41 -0
  38. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  39. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  40. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  41. data/test/dummy/config/initializers/inflections.rb +16 -0
  42. data/test/dummy/config/initializers/mime_types.rb +4 -0
  43. data/test/dummy/config/initializers/session_store.rb +3 -0
  44. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  45. data/test/dummy/config/locales/en.yml +23 -0
  46. data/test/dummy/config/routes.rb +56 -0
  47. data/test/dummy/config/secrets.yml +22 -0
  48. data/test/dummy/config.ru +4 -0
  49. data/test/dummy/db/test.sqlite3 +0 -0
  50. data/test/dummy/log/test.log +516 -0
  51. data/test/dummy/public/404.html +67 -0
  52. data/test/dummy/public/422.html +67 -0
  53. data/test/dummy/public/500.html +66 -0
  54. data/test/dummy/public/favicon.ico +0 -0
  55. data/test/flow_test.rb +112 -0
  56. data/test/lifecycle_test.rb +81 -0
  57. data/test/rworkflow_test.rb +7 -0
  58. data/test/sidekiq_flow_test.rb +173 -0
  59. data/test/state_test.rb +99 -0
  60. data/test/test_helper.rb +32 -0
  61. metadata +199 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0a21aaa1eb7353e45eb10cf8498ed9c8ae9cebfc
4
+ data.tar.gz: 05a018eb4b27041ed86515cb7db4e2c945708368
5
+ SHA512:
6
+ metadata.gz: f304c504e1f537f0430f94739997c858b23d2308844796d00b2bc40fb74be22cdbd0f018d6b004e60e05538e4c3283ba969ac5572ef967257e91c3cd4d5f611d
7
+ data.tar.gz: d89cddce1e44a96ef6252ff43a528780b7f4c023ee9e83d93bf08d679448704341a61614dec068dc73a1d028279f9c94c44641078bdb40449611426c5b96164b
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,117 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Rworkflow'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ Bundler::GemHelper.install_tasks
18
+
19
+ require 'rake/testtask'
20
+
21
+ Rake::TestTask.new(:test) do |t|
22
+ t.libs << 'lib'
23
+ t.libs << 'test'
24
+ t.pattern = 'test/**/*_test.rb'
25
+ t.verbose = false
26
+ end
27
+
28
+ task default: :test
29
+
30
+ namespace :cim do
31
+ desc 'Tags, updates README, and CHANGELOG and pushes to Github. Requires ruby-git'
32
+ task :release do
33
+ tasks = ['cim:assert_clean_repo', 'cim:git_fetch', 'cim:assert_version', 'cim:update_readme', 'cim:update_changelog', 'cim:commit_changes', 'cim:tag']
34
+ begin
35
+ tasks.each { |task| Rake::Task[task].invoke }
36
+ `git push && git push origin '#{Rworkflow::VERSION}'`
37
+ rescue => error
38
+ puts ">>> ERROR: #{error}; might want to reset your repository"
39
+ end
40
+ end
41
+
42
+ desc 'Fails if the current repository is not clean'
43
+ task :assert_clean_repo do
44
+ status = `git status -s`.chomp.strip
45
+ if status.empty?
46
+ status = `git log origin/master..HEAD`.chomp.strip # check if we have unpushed commits
47
+ if status.empty?
48
+ puts ">>> Repository is clean!"
49
+ else
50
+ puts ">>> Please push your committed changes before releasing!"
51
+ exit -1
52
+ end
53
+ else
54
+ puts ">>> Please stash or commit your changes before releasing!"
55
+ exit -1
56
+ end
57
+ end
58
+
59
+ desc 'Fetches latest tags/commits'
60
+ task :git_fetch do
61
+ puts '>>> Fetching latest git refs'
62
+ `git fetch --tags`
63
+ end
64
+
65
+ desc 'Fails if the current gem version is not greater than the last tag'
66
+ task :assert_version do
67
+ latest = `git describe --abbrev=0`.chomp.strip
68
+ current = Rworkflow::VERSION
69
+
70
+ unless Gem::Version.new(current) > Gem::Version.new(latest)
71
+ puts ">>> Latest tagged version is #{latest}; make sure gem version (#{current}) is greater!"
72
+ exit -1
73
+ end
74
+ end
75
+
76
+ desc 'Updates README with latest version'
77
+ task :update_readme do
78
+ puts '>>> Updating README.md'
79
+ replace = %([![GitHub release](https://img.shields.io/badge/release-#{Rworkflow::VERSION}-blue.png)](https://github.com/barcoo/rworkflow/releases/tag/#{Rworkflow::VERSION}))
80
+
81
+ `sed -i -u 's@^\\[\\!\\[GitHub release\\].*$@#{replace}@' README.md`
82
+ end
83
+
84
+ desc 'Updates CHANGELOG with commit log from last tag to this one'
85
+ task :update_changelog do
86
+ puts '>>> Updating CHANGELOG.md'
87
+ latest = `git describe --abbrev=0`.chomp.strip
88
+ log = `git log --pretty=format:'- [%h](https://github.com/barcoo/rworkflow/commit/%h) *%ad* __%s__ (%an)' --date=short '#{latest}'..HEAD`.chomp
89
+
90
+ changelog = File.open('.CHANGELOG.md', 'w')
91
+ changelog.write("# Changelog\n\n###{Rworkflow::VERSION}\n\n#{log}\n\n")
92
+ File.open('CHANGELOG.md', 'r') do |file|
93
+ file.readline # skip first two lines
94
+ file.readline
95
+ while buffer = file.read(2048)
96
+ changelog.write(buffer)
97
+ end
98
+ end
99
+
100
+ changelog.close
101
+ `mv '.CHANGELOG.md' 'CHANGELOG.md'`
102
+ end
103
+
104
+ desc 'Commits the README/CHANGELOG changes'
105
+ task :commit_changes do
106
+ puts '>>> Committing updates to README/CHANGELOG'
107
+ `git commit -am'Updated README.md and CHANGELOG.md on new release'`
108
+ end
109
+
110
+ desc 'Creates and pushes the tag to git'
111
+ task :tag do
112
+ puts '>>> Tagging'
113
+ message = STDOUT.print ">>> Please enter a tag message: "
114
+ input = STDIN.gets.strip.tr("'", "\'")
115
+ `git tag -a '#{Rworkflow::VERSION}' -m '#{input}'`
116
+ end
117
+ end
@@ -0,0 +1,450 @@
1
+ module Rworkflow
2
+ class Flow
3
+ STATE_SUCCESSFUL = :successful
4
+ STATE_FAILED = :failed
5
+ STATES_TERMINAL = [STATE_FAILED, STATE_SUCCESSFUL].freeze
6
+ STATES_FAILED = [STATE_FAILED].freeze
7
+
8
+ REDIS_NS = 'flow'.freeze
9
+ WORKFLOW_REGISTRY = "#{REDIS_NS}:__registry".freeze
10
+
11
+ attr_accessor :id
12
+ attr_reader :lifecycle
13
+
14
+ def initialize(id)
15
+ @id = id
16
+ @redis_key = "#{REDIS_NS}:#{id}"
17
+
18
+ @storage = RedisRds::Hash.new(@redis_key)
19
+ @flow_data = RedisRds::Hash.new("#{@redis_key}__data")
20
+ @processing = RedisRds::Hash.new("#{@redis_key}__processing")
21
+
22
+ load_lifecycle
23
+ end
24
+
25
+ def load_lifecycle
26
+ serialized = @storage.get(:lifecycle)
27
+ unless serialized.nil?
28
+ raw = self.class.serializer.load(serialized)
29
+ @lifecycle = Rworkflow::Lifecycle.unserialize(raw) unless raw.nil?
30
+ end
31
+ rescue
32
+ @lifecycle = nil
33
+ end
34
+ private :load_lifecycle
35
+
36
+ def lifecycle=(new_lifecycle)
37
+ @lifecycle = new_lifecycle
38
+ @storage.set(:lifecycle, self.class.serializer.dump(@lifecycle.serialize))
39
+ end
40
+
41
+ def finished?
42
+ return false unless started?
43
+ total = get_counters.reduce(0) do |sum, pair|
44
+ self.class.terminal?(pair[0]) ? sum : (sum + pair[1].to_i)
45
+ end
46
+
47
+ return total == 0
48
+ end
49
+
50
+ def status
51
+ status = 'Running'
52
+ status = (successful?) ? 'Finished' : 'Failed' if finished?
53
+
54
+ return status
55
+ end
56
+
57
+ def created_at
58
+ return @created_at ||= begin Time.at(get(:created_at, 0)) end
59
+ end
60
+
61
+ def started?
62
+ return !get(:start_time).nil?
63
+ end
64
+
65
+ def name
66
+ return get(:name, @id)
67
+ end
68
+
69
+ def name=(name)
70
+ return set(:name, name)
71
+ end
72
+
73
+ def start_time
74
+ return Time.at(get(:start_time, 0))
75
+ end
76
+
77
+ def finish_time
78
+ return Time.at(get(:finish_time, 0))
79
+ end
80
+
81
+ def expected_duration
82
+ return Float::INFINITY
83
+ end
84
+
85
+ def valid?
86
+ return !@lifecycle.nil?
87
+ end
88
+
89
+ def count(state)
90
+ return get_list(state).size
91
+ end
92
+
93
+ def get_counters
94
+ counters = @storage.get(:counters)
95
+ if !counters.nil?
96
+ counters = begin
97
+ self.class.serializer.load(counters)
98
+ rescue => e
99
+ Rails.logger.error("Error loading stored flow counters: #{e.message}")
100
+ nil
101
+ end
102
+ end
103
+ return counters || get_counters!
104
+ end
105
+
106
+ # fetches counters atomically
107
+ def get_counters!
108
+ counters = { processing: 0 }
109
+
110
+ names = @lifecycle.states.keys
111
+ results = RedisRds::Object.connection.multi do
112
+ self.class::STATES_TERMINAL.each { |name| get_list(name).size }
113
+ names.each { |name| get_list(name).size }
114
+ @processing.getall
115
+ end
116
+
117
+ (self.class::STATES_TERMINAL + names).each do |name|
118
+ counters[name] = results.shift.to_i
119
+ end
120
+
121
+ counters[:processing] = results.shift.reduce(0) { |sum, pair| sum + pair.last.to_i }
122
+
123
+ return counters
124
+ end
125
+ private :get_counters!
126
+
127
+ def fetch(fetcher_id, state_name)
128
+ @processing.set(fetcher_id, 1)
129
+ list = get_state_list(state_name)
130
+ unless list.nil?
131
+ failed = []
132
+ cardinality = @lifecycle.states[state_name].cardinality
133
+ cardinality = get(:start_count).to_i if cardinality == Lifecycle::CARDINALITY_ALL_STARTED
134
+ force_list_complete = @lifecycle.states[state_name].policy == State::STATE_POLICY_WAIT
135
+ raw_objects = list.lpop(cardinality, force_list_complete)
136
+ unless raw_objects.empty?
137
+ objects = raw_objects.map do |raw_object|
138
+ begin
139
+ self.class.serializer.load(raw_object)
140
+ rescue StandardError => _
141
+ failed << raw_object
142
+ nil
143
+ end
144
+ end.compact
145
+ @processing.set(fetcher_id, objects.size)
146
+
147
+ unless failed.empty?
148
+ push(failed, STATE_FAILED)
149
+ Rails.logger.error("Failed to parse #{failed.size} in workflow #{@id} for fetcher id #{fetcher_id} at state #{state_name}")
150
+ end
151
+
152
+ yield(objects) if block_given?
153
+ end
154
+ end
155
+ ensure
156
+ @processing.remove(fetcher_id)
157
+ terminate if finished?
158
+ end
159
+
160
+ def list_objects(state_name, limit = -1)
161
+ list = get_list(state_name)
162
+ return list.get(0, limit).map {|object| self.class.serializer.load(object)}
163
+ end
164
+
165
+ def get_state_list(state_name)
166
+ list = nil
167
+ state = @lifecycle.states[state_name]
168
+
169
+ if !state.nil?
170
+ list = get_list(state_name)
171
+ else
172
+ Rails.logger.error("Tried accessing invalid state #{state_name} for workflow #{id}")
173
+ end
174
+ return list
175
+ end
176
+ private :get_state_list
177
+
178
+ def terminate
179
+ mutex = RedisRds::Mutex.new(self.id)
180
+ mutex.synchronize do
181
+ if !self.cleaned_up?
182
+ set(:finish_time, Time.now.to_i)
183
+ post_process
184
+
185
+ if self.public?
186
+ counters = get_counters!
187
+ counters[:processing] = 0 # Some worker might have increased the processing flag at that time even if there is no more jobs to be done
188
+ @storage.setnx(:counters, self.class.serializer.dump(counters))
189
+ states_cleanup
190
+ else
191
+ self.cleanup
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ def post_process
198
+ end
199
+ protected :post_process
200
+
201
+ def metadata_string
202
+ return "Rworkflow: #{self.name}"
203
+ end
204
+
205
+ def cleaned_up?
206
+ return states_list.all? { |name| !get_list(name).exists? }
207
+ end
208
+
209
+ def states_list
210
+ states = self.class::STATES_TERMINAL
211
+ states += @lifecycle.states.keys if valid?
212
+
213
+ return states
214
+ end
215
+
216
+ def transition(from_state, name, objects)
217
+ objects = Array.wrap(objects)
218
+ to_state = begin
219
+ lifecycle.transition(from_state, name)
220
+ rescue Rworkflow::StateError => e
221
+ Rails.logger.error("Error transitioning: #{e}")
222
+ nil
223
+ end
224
+
225
+ if !to_state.nil?
226
+ push(objects, to_state)
227
+ log(from_state, name, objects.size)
228
+ end
229
+ end
230
+
231
+ def logging?
232
+ return get(:logging, false)
233
+ end
234
+
235
+ def log(from_state, transition, num_objects)
236
+ logger.incrby("#{from_state}__#{transition}", num_objects.to_i) if logging?
237
+ end
238
+
239
+ def logger
240
+ return @logger ||= begin
241
+ RedisRds::Hash.new("#{@redis_key}__logger")
242
+ end
243
+ end
244
+
245
+ def logs
246
+ logs = {}
247
+ if valid? && logging?
248
+ state_transition_counters = logger.getall
249
+ state_transition_counters.each do |state_transition, counter|
250
+ state, transition = state_transition.split('__')
251
+ logs[state] = {} unless logs.key?(state)
252
+ logs[state][transition] = counter.to_i
253
+ end
254
+ end
255
+
256
+ return logs
257
+ end
258
+
259
+ def get_state_cardinality(state_name)
260
+ cardinality = @lifecycle.states[state_name].cardinality
261
+ cardinality = self.get(:start_count).to_i if cardinality == Rworkflow::Lifecycle::CARDINALITY_ALL_STARTED
262
+ return cardinality
263
+ end
264
+
265
+ def set(key, value)
266
+ @flow_data.set(key, self.class.serializer.dump(value))
267
+ end
268
+
269
+ def get(key, default = nil)
270
+ value = @flow_data.get(key)
271
+ value = if value.nil? then default else self.class.serializer.load(value) end
272
+
273
+ return value
274
+ end
275
+
276
+ def incr(key, value = 1)
277
+ return @flow_data.incrby(key, value)
278
+ end
279
+
280
+ def push(objects, state)
281
+ objects = Array.wrap(objects)
282
+
283
+ return 0 if objects.empty?
284
+
285
+ list = get_list(state)
286
+ list.rpush(objects.map { |object| self.class.serializer.dump(object) })
287
+
288
+ return objects.size
289
+ end
290
+ private :push
291
+
292
+ def get_list(name)
293
+ return RedisRds::List.new("#{@redis_key}:lists:#{name}")
294
+ end
295
+ private :get_list
296
+
297
+ def cleanup
298
+ return if Rails.env.test?
299
+ states_cleanup
300
+ logger.delete
301
+ @processing.delete
302
+ self.class.unregister(self)
303
+ @flow_data.delete
304
+ @storage.delete
305
+ end
306
+
307
+ def states_cleanup
308
+ return if Rails.env.test?
309
+ states_list.each { |name| get_list(name).delete }
310
+ end
311
+ protected :states_cleanup
312
+
313
+ def start(objects)
314
+ objects = Array.wrap(objects)
315
+ set(:start_time, Time.now.to_i)
316
+ set(:start_count, objects.size)
317
+ push(objects, lifecycle.initial)
318
+ log(lifecycle.initial, 'initial', objects.size)
319
+ end
320
+
321
+ def total_objects_processed(counters = nil)
322
+ return (counters || get_counters).reduce(0) do |sum, pair|
323
+ if self.class.terminal?(pair[0])
324
+ sum + pair[1]
325
+ else
326
+ sum
327
+ end
328
+ end
329
+ end
330
+
331
+ def total_objects(counters = nil)
332
+ return (counters || get_counters).reduce(0) { |sum, pair| sum + pair[1] }
333
+ end
334
+
335
+ def total_objects_failed(counters = nil)
336
+ return (counters || get_counters).reduce(0) do |sum, pair|
337
+ if self.class.failure?(pair[0])
338
+ sum + pair[1]
339
+ else
340
+ sum
341
+ end
342
+ end
343
+ end
344
+
345
+ def successful?
346
+ return false if !finished?
347
+ return !failed?
348
+ end
349
+
350
+ def failed?
351
+ return false if !finished?
352
+ return total_objects_failed > 0
353
+ end
354
+
355
+ def public?
356
+ return @public ||= begin get(:public, false) end
357
+ end
358
+
359
+ class << self
360
+ def create(lifecycle, name = '', options = {})
361
+ id = generate_id(name)
362
+ workflow = new(id)
363
+ workflow.name = name
364
+ workflow.lifecycle = lifecycle
365
+ workflow.set(:created_at, Time.now.to_i)
366
+ workflow.set(:public, options.fetch(:public, false))
367
+ workflow.set(:logging, options.fetch(:logging, true))
368
+
369
+ register(workflow)
370
+
371
+ return workflow
372
+ end
373
+
374
+ def generate_id(workflow_name)
375
+ now = Time.now.to_f
376
+ random = Random.new(now)
377
+ return "#{name}__#{workflow_name}__#{(Time.now.to_f * 1000).to_i}__#{random.rand(now).to_i}"
378
+ end
379
+ private :generate_id
380
+
381
+ def cleanup(id)
382
+ workflow = new(id)
383
+ workflow.cleanup
384
+ end
385
+
386
+ def get_public_workflows(options = {})
387
+ return registry.public_flows(options.reverse_merge(parent_class: self)).map { |id| load(id) }
388
+ end
389
+
390
+ def get_private_workflows(options = {})
391
+ return registry.private_flows(options.reverse_merge(parent_class: self)).map { |id| load(id) }
392
+ end
393
+
394
+ def all(options = {})
395
+ return registry.all(options.reverse_merge(parent_class: self)).map { |id| load(id) }
396
+ end
397
+
398
+ def load(id, klass = nil)
399
+ workflow = nil
400
+
401
+ klass = read_flow_class(id) if klass.nil?
402
+ workflow = klass.new(id) if klass.respond_to?(:new)
403
+ return workflow
404
+ end
405
+
406
+ def read_flow_class(id)
407
+ klass = nil
408
+ raw_class = id.split('__').first
409
+ klass = begin
410
+ raw_class.constantize
411
+ rescue NameError => _
412
+ Rails.logger.warn("Unknown flow class for workflow id #{id}")
413
+ nil
414
+ end if !raw_class.nil?
415
+
416
+ return klass
417
+ end
418
+
419
+ def registered?(workflow)
420
+ return registry.include?(workflow)
421
+ end
422
+
423
+ def register(workflow)
424
+ registry.add(workflow)
425
+ end
426
+
427
+ def unregister(workflow)
428
+ registry.remove(workflow)
429
+ end
430
+
431
+ def terminal?(state)
432
+ return self::STATES_TERMINAL.include?(state)
433
+ end
434
+
435
+ def failure?(state)
436
+ return self::STATES_FAILED.include?(state)
437
+ end
438
+
439
+ def registry
440
+ return @registry ||= begin
441
+ FlowRegistry.new(Rworkflow::VERSION.to_s)
442
+ end
443
+ end
444
+
445
+ def serializer
446
+ YAML
447
+ end
448
+ end
449
+ end
450
+ end
@@ -0,0 +1,69 @@
1
+ module Rworkflow
2
+ class FlowRegistry
3
+ REDIS_PREFIX = 'flow:__registry'.freeze
4
+
5
+ def initialize(prefix = nil)
6
+ @redis_key = [REDIS_PREFIX, prefix].compact.join(':')
7
+ @public = RedisRds::SortedSet.new("#{@redis_key}:public")
8
+ @private = RedisRds::SortedSet.new("#{@redis_key}:private")
9
+ end
10
+
11
+ # Warning: using parent_class forces us to load everything, make this potentially much slower as we have to do the
12
+ # pagination in the app, not in the db
13
+ def all(options = {})
14
+ return self.public_flows(options) + self.private_flows(options)
15
+ end
16
+
17
+ def public_flows(options = {})
18
+ return get(@public, **options)
19
+ end
20
+
21
+ def private_flows(options = {})
22
+ return get(@private, **options)
23
+ end
24
+
25
+ def add(flow)
26
+ key = flow.created_at.to_i
27
+
28
+ if flow.public?
29
+ @public.add(key, flow.id)
30
+ else
31
+ @private.add(key, flow.id)
32
+ end
33
+ end
34
+
35
+ def remove(flow)
36
+ if flow.public?
37
+ @public.remove(flow.id)
38
+ else
39
+ @private.remove(flow.id)
40
+ end
41
+ end
42
+
43
+ def include?(flow)
44
+ if flow.public?
45
+ @public.include?(flow.id)
46
+ else
47
+ @private.include?(flow.id)
48
+ end
49
+ end
50
+
51
+ def get(zset, parent_class: nil, from: nil, to: nil, order: :asc)
52
+ from = from.to_i
53
+ to = to.nil? ? -1 : to.to_i
54
+
55
+ ids = []
56
+ if parent_class.nil? || parent_class == Flow
57
+ ids = zset.range(from, to, order: order)
58
+ else
59
+ ids = zset.range(0, -1, order: order).select do |id|
60
+ klass = Flow.read_flow_class(id)
61
+ !klass.nil? && klass <= parent_class
62
+ end.slice(from..to)
63
+ end
64
+
65
+ return ids
66
+ end
67
+ private :get
68
+ end
69
+ end