light-service 0.8.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/README.md +25 -33
  4. data/RELEASES.md +3 -0
  5. data/lib/light-service.rb +7 -0
  6. data/lib/light-service/orchestrator.rb +21 -3
  7. data/lib/light-service/organizer.rb +15 -20
  8. data/lib/light-service/organizer/execute.rb +14 -0
  9. data/lib/light-service/organizer/iterate.rb +22 -0
  10. data/lib/light-service/organizer/reduce_if.rb +17 -0
  11. data/lib/light-service/organizer/reduce_until.rb +20 -0
  12. data/lib/light-service/organizer/scoped_reducable.rb +13 -0
  13. data/lib/light-service/organizer/verify_call_method_exists.rb +28 -0
  14. data/lib/light-service/organizer/with_callback.rb +26 -0
  15. data/lib/light-service/organizer/with_reducer.rb +14 -4
  16. data/lib/light-service/organizer/with_reducer_factory.rb +2 -0
  17. data/lib/light-service/version.rb +1 -1
  18. data/light-service.gemspec +1 -1
  19. data/resources/orchestrators_deprecated.svg +10 -0
  20. data/spec/acceptance/orchestrator/context_failure_and_skipping_spec.rb +6 -4
  21. data/spec/acceptance/orchestrator/execute_spec.rb +6 -4
  22. data/spec/acceptance/orchestrator/iterate_spec.rb +7 -5
  23. data/spec/acceptance/orchestrator/organizer_action_combination_spec.rb +13 -11
  24. data/spec/acceptance/orchestrator/reduce_if_spec.rb +7 -5
  25. data/spec/acceptance/orchestrator/reduce_until_spec.rb +6 -4
  26. data/spec/acceptance/orchestrator/with_callback_spec.rb +8 -6
  27. data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
  28. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +65 -0
  29. data/spec/acceptance/organizer/execute_spec.rb +46 -0
  30. data/spec/acceptance/organizer/iterate_spec.rb +51 -0
  31. data/spec/acceptance/organizer/reduce_if_spec.rb +51 -0
  32. data/spec/acceptance/organizer/reduce_until_spec.rb +43 -0
  33. data/spec/acceptance/organizer/with_callback_spec.rb +169 -0
  34. data/spec/organizer/with_reducer_spec.rb +2 -7
  35. data/spec/spec_helper.rb +12 -0
  36. data/spec/support.rb +9 -0
  37. data/spec/test_doubles.rb +7 -3
  38. metadata +28 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fa927bdc5eaab0a8eed9f198bf61c28c042dc530
4
- data.tar.gz: 6855d34240f37a93ef2ece590074eabd30ff901d
3
+ metadata.gz: 30ef43a9d6e26800b7599546ffea8b1cb15b1533
4
+ data.tar.gz: 28ee4863b5ea1a859c9e4ca288c20b23f99bc4e9
5
5
  SHA512:
6
- metadata.gz: 5bb0724f04a18663620795c3f53fa048e88a6a07d441dd2250f1e0121f68f5b7f3d108aeb4ff64155ec83f6151756efc2f695df071d2f77dc14c3519380abddb
7
- data.tar.gz: be037dc8236f1d38b860e912226e28a951db55be761c6b3f8591adf4404c1d61b367538ee4bd9c6595d546e50d206604529b71ed6ddefe59cd93e0a7a68cb5e6
6
+ metadata.gz: dab3019cc90cecb1d97b2d36dcf5ad97dde57a22424eefbe91978d024205ea90215df4045594e8744fc9af5ae58ffd45f9c9727d921aee8af1bae50b0df754d3
7
+ data.tar.gz: 528fc079179b6e276aa6b4ec331326b51c279c3e234a0fc5998b336098375ad189c0bc274a616446d9aa851f10cda8653901d7f9e13a2e5ee0251f2f037e9324
data/.travis.yml CHANGED
@@ -1,5 +1,8 @@
1
1
  language: ruby
2
2
 
3
+ env:
4
+ - RUN_COVERAGE_REPORT=true
5
+
3
6
  rvm:
4
7
  - 2.1.8
5
8
  - 2.2.2
