taskinator 0.0.5 → 0.0.7

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
  SHA1:
3
- metadata.gz: 96866a7042bc2b7082aaeead94c5c90f00448f0b
4
- data.tar.gz: 42636386ce754b64ce167fe858a59e8c2a0a8286
3
+ metadata.gz: bb55f92dd0ef5d48b4fb0d1f3d82093c59fdca7c
4
+ data.tar.gz: c87595e40a584e0913571dbc50176ae8236010d2
5
5
  SHA512:
6
- metadata.gz: 061588463242d2d1d11827a339dc17a7903b07631b7194a7ce43639e1d3c4c1a96ec7a24cd62a79e7bb8fa0354a59539ae7fc026377acc22be801f816b6bd3e0
7
- data.tar.gz: 19da642b98496489815a1f2e741cda7e8f6fc9885c2becf896e09be7f48b7cdf0d85a5c0c8fb9e9bcd2aac26ecfed2fdcb7bdbb4d114d52faff356ca56eddf73
6
+ metadata.gz: 71858572f752ee6a0572607fb70607e36a49df6334e83c5c1a477675b70f41f75cbefc04e23cefb802bac39adc1207d947d69d580954d51e9992804a7ef2aa95
7
+ data.tar.gz: a405c8209923653109f732d03601f2747cf35642ae452b44598ad78487c3c639a3ae91ba065c887116e7b47c7e512374104994a8b8c9a9f7b812e47530af5b96
data/Gemfile.lock CHANGED
@@ -8,7 +8,7 @@ GIT
8
8
  PATH
9
9
  remote: .
10
10
  specs:
11
- taskinator (0.0.5)
11
+ taskinator (0.0.7)
12
12
  connection_pool (>= 2.0.0)
13
13
  json (>= 1.8.1)
14
14
  redis (>= 3.0.6)
@@ -38,18 +38,18 @@ GEM
38
38
  term-ansicolor
39
39
  thor
40
40
  debugger-linecache (1.2.0)
41
- delayed_job (4.0.3)
41
+ delayed_job (4.0.4)
42
42
  activesupport (>= 3.0, < 4.2)
43
43
  diff-lcs (1.2.5)
44
44
  docile (1.1.5)
45
45
  i18n (0.6.11)
46
46
  json (1.8.1)
47
47
  method_source (0.8.2)
48
- mime-types (2.3)
49
- minitest (5.4.1)
48
+ mime-types (2.4.2)
49
+ minitest (5.4.2)
50
50
  mono_logger (1.1.0)
51
51
  multi_json (1.10.1)
52
- netrc (0.7.7)
52
+ netrc (0.7.8)
53
53
  pry (0.9.12.6)
54
54
  coderay (~> 1.0)
55
55
  method_source (~> 0.8)
@@ -82,23 +82,23 @@ GEM
82
82
  rspec-core (~> 3.1.0)
83
83
  rspec-expectations (~> 3.1.0)
84
84
  rspec-mocks (~> 3.1.0)
85
- rspec-core (3.1.3)
85
+ rspec-core (3.1.7)
86
86
  rspec-support (~> 3.1.0)
87
- rspec-expectations (3.1.1)
87
+ rspec-expectations (3.1.2)
88
88
  diff-lcs (>= 1.2.0, < 2.0)
89
89
  rspec-support (~> 3.1.0)
90
- rspec-mocks (3.1.0)
90
+ rspec-mocks (3.1.3)
91
91
  rspec-support (~> 3.1.0)
92
- rspec-support (3.1.0)
92
+ rspec-support (3.1.2)
93
93
  sidekiq (3.2.5)
94
94
  celluloid (= 0.15.2)
95
95
  connection_pool (>= 2.0.0)
96
96
  json
97
97
  redis (>= 3.0.6)
98
98
  redis-namespace (>= 1.3.1)
99
- simplecov (0.9.0)
99
+ simplecov (0.9.1)
100
100
  docile (~> 1.1.0)
101
- multi_json
101
+ multi_json (~> 1.0)
102
102
  simplecov-html (~> 0.8.0)
103
103
  simplecov-html (0.8.0)
104
104
  sinatra (1.4.5)
@@ -112,7 +112,7 @@ GEM
112
112
  thread_safe (0.3.4)
113
113
  tilt (1.4.1)
114
114
  timers (1.1.0)
115
- tins (1.3.2)
115
+ tins (1.3.3)
116
116
  tzinfo (1.2.2)
117
117
  thread_safe (~> 0.1)
118
118
  vegas (0.1.11)
data/README.md CHANGED
@@ -69,8 +69,28 @@ module MyProcess
69
69
  end
70
70
  ```
71
71
 
72
- Specify the tasks with their corresponding implementation methods, that make up the process, using the `task` method and providing
73
- a `name` and `method` to execute for the task.
72
+ The `define_process` method optionally takes the list of expected arguments which are used to validate the
73
+ arguments supplied when creating a new process. These should be specified with symbols.
74
+
75
+ ```ruby
76
+ module MyProcess
77
+ extend Taskinator::Definition
78
+
79
+ # defines a process
80
+ define_process :date, :options do
81
+ # ...
82
+ end
83
+ end
84
+
85
+ # when creating a process, 2 arguments are expected
86
+ process = MyProcess.create_process Date.today, :option_1 => true
87
+ ```
88
+
89
+ NOTE: The current implementation performs a naive check on the count of arguments, but this will be
90
+ improved in subsequent versions.
91
+
92
+ Next, specify the tasks with their corresponding implementation methods, that make up the process,
93
+ using the `task` method and providing the `method` to execute for the task.
74
94
 
75
95
  ```ruby
76
96
  module MyProcess
@@ -124,7 +144,35 @@ module MyProcess
124
144
  end
125
145
  ```
126
146
 
127
- You can define data driven tasks using the `for_each` method, which takes an iterator method as an argument.
147
+ It is likely that you have already have worker classes for one of the queueing libraries, such as resque or delayed_job, and wish to
148
+ reuse them for executing them in the sequence defined by the process definition.
149
+
150
+ You define a `job` step, providing the class of the worker, and them taskinator will execute that worker as part of the process definition.
151
+ The `job` step will be queued and executed on the configured queue for `delayed_job`, or that of the worker for `resque` and `sidekiq`.
152
+
153
+ ```ruby
154
+ # E.g. A resque worker
155
+ class DoSomeWork
156
+ queue :high_priority
157
+
158
+ def self.perform(arg1, arg2)
159
+ # code to do the work
160
+ end
161
+ end
162
+
163
+ module MyProcess
164
+ extend Taskinator::Definition
165
+
166
+ # when creating the process, supply the same arguments
167
+ # that the DoSomeWork worker expects
168
+
169
+ define_process do
170
+ job DoSomeWork
171
+ end
172
+ end
173
+ ```
174
+
175
+ You can also define data driven tasks using the `for_each` method, which takes an iterator method name as an argument.
128
176
  The iterator method yields the items to produce a parameterized task for that item. Notice that the task method
129
177
  takes a parameter in this case, which will be the item provided by the iterator.
130
178
 
@@ -149,6 +197,67 @@ module MyProcess
149
197
  end
150
198
  ```
151
199
 
200
+ It is possible to branch the process logic based on the options hash passed in when creating a process.
201
+ The `options?` method takes the options key as an argument and calls the supplied block if the option
202
+ is present and it's value is truthy.
203
+
204
+ ```ruby
205
+ module MyProcess
206
+ extend Taskinator::Definition
207
+
208
+ define_process do
209
+
210
+ option?(:some_setting) do
211
+ task :prerequisite_step
212
+ end
213
+
214
+ task :work_step
215
+
216
+ end
217
+
218
+ def prerequisite_step
219
+ # ...
220
+ end
221
+
222
+ def work_step
223
+ # ...
224
+ end
225
+
226
+ end
227
+
228
+ # now when creating the process, the `:some_setting` option can be used to branch the logic
229
+ process1 = MyProcess.create_process :some_setting => true
230
+ process1.tasks.count #=> 2
231
+
232
+ process2 = MyProcess.create_process
233
+ process2.tasks.count #=> 1
234
+ ```
235
+
236
+ In addition, it is possible to transform the arguments used by a task or job, by including a `transform` step in the definition.
237
+ Similarly to the `for_each` method, `transform` takes a method name as an argument. The transformer method must yield the new arguments as required.
238
+
239
+ ```ruby
240
+ module MyProcess
241
+ extend Taskinator::Definition
242
+
243
+ # this process is created with a hash argument
244
+
245
+ define_process do
246
+ transform :convert_args do
247
+ task :work_step
248
+ end
249
+ end
250
+
251
+ def convert_args(options)
252
+ yield *[options[:date_from], options[:date_to]]
253
+ end
254
+
255
+ def work_step(date_from, date_to)
256
+ # TODO: supply implementation
257
+ end
258
+ end
259
+ ```
260
+
152
261
  Processes can be composed of other processes too:
153
262
 
154
263
  ```ruby
@@ -207,6 +316,33 @@ In this example, the `work_step_begin` is executed, followed by the `work_step_a
207
316
  the sub process `MySubProcess` is created and executed, followed by the `work_step_one_by_one` tasks which are executed sequentially and
208
317
  finally the `work_step_end` is executed.
209
318
 
319
+ It is also possible to embed conditional logic within the process definition stages in order to produce steps based on the required logic.
320
+ All builder methods are available within the scope of the `define_process` block. These methods include `args` and `options`
321
+ which are passed into the `create_process` method of the definition.
322
+
323
+ E.g.
324
+
325
+ ```ruby
326
+ module MyProcess
327
+ extend Taskinator::Definition
328
+
329
+ define_process do
330
+ task :task_1
331
+ task :task_2
332
+ task :task_3 if args[3] == 1
333
+ task :send_notification if options[:send_notification]
334
+ end
335
+
336
+ # "task" methods are omitted for brevity
337
+
338
+ end
339
+
340
+ # when creating this proces, you supply to option when calling `create_process`
341
+ # in this example, 'args' will be an array [1,2,3] and options will be a Hash {:send_notification => true}
342
+ MyProcess.create_process(1, 2, 3, :send_notification => true)
343
+
344
+ ```
345
+
210
346
  ### Execution
211
347
 
212
348
  A process is executed by calling the generated `create_process` method on your "process" module.
@@ -216,9 +352,201 @@ process = MyProcess.create_process
216
352
  process.enqueue!
