rworkflow 0.6.1

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