light-service 0.3.6 → 0.4.0

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
  SHA1:
3
- metadata.gz: fc46d59ad8bc4839942dc2977961ce978bae275f
4
- data.tar.gz: a2ca45e8cb9987ab44dfe5af7080dd45a943efc7
3
+ metadata.gz: 41c85f3b70d95380e2e7aa2c289f089b5415c357
4
+ data.tar.gz: 66e9cb452320935e674a1f1250db139b8a7927ed
5
5
  SHA512:
6
- metadata.gz: 061d0554efd265aa95e83cd7197e24aa12207ea3c4119d297bbd036bc9ed15c5be44af52bcb9b00975b3d0a779f039a1008d6efae6d2ceffa562b65ce9f0cbf0
7
- data.tar.gz: 66fe3f85032dc702b60edcb69cab36a2c6a6bd088f2f04a92a2554aff016d58889092f49baded959e6b161a3bd0490285ce0cc95de54647218cfb6d642ad7087
6
+ metadata.gz: 5783a1a16b678ad68c7352c74a75c506f8b1c3c41e25b43fa77e154f818a04bcff3a6adc641dd3bba779e6fd6cfe319916f4d1c4e3eacd8c06a192705b16a8ce
7
+ data.tar.gz: 0ce7a0c69be92173956db9280aac103654e961913242056c7a7a56264b6b2d22bf3322c764c5ea5d7188a09ebdac0b87de4100e62f0a35ba5508fd37ceffdb8a
data/README.md CHANGED
@@ -200,6 +200,71 @@ end
200
200
 
201
201
  Take a look at [this spec](spec/action_expects_and_promises_spec.rb) to see the refactoring in action.
202
202
 
203
+ ## Logging
204
+
205
+ Enable LightService's logging to better understand what goes on within the series of actions,
206
+ what's in the context or when an action fails.
207
+
208
+ Logging in LightService is turned off by default. However, turning it on is simple. Add this line to your
209
+ project's config file:
210
+
211
+ ```ruby
212
+ LightService::Configuration.logger = Logger.new(STDOUT)
213
+ ```
214
+
215
+ You can turn off the logger by setting it to nil or `/dev/null`.
216
+
217
+ ```ruby
218
+ LightService::Configuration.logger = Logger.new('/dev/null')
219
+ ```
220
+
221
+ In case you're using LightService with Rails, feel free use Rails logger for LightService:
222
+
223
+ ```ruby
224
+ LightService::Configuration.logger = Rails.logger # or config.logger in one of the config files
225
+ ```
226
+
227
+ Watch the console while you are executing the workflow through the organizer. You should see something like this:
228
+
229
+ ```bash
230
+ I, [DATE] INFO -- : [LightService] - calling organizer <TestDoubles::MakesTeaAndCappuccino>
231
+ I, [DATE] INFO -- : [LightService] - keys in context: :tea, :milk, :coffee
232
+ I, [DATE] INFO -- : [LightService] - executing <TestDoubles::MakesTeaWithMilkAction>
233
+ I, [DATE] INFO -- : [LightService] - expects: :tea, :milk
234
+ I, [DATE] INFO -- : [LightService] - promises: :milk_tea
235
+ I, [DATE] INFO -- : [LightService] - keys in context: :tea, :milk, :coffee, :milk_tea
236
+ I, [DATE] INFO -- : [LightService] - executing <TestDoubles::MakesLatteAction>
237
+ I, [DATE] INFO -- : [LightService] - expects: :coffee, :milk
238
+ I, [DATE] INFO -- : [LightService] - promises: :latte
239
+ I, [DATE] INFO -- : [LightService] - keys in context: :tea, :milk, :coffee, :milk_tea, :latte
240
+ ```
241
+
242
+ The log provides a blueprint of the series of actions. You can see what organizer is invoked, what actions
243
+ are called in what order, what do the expect and promise and most importantly what keys you have in the context
244
+ after each action is executed.
245
+
246
+ The logger logs its messages with "INFO" level. The exception to this is the event when an action fails the context.
247
+ That message is logged with "WARN" level:
248
+
249
+ ```bash
250
+ I, [DATE] INFO -- : [LightService] - calling organizer <TestDoubles::MakesCappuccinoAddsTwoAndFails>
251
+ I, [DATE] INFO -- : [LightService] - keys in context: :milk, :coffee
252
+ W, [DATE] WARN -- : [LightService] - :-((( <TestDoubles::MakesLatteAction> has failed...
253
+ W, [DATE] WARN -- : [LightService] - context message: Can't make a latte from a milk that's too hot!
254
+ ```
255
+
256
+ The log message will show you what message was added to the context when the action pushed the
257
+ context into a failure state.
258
+
259
+ The event of skipping the rest of the actions is also captured by its logs:
260
+
261
+ ```bash
262
+ I, [DATE] INFO -- : [LightService] - calling organizer <TestDoubles::MakesCappuccinoSkipsAddsTwo>
263
+ I, [DATE] INFO -- : [LightService] - keys in context: :milk, :coffee
264
+ I, [DATE] INFO -- : [LightService] - ;-) <TestDoubles::MakesLatteAction> has decided to skip the rest of the actions
265
+ I, [DATE] INFO -- : [LightService] - context message: Can't make a latte with a fatty milk like that!
266
+ ```
267
+
203
268
  ## Error Codes