217
353
  ```
218
354
 
355
+ Or, to start immediately, call the `start!` method.
356
+
357
+ ```ruby
358
+ process = MyProcess.create_process
359
+ process.start!
360
+ ```
361
+
219
362
  #### Arguments
220
363
 
221
- _TBD_
364
+ Argument handling for defining and executing process definitions is where things can get trickey.
365
+ _This may be something that gets refactored down the line_.
366
+
367
+ To best understand how arguments are handled, you need to break it down into 3 phases. Namely:
368
+
369
+ * Definition,
370
+ * Creation and
371
+ * Execution
372
+
373
+ Firstly, a process definition is declarative in that the `define_process` and a mix of `sequential`, `concurrent`, `for_each`,
374
+ `task` and `job` directives provide the way to specify the sequencing of the steps for the process.
375
+ Taskinator will interprete this definition and execute each step in the desired sequence or concurrency.
376
+
377
+ Consider the following process definition:
378
+
379
+ ```ruby
380
+ module MySimpleProcess
381
+ extend Taskinator::Definition
382
+
383
+ # definition
384
+
385
+ define_process do
386
+ task :work_step_1
387
+ task :work_step_2
388
+
389
+ for_each :additional_step do
390
+ task :work_step_3
391
+ end
392
+ end
393
+
394
+ # creation
395
+
396
+ def additional_step(options)
397
+ options.steps.each do |k, v|
398
+ yield k, v
399
+ end
400
+ end
401
+
402
+ # execution
403
+
404
+ def work_step_1(options)
405
+ # ...
406
+ end
407
+
408
+ def work_step_2(options)
409
+ # ...
410
+ end
411
+
412
+ def work_step_3(k, v)
413
+ # ...
414
+ end
415
+
416
+ end
417
+ ```
418
+
419
+ There are three tasks; namely `:work_step_1`, `:work_step_2` and `:work_step_3`.
420
+
421
+ The third task, `:work_step_3`, is built up using the `for_each` iterator, which means that the number of `:work_step_3` tasks
422
+ will depend on how many times the `additional_step` iterator method yields to the definition.
423
+
424
+ This brings us to the creation part. When `create_process` is called on the given module, you provide arguments to it, which will get
425
+ passed onto the respective `task` and `for_each` iterator methods.
426
+
427
+ So, considering the `MySimpleProcess` module shown above, `work_step_1`, `work_step_2` and `work_step_3` methods each expect arguments.
428
+ These will ultimately come from the arguments passed into the `create_process` method.
429
+
430
+ E.g.
431
+
432
+ ```ruby
433
+
434
+ # Given an options hash
435
+ options = {
436
+ :opt1 => true,
437
+ :opt2 => false,
438
+ :steps => {
439
+ :a => 1,
440
+ :b => 2,
441
+ :c => 3,
442
+ }
443
+ }
444
+
445
+ # You create the process, passing in the options hash
446
+ process = MySimpleProcess.create_process(options)
447
+
448
+ ```
449
+
450
+ To best understand how the process is created, consider the following "procedural" code for how it could work.
451
+
452
+ ```ruby
453
+ # A process, which maps the target and a list of steps
454
+ class Process
455
+ attr_reader :target
456
+ attr_reader :tasks
457
+
458
+ def initialize(target)
459
+ @target = target
460
+ @tasks = []
461
+ end
462
+ end
463
+
464
+ # A task, which maps the method to call and it's arguments
465
+ class Task
466
+ attr_reader :method
467
+ attr_reader :args
468
+
469
+ def initialize(method, args)
470
+ @method, @args = method, args
471
+ end
472
+ end
473
+
474
+ # Your module, with the methods which do the actual work
475
+ module MySimpleProcess
476
+
477
+ def self.work_step_1(options) ...
478
+ def self.work_step_2(options) ...
479
+ def self.work_step_3(k, v) ...
480
+
481
+ end
482
+
483
+ # Now, the creation phase of the definition
484
+ # create a process, providing the module
485
+
486
+ process = Process.new(MySimpleProcess)
487
+
488
+ # create the first and second tasks, providing the method
489
+ # for the task and it's arguments, which are the options defined above
490
+
491
+ process.tasks << Task.new(:work_step_1, options)
492
+ process.tasks << Task.new(:work_step_2, options)
493
+
494
+ # iterate over the steps hash in the options, and add the third step
495
+ # this time specify the key and value as the
496
+ # arguments for the work_step_3 method
497
+
498
+ options.steps.each do |k, v|
499
+ process.tasks << Task.new(:work_step_3, [k, v])
500
+ end
501
+
502
+ # we now have a process with the tasks defined
503
+
504
+ process.tasks #=> [<Task :method=>work_step_1, :args=>options, ...> ,
505
+ # <Task :method=>work_step_2, :args=>options, ...>,
506
+ # <Task :method=>work_step_3, :args=>[:a, 1], ...>,
507
+ # <Task :method=>work_step_3, :args=>[:b, 2], ...>,
508
+ # <Task :method=>work_step_3, :args=>[:c, 3], ...>]
509
+
510
+ ```
511
+
512
+ Finally, for the execution phase, the process and tasks will act on the supplied module.
513
+
514
+ ```ruby
515
+ # building out the "Process" class
516
+ class Process
517
+ #...
518
+
519
+ def execute
520
+ tasks.each {|task| task.execute(target) )
521
+ end
522
+ end
523
+
524
+ # and the "Task" class
525
+ class Task
526
+ #...
527
+
528
+ def execute(target)
529
+ puts "Calling '#{method}' on '#{target.name}' with #{args.inspect}..."
530
+ target.send(method, *args)
531
+ end
532
+ end
533
+
534
+ # executing the process iterates over each task and
535
+ # the target modules method is called with the arguments
536
+
537
+ process.execute
538
+
539
+ # Calling 'work_step_1' on 'MySimpleProcess' with {:opt1 => true, :opt2 => false, ...}
540
+ # Calling 'work_step_2' on 'MySimpleProcess' with {:opt1 => true, :opt2 => false, ...}
541
+ # Calling 'work_step_3' on 'MySimpleProcess' with [:a, 1]
542
+ # Calling 'work_step_3' on 'MySimpleProcess' with [:b, 2]
543
+ # Calling 'work_step_3' on 'MySimpleProcess' with [:c, 3]
544
+
545
+ ```
546
+
547
+ In reality, each task is executed by a worker process, possibly on another host, so the execution process isn't as simple,
548
+ but this example should help you to understand conceptually how the process is executed, and how the arguments are propagated
549
+ through.
222
550
 
223
551
  ### Monitoring
224
552
 
@@ -5,14 +5,20 @@ module Taskinator
5
5
  attr_reader :process
6
6
  attr_reader :definition
7
7
  attr_reader :args
8
+ attr_reader :options
8
9
 
9
- def initialize(process, definition, args)
10
+ def initialize(process, definition, *args)
10
11
  @process = process
11
12
  @definition = definition
12
13
  @args = args
14
+ @options = args.last.is_a?(Hash) ? args.last : {}
13
15
  @executor = Taskinator::Executor.new(@definition)
14
16
  end
15
17
 
18
+ def option?(key, &block)
19
+ yield if @options.key?(key) && @options[key]
20
+ end
21
+
16
22
  # defines a sub process of tasks which are executed sequentially
17
23
  def sequential(options={}, &block)
18
24
  raise ArgumentError, 'block' unless block_given?
@@ -3,10 +3,14 @@ module Taskinator
3
3
  class UndefinedProcessError < RuntimeError; end
4
4
 
5
5
  # defines a process
6
- def define_process(&block)
6
+ def define_process(*arg_list, &block)
7
7
  define_singleton_method :_create_process_ do |*args|
8
+
9
+ # TODO: better validation of arguments
10
+ raise ArgumentError, "wrong number of arguments (#{args.length} for #{arg_list.length})" if args.length < arg_list.length
11
+
8
12
  process = Process.define_sequential_process_for(self)
9
- Builder.new(process, self, args).instance_eval(&block)
13
+ Builder.new(process, self, *args).instance_eval(&block)
10
14
  process.save
11
15
  process
12
16
  end
@@ -1,3 +1,3 @@
1
1
  module Taskinator
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -1,7 +1,7 @@
1
1
  module TestFlow
2
2
  extend Taskinator::Definition
3
3
 
4
- define_process do
4
+ define_process :some_arg1, :some_arg2 do
5
5
  task :error_task, :continue_on_error => true
6
6
 
7
7
  task :the_task
@@ -54,7 +54,7 @@ module TestFlow
54
54
  module TestSubFlow
55
55
  extend Taskinator::Definition
56
56
 
57
- define_process do
57
+ define_process :some_arg1, :some_arg2 do
58
58
  task :the_task
59
59
  task :the_task
60
60
  task :the_task
@@ -15,7 +15,7 @@ describe Taskinator::Definition::Builder do
15
15
  Class.new(Taskinator::Process).new(definition)
16
16
  }
17
17
 
18
- let(:args) { [:arg1, :arg2] }
18
+ let(:args) { [:arg1, :arg2, {:option => 1, :another => false}] }
19
19
 
20
20
  let(:block) { SpecSupport::Block.new() }
21
21
 
@@ -24,12 +24,30 @@ describe Taskinator::Definition::Builder do
24
24
  Proc.new {|*args| the_block.call }
25
25
  }
26
26
 
27
- subject { Taskinator::Definition::Builder.new(process, definition, args) }
27
+ subject { Taskinator::Definition::Builder.new(process, definition, *args) }
28
28
 
29
29
  it "assign attributes" do
30
30
  expect(subject.process).to eq(process)
31
31
  expect(subject.definition).to eq(definition)
32
32
  expect(subject.args).to eq(args)
33
+ expect(subject.options).to eq({:option => 1, :another => false})
34
+ end
35
+
36
+ describe "#option?" do
37
+ it "invokes supplied block for 'option' option" do
38
+ expect(block).to receive(:call)
39
+ subject.option?(:option, &define_block)
40
+ end
41
+
42
+ it "does not invoke supplied block for 'another' option" do
43
+ expect(block).to_not receive(:call)
44
+ subject.option?(:another, &define_block)
45
+ end
46
+
47
+ it "does not invoke supplied block for an unspecified option" do
48
+ expect(block).to_not receive(:call)
49
+ subject.option?(:unspecified, &define_block)
50
+ end
33
51
  end
34
52
 
35
53
  describe "#sequential" do
@@ -154,7 +172,8 @@ describe Taskinator::Definition::Builder do
154
172
  Module.new() do
155
173
  extend Taskinator::Definition
156
174
 
157
- define_process {}
175
+ define_process :some_arg1, :some_arg2, :some_arg3 do
176
+ end
158
177
  end
159
178
  end
160
179
 
@@ -164,7 +183,7 @@ describe Taskinator::Definition::Builder do
164
183
  end
165
184
 
166
185
  it "creates a sub process task" do
167
- sub_process = sub_definition.create_process(:argX, :argY)
186
+ sub_process = sub_definition.create_process(:argX, :argY, :argZ)
168
187
  allow(sub_definition).to receive(:create_process) { sub_process }
169
188
  expect(Taskinator::Task).to receive(:define_sub_process_task).with(process, sub_process, {})
170
189
  subject.sub_process(sub_definition)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: taskinator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Stefano
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-09-17 00:00:00.000000000 Z
11
+ date: 2014-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis