dirty_pipeline 0.5.0 → 0.6.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: 2d7bb36236434b64e26818af5a9ba0aa299035304fbbedc2dc305111b93b58a2
4
- data.tar.gz: efed49a5b15c73610497df6ca1d6e05018efa586fbd8ae3d11df5f6092414fe6
3
+ metadata.gz: e4eef96b779d46b3c749e665cf467f69577b8b1dc1bac3dd1e107be269619a3d
4
+ data.tar.gz: 0a42107025850c20d9d2f4bfbeb5fb4b3fd42610c9eae3aacc3ba80e3600ad6d
5
5
  SHA512:
6
- metadata.gz: c4324152fcc2d3e403ae93e23916fa2745820d199ed277cdbeef6d93b5d8dc726235a368de1c6e95fe6b124442c32e0b7e98fd531b74baef29b197c8f2bf702d
7
- data.tar.gz: ec4cf7fbf3e02ec46bd89381f7534bde8a023cfa07f5cf0c3bfc5f3b040be49e7e4457ecfec6677fd01e7f2832b3012b7a824cf199acc94db5f6cb323d1fab13
6
+ metadata.gz: 9fcb6bcf6655ab497f97fcf0f72d54a46b4a12407d5d2567d6027442e2652d4c901f0ef1a5031de184c0714821c27f466511c0db803e072082bd0d121968cbf3
7
+ data.tar.gz: '085965b95df37be6654977fb820b3d8937abd2097bc01a340119a8e99170206ce3670ffdcc2698723af7a484403fe76359b9b0e55548d6d11f1e2573c2ade88b'
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+ Gemfile.lock
@@ -21,7 +21,15 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ["lib"]
23
23
 
24
+ # temporary dependency
25
+ spec.add_runtime_dependency "sidekiq", "~> 5.0"
26
+ spec.add_runtime_dependency "redis", "~> 4.0"
27
+ spec.add_runtime_dependency "nanoid", "~> 0.2.0"
28
+
24
29
  spec.add_development_dependency "bundler", "~> 1.16"
25
30
  spec.add_development_dependency "rake", "~> 10.0"
26
31
  spec.add_development_dependency "rspec", "~> 3.0"
32
+ spec.add_development_dependency "dotenv", "~> 2.2"
33
+ spec.add_development_dependency "timecop", "~> 0.9"
34
+ spec.add_development_dependency "pry", "~> 0.11"
27
35
  end
@@ -1,4 +1,5 @@
1
1
  require "dirty_pipeline/version"
2
+ require "nanoid"
2
3
 
3
4
  module DirtyPipeline
4
5
  require_relative "dirty_pipeline/ext/camelcase.rb"
@@ -22,7 +22,8 @@ module DirtyPipeline
22
22
  using StringCamelcase
23
23
 
24
24
  def transition(name, from:, to:, action: nil, attempts: 1)
