light-service 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +34 -0
  3. data/.travis.yml +16 -2
  4. data/Appraisals +7 -0
  5. data/Gemfile +2 -0
  6. data/README.md +126 -16
  7. data/RELEASES.md +4 -0
  8. data/Rakefile +3 -0
  9. data/gemfiles/activesupport_3.gemfile +8 -0
  10. data/gemfiles/activesupport_3.gemfile.lock +63 -0
  11. data/gemfiles/activesupport_4.gemfile +8 -0
  12. data/gemfiles/activesupport_4.gemfile.lock +71 -0
  13. data/lib/light-service.rb +1 -1
  14. data/lib/light-service/action.rb +6 -8
  15. data/lib/light-service/configuration.rb +0 -2
  16. data/lib/light-service/context.rb +32 -22
  17. data/lib/light-service/context/key_verifier.rb +87 -83
  18. data/lib/light-service/localization_adapter.rb +10 -7
  19. data/lib/light-service/organizer.rb +6 -3
  20. data/lib/light-service/organizer/with_reducer.rb +53 -39
  21. data/lib/light-service/organizer/with_reducer_factory.rb +3 -4
  22. data/lib/light-service/organizer/with_reducer_log_decorator.rb +81 -51
  23. data/lib/light-service/version.rb +2 -1
  24. data/light-service.gemspec +4 -4
  25. data/resources/fail_actions.png +0 -0
  26. data/resources/skip_actions.png +0 -0
  27. data/spec/acceptance/around_each_spec.rb +27 -0
  28. data/spec/acceptance/include_warning_spec.rb +6 -2
  29. data/spec/acceptance/log_from_organizer_spec.rb +39 -18
  30. data/spec/acceptance/message_localization_spec.rb +23 -23
  31. data/spec/acceptance/rollback_spec.rb +1 -3
  32. data/spec/action_expected_keys_spec.rb +32 -19
  33. data/spec/action_promised_keys_spec.rb +72 -54
  34. data/spec/action_spec.rb +23 -5
  35. data/spec/context_spec.rb +21 -17
  36. data/spec/localization_adapter_spec.rb +14 -10
  37. data/spec/organizer/with_reducer_spec.rb +19 -2
  38. data/spec/organizer_key_aliases_spec.rb +6 -5
  39. data/spec/organizer_spec.rb +32 -56
  40. data/spec/sample/calculates_tax_spec.rb +17 -9
  41. data/spec/sample/provides_free_shipping_action_spec.rb +3 -7
  42. data/spec/sample/tax/calculates_order_tax_action.rb +3 -2
  43. data/spec/sample/tax/calculates_tax.rb +3 -4
  44. data/spec/sample/tax/looks_up_tax_percentage_action.rb +10 -8
  45. data/spec/sample/tax/provides_free_shipping_action.rb +2 -4
  46. data/spec/spec_helper.rb +0 -1
  47. data/spec/test_doubles.rb +38 -15
  48. metadata +38 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 55ba3cb29c4ce4e3e59c3129bc8baf4287efe0c2
4
- data.tar.gz: ba0a1fc827bee6a92346c886948c024753796880
3
+ metadata.gz: 25a715c986fab5133c64ef50160f93524bfdcca2
4
+ data.tar.gz: 1034f64ec7c50288024a15cce41b29082983941a
5
5
  SHA512:
6
- metadata.gz: 64a014abda6e16cd5d1c4a62ea188b576a8878e46304f90d5768666f5e944869fc84814c250dfc986b7be67eb7c76fd736d74363171445f5401b5457704d36d4
7
- data.tar.gz: 77d665f3fcd08eb79c598871fe1c97e6bba9e34b773fe2b083ed8001fa356c383a13fba2195157d187d84110c57fc929f0de81874de07207dfa529b821319ed2
6
+ metadata.gz: 1781b4242bb655bf55e257d65f97a4e4d6bf2d7eb0899bbfc509e10f62fce25d594de73bdcf426490052e3ca54a764c5ae1da049d107693c5a23d69dbe704867
7
+ data.tar.gz: 4009c8844215156df49a668a6c953cb60ae577438d35fb3452309fb005084d64b24317de65f4544a4a93af3ebc058edf9174adbbed58c5f227b791d99577bf5c
@@ -0,0 +1,34 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'lib/light-service.rb'
4
+ - 'vendor/bundle/**/*'
5
+ - 'gemfiles/vendor/bundle/**/*'
6
+ - 'bin/*'
7
+ - 'light-service.gemspec'
8
+
9
+ Documentation:
10
+ Enabled: false
11
+
12
+ Style/Encoding:
13
+ Enabled: false
14
+
15
+ Style/StringLiterals:
16
+ Enabled: false
17
+
18
+ Style/TrailingBlankLines:
19
+ Enabled: false
20
+
21
+ Style/RedundantReturn:
22
+ Enabled: false
23
+
24
+ Style/HashSyntax:
25
+ EnforcedStyle: hash_rockets
26
+
27
+ Style/PredicateName:
28
+ Enabled: false
29
+
30
+ Style/TrivialAccessors:
31
+ AllowPredicates: true
32
+
33
+ Metrics/MethodLength:
34
+ Max: 15
@@ -1,7 +1,21 @@
1
1
  language: ruby
2
+
2
3
  rvm:
3
- - 1.9.3
4
4
  - 2.0.0
5
5
  - 2.1.2
6
+ - 2.2.0
7
+ - 2.3.0
8
+
9
+ before_install:
10
+ - 'echo ''gem: --no-ri --no-rdoc'' > ~/.gemrc'
11
+ - gem install bundler
12
+ - bundle install --path vendor/bundle
13
+
6
14
  # uncomment this line if your project needs to run something other than `rake`:
7
- script: bundle exec rspec spec
15
+ script:
16
+ - bundle exec rspec spec
17
+ - bundle exec rubocop
18
+
19
+ gemfile:
20
+ - gemfiles/activesupport_3.gemfile
21
+ - gemfiles/activesupport_4.gemfileset
@@ -0,0 +1,7 @@
1
+ appraise "activesupport-3" do
2
+ gem "activesupport", "~> 3.0"
3
+ end
4
+
5
+ appraise "activesupport-4" do
6
+ gem "activesupport", "~> 4.0"
7
+ end
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in light_service.gemspec
4
4
  gemspec
