conflow 0.1.0 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 736a50603837a0bd935229a07df384630c29f16d5d796e9d91ab9dc97c163d98
4
- data.tar.gz: 579a36c20e8b42a68657eafc9632725a3108bf5b9eeaab70f391f43629dedb01
3
+ metadata.gz: b85fd52b7c1a2161135fc389c45b3f1e6754462a17ef30463435f1c919685950
4
+ data.tar.gz: a5f05aa2c69b8426a72dc1070834ec74120a5053a74f93250098765c461105ef
5
5
  SHA512:
6
- metadata.gz: f850accd201ad219d809d45b874440d4c9aeff54400289bfcb7443ea9281eb240c52402a7ef38d1afaef56b7255776d763f011f3e06642f36c1b60cecbeec023
7
- data.tar.gz: 284ce027afa093cb9fb6e6f8389e9bf71cd966a801dc8c3407f32ad94b0596bcca7f864b78f7e40760b6afa28f889e8ce636719a9dce6d232045ce219dcf7f10
6
+ metadata.gz: 3291b765c557576651568d735b3bad21e7a191919a858e9365bbf7da97d632d09f99f3c7a2f35d3133b03d3e82a9802489dd1b63542bd1343325f89c74c22845
7
+ data.tar.gz: a6797479cf68b310791680b2709ed54ebb9e372e26e87a1ffb2e685418a33eefa1b621bf5f68aa6ec05024c54d58f427123335ba204049a3fd7bfba4aa6d026e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.2.0
2
+
3
+ - Flow and Jobs are now removed after they are finished
4
+
1
5
  ## 0.1.0
2
6
 
3
7
  - First release! :pizza:
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- conflow (0.1.0)
4
+ conflow (0.2.0)
5
5
  redis (~> 4.0)
6
6
 
7
7
  GEM
data/lib/conflow.rb CHANGED
@@ -12,6 +12,7 @@ require "conflow/redis/value_field"
12
12
  require "conflow/redis/hash_field"
13
13
  require "conflow/redis/array_field"
14
14
  require "conflow/redis/sorted_set_field"
15
+ require "conflow/redis/set_field"
15
16
  require "conflow/redis/field_builder"
16
17
  require "conflow/redis/model"
17
18
  require "conflow/redis/identifier"
@@ -20,6 +21,7 @@ require "conflow/redis/findable"
20
21
  require "conflow/redis/script"
21
22
  require "conflow/redis/add_job_script"
22
23
  require "conflow/redis/complete_job_script"
24
+ require "conflow/redis/queue_jobs_script"
23
25
 
24
26
  require "conflow/job"
25
27
  require "conflow/flow/job_handler"
data/lib/conflow/flow.rb CHANGED
@@ -16,6 +16,10 @@ module Conflow
16
16
  # are fulfilled.
17
17
  # @return [Conflow::Redis::SortedSetField] Set of jobs to be performed
18
18
  #
19
+ # @!attribute [r] queued_jobs
20
+ # Set of jobs that are currently queued (and not yet finished).
21
+ # @return [Conflow::Redis::SetField] Set of queued jobs
22
+ #
19
23
  # @!method queue(job)
20
24
  # @abstract
21
25
  # Queues job to be performed. Both id of the flow and id of the job must be preserved
@@ -35,7 +39,8 @@ module Conflow
35
39
  include JobHandler
36
40
 
37
41
  has_many :jobs, Conflow::Job
38
- field :indegree, :sorted_set
42
+ field :queued_jobs, :set
43
+ field :indegree, :sorted_set
39
44
 
40
45
  # Create new flow with given parameters
41
46
  # @example Simple configurable flow
@@ -52,6 +57,12 @@ module Conflow
52
57
  new.tap { |flow| flow.configure(*args) }
53
58
  end
54
59
 
60
+ # Returns whether or not the flow is finished (all jobs were processed)
61
+ # @return [Boolean] true if no pending jobs
62
+ def finished?
63
+ queued_jobs.size.zero? && indegree.size.zero?
64
+ end
65
+
55
66
  # @abstract
56
67
  # Override this method in order to contain your flow definition inside the class.
