light-service 0.10.1 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +6 -0
  4. data/.travis.yml +12 -10
  5. data/Appraisals +4 -0
  6. data/README.md +39 -20
  7. data/RELEASES.md +17 -0
  8. data/gemfiles/activesupport_6.gemfile +8 -0
  9. data/lib/light-service.rb +1 -0
  10. data/lib/light-service/context.rb +4 -1
  11. data/lib/light-service/localization_adapter.rb +1 -1
  12. data/lib/light-service/organizer.rb +32 -0
  13. data/lib/light-service/organizer/with_reducer.rb +4 -5
  14. data/lib/light-service/organizer/with_reducer_factory.rb +11 -7
  15. data/lib/light-service/organizer/with_reducer_log_decorator.rb +2 -2
  16. data/lib/light-service/testing/context_factory.rb +12 -7
  17. data/lib/light-service/version.rb +1 -1
  18. data/light-service.gemspec +5 -4
  19. data/spec/acceptance/after_actions_spec.rb +13 -0
  20. data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
  21. data/spec/acceptance/fail_spec.rb +42 -16
  22. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  23. data/spec/acceptance/organizer/add_to_context_spec.rb +30 -0
  24. data/spec/acceptance/organizer/execute_spec.rb +1 -1
  25. data/spec/acceptance/organizer/reduce_if_spec.rb +32 -0
  26. data/spec/acceptance/testing/context_factory_spec.rb +12 -3
  27. data/spec/organizer_spec.rb +37 -14
  28. data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
  29. data/spec/spec_helper.rb +2 -1
  30. data/spec/test_doubles.rb +93 -0
  31. data/spec/testing/context_factory_spec.rb +20 -0
  32. metadata +32 -15
  33. data/gemfiles/activesupport_3.gemfile.lock +0 -76
  34. data/gemfiles/activesupport_4.gemfile.lock +0 -82
  35. data/gemfiles/activesupport_5.gemfile.lock +0 -82
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1f6b68783827acc1479c9728d2483dcba8c9c0e7
4
- data.tar.gz: 0446e28166fd582394cb74819e855d26241e0393
2
+ SHA256:
3
+ metadata.gz: 1e9dfb5cf61024afa28719b84e160c9580fa39c99eda19b73cdb0cced9017da2
4
+ data.tar.gz: 7f389c208b3b06000c84859d595d9b49acf8dfe8d370736ee564a1ae8ef8b5d1
5
5
  SHA512:
6
- metadata.gz: 6ac7178a1ea5e9fa7cd22a16de9533c953b504b5a999c26912fbb512e578cd1dcde66fc44d4414b56bf6e0c5be4d679b85b595bdc4e708cc6ac1bbd177a61e94
7
- data.tar.gz: 5a881eca505d834cbf1b270215c0134f9b6d831fa37c29ca2737dec6d3fec6de9e224d0683f704e635301d6c9b47eb545e6e165d0c5a6aa69fd8f2b621e34376
6
+ metadata.gz: b1b7792d344efe1aab4297f8774649590b8e0336a44082da31a167a62b75a039b7f89759923beb9a463f6c017b0387297efc815f357deae31270b54e2c1c1bcc
7
+ data.tar.gz: 0d1d339a23d2d7b39bd5403d81fe1e1519a89eec22fd64f00931d9dc49a81c5476f67e6dd28591e0e88c55b1378d1155ed0d1700100569aa9b43a1922c860f3e
data/.gitignore CHANGED
@@ -17,3 +17,4 @@ test/version_tmp
17
17
  tmp
18
18
  vendor/bundle
19
19
  bin
20
+ .idea
@@ -1,4 +1,7 @@
1
+ require: rubocop-performance
2
+
1
3
  AllCops:
4
+ TargetRubyVersion: 2.3
2
5
  Exclude:
3
6
  - 'lib/light-service.rb'
4
7
  - 'vendor/bundle/**/*'
@@ -46,3 +49,6 @@ Metrics/BlockLength:
46
49
 
47
50
  Layout/TrailingBlankLines:
48
51
  Enabled: false
52
+
53
+ Layout/EndOfLine:
54
+ EnforcedStyle: lf
@@ -4,16 +4,15 @@ env:
4
4
  - RUN_COVERAGE_REPORT=true
5
5
 
6
6
  rvm:
7
- - 2.2.2
8
- - 2.3.3
9
- - 2.4.1
10
- - 2.5.0
7
+ - 2.4.2
8
+ - 2.5.3
9
+ - 2.6.0
10
+ - 2.7.0
11
11
 
12
12
  before_install:
13
13
  - 'echo ''gem: --no-ri --no-rdoc'' > ~/.gemrc'
14
14
  - gem install bundler
15
- - bundle update simplecov
16
- - bundle install --path vendor/bundle
15
+ - bundle install --clean --path vendor/bundle
17
16
 
18
17
  # uncomment this line if your project needs to run something other than `rake`:
19
18
  script:
@@ -24,10 +23,13 @@ gemfile:
24
23
  - gemfiles/activesupport_3.gemfile
25
24
  - gemfiles/activesupport_4.gemfile
26
25
  - gemfiles/activesupport_5.gemfile
26
+ - gemfiles/activesupport_6.gemfile
27
27
 
28
28
  matrix:
29
29
  exclude:
30
- - rvm: 2.1.8
31
- gemfile: gemfiles/activesupport_5.gemfile
32
- - rvm: 2.2.2
33
- gemfile: gemfiles/activesupport_5.gemfile
30
+ - rvm: 2.4.2
31
+ gemfile: gemfiles/activesupport_6.gemfile
32
+ - rvm: 2.7.0
33
+ gemfile: gemfiles/activesupport_3.gemfile
34
+ - rvm: 2.7.0
35
+ gemfile: gemfiles/activesupport_4.gemfile
data/Appraisals CHANGED
@@ -9,3 +9,7 @@ end
9
9
  appraise "activesupport-5" do
10
10
  gem "activesupport", "~> 5.0"
11
11
  end