5
+
6
+ gem 'appraisal', '~> 2.0'
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![LightService](https://raw.github.com/adomokos/light-service/master/resources/light-service.png)
1
+ ![LightService](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
4
  [![Build Status](https://secure.travis-ci.org/adomokos/light-service.png)](http://travis-ci.org/adomokos/light-service)
@@ -62,7 +62,7 @@ and executes them one-by-one. Then you need to create the actions which will onl
62
62
 
63
63
  This is how the organizer and actions interact with eachother:
64
64
 
65
- ![LightService](https://raw.github.com/adomokos/light-service/master/resources/organizer_and_actions.png)
65
+ ![LightService](resources/organizer_and_actions.png)
66
66
 
67
67
  ```ruby
68
68
  class CalculatesTax
@@ -146,6 +146,124 @@ end
146
146
  I gave a [talk at RailsConf 2013](http://www.adomokos.com/2013/06/simple-and-elegant-rails-code-with.html) on
147
147
  simple and elegant Rails code where I told the story of how LightService was extracted from the projects I had worked on.
148
148
 
149
+
150
+ ## Table of Content
151
+ * [Stopping the Series of Actions](#stopping-the-series-of-actions)
152
+ * [Failing the Context](#failing-the-context)
153
+ * [Skipping the Rest of the Actions](#skipping-the-rest-of-the-actions)
154
+ * [Benchmarking Actions with Around Advice](#benchmarking-actions-with-around-advice)
155
+ * [Key Aliases](#key-aliases)
156
+ * [Logging](#logging)
157
+ * [Error Codes](#error-codes)
158
+ * [Action Rollback](#action-rollback)
159
+ * [Localizing Messages](#localizing-messages)
160
+
161
+ ## Stopping the Series of Actions
162
+ When nothing unexpected happens during the organizer's call, the returned `context` will be successful. Here is how you can check for this:
163
+ ```ruby
164
+ class SomeController < ApplicationController
165
+ def index
166
+ result_context = SomeOrganizer.call(current_user.id)
167
+
168
+ if result_context.success?
169
+ redirect_to foo_path, :notice => "Everything went OK! Thanks!"
170
+ else
171
+ flash[:error] = result_context.message
172
+ render :action => "new"
173
+ end
174
+ end
175
+ end
176
+ ```
177
+ However, sometimes not everything will play out as you expect it. An external API call might not be available or some complex business logic will need to stop the processing of the Series of Actions.
178
+ You have two options to stop the call chain:
179
+
180
+ 1. Failing the context
181
+ 2. Skipping the rest of the actions
182
+
183
+ ### Failing the Context
184
+ When something goes wrong in an action and you want to halt the chain, you need to call `fail!` on the context object. This will push the context in a failure state (`context.failure? # will evalute to true`).
185
+ The context's `fail!` method can take an optional message argument, this message might help describing what went wrong.
186
+ In case you need to return immediately from the point of failure, you have to do that by calling `next context`.
187
+
188
+ Here is an example:
189
+ ```ruby
190
+ class SubmitsOrderAction
191
+ extend LightService::Action
192
+ expects :order, :mailer
193
+
194
+ executed do |context|
195
+ unless context.order.submit_order_succeful?
196
+ context.fail!("Failed to submit the order")
197
+ next context
198
+ end
199
+
200
+ context.mailer.send_order_notification!
201
+ end
202
+ end
203
+ ```
204
+ ![LightService](resources/fail_actions.png)
205
+
206
+ In the example above the organizer called 4 actions. The first 2 actions got executed successfully. The 3rd had a failure, that pushed the context into a failure state and the 4th action was skipped.
207
+
208
+ ### Skipping the rest of the actions
209
+ You can skip the rest of the actions by calling `context.skip_all!`. This behaves very similarly to the above-mentioned `fail!` mechanism, except this will not push the context into a failure state.
210
+ A good use case for this is executing the first couple of action and based on a check you might not need to execute the rest.
211
+ Here is an example of how you do it:
212
+ ```ruby
213
+ class ChecksOrderStatusAction
214
+ extend LightService::Action
215
+ expects :order
216
+
217
+ executed do |context|
218
+ if context.order.send_notification?
219
+ context.skip_all!("Everything is good, no need to execute the rest of the actions")
220
+ end
221
+ end
222
+ end
223
+ ```
224
+ ![LightService](resources/skip_actions.png)
225
+
226
+ In the example above the organizer called 4 actions. The first 2 actions got executed successfully. The 3rd decided to skip the rest, the 4th action was not invoked. The context was successful.
227
+
228
+
229
+ ## Benchmarking Actions with Around Advice
230
+ Benchmarking your action is needed when you profile the series of actions. You could add benchmarking logic to each and every action, however, that would blur the business logic you have in your actions.
231
+
232
+ Take advantage of the organizer's `around_each` method, which wraps the action calls as its reducing them in order.
233
+
234
+ Check out this example:
235
+
236
+ ```ruby
237
+ class LogDuration
238
+ def self.call(action, context)
239
+ start_time = Time.now
240
+ result = yield
241
+ duration = Time.now - start_time
242
+ LightService::Configuration.logger.info(
243
+ :action => action,
244
+ :duration => duration
245
+ )
246
+
247
+ result
248
+ end
249
+ end
250
+
251
+ class CalculatesTax
252
+ extend LightService::Organizer
253
+
254
+ def self.for_order(order)
255
+ with(:order => order).around_each(LogDuration).reduce(
256
+ LooksUpTaxPercentageAction,
257
+ CalculatesOrderTaxAction,
258
+ ProvidesFreeShippingAction
259
+ )
260
+ end
261
+ end
262
+ ```
263
+
264
+ Any object passed into `around_each` must respond to #call with two arguments: the action name and the context it will execute with. It is also passed a block, where LightService's action execution will be done in, so the result must be returned. While this is a little work, it also gives you before and after state access to the data for any auditing and/or checks you may need to accomplish.
265
+
266
+
149
267
  ## Expects and Promises
150
268
  The `expects` and `promises` macros are rules for the inputs/outputs of an action.
151
269
  `expects` describes what keys it needs to execute, and `promises` makes sure the keys are in the context after the
@@ -201,9 +319,9 @@ end
201
319
  Take a look at [this spec](spec/action_expects_and_promises_spec.rb) to see the refactoring in action.
202
320
 
203
321
  ## Key Aliases
204
- The `aliases` macro sets up pairs of keys and aliases in an Organizer. Actions can access the context using the aliases.
322
+ The `aliases` macro sets up pairs of keys and aliases in an organizer. Actions can access the context using the aliases.
205
323
 
206
- This allows you to put together existing Actions from different sources and have them work together without having to modify their code. Aliases will work with or without Action `expects`.
324
+ This allows you to put together existing actions from different sources and have them work together without having to modify their code. Aliases will work with or without action `expects`.
207
325
 
208
326
  Say for example you have actions `AnAction` and `AnotherAction` that you've used in previous projects. `AnAction` provides `:my_key` but `AnotherAction` needs to use that value but expects `:key_alias`. You can use them together in an organizer like so:
209
327
 
@@ -241,7 +359,6 @@ end
241
359
  ```
242
360
 
243
361
  ## Logging
244
-
245
362
  Enable LightService's logging to better understand what goes on within the series of actions,
246
363
  what's in the context or when an action fails.
247
364
 
@@ -300,7 +417,6 @@ I, [DATE] INFO -- : [LightService] - context message: Can't make a latte with a
300
417
  ```
301
418
 
302
419
  ## Error Codes
303
-
304
420
  You can add some more structure to your error handling by taking advantage of error codes in the context.
305
421
  Normally, when something goes wrong in your actions, you fail the process by setting the context to failure:
306
422
 
@@ -337,7 +453,6 @@ end
337
453
  ```
338
454
 
339
455
  ## Action Rollback
340
-
341
456
  Sometimes your action has to undo what it did when an error occurs. Think about a chain of actions where you need
342
457
  to persist records in your data store in one action and you have to call an external service in the next. What happens if there
343
458
  is an error when you call the external service? You want to remove the records you previously saved. You can do it now with
@@ -380,7 +495,6 @@ The actions are rolled back in reversed order from the point of failure starting
380
495
  See [this](spec/acceptance/rollback_spec.rb) acceptance test to learn more about this functionality.
381
496
 
382
497
  ## Localizing Messages
383
-
384
498
  By default LightService provides a mechanism for easily translating your error or success messages via I18n. You can also provide your own custom localization adapter if your application's logic is more complex than what is shown here.
385
499
 
386
500
  ```ruby
@@ -446,7 +560,7 @@ LightService::Configuration.localization_adapter = MyLocalizer.new
446
560
 
447
561
  # lib/my_localizer.rb
448
562
  class MyLocalizer < LightService::LocalizationAdapter
449
-
563
+
450
564
  # I just want to change the default lookup path
451
565
  # => "light_service.failures.payment_gateway/capture_funds"
452
566
  def i18n_scope_from_class(action_class, type)
@@ -455,12 +569,12 @@ class MyLocalizer < LightService::LocalizationAdapter
455
569
  end
456
570
  ```
457
571
 
458
- ## Requirements
572
+ To get the value of a `fail!` or `succeed!` message, simply call `#message` on the returned context.
459
573
 
460
- This gem requires ruby 1.9.x
574
+ ## Requirements
575
+ This gem requires ruby 2.x
461
576
 
462
577
  ## Installation
463
-
464
578
  Add this line to your application's Gemfile:
465
579
 
466
580
  gem 'light-service'
@@ -474,14 +588,12 @@ Or install it yourself as:
474
588
  $ gem install light-service
475
589
 
476
590
  ## Usage
477
-
478
591
  Based on the refactoring example above, just create an organizer object that calls the
479
592
  actions in order and write code for the actions. That's it.
480
593
 
481
594
  For further examples, please visit the project's [Wiki](https://github.com/adomokos/light-service/wiki).
482
595
 
483
596
  ## Contributing
484
-
485
597
  1. Fork it
486
598
  2. Create your feature branch (`git checkout -b my-new-feature`)
487
599
  3. Commit your changes (`git commit -am 'Added some feature'`)
@@ -491,9 +603,7 @@ For further examples, please visit the project's [Wiki](https://github.com/adomo
491
603
  Huge thanks to the [contributors](https://github.com/adomokos/light-service/graphs/contributors)!
492
604
 
493
605
  ## Release Notes
494
-
495
606
  Follow the release notes in this [document](https://github.com/adomokos/light-service/blob/master/RELEASES.md).
496
607
 
497
608
  ## License
498
-
499
609
  LightService is released under the [MIT License](http://www.opensource.org/licenses/MIT).
@@ -1,5 +1,9 @@
1
1
  A brief list of new features and changes introduced with the specified version.
2
2
 
3
+ ### 0.6.1
4
+ * Introducing [around_each](https://github.com/adomokos/light-service/pull/79) for AOP style logging and code execution
5
+ * Introducing [Rubocop](https://github.com/adomokos/light-service/commit/39aa7ea39f69a16c2df66b213fb6d638796e25f2) to the project, forcing consistant style
6
+
3
7
  ### 0.6.0
4
8
  * Using [extend](https://github.com/adomokos/light-service/pull/64) for using class methods in Actions and Organizers
5
9
  * Setting [key aliases](https://github.com/adomokos/light-service/pull/69) for the Context from the Organizer
data/Rakefile CHANGED
@@ -1,2 +1,5 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
+
4
+ require "rubygems"
5
+ require "bundler/setup"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal", "~> 2.0"
6
+ gem "activesupport", "~> 3.0"
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,63 @@
1
+ PATH
2
+ remote: ../
3
+ specs:
4
+ light-service (0.6.0)
5
+ activesupport (>= 3.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (3.2.22)
11
+ i18n (~> 0.6, >= 0.6.4)
12
+ multi_json (~> 1.0)
13
+ appraisal (2.0.2)
14
+ bundler
15
+ rake
16
+ thor (>= 0.14.0)
17
+ coderay (1.0.9)
18
+ diff-lcs (1.2.5)
19
+ i18n (0.7.0)
20
+ method_source (0.8.2)
21
+ multi_json (1.11.2)
22
+ pry (0.9.12.2)
23
+ coderay (~> 1.0.5)
24
+ method_source (~> 0.8)
25
+ slop (~> 3.4)
26
+ rake (10.4.2)
27
+ rspec (3.3.0)
28
+ rspec-core (~> 3.3.0)
29
+ rspec-expectations (~> 3.3.0)
30
+ rspec-mocks (~> 3.3.0)
31
+ rspec-core (3.3.2)
32
+ rspec-support (~> 3.3.0)
33
+ rspec-expectations (3.3.1)
34
+ diff-lcs (>= 1.2.0, < 2.0)
35
+ rspec-support (~> 3.3.0)
36
+ rspec-its (1.2.0)
37
+ rspec-core (>= 3.0.0)
38
+ rspec-expectations (>= 3.0.0)
39
+ rspec-mocks (3.3.2)
40
+ diff-lcs (>= 1.2.0, < 2.0)
41
+ rspec-support (~> 3.3.0)
42
+ rspec-support (3.3.0)
43
+ simplecov (0.7.1)
44
+ multi_json (~> 1.0)
45
+ simplecov-html (~> 0.7.1)
46
+ simplecov-html (0.7.1)
47
+ slop (3.6.0)
48
+ thor (0.19.1)
49
+
50
+ PLATFORMS
51
+ ruby
52
+
53
+ DEPENDENCIES
54
+ activesupport (~> 3.0)
55
+ appraisal (~> 2.0)
56
+ light-service!
57
+ pry (= 0.9.12.2)
58
+ rspec (~> 3.0)
59
+ rspec-its (~> 1.0)
60
+ simplecov (~> 0.7.1)
61
+
62
+ BUNDLED WITH
63
+ 1.10.6
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal", "~> 2.0"
6
+ gem "activesupport", "~> 4.0"
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,71 @@
1
+ PATH
2
+ remote: ../
3
+ specs:
4
+ light-service (0.6.0)
5
+ activesupport (>= 3.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (4.2.3)
11
+ i18n (~> 0.7)
12
+ json (~> 1.7, >= 1.7.7)
13
+ minitest (~> 5.1)
14
+ thread_safe (~> 0.3, >= 0.3.4)
15
+ tzinfo (~> 1.1)
16
+ appraisal (2.0.2)
17
+ bundler
18
+ rake
19
+ thor (>= 0.14.0)
20
+ coderay (1.0.9)
21
+ diff-lcs (1.2.5)
22
+ i18n (0.7.0)
23
+ json (1.8.3)
24
+ method_source (0.8.2)
25
+ minitest (5.7.0)
26
+ multi_json (1.11.2)
27
+ pry (0.9.12.2)
28
+ coderay (~> 1.0.5)
29
+ method_source (~> 0.8)
30
+ slop (~> 3.4)
31
+ rake (10.4.2)
32
+ rspec (3.3.0)
33
+ rspec-core (~> 3.3.0)
34
+ rspec-expectations (~> 3.3.0)
35
+ rspec-mocks (~> 3.3.0)
36
+ rspec-core (3.3.2)
37
+ rspec-support (~> 3.3.0)
38
+ rspec-expectations (3.3.1)
39
+ diff-lcs (>= 1.2.0, < 2.0)
40
+ rspec-support (~> 3.3.0)
41
+ rspec-its (1.2.0)
42
+ rspec-core (>= 3.0.0)
43
+ rspec-expectations (>= 3.0.0)
44
+ rspec-mocks (3.3.2)
45
+ diff-lcs (>= 1.2.0, < 2.0)
46
+ rspec-support (~> 3.3.0)
47
+ rspec-support (3.3.0)
48
+ simplecov (0.7.1)
49
+ multi_json (~> 1.0)
50
+ simplecov-html (~> 0.7.1)
51
+ simplecov-html (0.7.1)
52
+ slop (3.6.0)
53
+ thor (0.19.1)
54
+ thread_safe (0.3.5)
55
+ tzinfo (1.2.2)
56
+ thread_safe (~> 0.1)
57
+
58
+ PLATFORMS
59
+ ruby
60
+
61
+ DEPENDENCIES
62
+ activesupport (~> 4.0)
63
+ appraisal (~> 2.0)
64
+ light-service!
65
+ pry (= 0.9.12.2)
66
+ rspec (~> 3.0)
67
+ rspec-its (~> 1.0)
68
+ simplecov (~> 0.7.1)
69
+
70
+ BUNDLED WITH
71
+ 1.10.6