57
68
  # This method will be called if flow is created using {.create} method.
@@ -18,18 +18,21 @@ module Conflow
18
18
  after = prepare_dependencies(after)
19
19
 
20
20
  call_script(Conflow::Redis::AddJobScript, job, after: after)
21
+ queue_available_jobs
21
22
  end
22
23
  end
23
24
 
24
25
  def finish(job, result = nil)
25
26
  send(job.hook.to_s, result) unless job.hook.nil?
26
27
  call_script(Conflow::Redis::CompleteJobScript, job)
28
+ queue_available_jobs
29
+ destroy! if finished?
27
30
  end
28
31
 
29
32
  private
30
33
 
31
34
  def queue_available_jobs
32
- indegree.delete_if(score: 0).each do |job_id|
35
+ call_script(Conflow::Redis::QueueJobsScript)&.each do |job_id|
33
36
  queue Conflow::Job.new(job_id)
34
37
  end
35
38
  end
@@ -44,7 +47,6 @@ module Conflow
44
47
 
45
48
  def call_script(script, *args)
46
49
  script.call(self, *args)
47
- queue_available_jobs
48
50
  end
49
51
 
50
52
  def prepare_dependencies(dependencies)
@@ -6,15 +6,18 @@ module Conflow
6
6
  class CompleteJobScript < Script
7
7
  self.script = <<~LUA
8
8
  local indegree_set = KEYS[1]
9
- local job_id = KEYS[2]
9
+ local queued_jobs = KEYS[2]
10
+ local job_key = KEYS[3]
11
+ local job_id = ARGV[1]
10
12
 
11
- local successors = redis.call('lrange', job_id .. ':successor_ids', 0, -1)
13
+ local successors = redis.call('lrange', job_key .. ':successor_ids', 0, -1)
12
14
 
13
15
  for i=1,#successors do
14
16
  redis.call('zincrby', indegree_set, -1, successors[i])
15
17
  end
16
18
 
17
- return redis.call('set', job_id .. ':status', '1')
19
+ redis.call('srem', queued_jobs, job_id)
20
+ return redis.call('set', job_key .. ':status', '1')
18
21
  LUA
19
22
 
20
23
  class << self
@@ -24,7 +27,7 @@ module Conflow
24
27
  # @param flow [Conflow::Flow] Flow to which job belongs to
25
28
  # @param job [Conflow::Job] Job to be marked as completed
26
29
  def call(flow, job)
27
- super([flow.indegree.key, job.key])
30
+ super([flow.indegree.key, flow.queued_jobs.key, job.key], [job.id])
28
31
  end
29
32
  end
30
33
  end
@@ -20,27 +20,6 @@ module Conflow
20
20
  def command(command, args)
21
21
  redis.with { |conn| conn.send(command, *args) }
22
22
  end
23
-
24
- def transaction(max_retries: 5)
25
- result = redis.with do |conn|
26
- retryable(max_retries, conn) do
27
- conn.watch(key) do
28
- conn.multi { |multi| yield(multi) }
29
- end
30
- end
31
- end
32
-
33
- raise ::Redis::CommandError, "Transaction failed #{max_retries} times" unless result
34
- result
35
- end
36
-
37
- def retryable(max_retries, *args)
38
- loop do
39
- result = yield(*args)
40
- break result if result || max_retries.zero?
41
- max_retries -= 1
42
- end
43
- end
44
23
  end
45
24
  end
46
25
  end
@@ -16,16 +16,49 @@ module Conflow
16
16
  other.is_a?(Model) ? key == other.key : super
17
17
  end
18
18
 
19
+ # Removes this and all related models.
20
+ # @param execute [Boolean] if false, only returns keys that would be removed
21
+ def destroy!(execute: true)
22
+ keys = self.class.fields.map { |name| send(name).key }
23
+ related_keys = self.class.relations.map(&method(:collect_relation_keys))
24
+
25
+ (keys + related_keys).flatten.tap do |key_list|
26
+ command :del, [key_list] if execute && key_list.any?
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def collect_relation_keys(relation)
33
+ send(relation).map { |object| object.destroy!(execute: false) }
34
+ end
35
+
19
36
  # Methods for defining fields on model