25
- action ||= const_get(name.to_s.camelcase)
25
+ action ||= const_get(name.to_s.camelcase(:upper)) rescue nil
26
+ action ||= method(name) if respond_to?(name)
26
27
  @transitions_map[name.to_s] = {
27
28
  action: action,
28
29
  from: Array(from).map(&:to_s),
@@ -55,7 +56,7 @@ module DirtyPipeline
55
56
  end
56
57
 
57
58
  def clear!
58
- storage.clear!
59
+ storage.reset!
59
60
  reset!
60
61
  end
61
62
 
@@ -66,7 +67,7 @@ module DirtyPipeline
66
67
 
67
68
  def call
68
69
  return self if (serialized_event = railway.next).nil?
69
- execute(load_event(serialized_event))
70
+ execute(load_event(serialized_event), tx_method: :call)
70
71
  end
71
72
  alias :call_next :call
72
73
 
@@ -78,7 +79,7 @@ module DirtyPipeline
78
79
 
79
80
  def retry
80
81
  return unless (event = load_event(railway.queue.processing_event))
81
- execute(event, :retry)
82
+ execute(event, tx_id: :retry)
82
83
  end
83
84
 
84
85
  def schedule_cleanup
@@ -116,8 +117,8 @@ module DirtyPipeline
116
117
 
117
118
  private
118
119
 
119
- def execute(event, type = :call)
120
- transaction(event).public_send(type) do |destination, action, *args|
120
+ def execute(event, tx_method:)
121
+ transaction(event).public_send(tx_method) do |destination, action, *args|
121
122
  state_changes = process_action(action, event, *args)
122
123
  next if status.failure?
123
124
  Success(event, state_changes, destination)
@@ -134,9 +135,7 @@ module DirtyPipeline
134
135
  def process_action(action, event, *args)
135
136
  return catch(:success) do
136
137
  return if interupt_on_error(event) do
137
- return if interupt_on_abort(event) do
138
- throw :success, run_operation(action, event, *args); nil
139
- end
138
+ throw :success, run_operation(action, event, *args)
140
139
  end
141
140
  nil
142
141
  end
@@ -151,14 +150,10 @@ module DirtyPipeline
151
150
  end
152
151
 
153
152
  def interupt_on_error(event)
154
- return if (fail_cause = catch(:fail_with_error) { yield; nil }).nil?
153
+ return unless (fail_cause = catch(:fail_operation) { yield; nil })
155
154
  Failure(event, fail_cause)
156
155
  end
157
156
 
158
- def interupt_on_abort(event)
159
- Abort(event) if catch(:abort) { yield; nil }
160
- end
161
-
162
157
  def find_subject_args
163
158
  subject.id
164
159
  end
@@ -172,20 +167,18 @@ module DirtyPipeline
172
167
  end
173
168
 
174
169
  def transaction(event)
175
- ::DirtyPipeline::Transaction.new(self, railway.queue, event)
170
+ Transaction.new(self, event)
176
171
  end
177
172
 
178
173
  def Failure(event, cause)
179
- event.failure!
180
- railway.switch_to(:undo)
181
- @status = Status.failure(cause, tag: :error)
182
- throw :abort_transaction, true
183
- end
184
-
185
- def Abort(event)
186
- event.failure!
187
174
  railway.switch_to(:undo)
188
- @status = Status.failure(subject, tag: :aborted)
175
+ if cause.eql?(:abort)
176
+ event.abort!
177
+ @status = Status.failure(subject, tag: :aborted)
178
+ else
179
+ event.failure!
180
+ @status = Status.failure(cause, tag: :error)
181
+ end
189
182
  throw :abort_transaction, true
190
183
  end
191
184
 
@@ -5,8 +5,9 @@ module DirtyPipeline
5
5
  NEW = "new".freeze
6
6
  START = "started".freeze
7
7
  FAILURE = "failed".freeze
8
+ ABORT = "aborted".freeze
8
9
  RETRY = "retry".freeze
9
- SUCCESS = "success".freeze
10
+ SUCCESS = "succeeded".freeze
10
11
 
11
12
  def self.create(transition, *args, tx_id:)
12
13
  new(
@@ -19,33 +20,20 @@ module DirtyPipeline
19
20
  )
20
21
  end
21
22
 
22
- def self.load(json)
23
- return unless json
24
- new(JSON.load(json))
25
- end
26
-
27
- def self.dump(event)
28
- JSON.dump(event.to_h)
29
- end
30
-
31
- def dump
32
- self.class.dump(self)
33
- end
34
-
35
23
  attr_reader :id, :tx_id, :error, :data
36
24
  def initialize(options = {}, data: nil, error: nil)
37
25
  unless options.empty?
38
26
  options_hash = options.to_h
39
27
  data ||= options_hash["data"]
40
28
  error ||= options_hash["error"]
41
- transition = options_hash["transition"]
42
- args = options_hash["args"]
43
29
  end
44
30
 
45
31
  data_hash = data.to_h
46
32
 
47
- @tx_id = data_hash.fetch("transaction_uuid")
48
- @id = data_hash.fetch("uuid")
33
+ @tx_id = data_hash.fetch("transaction_uuid")
34
+ @id = data_hash.fetch("uuid")
35
+ transition = data_hash.fetch("transition")
36
+ args = data_hash.fetch("args").to_a
49
37
  @data = {
50
38
  "uuid" => @id,
51
39
  "transaction_uuid" => @tx_id,
@@ -67,7 +55,7 @@ module DirtyPipeline
67
55
  define_method("#{method_name}") { @data[method_name] }
68
56
  end
69
57
 
70
- %w(new start retry failure).each do |method_name|
58
+ %w(new start retry failure success abort).each do |method_name|
71
59
  define_method("#{method_name}?") do
72
60
  @data["status"] == self.class.const_get(method_name.upcase)
73
61
  end
@@ -81,7 +69,7 @@ module DirtyPipeline
81
69
  @error = {
82
70
  "exception" => exception.class.to_s,
83
71
  "exception_message" => exception.message,
84
- "created_at" => Time.current,
72
+ "created_at" => Time.now,
85
73
  }
86
74
  failure!
87
75
  end
@@ -90,7 +78,7 @@ module DirtyPipeline
90
78
  @data["attempts_count"].to_i
91
79
  end
92
80
 
93
- def attempt_retry
81
+ def attempt_retry!
94
82
  @data["updated_at"] = Time.now
95
83
  @data["attempts_count"] = attempts_count + 1
96
84
  end
@@ -1,8 +1,7 @@
1
1
  module DirtyPipeline
2
2
  class Queue
3
- attr_reader :root
4
- def initialize(operation, subject, transaction_id)
5
- @root = "dirty-pipeline-queue:#{subject.class}:#{subject.id}:" \
3
+ def initialize(operation, subject_class, subject_id, transaction_id)
4
+ @root = "dirty-pipeline-queue:#{subject_class}:#{subject_id}:" \
6
5
  "op_#{operation}:txid_#{transaction_id}"
7
6
  end
8
7
 
@@ -14,37 +13,34 @@ module DirtyPipeline
14
13
  end
15
14
 
16
15
  def to_a
17
- DirtyPipeline.with_redis { |r| r.lrange(events_queue_key, 0, -1) }
16
+ DirtyPipeline.with_redis do |r|
17
+ r.lrange(events_queue_key, 0, -1).map! do |packed_event|
18
+ unpack(packed_event)
19
+ end
20
+ end
18
21
  end
19
22
 
20
23
  def push(event)
21
24
  DirtyPipeline.with_redis { |r| r.rpush(events_queue_key, pack(event)) }
25
+ self
22
26
  end
23
27
  alias :<< :push
24
28
 
25
29
  def unshift(event)
26
30
  DirtyPipeline.with_redis { |r| r.lpush(events_queue_key, pack(event)) }
31
+ self
27
32
  end
28
33
 
29
- def dequeue
34
+ def pop
30
35
  DirtyPipeline.with_redis do |r|
31
36
  data = r.lpop(events_queue_key)
32
37
  data.nil? ? r.del(active_event_key) : r.set(active_event_key, data)
33
- return unpack(data)
34
- end
35
- end
36
- alias :pop :dequeue
37
-
38
- def event_in_progress?(event = nil)
39
- if event.nil?
40
- !processing_event.nil?
41
- else
42
- processing_event.id == event.id
38
+ unpack(data)
43
39
  end
44
40
  end
45
41
 
46
42
  def processing_event
47
- DirtyPipeline.with_redis { |r| unpack r.get(active_event_key) }
43
+ DirtyPipeline.with_redis { |r| unpack(r.get(active_event_key)) }
48
44
  end
49
45
 
50
46
  private
@@ -72,11 +68,11 @@ module DirtyPipeline
72
68
  end
73
69
 
74
70
  def events_queue_key
75
- "#{root}:events"
71
+ "#{@root}:events"
76
72
  end
77
73
 
78
74
  def active_event_key
79
- "#{root}:active"
75
+ "#{@root}:active"
80
76
  end
81
77
  end
82
78
  end
@@ -1,37 +1,45 @@
1
1
  module DirtyPipeline
2
2
  class Railway
3
- OPERATIONS = %w(call undo finalize)
3
+ DEFAULT_OPERATIONS = %w(call undo finalize)
4
4
 
5
5
  def initialize(subject, transaction_id)
6
6
  @tx_id = transaction_id
7
- @root = "dirty-pipeline-rail:#{subject.class}:#{subject.id}:" \
8
- ":txid_#{transaction_id}"
7
+ @subject_class = subject.class.to_s
8
+ @subject_id = subject.id.to_s
9
+ @root = "dirty-pipeline-rail:#{subject.class}:#{subject.id}:"
9
10
  @queues = Hash[
10
- OPERATIONS.map do |operation|
11
- [operation, Queue.new(operation, subject, transaction_id)]
11
+ DEFAULT_OPERATIONS.map do |operation|
12
+ [operation, create_queue(operation)]
12
13
  end
13
14
  ]
14
15
  end
15
16
 
16
17
  def clear!
17
18
  @queues.values.each(&:clear!)
18
- DirtyPipeline.with_redis { |r| r.del(active_operation_key) }
19
+ DirtyPipeline.with_redis do |r|
20
+ r.multi do |mr|
21
+ mr.del(active_operation_key)
22
+ mr.del(active_transaction_key)
23
+ end
24
+ end
19
25
  end
20
26
 
21
27
  def next
22
28
  return if other_transaction_in_progress?
23
- start_transaction! if running_transaction.nil?
29
+ start_transaction! unless running_transaction
24
30
 
25
31
  queue.pop.tap { |event| finish_transaction! if event.nil? }
26
32
  end
27
33
 
28
- def queue(name = active)
29
- @queues[name.to_s]
34
+ def queue(operation_name = active)
35
+ @queues.fetch(operation_name.to_s) do
36
+ @queues.store(operation_name, create_queue(operation_name))
37
+ end
30
38
  end
31
39
  alias :[] :queue
32
40
 
33
41
  def switch_to(name)
34
- raise ArgumentError unless OPERATIONS.include?(name.to_s)
42
+ raise ArgumentError unless DEFAULT_OPERATIONS.include?(name.to_s)
35
43
  return if name.to_s == active
36
44
  DirtyPipeline.with_redis { |r| r.set(active_operation_key, name) }
37
45
  end
@@ -41,8 +49,16 @@ module DirtyPipeline
41
49
  end
42
50
  alias :operation :active
43
51
 
52
+ def running_transaction
53
+ DirtyPipeline.with_redis { |r| r.get(active_transaction_key) }
54
+ end
55
+
44
56
  private
45
57
 
58
+ def create_queue(operation_name)
59
+ Queue.new(operation_name, @subject_class, @subject_id, @tx_id)
60
+ end
61
+
46
62
  def active_transaction_key
47
63
  "#{@root}:active_transaction"
48
64
  end
@@ -52,20 +68,14 @@ module DirtyPipeline
52
68
  end
53
69
 
54
70
  def start_transaction!
55
- switch_to(OPERATIONS.first)
71
+ switch_to(DEFAULT_OPERATIONS.first) unless active
56
72
  DirtyPipeline.with_redis { |r| r.set(active_transaction_key, @tx_id) }
57
73
  end
58
74
 
59
75
  def finish_transaction!
60
- return unless running_transaction == @tx_id
61
- DirtyPipeline.with_redis { |r| r.del(active_transaction_key) }
62
- @queues.values.each(&:clear!)
76
+ clear! if running_transaction == @tx_id
63
77
  end
64
78
 
65
- def running_transaction
66
- DirtyPipeline.with_redis { |r| r.get(active_transaction_key) }
67
- end
68
-
69
79
  def other_transaction_in_progress?
70
80
  return false if running_transaction.nil?
71
81
  running_transaction != @tx_id
@@ -1,68 +1,45 @@
1
1
  module DirtyPipeline
2
+ # Storage structure
3
+ # {
4
+ # status: :errored,
5
+ # state: {
6
+ # field: "value",
7
+ # },
8
+ # errors: {
9
+ # "<event_id>": {
10
+ # error: "RuPost::API::Error",
11
+ # error_message: "Timeout error",
12
+ # created_at: 2018-01-01T13:22Z
13
+ # },
14
+ # },
15
+ # events: {
16
+ # <event_id>: {
17
+ # transition: "Create",
18
+ # args: ...,
19
+ # changes: ...,
20
+ # created_at: ...,
21
+ # updated_at: ...,
22
+ # attempts_count: 2,
23
+ # },
24
+ # <event_id>: {...},
25
+ # }
26
+ # }
2
27
  class Storage
3
- SUCCESS_STATUS = "success".freeze
4
- FAILURE_STATUS = "failure".freeze
5
- RETRY_STATUS = "retry".freeze
6
- PROCESSING_STATUS = "processing".freeze
7
28
  class InvalidPipelineStorage < StandardError; end
8
29
 
9
- attr_reader :subject, :field, :transactions_queue
10
- attr_accessor :store
30
+ attr_reader :subject, :field, :store
11
31
  alias :to_h :store
12
32
  def initialize(subject, field)
13
33
  @subject = subject
14
34
  @field = field
15
- init_store(field)
35
+ @store = subject.send(@field).to_h
36
+ reset if @store.empty?
37
+ raise InvalidPipelineStorage, store unless valid_store?
16
38
  end
17
39
 
18
- def init_store(store_field)
19
- self.store = subject.send(store_field).to_h
20
- clear if store.empty?
21
- return if valid_store?
22
- raise InvalidPipelineStorage, store
23
- end
24
-
25
- def valid_store?
26
- (store.keys & %w(status events errors state)).size.eql?(4)
27
- end
28
-
29
- # PG JSONB column
30
- # {
31
- # status: :errored,
32
- # state: {
33
- # field: "value",
34
- # },
35
- # errors: {
36
- # "<event_id>": {
37
- # error: "RuPost::API::Error",
38
- # error_message: "Timeout error",
39
- # created_at: 2018-01-01T13:22Z
40
- # },
41
- # },
42
- # events: {
43
- # <event_id>: {
44
- # action: Init,
45
- # input: ...,
46
- # created_at: ...,
47
- # updated_at: ...,
48
- # attempts_count: 2,
49
- # },
50
- # <event_id>: {...},
51
- # }
52
- # }
53
- def clear
54
- self.store = subject.send(
55
- "#{field}=",
56
- "status" => nil,
57
- "state" => {},
58
- "events" => {},
59
- "errors" => {}
60
- )
61
- end
62
-
63
- def clear!
64
- clear
65
- subject.update_attributes!(field => store)
40
+ def reset!
41
+ reset
42
+ save!
66
43
  end
67
44
 
68
45
  def status
@@ -71,21 +48,38 @@ module DirtyPipeline
71
48
 
72
49
  def commit!(event)
73
50
  store["status"] = event.destination if event.destination
74
- require'pry';binding.pry unless event.changes.respond_to?(:to_h)
75
51
  store["state"].merge!(event.changes) unless event.changes.to_h.empty?
76
52
  store["errors"][event.id] = event.error unless event.error.to_h.empty?
77
53
  store["events"][event.id] = event.data unless event.data.to_h.empty?
78
- subject.assign_attributes(field => store)
79
- subject.save!
54
+ save!
80
55
  end
81
56
 
82
- def processing_event
83
- find_event(transactions_queue.processing_event.id)
57
+ def find_event(event_id)
58
+ return unless (found_event = store.dig("events", event_id))
59
+ Event.new(data: found_event, error: store.dig("errors", event_id))
84
60
  end
85
61
 
86
- def find_event(event_id)
87
- return unless (found_event = store["events"][event_id])
88
- Event.new(data: found_event, error: store["errors"][event_id])
62
+ private
63
+
64
+ def valid_store?
65
+ (store.keys & %w(status events errors state)).size.eql?(4)
66
+ end
67
+
68
+ def save!
69
+ subject.send("#{field}=", store)
70
+ subject.save!
71
+ end
72
+
73
+ def reset
74
+ @store = subject.send(
75
+ "#{field}=",
76
+ {
77
+ "status" => nil,
78
+ "state" => {},
79
+ "events" => {},
80
+ "errors" => {}
81
+ }
82
+ )
89
83
  end
90
84
  end
91
85
  end
@@ -1,56 +1,52 @@
1
1
  module DirtyPipeline
2
2
  class Transaction
3
- attr_reader :locker, :storage, :subject, :pipeline, :queue, :event
4
- def initialize(pipeline, queue, event)
3
+ attr_reader :locker, :storage, :subject, :pipeline, :event
4
+ def initialize(pipeline, event)
5
5
  @pipeline = pipeline
6
6
  @subject = pipeline.subject
7
7
  @storage = pipeline.storage
8
- @queue = queue
9
8
  @event = event
10
9
  end
11
10
 
12
- def retry
13
- event.attempt_retry!
14
- pipeline.schedule_cleanup
15
-
16
- with_transaction { |*targs| yield(*targs) }
17
- end
18
-
19
11
  def call
20
- # return unless queue.event_in_progress?(event)
21
-
22
12
  event.start!
23
- pipeline.schedule_cleanup
13
+ with_transaction { |*targs| yield(*targs) }
14
+ end
24
15
 
16
+ def retry
17
+ event.attempt_retry!
25
18
  with_transaction { |*targs| yield(*targs) }
26
19
  end
27
20
 
28
21
  private
29
22
 
30
23
  def with_transaction
24
+ pipeline.schedule_cleanup
25
+
31
26
  destination, action, max_attempts_count =
32
27
  pipeline.find_transition(event.transition)
33
28
  .values_at(:to, :action, :attempts)
34
29
 
35
30
  storage.commit!(event)
36
31
 
37
- # status.action_pool.unshift(action)
38
32
  subject.transaction(requires_new: true) do
39
- raise ActiveRecord::Rollback if catch(:abort_transaction) do
40
- yield(destination, action, *event.args); nil
41
- end
33
+ with_abort_handling { yield(destination, action, *event.args) }
42
34
  end
43
35
  rescue => exception
44
36
  event.link_exception(exception)
45
37
  if max_attempts_count.to_i > event.attempts_count
46
38
  event.retry!
47
39
  pipeline.schedule_retry
48
- else
49
- pipeline.schedule_cleanup
50
40
  end
51
41
  raise
52
42
  ensure
53
43
  storage.commit!(event)
54
44
  end
45
+
46
+ def with_abort_handling
47
+ return unless catch(:abort_transaction) { yield; nil }
48
+ event.abort! unless event.abort?
49
+ raise ActiveRecord::Rollback
50
+ end
55
51
  end
56
52
  end
@@ -1,11 +1,11 @@
1
1
  module DirtyPipeline
2
2
  class Transition
3
3
  def Abort()
4
- throw :abort, true
4
+ throw :fail_operation, :abort
5
5
  end
6
6
 
7
7
  def Error(error)
8
- throw :fail_with_error, error
8
+ throw :fail_operation, error
9
9
  end
10
10
 
11
11
  def Success(changes = nil)
@@ -15,8 +15,8 @@ module DirtyPipeline
15
15
  def self.finalize(*args, **kwargs)
16
16
  event, pipeline, *args = args
17
17
  instance = new(event, *args, **kwargs)
18
- pipeline.railway.switch_to(:call)
19
18
  return unless instance.respond_to?(:finalize)
19
+ pipeline.railway.switch_to(:call)
20
20
  instance.finalize(pipeline.subject)
21
21
  end
22
22
 
@@ -31,8 +31,10 @@ module DirtyPipeline
31
31
  event, pipeline, *args = args
32
32
  instance = new(event, *args, **kwargs)
33
33
  pipeline.railway[:undo] << event if instance.respond_to?(:undo)
34
- pipeline.railway[:finalize] << event if instance.respond_to?(:finalize)
35
- pipeline.railway.switch_to(:finalize) if instance.respond_to?(:finalize)
34
+ if instance.respond_to?(:finalize)
35
+ pipeline.railway[:finalize] << event
36
+ pipeline.railway.switch_to(:finalize)
37
+ end
36
38
  new(event, *args, **kwargs).call(pipeline.subject)
37
39
  end
38
40
 
@@ -1,3 +1,3 @@
1
1
  module DirtyPipeline
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
metadata CHANGED
@@ -1,15 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dirty_pipeline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Dolganov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-02 00:00:00.000000000 Z
11
+ date: 2018-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sidekiq
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nanoid
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.2.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.2.0
13
55
  - !ruby/object:Gem::Dependency
14
56
  name: bundler
15
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +94,48 @@ dependencies:
52
94
  - - "~>"
53
95
  - !ruby/object:Gem::Version
54
96
  version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: dotenv
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.2'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: timecop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.9'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.9'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.11'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.11'
55
139
  description: Simple state machine designed for non-pure transitions. E.g. for wizard-like
56
140
  systems with a lot of external API calls.
57
141
  email: