conflow 0.1.0 → 0.2.0

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