20
37
  module ClassMethods
38
+ # Maps types (option for {Conflow::Redis::Model::ClassMethods#field}) to specific type field
39
+ ALLOWED_TYPES = {
40
+ hash: Conflow::Redis::HashField,
41
+ array: Conflow::Redis::ArrayField,
42
+ value: Conflow::Redis::ValueField,
43
+ sorted_set: Conflow::Redis::SortedSetField,
44
+ set: Conflow::Redis::SetField
45
+ }.freeze
46
+
47
+ # Copies field and relations information
48
+ def inherited(subclass)
49
+ subclass.instance_variable_set("@fields", fields.dup)
50
+ subclass.instance_variable_set("@relations", relations.dup)
51
+ end
52
+
21
53
  # Defines Redis field accessors.
22
54
  # @param name [Symbol] name of the field
23
- # @param type [:hash, :array, :value, :sorted_set] type of the new field
55
+ # @param type [:hash, :array, :value, :sorted_set, :set] type of the new field
24
56
  #
25
57
  # @see Conflow::Redis::HashField
26
58
  # @see Conflow::Redis::ArrayField
27
59
  # @see Conflow::Redis::ValueField
28
60
  # @see Conflow::Redis::SortedSetField
61
+ # @see Conflow::Redis::SetField
29
62
  # @example
30
63
  # model_class.field :data, :hash
31
64
  # instance = model_class.new
@@ -33,13 +66,11 @@ module Conflow
33
66
  # instance.hash = { something: "else"}
34
67
  # instance.hash["something"] #=> "else"
35
68
  def field(name, type)
36
- case type
37
- when :hash then FieldBuilder.new(name, Conflow::Redis::HashField).call(self)
38
- when :array then FieldBuilder.new(name, Conflow::Redis::ArrayField).call(self)
39
- when :value then FieldBuilder.new(name, Conflow::Redis::ValueField).call(self)
40
- when :sorted_set then FieldBuilder.new(name, Conflow::Redis::SortedSetField).call(self)
41
- else raise ArgumentError, "Unknown type: #{type}. Should be one of: [:hash, :array]"
42
- end
69
+ type_class = ALLOWED_TYPES[type]
70
+ raise ArgumentError, "Unknown type: #{type}. Should be one of: #{ALLOWED_TYPES.keys.inspect}" unless type_class
71
+
72
+ fields << name
73
+ FieldBuilder.new(name, type_class).call(self)
43
74
  end
44
75
 
45
76
  # Convienience method for defining relation-like accessor.
@@ -47,8 +78,19 @@ module Conflow
47
78
  # has_many :jobs, Conflow::Job # defines #job_ids and #jobs
48
79
  def has_many(name, klass, field_name: "#{name.to_s.chop}_ids")
49
80
  field(field_name, :array)
81
+ relations << name
50
82
  define_method(name) { send(field_name).map { |id| klass.new(id) } }
51
83
  end
84
+
85
+ # @return [Array<Symbol>] fields defined on this class
86
+ def fields
87
+ @fields ||= []
88
+ end
89
+
90
+ # @return [Array<Symbol>] relations defined on this class
91
+ def relations
92
+ @relations ||= []
93
+ end
52
94
  end
53
95
  end