data/README.md CHANGED
@@ -3,6 +3,15 @@
3
3
  [![Gem Version](https://img.shields.io/gem/v/light-service.svg)](https://rubygems.org/gems/light-service)
4
4
  [![Build Status](https://secure.travis-ci.org/adomokos/light-service.png)](http://travis-ci.org/adomokos/light-service)
5
5
  [![Code Climate](https://codeclimate.com/github/adomokos/light-service.png)](https://codeclimate.com/github/adomokos/light-service)
6
+ [![Dependency Status](https://beta.gemnasium.com/badges/github.com/adomokos/light-service.svg)](https://beta.gemnasium.com/projects/github.com/adomokos/light-service)
7
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)
8
+
9
+ <br><br>
10
+
11
+ ![Orchestrators-Deprecated](resources/orchestrators_deprecated.svg)
12
+ <br>Version 0.9.0 deprecates Orchestrators and moves all their functionalities into Organizers. Please check out [this PR](https://github.com/adomokos/light-service/pull/132) to see the changes.
13
+
14
+ <br><br>
6
15
 
7
16
  What do you think of this code?
8
17
 
@@ -157,7 +166,7 @@ simple and elegant Rails code where I told the story of how LightService was ext
157
166
  * [Error Codes](#error-codes)
158
167
  * [Action Rollback](#action-rollback)
159
168
  * [Localizing Messages](#localizing-messages)
160
- * [Orchestrators](#orchestrators)
169
+ * [Orchestrator Logic in Organizers](#orchestrator-logic-in-organizers)
161
170
  * [ContextFactory for Faster Action Testing](#contextfactory-for-faster-action-testing)
162
171
 
163
172
  ## Stopping the Series of Actions
@@ -576,7 +585,7 @@ end
576
585
 
577
586
  To get the value of a `fail!` or `succeed!` message, simply call `#message` on the returned context.
578
587
 
579
- ## Orchestrators
588
+ ## Orchestrator Logic in Organizers
580
589
 
581
590
  The Organizer - Action combination works really well for simple use cases. However, as business logic gets more complex, or when LightService is used in an ETL workflow, the code that routes the different organizers becomes very complex and imperative. Let's look at a piece of code that does basic data transformations:
582
591
 
@@ -607,13 +616,13 @@ The `LightService::Context` is initialized with the first action, that context i
607
616
 
608
617
  ```ruby
609
618
  class ExtractsTransformsLoadsData
610
- extend LightService::Orchestrator
619
+ extend LightService::Organizer
611
620
 
612
- def self.run(connection)
621
+ def self.call(connection)
613
622
  with(:connection => connection).reduce(steps)
614
623
  end
615
624
 
616
- def self.steps
625
+ def self.actions
617
626
  [
618
627
  RetrievesConnectionInfo,
619
628
  PullsDataFromRemoteApi,
@@ -632,40 +641,23 @@ end
632
641
 
633
642
  This code is much easier to reason about, it's less noisy and it captures the goal of LightService well: simple, declarative code that's easy to understand.
634
643
 
635
- Our convention for naming the public methods on the different items at different levels is this:
636
- ```
637
- Orchestrators
638
- |-> run - steps
639
- Organizers
640
- |-> call - actions
641
- Actions
642
- |-> execute
643
- ```
644
-
645
- You can mix organizers with actions in the orchestrator steps, but mixing other organizers with actions in an organizer is discouraged for the sake of simplicity.
646
-
647
- The 6 different constructs an orchestrator can have:
648
-
649
- 1. `reduce`
650
- 2. `reduce_until`
651
- 3. `reduce_if`
652
- 4. `iterate`
653
- 5. `execute`
654
- 6. `with_callback`
655
-
656
- The `reduce` method needs no introduction, it behaves similarly to organizers' `reduce` method.
644
+ The 5 different orchestrator constructs an organizer can have:
657
645
 
658
- `reduce_until` behaves like a while loop in imperative languages, it iterates until the provided predicate in the lambda evaluates to true. Take a look at [this acceptance test](spec/acceptance/orchestrator/reduce_until_spec.rb) to see how it's used.
646
+ 1. `reduce_until`
647
+ 2. `reduce_if`
648
+ 3. `iterate`
649
+ 4. `execute`
650
+ 5. `with_callback`
659
651
 
660
- `reduce_if` will reduce the included organizers and/or actions if the predicate in the lambda evaluates to true. [This acceptance test](spec/acceptance/orchestrator/reduce_if_spec.rb) describes this functionality.
652
+ `reduce_until` behaves like a while loop in imperative languages, it iterates until the provided predicate in the lambda evaluates to true. Take a look at [this acceptance test](spec/acceptance/organizer/reduce_until_spec.rb) to see how it's used.
661
653
 
662
- `iterate` gives your iteration logic, the symbol you define there has to be in the context as a key. For example. to iterate over items you will use `iterate(:items)` in your steps, the context needs to have `items` as a key, otherwise it will fail. The orchestrator will singularize the collection name and will put the actual item into the context under that name. Remaining with the example above, each element will be accessible by the name `item` for the actions in the `iterate` steps. [This acceptance test](spec/acceptance/orchestrator/iterate_spec.rb) should provide you with an example.
654
+ `reduce_if` will reduce the included organizers and/or actions if the predicate in the lambda evaluates to true. [This acceptance test](spec/acceptance/organizer/reduce_if_spec.rb) describes this functionality.
663
655
 
664
- To take advantage of another organizer or action, you might need to tweak the context a bit. Let's say you have a hash, and you need to iterate over its values in a series of action. To alter the context and have the values assigned into a variable, you need to create a new action with 1 line of code in it. That seems a lot of ceremony for a simple change. You can do that in a `execute` method like this `execute(->(ctx) { ctx[:some_values] = ctx.some_hash.values })`. [This test](spec/acceptance/orchestrator/execute_spec.rb) describes how you can use it.
656
+ `iterate` gives your iteration logic, the symbol you define there has to be in the context as a key. For example, to iterate over items you will use `iterate(:items)` in your steps, the context needs to have `items` as a key, otherwise it will fail. The organizer will singularize the collection name and will put the actual item into the context under that name. Remaining with the example above, each element will be accessible by the name `item` for the actions in the `iterate` steps. [This acceptance test](spec/acceptance/organizer/iterate_spec.rb) should provide you with an example.
665
657
 
666
- Use `with_callback` when you want to execute actions with a deferred and controlled callback. It works similar to a Sax parser, I've used it for processing large files. The advantage of it is not having to keep large amount of data in memory. See [this acceptance test](spec/acceptance/orchestrator/with_callback_spec.rb) as a working example.
658
+ To take advantage of another organizer or action, you might need to tweak the context a bit. Let's say you have a hash, and you need to iterate over its values in a series of action. To alter the context and have the values assigned into a variable, you need to create a new action with 1 line of code in it. That seems a lot of ceremony for a simple change. You can do that in a `execute` method like this `execute(->(ctx) { ctx[:some_values] = ctx.some_hash.values })`. [This test](spec/acceptance/organizer/execute_spec.rb) describes how you can use it.
667
659
 
668
- ** Thanks to [@bwvoss](https://github.com/bwvoss) for writing most of the Orchestrators code, I only ported his changes to LS and submitted the PR.
660
+ Use `with_callback` when you want to execute actions with a deferred and controlled callback. It works similar to a Sax parser, I've used it for processing large files. The advantage of it is not having to keep large amount of data in memory. See [this acceptance test](spec/acceptance/organizer/with_callback_spec.rb) as a working example.
669
661
 
670
662
  ## ContextFactory for Faster Action Testing
671
663
 
data/RELEASES.md CHANGED
@@ -1,5 +1,8 @@
1
1
  A brief list of new features and changes introduced with the specified version.
2
2
 
3
+ ### 0.9.0
4
+ * [Deprecate Orchestrator](https://github.com/adomokos/light-service/pull/132) by moving its functionality to Organizers.
5
+
3
6
  ### 0.8.4
4
7
  * Only pass [default argument](https://github.com/adomokos/light-service/pull/123) to Hash#fetch in context if no block given.
5
8
 
data/lib/light-service.rb CHANGED
@@ -7,9 +7,16 @@ require 'light-service/configuration'
7
7
  require 'light-service/localization_adapter'
8
8
  require 'light-service/context'
9
9
  require 'light-service/context/key_verifier'
10
+ require 'light-service/organizer/scoped_reducable'
10
11
  require 'light-service/organizer/with_reducer'
11
12
  require 'light-service/organizer/with_reducer_log_decorator'
12
13
  require 'light-service/organizer/with_reducer_factory'
14
+ require 'light-service/organizer/reduce_if'
15
+ require 'light-service/organizer/reduce_until'
16
+ require 'light-service/organizer/iterate'
17
+ require 'light-service/organizer/execute'
18
+ require 'light-service/organizer/with_callback'
19
+ require 'light-service/organizer/verify_call_method_exists'
13
20
  require 'light-service/action'
14
21
  require 'light-service/organizer'
15
22
  require 'light-service/orchestrator'
@@ -11,11 +11,13 @@ module LightService
11
11
  end
12
12
 
13
13
  def reduce(steps, context = @context)
14
+ issue_deprecation_warning_for(__method__)
15
+
14
16
  steps.each_with_object(context) do |step, ctx|
15
- if step.respond_to?(:execute)
16
- step.execute(ctx)
17
- elsif step.respond_to?(:call)
17
+ if step.respond_to?(:call)
18
18
  step.call(ctx)
19
+ elsif step.respond_to?(:execute)
20
+ step.execute(ctx)
19
21
  else
20
22
  raise 'Pass either an action or organizer'
21
23
  end
@@ -23,6 +25,8 @@ module LightService
23
25
  end
24
26
 
25
27
  def reduce_until(condition_block, steps)
28
+ issue_deprecation_warning_for(__method__)
29
+
26
30
  lambda do |ctx|
27
31
  return ctx if ctx.stop_processing?
28
32
 
@@ -36,6 +40,8 @@ module LightService
36
40
  end
37
41
 
38
42
  def reduce_if(condition_block, steps)
43
+ issue_deprecation_warning_for(__method__)
44
+
39
45
  lambda do |ctx|
40
46
  return ctx if ctx.stop_processing?
41
47
 
@@ -45,6 +51,8 @@ module LightService
45
51
  end
46
52
 
47
53
  def execute(code_block)
54
+ issue_deprecation_warning_for(__method__)
55
+
48
56
  lambda do |ctx|
49
57
  return ctx if ctx.stop_processing?
50
58
 
@@ -54,6 +62,8 @@ module LightService
54
62
  end
55
63
 
56
64
  def iterate(collection_key, steps)
65
+ issue_deprecation_warning_for(__method__)
66
+
57
67
  lambda do |ctx|
58
68
  return ctx if ctx.stop_processing?
59
69
 
@@ -69,6 +79,8 @@ module LightService
69
79
  end
70
80
 
71
81
  def with_callback(action, steps)
82
+ issue_deprecation_warning_for(__method__)
83
+
72
84
  lambda do |ctx|
73
85
  return ctx if ctx.stop_processing?
74
86
 
@@ -100,6 +112,12 @@ module LightService
100
112
 
101
113
  ctx
102
114
  end
115
+
116
+ def issue_deprecation_warning_for(method_name)
117
+ msg = "`Orchestrator##{method_name}` is DEPRECATED and will be " \
118
+ "removed, please switch to `Organizer##{method_name} instead. "
119
+ ActiveSupport::Deprecation.warn(msg)
120
+ end
103
121
  end
104
122
  end
105
123
  end
@@ -17,7 +17,7 @@ module LightService
17
17
  # In case this module is included
18
18
  module ClassMethods
19
19
  def with(data = {})
20
- VerifyCallMethodExists.call(self, caller(1..1).first)
20
+ VerifyCallMethodExists.run(self, caller(1..1).first)
21
21
  data[:_aliases] = @aliases if @aliases
22
22
  WithReducerFactory.make(self).with(data)
23
23
  end
@@ -26,29 +26,24 @@ module LightService
26
26
  with({}).reduce(actions)
27
27
  end
28
28
 
29
- # We need to make sure existing users will
30
- # use `call` method name going forward.
31
- # This should be removed eventually.
32
- class VerifyCallMethodExists
33
- def self.call(klass, first_caller = '')
34
- invoker_method = caller_method(first_caller)
35
- return if invoker_method == 'call'
29
+ def reduce_if(condition_block, steps)
30
+ ReduceIf.run(self, condition_block, steps)
31
+ end
36
32
 
37
- call_method_exists = klass.methods.include?(:call)
38
- return if call_method_exists
33
+ def reduce_until(condition_block, steps)
34
+ ReduceUntil.run(self, condition_block, steps)
35
+ end
39
36
 
40
- warning_msg = "The <#{klass.name}> class is an organizer, " \
41
- "its entry method (the one that calls with & reduce) " \
42
- "should be named `call`. " \
43
- "Please use #{klass}.call going forward."
44
- ActiveSupport::Deprecation.warn(warning_msg)
45
- end
37
+ def iterate(collection_key, steps)
38
+ Iterate.run(self, collection_key, steps)
39
+ end
46
40
 
47
- def self.caller_method(first_caller)
48
- return nil unless first_caller =~ /`(.*)'/
41
+ def execute(code_block)
42
+ Execute.run(code_block)
43
+ end
49
44
 
50
- Regexp.last_match[1]
51
- end
45
+ def with_callback(action, steps)
46
+ WithCallback.run(self, action, steps)
52
47
  end
53
48
  end
54
49
 
@@ -0,0 +1,14 @@
1
+ module LightService
2
+ module Organizer
3
+ class Execute
4
+ def self.run(code_block)
5
+ lambda do |ctx|
6
+ return ctx if ctx.stop_processing?
7
+
8
+ code_block.call(ctx)
9
+ ctx
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ module LightService
2
+ module Organizer
3
+ class Iterate
4
+ extend ScopedReducable
5
+
6
+ def self.run(organizer, collection_key, steps)
7
+ lambda do |ctx|
8
+ return ctx if ctx.stop_processing?
9
+
10
+ collection = ctx[collection_key]
11
+ item_key = collection_key.to_s.singularize.to_sym
12
+ collection.each do |item|
13
+ ctx[item_key] = item
14
+ ctx = scoped_reduce(organizer, ctx, steps)
15
+ end
16
+
17
+ ctx
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module LightService
2
+ module Organizer
3
+ class ReduceIf
4
+ extend ScopedReducable
5
+
6
+ def self.run(organizer, condition_block, steps)
7
+ lambda do |ctx|
8
+ return ctx if ctx.stop_processing?
9
+
10
+ ctx = scoped_reduce(organizer, ctx, steps) \
11
+ if condition_block.call(ctx)
12
+ ctx
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module LightService
2
+ module Organizer
3
+ class ReduceUntil
4
+ extend ScopedReducable
5
+
6
+ def self.run(organizer, condition_block, steps)
7
+ lambda do |ctx|
8
+ return ctx if ctx.stop_processing?
9
+
10
+ loop do
11
+ ctx = scoped_reduce(organizer, ctx, steps)
12
+ break if condition_block.call(ctx) || ctx.failure?
13
+ end
14
+
15
+ ctx
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ module LightService
2
+ module Organizer
3
+ module ScopedReducable
4
+ def scoped_reduce(organizer, ctx, steps)
5
+ ctx.reset_skip_remaining! unless ctx.failure?
6
+ ctx = organizer.with(ctx).reduce([steps])
7
+ ctx.reset_skip_remaining! unless ctx.failure?
8
+
9
+ ctx
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ module LightService
2
+ module Organizer
3
+ # We need to make sure existing users will
4
+ # use `call` method name going forward.
5
+ # This should be removed eventually.
6
+ class VerifyCallMethodExists
7
+ def self.run(klass, first_caller = '')
8
+ invoker_method = caller_method(first_caller)
9
+ return if invoker_method == 'call'
10
+
11
+ call_method_exists = klass.methods.include?(:call)
12
+ return if call_method_exists
13
+
14
+ warning_msg = "The <#{klass.name}> class is an organizer, " \
15
+ "its entry method (the one that calls with & reduce) " \
16
+ "should be named `call`. " \
17
+ "Please use #{klass}.call going forward."
18
+ ActiveSupport::Deprecation.warn(warning_msg)
19
+ end
20
+
21
+ def self.caller_method(first_caller)
22
+ return nil unless first_caller =~ /`(.*)'/
23
+
24
+ Regexp.last_match[1]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ module LightService
2
+ module Organizer
3
+ class WithCallback
4
+ extend ScopedReducable
5
+
6
+ def self.run(organizer, action, steps)
7
+ lambda do |ctx|
8
+ return ctx if ctx.stop_processing?
9
+
10
+ # This will only allow 2 level deep nesting of callbacks
11
+ previous_callback = ctx[:callback]
12
+
13
+ ctx[:callback] = lambda do |context|
14
+ ctx = scoped_reduce(organizer, context, steps)
15
+ ctx
16
+ end
17
+
18
+ ctx = action.execute(ctx)
19
+ ctx[:callback] = previous_callback
20
+
21
+ ctx
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,7 +1,7 @@
1
1
  module LightService
2
2
  module Organizer
3
3
  class WithReducer
4
- attr_reader :context, :around_each_handler
4
+ attr_reader :context
5
5
 
6
6
  def with(data = {})
7
7
  @context = LightService::Context.make(data)
@@ -13,6 +13,14 @@ module LightService
13
13
  self
14
14
  end
15
15
 
16
+ def around_each_handler
17
+ @around_each_handler ||= Class.new do
18
+ def self.call(_context)
19
+ yield
20
+ end
21
+ end
22
+ end
23
+
16
24
  def reduce(*actions)
17
25
  raise "No action(s) were provided" if actions.empty?
18
26
  actions.flatten!
@@ -46,10 +54,12 @@ module LightService
46
54
  private
47
55
 
48
56
  def invoke_action(current_context, action)
49
- return action.execute(current_context) unless around_each_handler
50
-
51
57
  around_each_handler.call(current_context) do
52
- action.execute(current_context)
58
+ if action.respond_to?(:call)
59
+ action.call(current_context)
60
+ else
61
+ action.execute(current_context)
62
+ end
53
63
  end
54
64
  end
55
65