burstflow 0.2.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 435e3f0b3dba1c654d192d3f5f4cb1687bfe55e6c60f03e6fcb4643946603f0a
4
- data.tar.gz: e5390d5f58fb4a2e3503516f0c0744bce0cac68411edf56477e82d84a9f9ed87
3
+ metadata.gz: 4d68ef2fa6c3af3c066b65ae6213d3ac96216ea1eb66b849bb979759ed25c67b
4
+ data.tar.gz: b7868f4aeca3863996bba8f256fa72b79f244375bc6c82c301386ca88b7f66f8
5
5
  SHA512:
6
- metadata.gz: fcb37c6b28837184136c25333cfaa3bd403bb953b9aa8dc8fe5971d499013650664444b5415aa50e2530218a88d07107e5032f90cc3ef3cd5114bd8593752669
7
- data.tar.gz: ae739a9ab5a48b10512c3461e4720e54e564722192fca3aaf36123a48ed3a1c36de28483d19fd975659b2586637120be3dd32068f62e5bb2a294a1f6c8177e33
6
+ metadata.gz: 59e06e384d30bec993c880bce050f41dbc017cf1b0814855dca2d538ea270a79d9a9cb6a8f4601b47848f810cc768508e4a0c7aa5eade69717c194dc52b07526
7
+ data.tar.gz: dfba34ed82a10b598dfac4539be6f0b8d6880061eb61ac85780c9fcd84f5f498e72ce9228c3bb088d83bba32bcda75d4238b03e070ea64eb3b9d334a88718636
data/Gemfile CHANGED
@@ -7,8 +7,8 @@ end
7
7
 
8
8
  group :test do
9
9
  gem 'activesupport'
10
+ gem 'awesome_print'
10
11
  gem 'database_cleaner'
11
12
  gem 'otr-activerecord'
12
13
  gem 'pg', '~> 0.21.0'
13
- gem 'awesome_print'
14
14
  end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- burstflow (0.2.0)
4
+ burstflow (0.2.1)
5
5
  activejob
6
6
  activerecord
7
7
 
data/README.md CHANGED
@@ -2,25 +2,33 @@
2
2
 
3
3
  <!-- ## [![](http://i.imgur.com/ya8Wnyl.png)](https://chaps.io) proudly made by [Chaps](https://chaps.io) -->
4
4
 
5
- Burst is a parallel workflow runner using [ActiveRecord] and [ActiveJob](https://guides.rubyonrails.org/v4.2/active_job_basics.html) for scheduling and executing jobs.
5
+ [![Build Status](https://travis-ci.com/RnD-Soft/burstflow.svg?branch=master)](https://travis-ci.com/RnD-Soft/burstflow) [![Gem Version](https://badge.fury.io/rb/burstflow.svg)](https://badge.fury.io/rb/burstflow)
6
6
 
7
- This gem is higly inspired by [Gush](https://github.com/chaps-io/gush). But burst not tied to Reddis or Sidekiq.
7
+ Burstflow is a parallel workflow runner using [ActiveRecord] and [ActiveJob](https://guides.rubyonrails.org/v4.2/active_job_basics.html) for scheduling and executing jobs.
8
8
 
9
- The main feature of this runner is availablity to **suspend** job(and whole workflow) and **resume** it in future. For example if your job makes asynchronous request and will receive a response some time later. In this case, the job can send request and suspend until some external event resumes it eventually.
9
+ This gem is higly inspired by [Gush](https://github.com/chaps-io/gush). But not tied to Reddis or Sidekiq like Gush.
10
+
11
+ ActiveRecord used as persisted store during workflow execution.
12
+
13
+ Additional features:
14
+
15
+ * **susped** and **resume** job(and whole workflow). For example if your job makes asynchronous request and will receive a response some time later. In this case, the job can send request and suspend until some external event resumes it eventually.
16
+ * before/after callbacks on jobs level
17
+ * before/after callbacks on workflow level
18
+ * **Dynamic workflows**. Any job can produce another jobs while executing. This jobs has its parent as incomming jobs, and all parents outgoing jobs as own outgoings.
10
19
 
11
- Another difference from Gush is **Dynamic workflows**. Any job can produce another jobs while executing. This jobs has its parent as incomming jobs, and all parents outgoing jobs as own outgoings.
12
20
 
13
21
  ## Installation
14
22
 
15
23
  ### 1. Add `burst` to Gemfile
16
24
 
17
25
  ```ruby
18
- gem 'burst', '~> 0.1.0'
26
+ gem 'burstflow', '~> 0.2.0'
19
27
  ```
20
28
 
21
29
  ### 2. Run migrations
22
30
 
23
- Under development
31
+ ```rake burstflow:install:migrations```
24
32
 
25
33
  ## Example
26
34
 
@@ -29,7 +37,7 @@ Here is a complete example of a workflow you can create:
29
37
 
30
38
  ```ruby
31
39
  # app/workflows/sample_workflow.rb
32
- class SampleWorkflow < Burst::Workflow
40
+ class SampleWorkflow < Burstflow::Workflow
33
41
  configure do |url_to_fetch_from|
34
42
  run FetchJob1, params: { url: url_to_fetch_from }
35
43
  run FetchJob2, params: { some_flag: true, url: 'http://url.com' }
@@ -56,7 +64,7 @@ and this is how the graph will look like:
56
64
  Let's start with the simplest workflow possible, consisting of a single job:
57
65
 
58
66
  ```ruby
59
- class SimpleWorkflow < Burst::Workflow
67
+ class SimpleWorkflow < Burstflow::Workflow
60
68
  configure do
61
69
  run DownloadJob
62
70
  end
@@ -74,12 +82,12 @@ class SimpleWorkflow < Burst::Workflow
74
82
  end
75
83
  ```
76
84
 
77
- We just told Burst to execute `SaveJob` right after `DownloadJob` finishes **successfully**.
85
+ We just told Burstflow to execute `SaveJob` right after `DownloadJob` finishes **successfully**.
78
86
 
79
87
  But what if your job must have multiple dependencies? That's easy, just provide an array to the `after` attribute:
80
88
 
81
89
  ```ruby
82
- class SimpleWorkflow < Burst::Workflow
90
+ class SimpleWorkflow < Burstflow::Workflow
83
91
  configure do
84
92
  run FirstDownloadJob
85
93
  run SecondDownloadJob
@@ -98,7 +106,7 @@ With this simple syntax you can build any complex workflows you can imagine!
98
106
  `run` method also accepts `before:` attribute to define the opposite association. So we can write the same workflow as above, but like this:
99
107
 
100
108
  ```ruby
101
- class SimpleWorkflow < Burst::Workflow
109
+ class SimpleWorkflow < Burstflow::Workflow
102
110
  configure do
103
111
  run FirstDownloadJob, before: SaveJob
104
112
  run SecondDownloadJob, before: SaveJob
@@ -118,7 +126,7 @@ Workflows can accept any primitive arguments in their constructor, which then wi
118
126
  Let's assume we are writing a book publishing workflow which needs to know where the PDF of the book is and under what ISBN it will be released:
119
127
 
120
128
  ```ruby
121
- class PublishBookWorkflow < Burst::Workflow
129
+ class PublishBookWorkflow < Burstflow::Workflow
122
130
  configure do |url, isbn|
123
131
  run FetchBook, params: { url: url }
124
132
  run PublishBook, params: { book_isbn: isbn }, after: FetchBook
@@ -136,7 +144,7 @@ and that's basically it for defining workflows, see below on how to define jobs:
136
144
 
137
145
  ## Defining jobs
138
146
 
139
- The simplest job is a class inheriting from `Burst::Job` and responding to `perform` and `resume` method. Much like any other ActiveJob class.
147
+ The simplest job is a class inheriting from `Burstflow::Job` and responding to `perform` and `resume` method. Much like any other ActiveJob class.
140
148
 
141
149
  ```ruby
142
150
  class FetchBook < Burst::Job
@@ -189,14 +197,14 @@ flow = PublishBookWorkflow.build("http://url.com/book.pdf", "978-0470081204")
189
197
  flow.start!
190
198
  ```
191
199
 
192
- Now Burst will start processing jobs in the background using ActiveJob and your chosen backend.
200
+ Now Burstflow will start processing jobs in the background using ActiveJob and your chosen backend.
193
201
 
194
202
  ### 3. Monitor its progress:
195
203
 
196
204
  ```ruby
197
205
  flow.reload
198
206
  flow.status
199
- #=> :running|:finished|:failed
207
+ #=> :running|:finished|:failed|:suspended
200
208
  ```
201
209
 
202
210
  `reload` is needed to see the latest status, since workflows are updated asynchronously.
@@ -205,7 +213,7 @@ flow.status
205
213
 
206
214
  ### Pipelining
207
215
 
208
- Burst offers a useful tool to pass results of a job to its dependencies, so they can act differently.
216
+ Burstflow offers a useful tool to pass results of a job to its dependencies, so they can act differently.
209
217
 
210
218
  **Example:**
211
219
 
@@ -228,9 +236,9 @@ end
228
236
  Now, since `DownloadVideo` finished and its dependant job `EncodeVideo` started, we can access that payload inside it:
229
237
 
230
238
  ```ruby
231
- class EncodeVideo < Burst::Job
239
+ class EncodeVideo < Burstflow::Job
232
240
  def perform
233
- video_path = payloads.first[:output]
241
+ video_path = payloads.first[:value]
234
242
  end
235
243
  end
236
244
  ```
@@ -243,7 +251,7 @@ end
243
251
  {
244
252
  id: "DownloadVideo-41bfb730-b49f-42ac-a808-156327989294" # unique id of the ancestor job
245
253
  class: "DownloadVideo",
246
- output: "https://s3.amazonaws.com/somebucket/downloaded-file.mp4" #the payload returned by DownloadVideo job using `output()` method
254
+ value: "https://s3.amazonaws.com/somebucket/downloaded-file.mp4" #the payload returned by DownloadVideo job using `output()` method
247
255
  }
248
256
  ]
249
257
  ```
@@ -259,7 +267,7 @@ As an example, let's write a workflow which accepts an array of users and has to
259
267
 
260
268
  ```ruby
261
269
 
262
- class ParentJob < Burst::Job
270
+ class ParentJob < Burstflow::Job
263
271
  def perform
264
272
  configure do
265
273
  params[:user_ids].map do |user_id|
@@ -286,7 +294,7 @@ https://github.com/chaps-io/gush#contributors
286
294
 
287
295
  ## Contributing
288
296
 
289
- 1. Fork it ( https://github.com/RnD-Soft/burst/fork )
297
+ 1. Fork it ( https://github.com/RnD-Soft/burstflow/fork )
290
298
  2. Create your feature branch (`git checkout -b my-new-feature`)
291
299
  3. Commit your changes (`git commit -am 'Add some feature'`)
292
300
  4. Push to the branch (`git push origin my-new-feature`)
data/burstflow.gemspec CHANGED
@@ -1,7 +1,7 @@
1
- $:.push File.expand_path("lib", __dir__)
1
+ $:.push File.expand_path('lib', __dir__)
2
2
 
3
3
  # Maintain your gem's version:
4
- require "burstflow/version"
4
+ require 'burstflow/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'burstflow'
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.add_dependency 'activejob'
21
21
  spec.add_dependency 'activerecord'
22
-
22
+
23
23
  spec.add_development_dependency 'bundler'
24
+ spec.add_development_dependency 'generator_spec'
24
25
  spec.add_development_dependency 'rake'
25
26
  spec.add_development_dependency 'rspec'
26
- spec.add_development_dependency 'generator_spec'
27
27
  end
data/db/schema.rb CHANGED
@@ -10,20 +10,18 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 2018_01_01_000001) do
14
-
13
+ ActiveRecord::Schema.define(version: 20_180_101_000_001) do
15
14
  # These are extensions that must be enabled in order to support this database
16
- enable_extension "pgcrypto"
17
- enable_extension "plpgsql"
15
+ enable_extension 'pgcrypto'
16
+ enable_extension 'plpgsql'
18
17
 
19
- create_table "burstflow_workflows", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
20
- t.string "type"
21
- t.string "status"
22
- t.jsonb "flow", default: {}, null: false
23
- t.datetime "created_at", null: false
24
- t.datetime "updated_at", null: false
25
- t.index ["status"], name: "index_burstflow_workflows_on_status"
26
- t.index ["type"], name: "index_burstflow_workflows_on_type"
18
+ create_table 'burstflow_workflows', id: :uuid, default: -> { 'gen_random_uuid()' }, force: :cascade do |t|
19
+ t.string 'type'
20
+ t.string 'status'
21
+ t.jsonb 'flow', default: {}, null: false
22
+ t.datetime 'created_at', null: false
23
+ t.datetime 'updated_at', null: false
24
+ t.index ['status'], name: 'index_burstflow_workflows_on_status'
25
+ t.index ['type'], name: 'index_burstflow_workflows_on_type'
27
26
  end
28
-
29
27
  end
data/lib/burstflow/job.rb CHANGED
@@ -1,12 +1,13 @@
1
- require "active_support/rescuable"
2
- require "active_job/callbacks"
1
+ require 'active_support/rescuable'
2
+ require 'active_job/callbacks'
3
3
 
4
4
  class Burstflow::Job
5
- require "burstflow/job/exception"
6
- require "burstflow/job/model"
7
- require "burstflow/job/initialization"
8
- require "burstflow/job/state"
9
- require "burstflow/job/callbacks"
5
+
6
+ require 'burstflow/job/exception'
7
+ require 'burstflow/job/model'
8
+ require 'burstflow/job/initialization'
9
+ require 'burstflow/job/state'
10
+ require 'burstflow/job/callbacks'
10
11
 
11
12
  include Burstflow::Job::Model
12
13
  include Burstflow::Job::Initialization
@@ -30,28 +31,28 @@ class Burstflow::Job
30
31
  end
31
32
 
32
33
  # execute this code by ActiveJob. You may return Burstflow::Job::SUSPEND to suspend job, or call suspend method
33
- def perform
34
- end
34
+ def perform; end
35
35
 
36
36
  def perform_now
37
37
  run_callbacks :perform do
38
38
  perform
39
39
  end
40
- rescue => exception
40
+ rescue StandardError => exception
41
41
  rescue_with_handler(exception) || raise
42
42
  end
43
43
 
44
44
  # execute this code when resumes after suspending
45
45
  def resume(data)
46
- raise InternalError.new(self, "Can't perform resume: not resumed") if !resumed?
46
+ raise InternalError.new(self, "Can't perform resume: not resumed") unless resumed?
47
+
47
48
  set_output(data)
48
49
  end
49
50
 
50
- def resume_now data
51
+ def resume_now(data)
51
52
  run_callbacks :resume do
52
53
  resume(data)
53
54
  end
54
- rescue => exception
55
+ rescue StandardError => exception
55
56
  rescue_with_handler(exception) || raise
56
57
  end
57
58
 
@@ -62,13 +63,13 @@ class Burstflow::Job
62
63
 
63
64
  # mark execution as suspended
64
65
  def suspend
65
- raise InternalError.new(self, "Can't suspend: not running") if !running?
66
+ raise InternalError.new(self, "Can't suspend: not running") unless running?
67
+
66
68
  set_output(SUSPEND)
67
69
  end
68
70
 
69
- def configure *args, &block
71
+ def configure(*args, &block)
70
72
  workflow.with_lock do
71
-
72
73
  builder = Burstflow::Workflow::Builder.new(workflow, *args, &block)
73
74
  workflow.flow['jobs_config'] = builder.as_json
74
75
  workflow.save!
@@ -78,12 +79,12 @@ class Burstflow::Job
78
79
 
79
80
  def attributes
80
81
  {
81
- workflow_id: self.workflow_id,
82
- id: self.id,
83
- klass: self.klass,
82
+ workflow_id: workflow_id,
83
+ id: id,
84
+ klass: klass,
84
85
  params: params,
85
- incoming: self.incoming,
86
- outgoing: self.outgoing,
86
+ incoming: incoming,
87
+ outgoing: outgoing,
87
88
  output: output,
88
89
  started_at: started_at,
89
90
  enqueued_at: enqueued_at,
@@ -91,12 +92,8 @@ class Burstflow::Job
91
92
  failed_at: failed_at,
92
93
  suspended_at: suspended_at,
93
94
  resumed_at: resumed_at,
94
- failure: self.failure
95
+ failure: failure
95
96
  }
96
97
  end
97
98
 
98
-
99
-
100
-
101
-
102
99
  end
@@ -1,17 +1,15 @@
1
1
  module Burstflow::Job::Callbacks
2
+
2
3
  extend ActiveSupport::Concern
3
4
  include ActiveJob::Callbacks
4
5
 
5
6
  included do
6
-
7
7
  define_callbacks :suspend
8
8
  define_callbacks :resume
9
9
  define_callbacks :failure
10
-
11
10
  end
12
11
 
13
12
  class_methods do
14
-
15
13
  def before_suspend(*filters, &blk)
16
14
  set_callback(:suspend, :before, *filters, &blk)
17
15
  end
@@ -24,7 +22,6 @@ module Burstflow::Job::Callbacks
24
22
  set_callback(:suspend, :around, *filters, &blk)
25
23
  end
26
24
 
27
-
28
25
  def before_resume(*filters, &blk)
29
26
  set_callback(:resume, :before, *filters, &blk)
30
27
  end
@@ -37,19 +34,17 @@ module Burstflow::Job::Callbacks
37
34
  set_callback(:resume, :around, *filters, &blk)
38
35
  end
39
36
 
40
-
41
37
  def before_failure(*filters, &blk)
42
38
  set_callback(:failure, :before, *filters, &blk)
43
39
  end
44
-
40
+
45
41
  def after_failure(*filters, &blk)
46
42
  set_callback(:failure, :after, *filters, &blk)
47
43
  end
48
-
44
+
49
45
  def around_failure(*filters, &blk)
50
46
  set_callback(:failure, :around, *filters, &blk)
51
47
  end
52
-
53
48
  end
54
49
 
55
- end
50
+ end
@@ -1,8 +1,10 @@
1
1
  class Burstflow::Job::InternalError < ::RuntimeError
2
+
2
3
  attr_accessor :job
3
4
 
4
5
  def initialize(job, message)
5
6
  @job = job
6
7
  super(message)
7
8
  end
8
- end
9
+
10
+ end
@@ -1,8 +1,8 @@
1
1
  module Burstflow::Job::Initialization
2
- extend ActiveSupport::Concern
3
2
 
4
- included do
3
+ extend ActiveSupport::Concern
5
4
 
5
+ included do
6
6
  def initialize(workflow, job_config = {})
7
7
  @workflow = workflow
8
8
  assign_default_values(job_config)
@@ -21,15 +21,12 @@ module Burstflow::Job::Initialization
21
21
  def reload
22
22
  assign_default_values(@workflow.job_hash(self.id))
23
23
  end
24
-
25
24
  end
26
25
 
27
26
  class_methods do
28
-
29
27
  def from_hash(workflow, job_config)
30
28
  job_config[:klass].constantize.new(workflow, job_config)
31
29
  end
32
-
33
30
  end
34
31
 
35
- end
32
+ end
@@ -1,11 +1,12 @@
1
1
  module Burstflow::Job::State
2
- extend ActiveSupport::Concern
3
2
 
4
- included do
3
+ extend ActiveSupport::Concern
5
4
 
5
+ included do
6
6
  # mark job as enqueued when it is scheduled to queue
7
7
  def enqueue!
8
8
  raise Burstflow::Job::InternalError.new(self, "Can't enqueue: already enqueued") if enqueued?
9
+
9
10
  self.enqueued_at = current_timestamp
10
11
  self.started_at = nil
11
12
  self.finished_at = nil
@@ -17,22 +18,25 @@ module Burstflow::Job::State
17
18
  # mark job as started when it is start performing
18
19
  def start!
19
20
  raise Burstflow::Job::InternalError.new(self, "Can't start: already started") if started?
20
- raise Burstflow::Job::InternalError.new(self, "Can't start: not enqueued") if !enqueued?
21
+ raise Burstflow::Job::InternalError.new(self, "Can't start: not enqueued") unless enqueued?
22
+
21
23
  self.started_at = current_timestamp
22
24
  end
23
25
 
24
26
  # mark job as finished when it is finish performing
25
27
  def finish!
26
28
  raise Burstflow::Job::InternalError.new(self, "Can't finish: already finished") if finished?
27
- raise Burstflow::Job::InternalError.new(self, "Can't finish: not started") if !started?
29
+ raise Burstflow::Job::InternalError.new(self, "Can't finish: not started") unless started?
30
+
28
31
  self.finished_at = current_timestamp
29
32
  end
30
33
 
31
34
  # mark job as failed when it is failed
32
- def fail! msg_or_exception
33
- #raise Burstflow::Job::InternalError.new(self, "Can't fail: already failed") if failed?
34
- #raise Burstflow::Job::InternalError.new(self, Can't fail: already finished") if finished?
35
- raise Burstflow::Job::InternalError.new(self, "Can't fail: not started") if !started?
35
+ def fail!(msg_or_exception)
36
+ # raise Burstflow::Job::InternalError.new(self, "Can't fail: already failed") if failed?
37
+ # raise Burstflow::Job::InternalError.new(self, Can't fail: already finished") if finished?
38
+ raise Burstflow::Job::InternalError.new(self, "Can't fail: not started") unless started?
39
+
36
40
  self.finished_at = self.failed_at = current_timestamp
37
41
 
38
42
  context = {}
@@ -51,14 +55,16 @@ module Burstflow::Job::State
51
55
  # mark job as suspended
52
56
  def suspend!
53
57
  raise Burstflow::Job::InternalError.new(self, "Can't suspend: already suspended") if suspended?
54
- raise Burstflow::Job::InternalError.new(self, "Can't suspend: not runnig") if !running?
58
+ raise Burstflow::Job::InternalError.new(self, "Can't suspend: not runnig") unless running?
59
+
55
60
  self.suspended_at = current_timestamp
56
61
  end
57
62
 
58
63
  # mark job as resumed
59
64
  def resume!
60
65
  raise Burstflow::Job::InternalError.new(self, "Can't resume: already resumed") if resumed?
61
- raise Burstflow::Job::InternalError.new(self, "Can't resume: not suspended") if !suspended?
66
+ raise Burstflow::Job::InternalError.new(self, "Can't resume: not suspended") unless suspended?
67
+
62
68
  self.resumed_at = current_timestamp
63
69
  end
64
70
 
@@ -115,11 +121,9 @@ module Burstflow::Job::State
115
121
  def current_timestamp
116
122
  Time.now.to_i
117
123
  end
118
-
119
124
  end
120
125
 
121
126
  class_methods do
122
-
123
127
  end
124
128
 
125
- end
129
+ end