54
96
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Conflow
4
+ module Redis
5
+ # Removes jobs from indegree set, adds them to queued list, and returns IDs of queued jobs.
6
+ class QueueJobsScript < Script
7
+ self.script = <<~LUA
8
+ local indegree_set = KEYS[1]
9
+ local queued_set = KEYS[2]
10
+
11
+ local ids = redis.call('zrangebyscore', indegree_set, 0, 0)
12
+ redis.call('zremrangebyscore', indegree_set, 0, 0)
13
+
14
+ if #ids ~= 0 then
15
+ redis.call('sadd', queued_set, unpack(ids))
16
+ end
17
+
18
+ return ids
19
+ LUA
20
+
21
+ class << self
22
+ # Call the script.
23
+ # Script removes jobs which have score 0 in Flow's indegree set and moves them to queued_jobs list.
24
+ # @param flow [Conflow::Flow] Flow from which jobs should be enqueued
25
+ def call(flow)
26
+ super([flow.indegree.key, flow.queued_jobs.key])
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Conflow
4
+ module Redis
5
+ # Represents Redis list. It's methods mirror most used Set methods.
6
+ class SetField < Field
7
+ include Enumerable
8
+
9
+ # Adds one or more elements to the set.
10
+ # @param values [String] list of values to be added
11
+ # @return [String] Redis response
12
+ #
13
+ # @example Adding multiple fields
14
+ # field.add(:last, :first, :other, :first)
15
+ def add(*values)
16
+ command :sadd, [key, values]
17
+ end
18
+
19
+ # @return [Integer] Number of elements in the set
20
+ def size
21
+ command :scard, [key]
22
+ end
23
+
24
+ # Removes old values from the set and overrides them with new.
25
+ # @param enumerable [Enumerable] new values of the set
26
+ # @return [String] Redis response
27
+ def overwrite(enumerable)
28
+ redis.with do |conn|
29
+ conn.pipelined do
30
+ conn.del(key)
31
+ conn.sadd(key, enumerable)
32
+ end
33
+ end
34
+ end
35
+
36
+ # Returns array with set values
37
+ # @return [Array] values from the set
38
+ def to_a
39
+ command :smembers, [key]
40
+ end; alias to_ary to_a
41
+ end
42
+ end
43
+ end
@@ -55,36 +55,6 @@ module Conflow
55
55
  command :zrem, [key, value]
56
56
  end
57
57
 
58
- # Return elements with given score
59
- # @param score [Numeric, Hash]
60
- # - when Numeric, only elements with that exact score will be returned
61
- # - when Hash, elements within min..max range will be returned. See {https://redis.io/commands/zrange Redis docs}
62
- # @option score [String, Numeric] :min minimal score
63
- # @option score [String, Numeric] :max maximal score
64
- # @return [Array<String>] Elements with given score
65
- #
66
- # @example with specific score
67
- # field.where(score: 2) #=> ["first", "tie"]
68
- # @example with only min set
69
- # field.where(score: { min: 3 }) #=> ["last", "second"]
70
- # @example with both min and max set
71
- # field.where(score: { min: 3, max: "(10" }) #=> ["last"]
72
- def where(score:)
73
- command :zrangebyscore, [key, *prepare_score_bounds(score)]
74
- end
75
-
76
- # Removes elements of the set with given score and returns them.
77
- # See {where} for details on how to choose score.
78
- # @return [Array<String>] Elements with given score
79
- def delete_if(score:)
80
- score_bounds = prepare_score_bounds(score)
81
-
82
- transaction do |conn|
83
- conn.zrangebyscore key, *score_bounds
84
- conn.zremrangebyscore key, *score_bounds
85
- end[0]
86
- end
87
-
88
58
  # Returns first *n* elements of the sorted set
89
59
  # @param num [Integer] amount of elements to be returned. Defaults to 1.
90
60
  # @return [String, Array<String>] first *num* elements from the set
@@ -129,13 +99,6 @@ module Conflow
129
99
  result[index * 2 + 1] = value
130
100
  end
131
101
  end
132
-
133
- def prepare_score_bounds(score)
134
- case score
135
- when Hash then { min: "-inf", max: "+inf" }.merge(score).values
136
- when Numeric then [score, score]
137
- end
138
- end
139
102
  end
140
103
  end
141
104
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Conflow
4
4
  # Current version of the gem
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michał Begejowicz
@@ -158,7 +158,9 @@ files:
158
158
  - lib/conflow/redis/hash_field.rb
159
159
  - lib/conflow/redis/identifier.rb
160
160
  - lib/conflow/redis/model.rb
161
+ - lib/conflow/redis/queue_jobs_script.rb
161
162
  - lib/conflow/redis/script.rb
163
+ - lib/conflow/redis/set_field.rb
162
164
  - lib/conflow/redis/sorted_set_field.rb
163
165
  - lib/conflow/redis/value_field.rb
164
166
  - lib/conflow/version.rb