12
+
13
+ appraise "activesupport-6" do
14
+ gem "activesupport", "~> 6.0"
15
+ end
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  ![LightService](https://raw.githubusercontent.com/adomokos/light-service/master/resources/light-service.png)
2
2
 
3
3
  [![Gem Version](https://img.shields.io/gem/v/light-service.svg)](https://rubygems.org/gems/light-service)
4
- [![Build Status](https://secure.travis-ci.org/adomokos/light-service.png)](http://travis-ci.org/adomokos/light-service)
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)
4
+ [![Build Status](https://secure.travis-ci.org/adomokos/light-service.svg)](http://travis-ci.org/adomokos/light-service)
5
+ [![Code Climate](https://codeclimate.com/github/adomokos/light-service.svg)](https://codeclimate.com/github/adomokos/light-service)
7
6
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)
7
+ [![Download Count](https://ruby-gem-downloads-badge.herokuapp.com/light-service?type=total)](https://rubygems.org/gems/light-service)
8
8
 
9
9
  <br><br>
10
10
 
@@ -79,7 +79,7 @@ Wouldn't it be nice to see this instead?
79
79
  (
80
80
  LooksUpTaxPercentage,
81
81
  CalculatesOrderTax,
82
- ChecksFreeShipping
82
+ ProvidesFreeShipping
83
83
  )
84
84
  ```
85
85
 
@@ -257,12 +257,12 @@ Check out this example:
257
257
 
258
258
  ```ruby
259
259
  class LogDuration
260
- def self.call(action, context)
260
+ def self.call(context)
261
261
  start_time = Time.now
262
262
  result = yield
263
263
  duration = Time.now - start_time
264
264
  LightService::Configuration.logger.info(
265
- :action => action,
265
+ :action => context.current_action,
266
266
  :duration => duration
267
267
  )
268
268
 
@@ -295,14 +295,16 @@ Consider this code:
295
295
  class SomeOrganizer
296
296
  extend LightService::Organizer
297
297
 
298
- def call(ctx)
298
+ def self.call(ctx)
299
299
  with(ctx).reduce(actions)
300
300
  end
301
301
 
302
- def actions
303
- OneAction,
304
- TwoAction,
305
- ThreeAction
302
+ def self.actions
303
+ [
304
+ OneAction,
305
+ TwoAction,
306
+ ThreeAction
307
+ ]
306
308
  end
307
309
  end
308
310
 
@@ -346,14 +348,16 @@ class SomeOrganizer
346
348
  end
347
349
  end)
348
350
 
349
- def call(ctx)
351
+ def self.call(ctx)
350
352
  with(ctx).reduce(actions)
351
353
  end
352
354
 
353
- def actions
354
- OneAction,
355
- TwoAction,
356
- ThreeAction
355
+ def self.actions
356
+ [
357
+ OneAction,
358
+ TwoAction,
359
+ ThreeAction
360
+ ]
357
361
  end
358
362
  end
359
363
 
@@ -452,9 +456,9 @@ class AnOrganizer
452
456
 
453
457
  def self.call(order)
454
458
  with(:order => order).reduce(
455
- AnAction,
456
- AnotherAction,
457
- )
459
+ AnAction,
460
+ AnotherAction,
461
+ )
458
462
  end
459
463
  end
460
464
 
@@ -535,6 +539,15 @@ I, [DATE] INFO -- : [LightService] - ;-) <TestDoubles::MakesLatteAction> has de
535
539
  I, [DATE] INFO -- : [LightService] - context message: Can't make a latte with a fatty milk like that!
536
540
  ```
537
541
 
542
+ You can specify the logger on the organizer level, so the organizer does not use the global logger.
543
+
544
+ ```ruby
545
+ class FooOrganizer
546
+ extend LightService::Organizer
547
+ log_with Logger.new("/my/special.log")
548
+ end
549
+ ```
550
+
538
551
  ## Error Codes
539
552
  You can add some more structure to your error handling by taking advantage of error codes in the context.
540
553
  Normally, when something goes wrong in your actions, you fail the process by setting the context to failure:
@@ -746,13 +759,15 @@ end
746
759
 
747
760
  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.
748
761
 
749
- The 5 different orchestrator constructs an organizer can have:
762
+ The 7 different orchestrator constructs an organizer can have:
750
763
 
751
764
  1. `reduce_until`
752
765
  2. `reduce_if`
753
766
  3. `iterate`
754
767
  4. `execute`
755
768
  5. `with_callback`
769
+ 6. `add_to_context`
770
+ 7. `add_aliases`
756
771
 
757
772
  `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.
758
773
 
@@ -764,6 +779,10 @@ To take advantage of another organizer or action, you might need to tweak the co
764
779
 
765
780
  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.
766
781
 
782
+ `add_to_context` can add key-value pairs on the fly to the context. This functionality is useful when you need a value injected into the context under a specific key right before the subsequent actions are executed. [This test](spec/acceptance/organizer/add_to_context_spec.rb) describes its functionality.
783
+
784
+ Your action needs a certain key in the context but it's under a different one? Use the function `add_aliases` to alias an existing key in the context under the desired key. Take a look at [this test](spec/acceptance/organizer/add_aliases_spec.rb) to see an example.
785
+
767
786
  ## ContextFactory for Faster Action Testing
768
787
 
769
788
  As the complexity of your workflow increases, you will find yourself spending more and more time creating a context (LightService::Context it is) for your action tests. Some of this code can be reused by clever factories, but still, you are using a context that is artificial, and can be different from what the previous actions produced. This is especially true, when you use LightService in ETLs, where you start out with initial data and your actions are mutating its state.
@@ -1,5 +1,22 @@
1
1
  A brief list of new features and changes introduced with the specified version.
2
2
 
3
+ ### 0.13.0
4
+ * [Add 'add_to_context' and 'add_aliases'](https://github.com/adomokos/light-service/pull/172)
5
+ * Updating Ruby compatibility, minor fixes
6
+
7
+ ### 0.12.0
8
+ * [Per organizer logger](https://github.com/adomokos/light-service/pull/162)
9
+ * [Fix 'fail_and_return!' not accepting 'error_code' option](https://github.com/adomokos/light-service/pull/168)
10
+
11
+ ### 0.11.0
12
+ * [Switch to 'each_with_object' in WithReducer](https://github.com/adomokos/light-service/pull/149).
13
+
14
+ ### 0.10.3
15
+ * [Adding ContextFactory](https://github.com/adomokos/light-service/pull/147).
16
+
17
+ ### 0.10.2
18
+ * [Revert 0.10.1](https://github.com/adomokos/light-service/pull/146), it breaks tests in our apps :-(.
19
+
3
20
  ### 0.10.1
4
21
  * [Fixing ContextFactory](https://github.com/adomokos/light-service/pull/141) for orchestrator methods in Organizers.
5
22
 
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activesupport", "~> 6.0"
6
+ gem "appraisal", "~> 2.0"
7
+
8
+ gemspec :path => "../"
@@ -1,4 +1,5 @@
1
1
  require 'logger'
2
+ require 'active_support/core_ext/string'
2
3
 
3
4
  require 'light-service/version'
4
5
 
@@ -88,7 +88,7 @@ module LightService
88
88
 
89
89
  def fail_and_return!(*args)
90
90
  fail!(*args)
91
- throw(:jump_when_failed, *args)
91
+ throw(:jump_when_failed)
92
92
  end
93
93
 
94
94
  def fail_with_rollback!(message = nil, error_code = nil)
@@ -115,8 +115,10 @@ module LightService
115
115
 
116
116
  def define_accessor_methods_for_keys(keys)
117
117
  return if keys.nil?
118
+
118
119
  keys.each do |key|
119
120
  next if respond_to?(key.to_sym)
121
+
120
122
  define_singleton_method(key.to_s) { fetch(key) }
121
123
  define_singleton_method("#{key}=") { |value| self[key] = value }
122
124
  end
@@ -161,6 +163,7 @@ module LightService
161
163
 
162
164
  def check_nil(value)
163
165
  return 'nil' unless value
166
+
164
167
  "'#{value}'"
165
168
  end
166
169
  end
@@ -34,7 +34,7 @@ module LightService
34
34
  scope = i18n_scope_from_class(action_class, type)
35
35
  options[:scope] = scope
36
36
 
37
- I18n.t(key, options)
37
+ I18n.t(key, **options)
38
38
  end
39
39
 
40
40
  def i18n_scope_from_class(action_class, type)
@@ -56,6 +56,24 @@ module LightService
56
56
  def with_callback(action, steps)
57
57
  WithCallback.run(self, action, steps)
58
58
  end
59
+
60
+ def log_with(logger)
61
+ @logger = logger
62
+ end
63
+
64
+ def logger
65
+ @logger
66
+ end
67
+
68
+ def add_to_context(**args)
69
+ args.map do |key, value|
70
+ execute(->(ctx) { ctx[key.to_sym] = value })
71
+ end
72
+ end
73
+
74
+ def add_aliases(args)
75
+ execute(->(ctx) { ctx.assign_aliases(ctx.aliases.merge(args)) })
76
+ end
59
77
  end
60
78
 
61
79
  module Macros
@@ -63,6 +81,8 @@ module LightService
63
81
  @aliases = key_hash
64
82
  end
65
83
 
84
+ # This looks like an accessor,
85
+ # but it's used as a macro in the Organizer
66
86
  def before_actions(*logic)
67
87
  self.before_actions = logic
68
88
  end
@@ -71,6 +91,13 @@ module LightService
71
91
  @before_actions = [logic].flatten
72
92
  end
73
93
 
94
+ def append_before_actions(action)
95
+ @before_actions ||= []
96
+ @before_actions.push(action)
97
+ end
98
+
99
+ # This looks like an accessor,
100
+ # but it's used as a macro in the Organizer
74
101
  def after_actions(*logic)
75
102
  self.after_actions = logic
76
103
  end
@@ -78,6 +105,11 @@ module LightService
78
105
  def after_actions=(logic)
79
106
  @after_actions = [logic].flatten
80
107
  end
108
+
109
+ def append_after_actions(action)
110
+ @after_actions ||= []
111
+ @after_actions.push(action)
112
+ end
81
113
  end
82
114
  end
83
115
  end
@@ -23,19 +23,18 @@ module LightService
23
23
 
24
24
  def reduce(*actions)
25
25
  raise "No action(s) were provided" if actions.empty?
26
+
26
27
  actions.flatten!
27
28
 
28
- actions.reduce(context) do |current_context, action|
29
+ actions.each_with_object(context) do |action, current_context|
29
30
  begin
30
- result = invoke_action(current_context, action)
31
+ invoke_action(current_context, action)
31
32
  rescue FailWithRollbackError
32
- result = reduce_rollback(actions)
33
+ reduce_rollback(actions)
33
34
  ensure
34
35
  # For logging
35
36
  yield(current_context, action) if block_given?
36
37
  end
37
-
38
- result
39
38
  end
40
39
  end
41
40
 
@@ -2,13 +2,17 @@ module LightService
2
2
  module Organizer
3
3
  class WithReducerFactory
4
4
  def self.make(monitored_organizer)
5
- if LightService::Configuration.logger.nil?
6
- # :nocov:
7
- WithReducer.new
8
- # :nocov:
9
- else
10
- WithReducerLogDecorator.new(monitored_organizer, WithReducer.new)
11
- end
5
+ logger = monitored_organizer.logger ||
6
+ LightService::Configuration.logger
7
+ decorated = WithReducer.new
8
+
9
+ return decorated if logger.nil?
10
+
11
+ WithReducerLogDecorator.new(
12
+ monitored_organizer,
13
+ :decorated => decorated,
14
+ :logger => logger
15
+ )
12
16
  end
13
17
  end
14
18
  end
@@ -5,10 +5,10 @@ module LightService
5
5
 
6
6
  alias logged? logged
7
7
 
8
- def initialize(organizer, decorated = WithReducer.new)
8
+ def initialize(organizer, decorated: WithReducer.new, logger:)
9
9
  @decorated = decorated
10
10
  @organizer = organizer
11
- @logger = LightService::Configuration.logger
11
+ @logger = logger
12
12
  @logged = false
13
13
  end
14
14
 
@@ -8,23 +8,28 @@ module LightService
8
8
  end
9
9
 
10
10
  def for(action)
11
- @organizer.before_actions = [
11
+ @organizer.append_before_actions(
12
12
  lambda do |ctx|
13
13
  if ctx.current_action == action
14
+ # The last `:_before_actions` hook is for
15
+ # ContextFactory, remove it, so it won't
16
+ # be invoked again
17
+ ctx[:_before_actions].pop
18
+
14
19
  throw(:return_ctx_from_execution, ctx)
15
20
  end
16
21
  end
17
- ]
22
+ )
18
23
 
19
24
  self
20
25
  end
21
26
 
22
- def with(ctx)
23
- escaped = catch(:return_ctx_from_execution) do
24
- @organizer.call(ctx)
27
+ # More than one arguments can be passed to the
28
+ # Organizer's #call method
29
+ def with(*args, &block)
30
+ catch(:return_ctx_from_execution) do
31
+ @organizer.call(*args, &block)
25
32
  end
26
-
27
- escaped
28
33
  end
29
34
 
30
35
  def initialize(organizer)