204
269
 
205
270
  You can add some more structure to your error handling by taking advantage of error codes in the context.
@@ -273,40 +338,8 @@ For further examples, please visit the project's [Wiki](https://github.com/adomo
273
338
  Huge thanks to the [contributors](https://github.com/adomokos/light-service/graphs/contributors)!
274
339
 
275
340
  ## Release Notes
276
- ### 0.3.6
277
- * [Collecting](https://github.com/adomokos/light-service/commit/29817de3ad589441788077368ad1d7e723286def) the `expects` and `promises` keys when they are called multiple times in an action
278
-
279
- ### 0.3.5
280
- * remove previously deprecated method Context#context_hash
281
- * [Skipping](https://github.com/adomokos/light-service/commit/d2bd05455a7e4f78aa448db1ea1d692f7b8b67d3) the promised keys check in the context when the context is in failure state
282
-
283
- ### 0.3.4
284
- * The method call `with` is [now optional](https://github.com/adomokos/light-service/blob/master/spec/organizer_spec.rb#L18) in case you have nothing to put into the context.
285
- * Action name is being displayed in the error message when the expected or promised key is not in the context.
286
-
287
- ### 0.3.3
288
- * Switching the promises and expects key accessors from Action to Context
289
-
290
- ### 0.3.2
291
- * Fixing documentation and using separate arguments instead of a hash when setting the context to failure with error code
292
-
293
- ### 0.3.1
294
- * Adding [error codes](https://github.com/adomokos/light-service#error-codes) to the context
295
-
296
- ### 0.3.0
297
- * Adding the `expects` and `promises` macros - Read more about it in [this blog post](http://www.adomokos.com/2014/05/expects-and-promises-in-lightservice.html)
298
-
299
- ### 0.2.2
300
- * Adding the gem version icon to README
301
- * Actions can be invoked now [without arguments](https://github.com/adomokos/light-service/commit/244d5f03b9dbf61c97c1fdb865e6587f9aea177d), this makes it super easy to play with an action in the command line
302
-
303
- ### 0.2.1
304
- * [Improving](https://github.com/adomokos/light-service/commit/fc7043241396b4a2556e9664c13c6929f8330025) deprecation warning for the renamed methods
305
- * Making the message an optional argument for `succeed!` and `fail!` methods
306
341
 
307
- ### 0.2.0
308
- * [Renaming](https://github.com/adomokos/light-service/commit/8d40ff7d393a157a8a558f9e4e021b8731550834) the `set_success!` and `set_failure!` methods to `succeed!` and `fail!`
309
- * [Throwing](https://github.com/adomokos/light-service/commit/5ef315b8aeeafc99e38676adad3c11df5d93b0e3) an ArgumentError if the `make` method's argument is not Hash or LightService::Context
342
+ Follow the release notes in this [document](https://github.com/adomokos/light-service/blob/master/RELEASES.md).
310
343
 
311
344
  ## License
312
345
 
data/RELEASES.md ADDED
@@ -0,0 +1,39 @@
1
+ A brief list of new features and changes introduced with the specified version.
2
+
3
+ ### 0.4.0
4
+ * Adding [logging](https://github.com/adomokos/light-service#logging) to LightService
5
+
6
+ ### 0.3.6
7
+ * [Collecting](https://github.com/adomokos/light-service/commit/29817de3ad589441788077368ad1d7e723286def) the `expects` and `promises` keys when they are called multiple times in an action
8
+
9
+ ### 0.3.5
10
+ * remove previously deprecated method Context#context_hash
11
+ * [Skipping](https://github.com/adomokos/light-service/commit/d2bd05455a7e4f78aa448db1ea1d692f7b8b67d3) the promised keys check in the context when the context is in failure state
12
+
13
+ ### 0.3.4
14
+ * The method call `with` is [now optional](https://github.com/adomokos/light-service/blob/master/spec/organizer_spec.rb#L18) in case you have nothing to put into the context.
15
+ * Action name is being displayed in the error message when the expected or promised key is not in the context.
16
+
17
+ ### 0.3.3
18
+ * Switching the promises and expects key accessors from Action to Context
19
+
20
+ ### 0.3.2
21
+ * Fixing documentation and using separate arguments instead of a hash when setting the context to failure with error code
22
+
23
+ ### 0.3.1
24
+ * Adding [error codes](https://github.com/adomokos/light-service#error-codes) to the context
25
+
26
+ ### 0.3.0
27
+ * Adding the `expects` and `promises` macros - Read more about it in [this blog post](http://www.adomokos.com/2014/05/expects-and-promises-in-lightservice.html)
28
+
29
+ ### 0.2.2
30
+ * Adding the gem version icon to README
31
+ * Actions can be invoked now [without arguments](https://github.com/adomokos/light-service/commit/244d5f03b9dbf61c97c1fdb865e6587f9aea177d), this makes it super easy to play with an action in the command line
32
+
33
+ ### 0.2.1
34
+ * [Improving](https://github.com/adomokos/light-service/commit/fc7043241396b4a2556e9664c13c6929f8330025) deprecation warning for the renamed methods
35
+ * Making the message an optional argument for `succeed!` and `fail!` methods
36
+
37
+ ### 0.2.0
38
+ * [Renaming](https://github.com/adomokos/light-service/commit/8d40ff7d393a157a8a558f9e4e021b8731550834) the `set_success!` and `set_failure!` methods to `succeed!` and `fail!`
39
+ * [Throwing](https://github.com/adomokos/light-service/commit/5ef315b8aeeafc99e38676adad3c11df5d93b0e3) an ArgumentError if the `make` method's argument is not Hash or LightService::Context
data/lib/light-service.rb CHANGED
@@ -1,6 +1,12 @@
1
+ require 'logger'
2
+
1
3
  require "light-service/version"
2
4
 
5
+ require 'light-service/configuration'
3
6
  require 'light-service/context'
4
7
  require 'light-service/context_key_verifier'
8
+ require 'light-service/organizer/with_reducer'
9
+ require 'light-service/organizer/with_reducer_log_decorator'
10
+ require 'light-service/organizer/with_reducer_factory'
5
11
  require 'light-service/action'
6
12
  require 'light-service/organizer'
@@ -0,0 +1,19 @@
1
+ module LightService
2
+ class Configuration
3
+
4
+ class << self
5
+ attr_writer :logger
6
+ end
7
+
8
+ def self.logger
9
+ @logger ||= self._default_logger
10
+ end
11
+
12
+ def self._default_logger
13
+ logger = ::Logger.new("/dev/null")
14
+ logger.level = ::Logger::INFO
15
+ logger
16
+ end
17
+
18
+ end
19
+ end
@@ -7,7 +7,10 @@ module LightService
7
7
  class << self
8
8
  def verify_expected_keys_are_in_context(context, action)
9
9
  verify_keys_are_in_context(context, action.expected_keys) do |not_found_keys|
10
- fail ExpectedKeysNotInContextError, "expected #{format_keys(not_found_keys)} to be in the context during #{action}"
10
+ error_message = "expected #{format_keys(not_found_keys)} to be in the context during #{action}"
11
+
12
+ Configuration.logger.error error_message
13
+ fail ExpectedKeysNotInContextError, error_message
11
14
  end
12
15
  end
13
16
 
@@ -15,7 +18,10 @@ module LightService
15
18
  return context if context.failure?
16
19
 
17
20
  verify_keys_are_in_context(context, action.promised_keys) do |not_found_keys|
18
- fail PromisedKeysNotInContextError, "promised #{format_keys(not_found_keys)} to be in the context during #{action}"
21
+ error_message = "promised #{format_keys(not_found_keys)} to be in the context during #{action}"
22
+
23
+ Configuration.logger.error error_message
24
+ fail PromisedKeysNotInContextError, error_message
19
25
  end
20
26
  end
21
27
 
@@ -4,25 +4,25 @@ module LightService
4
4
  base_class.extend ClassMethods
5
5
  end
6
6
 
7
+ # In case this module is included
7
8
  module ClassMethods
8
9
  def with(data)
9
- new.with(data)
10
+ WithReducerFactory.make(self).with(data)
10
11
  end
11
12
 
12
- def reduce(actions)
13
- new.with.reduce(actions)
13
+ def reduce(*actions)
14
+ WithReducerFactory.make(self).with.reduce(actions)
14
15
  end
15
16
  end
16
17
 
18
+ # Provide hooks for extending the class with these methods
17
19
  def with(data = {})
18
- @context = LightService::Context.make(data)
19
- self
20
+ WithReducerFactory.make(self).with(data)
20
21
  end
21
22
 
22
23
  def reduce(*actions)
23
- raise "No action(s) were provided" if actions.empty?
24
- actions.flatten!
25
- actions.reduce(@context) { |context, action| action.execute(context) }
24
+ WithReducerFactory.make(self).with.reduce(actions)
26
25
  end
26
+
27
27
  end
28
28
  end
@@ -0,0 +1,21 @@
1
+ module LightService; module Organizer
2
+ class WithReducer
3
+ attr_reader :context
4
+
5
+ def with(data = {})
6
+ @context = LightService::Context.make(data)
7
+ self
8
+ end
9
+
10
+ def reduce(*actions)
11
+ raise "No action(s) were provided" if actions.empty?
12
+ actions.flatten!
13
+
14
+ actions.reduce(context) do |context, action|
15
+ result = action.execute(context)
16
+ yield(context, action) if block_given?
17
+ result
18
+ end
19
+ end
20
+ end
21
+ end; end
@@ -0,0 +1,14 @@
1
+ module LightService
2
+ module Organizer
3
+ class WithReducerFactory
4
+ def self.make(monitored_organizer)
5
+ if (LightService::Configuration.logger.nil?)
6
+ ::LightService::Organizer::WithReducer.new
7
+ else
8
+ ::LightService::Organizer::WithReducerLogDecorator.new(
9
+ ::LightService::Organizer::WithReducer.new, monitored_organizer)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,65 @@
1
+ module LightService; module Organizer
2
+ class WithReducerLogDecorator
3
+ attr_reader :logger, :decorated, :organizer
4
+
5
+ def initialize(decorated = WithReducer.new, organizer)
6
+ @decorated, @organizer = decorated, organizer
7
+ @logger = ::LightService::Configuration.logger
8
+ @logged = false
9
+ end
10
+
11
+ def with(data = {})
12
+ logger.info("[LightService] - calling organizer <#{organizer.to_s}>")
13
+
14
+ decorated.with(data)
15
+
16
+ logger.info("[LightService] - keys in context: #{extract_keys(decorated.context.keys)}")
17
+ self
18
+ end
19
+
20
+ def reduce(*actions)
21
+ decorated.reduce(*actions) do |context, action|
22
+ next if has_failure?(context, action)
23
+ next if skip_all?(context, action)
24
+
25
+ logger.info("[LightService] - executing <#{action.to_s}>")
26
+ if defined? action.expects and action.expects.any?
27
+ logger.info("[LightService] - expects: #{extract_keys(action.expects)}")
28
+ end
29
+ if defined? action.promises and action.promises.any?
30
+ logger.info("[LightService] - promises: #{extract_keys(action.promises)}")
31
+ end
32
+ logger.info("[LightService] - keys in context: #{extract_keys(context.keys)}")
33
+ end
34
+ end
35
+
36
+ private
37
+ def logged?
38
+ @logged
39
+ end
40
+
41
+ def extract_keys(keys)
42
+ keys.map {|key| ":#{key}" }.join(', ')
43
+ end
44
+
45
+ def has_failure?(context, action)
46
+ return false unless context.respond_to?(:failure?)
47
+ return false unless context.failure?
48
+ return true if logged?
49
+
50
+ logger.warn("[LightService] - :-((( <#{action.to_s}> has failed...")
51
+ logger.warn("[LightService] - context message: #{context.message}")
52
+ @logged = true
53
+ end
54
+
55
+ def skip_all?(context, action)
56
+ return false unless context.respond_to?(:skip_all?)
57
+ return false unless context.skip_all?
58
+ return true if logged?
59
+
60
+ logger.info("[LightService] - ;-) <#{action.to_s}> has decided to skip the rest of the actions")
61
+ logger.info("[LightService] - context message: #{context.message}")
62
+ @logged = true
63
+ end
64
+ end
65
+ end; end
@@ -1,3 +1,3 @@
1
1
  module LightService
2
- VERSION = "0.3.6"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
- class Organizer
4
- include LightService::Organizer
3
+ class AdditionOrganizer
4
+ extend LightService::Organizer
5
5
 
6
6
  def self.add_numbers(number)
7
7
  with(:number => number).reduce(
@@ -25,7 +25,6 @@ end
25
25
  class AddsTwoAction
26
26
  include LightService::Action
27
27
  expects :number
28
- promises :number
29
28
 
30
29
  executed do |context|
31
30
  context.number += 2
@@ -35,17 +34,17 @@ end
35
34
  class AddsThreeAction
36
35
  include LightService::Action
37
36
  expects :number
38
- promises :number
37
+ promises :product
39
38
 
40
39
  executed do |context|
41
- context.number += 3
40
+ context.product = context.number + 3
42
41
  end
43
42
  end
44
43
 
45
- describe Organizer do
44
+ describe AdditionOrganizer do
46
45
  it "Adds 1 2 3 and through to 1" do
47
- result = Organizer.add_numbers 1
48
- number = result.fetch(:number)
46
+ result = AdditionOrganizer.add_numbers 1
47
+ number = result.fetch(:product)
49
48
 
50
49
  expect(number).to eq(7)
51
50
  end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+ require 'stringio'
4
+
5
+ describe "Logs from organizer" do
6
+ def collects_log
7
+ original_logger = LightService::Configuration.logger
8
+
9
+ strio = StringIO.new
10
+ LightService::Configuration.logger = Logger.new(strio)
11
+
12
+ result = yield
13
+
14
+ LightService::Configuration.logger = original_logger
15
+
16
+ strio.string
17
+ end
18
+
19
+ context "when every action has expects or promises" do
20
+ subject(:log_message) do
21
+ collects_log do
22
+ TestDoubles::MakesTeaAndCappuccino.call("black tea", "2% milk", "espresso coffee")
23
+ end
24
+ end
25
+
26
+ it "describes what organizer was invoked" do
27
+ organizer_log_message = "[LightService] - calling organizer <TestDoubles::MakesTeaAndCappuccino>"
28
+ expect(log_message).to include(organizer_log_message)
29
+ end
30
+
31
+ it "describes the actions invoked" do
32
+ organizer_log_message = "[LightService] - executing <TestDoubles::MakesTeaWithMilkAction>"
33
+ expect(log_message).to include(organizer_log_message)
34
+ organizer_log_message = "[LightService] - executing <TestDoubles::MakesLatteAction>"
35
+ expect(log_message).to include(organizer_log_message)
36
+ end
37
+
38
+ it "lists the keys in context before the actions are executed" do
39
+ organizer_log_message = "[LightService] - keys in context: :tea, :milk, :coffee"
40
+ expect(log_message).to include(organizer_log_message)
41
+ end
42
+
43
+ it "lists the expects actions are expecting" do
44
+ organizer_log_message = "[LightService] - expects: :tea, :milk"
45
+ expect(log_message).to include(organizer_log_message)
46
+ organizer_log_message = "[LightService] - expects: :coffee, :milk"
47
+ expect(log_message).to include(organizer_log_message)
48
+ end
49
+
50
+ it "lists the promises actions are promising" do
51
+ organizer_log_message = "[LightService] - promises: :milk_tea"
52
+ expect(log_message).to include(organizer_log_message)
53
+ organizer_log_message = "[LightService] - promises: :latte"
54
+ expect(log_message).to include(organizer_log_message)
55
+ end
56
+
57
+ it "lists the keys in contect after the actions are executed" do
58
+ organizer_log_message = "[LightService] - keys in context: :tea, :milk, :coffee, :milk_tea, :latte"
59
+ expect(log_message).to include(organizer_log_message)
60
+ end
61
+ end
62
+
63
+ context "when NOT every action expects or promises" do
64
+ subject(:log_message) do
65
+ collects_log do
66
+ TestDoubles::MakesCappuccinoAddsTwo.call("2% milk", "espresso coffee")
67
+ end
68
+ end
69
+
70
+ it "describes what organizer was invoked" do
71
+ organizer_log_message = "[LightService] - calling organizer <TestDoubles::MakesCappuccinoAddsTwo>"
72
+ expect(log_message).to include(organizer_log_message)
73
+ end
74
+
75
+ it "does not list empty expects or promises" do
76
+ organizer_log_message = "[LightService] - expects:\n"
77
+ expect(log_message).not_to include(organizer_log_message)
78
+ organizer_log_message = "[LightService] - promises:\n"
79
+ expect(log_message).not_to include(organizer_log_message)
80
+ end
81
+ end
82
+
83
+ context "when the context has failed" do
84
+ subject(:log_message) do
85
+ collects_log do
86
+ TestDoubles::MakesCappuccinoAddsTwoAndFails.call("espresso coffee")
87
+ end
88
+ end
89
+
90
+ it "logs it with a warning" do
91
+ organizer_log_message = "WARN -- : [LightService] - :-((( <TestDoubles::MakesLatteAction> has failed..."
92
+ expect(log_message).to include(organizer_log_message)
93
+ organizer_log_message = "WARN -- : [LightService] - context message: Can't make a latte from a milk that's too hot!"
94
+ expect(log_message).to include(organizer_log_message)
95
+ organizer_log_message = "[LightService] - :-((( <TestDoubles::AddsTwoAction> has failed..."
96
+ expect(log_message).not_to include(organizer_log_message)
97
+ end
98
+ end
99
+
100
+ context "when the context is skipping the rest" do
101
+ subject(:log_message) do
102
+ collects_log do
103
+ TestDoubles::MakesCappuccinoSkipsAddsTwo.call("espresso coffee")
104
+ end
105
+ end
106
+
107
+ it "logs it with a warning" do
108
+ organizer_log_message = "INFO -- : [LightService] - ;-) <TestDoubles::MakesLatteAction> has decided to skip the rest of the actions"
109
+ expect(log_message).to include(organizer_log_message)
110
+ organizer_log_message = "INFO -- : [LightService] - context message: Can't make a latte with a fatty milk like that!"
111
+ expect(log_message).to include(organizer_log_message)
112
+ organizer_log_message = "INFO -- : [LightService] - ;-) <TestDoubles::AddsTwoAction> has decided to skip the rest of the actions"
113
+ expect(log_message).not_to include(organizer_log_message)
114
+ end
115
+ end
116
+ end