light-service 0.11.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/project-build.yml +28 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +6 -0
  5. data/.travis.yml +7 -11
  6. data/Appraisals +4 -4
  7. data/Gemfile +0 -2
  8. data/README.md +257 -42
  9. data/RELEASES.md +21 -0
  10. data/gemfiles/activesupport_5.gemfile +0 -1
  11. data/gemfiles/{activesupport_3.gemfile → activesupport_6.gemfile} +1 -2
  12. data/lib/generators/light_service/action_generator.rb +90 -0
  13. data/lib/generators/light_service/generator_utils.rb +45 -0
  14. data/lib/generators/light_service/organizer_generator.rb +66 -0
  15. data/lib/generators/light_service/templates/action_spec_template.erb +31 -0
  16. data/lib/generators/light_service/templates/action_template.erb +30 -0
  17. data/lib/generators/light_service/templates/organizer_spec_template.erb +20 -0
  18. data/lib/generators/light_service/templates/organizer_template.erb +22 -0
  19. data/lib/light-service.rb +1 -0
  20. data/lib/light-service/action.rb +3 -0
  21. data/lib/light-service/context.rb +8 -4
  22. data/lib/light-service/context/key_verifier.rb +18 -1
  23. data/lib/light-service/localization_adapter.rb +1 -1
  24. data/lib/light-service/organizer.rb +27 -0
  25. data/lib/light-service/organizer/with_reducer.rb +8 -1
  26. data/lib/light-service/organizer/with_reducer_factory.rb +11 -7
  27. data/lib/light-service/organizer/with_reducer_log_decorator.rb +5 -2
  28. data/lib/light-service/version.rb +1 -1
  29. data/light-service.gemspec +10 -4
  30. data/spec/acceptance/after_actions_spec.rb +17 -0
  31. data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
  32. data/spec/acceptance/fail_spec.rb +42 -16
  33. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  34. data/spec/acceptance/organizer/add_to_context_spec.rb +57 -0
  35. data/spec/acceptance/organizer/execute_spec.rb +1 -1
  36. data/spec/acceptance/organizer/execute_with_add_to_context_spec.rb +28 -0
  37. data/spec/acceptance/organizer/iterate_spec.rb +7 -0
  38. data/spec/acceptance/organizer/reduce_if_spec.rb +38 -0
  39. data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
  40. data/spec/action_spec.rb +8 -0
  41. data/spec/lib/generators/action_generator_advanced_spec.rb +43 -0
  42. data/spec/lib/generators/action_generator_simple_spec.rb +37 -0
  43. data/spec/lib/generators/full_generator_test_blobs.rb +193 -0
  44. data/spec/lib/generators/organizer_generator_advanced_spec.rb +37 -0
  45. data/spec/lib/generators/organizer_generator_simple_spec.rb +37 -0
  46. data/spec/organizer_spec.rb +21 -0
  47. data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
  48. data/spec/spec_helper.rb +7 -2
  49. data/spec/test_doubles.rb +47 -0
  50. metadata +111 -21
  51. data/gemfiles/activesupport_3.gemfile.lock +0 -76
  52. data/gemfiles/activesupport_4.gemfile +0 -8
  53. data/gemfiles/activesupport_4.gemfile.lock +0 -82
  54. data/gemfiles/activesupport_5.gemfile.lock +0 -82
  55. data/resources/orchestrators_deprecated.svg +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 591c6fcf80629485e608c41881fc41ebd8db307c
4
- data.tar.gz: fdb6a27728e5201513be58cefd4b4335274509cd
2
+ SHA256:
3
+ metadata.gz: d41ecb22a4b0d3cd9779c1e5726c8f34e6f186a34f04ec97ea5928f682f08fd8
4
+ data.tar.gz: 64f4d7f52607048313e9f39bea158899ef804deb32ae9744715e5969cc7b1567
5
5
  SHA512:
6
- metadata.gz: a2ea84a98fb446cae6616d0c02b30d21e1f0c1a29dc5a5adb7692c4acc9be69a4769ec799c51e5c16ee4888a370d551e915e076b5e7ae9985b28ccac216ea46c
7
- data.tar.gz: 3770373e92ac2498a72bb9833097541a3f35f44c5c383636e0920cd50eb53c4260abccc156772285da1411486ed8e309c10323602b282cab02ecfaf210549194
6
+ metadata.gz: 2345b72e3d9581aecaea2fe5eca1bd3c538cdb0cfcd2d943ac021ca4f3ef31913ac4b06011944c017df330f3ee8dc385793e8185f3e79cba6c51fbe6ee6acba6
7
+ data.tar.gz: 703b9608edfa42bf0d73135fa5b4eafa789e8a31c4239056fd8b7397d6f0a212f8c5c69d66834f1f5b9fe2431b234f8c0373ccaaa86f286d82671bfe8d8b705b
@@ -0,0 +1,28 @@
1
+ name: CI Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu, macos]
16
+ ruby: [2.5.3, 2.6.6, 2.7.2]
17
+ gemfile: [activesupport_5, activesupport_6]
18
+ continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' }}
19
+ env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
20
+ BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
21
+ steps:
22
+ - uses: actions/checkout@v2
23
+ - uses: ruby/setup-ruby@v1
24
+ with:
25
+ ruby-version: ${{ matrix.ruby }}
26
+ - run: bundle install
27
+ - run: bundle exec rspec spec
28
+ - run: bundle exec rubocop
data/.gitignore CHANGED
@@ -17,3 +17,4 @@ test/version_tmp
17
17
  tmp
18
18
  vendor/bundle
19
19
  bin
20
+ .idea
data/.rubocop.yml CHANGED
@@ -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
data/.travis.yml CHANGED
@@ -4,16 +4,14 @@ 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.5.3
8
+ - 2.6.6
9
+ - 2.7.2
11
10
 
12
11
  before_install:
13
12
  - 'echo ''gem: --no-ri --no-rdoc'' > ~/.gemrc'
14
13
  - gem install bundler
15
- - bundle update simplecov
16
- - bundle install --path vendor/bundle
14
+ - bundle install --clean --path vendor/bundle
17
15
 
18
16
  # uncomment this line if your project needs to run something other than `rake`:
19
17
  script:
@@ -21,13 +19,11 @@ script:
21
19
  - bundle exec rubocop
22
20
 
23
21
  gemfile:
24
- - gemfiles/activesupport_3.gemfile
25
22
  - gemfiles/activesupport_4.gemfile
26
23
  - gemfiles/activesupport_5.gemfile
24
+ - gemfiles/activesupport_6.gemfile
27
25
 
28
26
  matrix:
29
27
  exclude:
30
- - rvm: 2.1.8
31
- gemfile: gemfiles/activesupport_5.gemfile
32
- - rvm: 2.2.2
33
- gemfile: gemfiles/activesupport_5.gemfile
28
+ - rvm: 2.7.2
29
+ gemfile: gemfiles/activesupport_4.gemfile
data/Appraisals CHANGED
@@ -1,7 +1,3 @@
1
- appraise "activesupport-3" do
2
- gem "activesupport", "~> 3.0"
3
- end
4
-
5
1
  appraise "activesupport-4" do
6
2
  gem "activesupport", "~> 4.0"
7
3
  end
@@ -9,3 +5,7 @@ end
9
5
  appraise "activesupport-5" do
10
6
  gem "activesupport", "~> 5.0"
11
7
  end
8
+
9
+ appraise "activesupport-6" do
10
+ gem "activesupport", "~> 6.0"
11
+ end
data/Gemfile CHANGED
@@ -2,5 +2,3 @@ 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,19 +1,21 @@
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)
4
+ [![CI Tests](https://github.com/adomokos/light-service/actions/workflows/project-build.yml/badge.svg)](https://github.com/adomokos/light-service/actions/workflows/project-build.yml)
5
+ [![codecov](https://codecov.io/gh/adomokos/light-service/branch/master/graph/badge.svg)](https://codecov.io/gh/adomokos/light-service)
6
+ [![Code Climate](https://codeclimate.com/github/adomokos/light-service.svg)](https://codeclimate.com/github/adomokos/light-service)
6
7
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)
8
+ [![Download Count](https://ruby-gem-downloads-badge.herokuapp.com/light-service?type=total)](https://rubygems.org/gems/light-service)
7
9
 
8
- <br><br>
10
+ LightService is a powerful and flexible service skeleton framework with an emphasis on simplicity
9
11
 
10
- ![Orchestrators-Deprecated](resources/orchestrators_deprecated.svg)
11
- <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.
12
-
13
- <br>
14
-
15
- ## Table of Content
12
+ ## Table of Contents
16
13
  * [Why LightService?](#why-lightservice)
14
+ * [Getting Started](#getting-started)
15
+ * [Requirements](#requirements)
16
+ * [Installation](#installation)
17
+ * [Your first action](#your-first-action)
18
+ * [Your first organizer](#your-first-organizer)
17
19
  * [Stopping the Series of Actions](#stopping-the-series-of-actions)
18
20
  * [Failing the Context](#failing-the-context)
19
21
  * [Skipping the Rest of the Actions](#skipping-the-rest-of-the-actions)
@@ -24,9 +26,11 @@
24
26
  * [Error Codes](#error-codes)
25
27
  * [Action Rollback](#action-rollback)
26
28
  * [Localizing Messages](#localizing-messages)
27
- * [Orchestrator Logic in Organizers](#orchestrator-logic-in-organizers)
29
+ * [Orchestrating Logic in Organizers](#orchestrating-logic-in-organizers)
28
30
  * [ContextFactory for Faster Action Testing](#contextfactory-for-faster-action-testing)
29
-
31
+ * [Rails support](#rails-support)
32
+ * [Implementations in other languages](#other-implementations)
33
+ * [Contributing](#contributing)
30
34
 
31
35
  ## Why LightService?
32
36
 
@@ -78,7 +82,7 @@ Wouldn't it be nice to see this instead?
78
82
  (
79
83
  LooksUpTaxPercentage,
80
84
  CalculatesOrderTax,
81
- ChecksFreeShipping
85
+ ProvidesFreeShipping
82
86
  )
83
87
  ```
84
88
 
@@ -174,7 +178,143 @@ end
174
178
  I gave a [talk at RailsConf 2013](http://www.adomokos.com/2013/06/simple-and-elegant-rails-code-with.html) on
175
179
  simple and elegant Rails code where I told the story of how LightService was extracted from the projects I had worked on.
176
180
 
181
+ ## Getting started
182
+
183
+ ### Requirements
184
+
185
+ This gem requires ruby 2.x. Use of [generators](#rails-support) requires Rails 5+ (tested on Rails 5.x & 6.x only. Will probably work on
186
+ Rails versions as old as 3.2)
187
+
188
+ ### Installation
189
+
190
+ In your Gemfile:
191
+
192
+ ```ruby
193
+ gem 'light-service'
194
+ ```
195
+
196
+ And then
197
+
198
+ ```shell
199
+ bundle install
200
+ ```
201
+
202
+ Or install it yourself as:
203
+
204
+ ```shell
205
+ gem install light-service
206
+ ```
207
+
208
+ ### Your first action
209
+
210
+ LightService's building blocks are actions that are normally composed within an organizer, but can be run independently.
211
+ Let's make a simple greeter action. Each action can take an optional list of expected inputs and promised outputs. If
212
+ these are specified and missing at action start and stop respectively, an exception will be thrown.
213
+
214
+ ```ruby
215
+ class GreetsPerson
216
+ extend ::LightService::Action
217
+
218
+ expects :name
219
+ promises :greeting
220
+
221
+ executed do |context|
222
+ context.greeting = "Hey there, #{name}. You enjoying LightService so far?"
223
+ end
224
+ end
225
+ ```
226
+
227
+ When an action is run, you have access to its returned context, and the status of the action. You can invoke an
228
+ action by calling `.execute` on its class with `key: value` arguments, and inspect its status and context like so:
229
+
230
+ ```ruby
231
+ outcome = GreetsPerson.execute(name: "Han")
232
+
233
+ if outcome.success?
234
+ puts outcome.greeting # which was a promised context value
235
+ elsif outcome.failure?
236
+ puts "Rats... I can't say hello to you"
237
+ end
238
+ ```
239
+
240
+ You will notice that actions are set up to promote simplicity, i.e. they either succeed or fail, and they have
241
+ very clear inputs and outputs. Ideally, they should do [exactly one thing](https://en.wikipedia.org/wiki/Single-responsibility_principle). This makes them as easy to test as unit tests.
242
+
243
+ ### Your first organizer
244
+
245
+ LightService provides a facility to compose actions using organizers. This is great when you have a business process
246
+ to execute that has multiple steps. By composing actions that do exactly one thing, you can sequence simple
247
+ actions together to perform complex multi-step business processes in a clear manner that is very easy
248
+ to reason about.
249
+
250
+ There are advanced ways to sequence actions that can be found later in the README, but we'll keep this simple for now.
251
+ First, let's add a second action that we can sequence to run after the `GreetsPerson` action from above:
252
+
253
+ ```ruby
254
+ class RandomlyAwardsPrize
255
+ extend ::LightService::Action
256
+
257
+ expects :name, :greeting
258
+ promises :did_i_win
259
+
260
+ executed do |context|
261
+ prize_num = "#{context.name}__#{context.greeting}".length
262
+ prizes = ["jelly beans", "ice cream", "pie"]
263
+ did_i_win = rand((1..prize_num)) % 7 == 0
264
+ did_i_lose = rand((1..prize_num)) % 13 == 0
265
+
266
+ if did_i_lose
267
+ # When failing, send a message as an argument, readable from the return context
268
+ context.fail!("you are exceptionally unlucky")
269
+ else
270
+ # You can specify 'optional' context items by treating context like a hash.
271
+ # Useful for when you may or may not be returning extra data. Ideally, selecting
272
+ # a prize should be a separate action that is only run if you win.
273
+ context[:prize] = "lifetime supply of #{prizes.sample}" if did_i_win
274
+ context.did_i_win = did_i_win
275
+ end
276
+ end
277
+ end
278
+ ```
279
+
280
+ And here's the organizer that ties the two together. You implement a `call` class method that takes some arguments and
281
+ from there sends them to `with` in `key: value` format which forms the initial state of the context. From there, chain
282
+ `reduce` to `with` and send it a list of action class names in sequence. The organizer will call each action, one
283
+ after the other, and build up the context as it goes along.
284
+
285
+ ```ruby
286
+ class WelcomeAPotentiallyLuckyPerson
287
+ extend LightService::Organizer
288
+
289
+ def self.call(name)
290
+ with(:name => name).reduce(GreetsPerson, RandomlyAwardsPrize)
291
+ end
292
+ end
293
+ ```
294
+
295
+ When an organizer is run, you have access to the context as it passed through all actions, and the overall status
296
+ of the organized execution. You can invoke an organizer by calling `.call` on the class with the expected arguments,
297
+ and inspect its status and context just like you would an action:
298
+
299
+ ```ruby
300
+ outcome = WelcomeAPotentiallyLuckyPerson.call("Han")
301
+
302
+ if outcome.success?
303
+ puts outcome.greeting # which was a promised context value
304
+
305
+ if outcome.did_i_win
306
+ puts "And you've won a prize! Lucky you. Please see the front desk for your #{outcome.prize}."
307
+ end
308
+ else # outcome.failure? is true, and we can pull the failure message out of the context for feedback to the user.
309
+ puts "Rats... I can't say hello to you, because #{outcome.message}."
310
+ end
311
+ ```
312
+
313
+ Because organizers generally run through complex business logic, and every action has the potential to cause a failure,
314
+ testing an organizer is functionally equivalent to an integration test.
177
315
 
316
+ For further examples, please visit the project's [Wiki](https://github.com/adomokos/light-service/wiki) and review
317
+ the ["Why LightService" section](#why-lightservice) above.
178
318
 
179
319
  ## Stopping the Series of Actions
180
320
  When nothing unexpected happens during the organizer's call, the returned `context` will be successful. Here is how you can check for this:
@@ -294,14 +434,16 @@ Consider this code:
294
434
  class SomeOrganizer
295
435
  extend LightService::Organizer
296
436
 
297
- def call(ctx)
437
+ def self.call(ctx)
298
438
  with(ctx).reduce(actions)
299
439
  end
300
440
 
301
- def actions
302
- OneAction,
303
- TwoAction,
304
- ThreeAction
441
+ def self.actions
442
+ [
443
+ OneAction,
444
+ TwoAction,
445
+ ThreeAction
446
+ ]
305
447
  end
306
448
  end
307
449
 
@@ -345,14 +487,16 @@ class SomeOrganizer
345
487
  end
346
488
  end)
347
489
 
348
- def call(ctx)
490
+ def self.call(ctx)
349
491
  with(ctx).reduce(actions)
350
492
  end
351
493
 
352
- def actions
353
- OneAction,
354
- TwoAction,
355
- ThreeAction
494
+ def self.actions
495
+ [
496
+ OneAction,
497
+ TwoAction,
498
+ ThreeAction
499
+ ]
356
500
  end
357
501
  end
358
502
 
@@ -451,9 +595,9 @@ class AnOrganizer
451
595
 
452
596
  def self.call(order)
453
597
  with(:order => order).reduce(
454
- AnAction,
455
- AnotherAction,
456
- )
598
+ AnAction,
599
+ AnotherAction,
600
+ )
457
601
  end
458
602
  end
459
603
 
@@ -534,6 +678,15 @@ I, [DATE] INFO -- : [LightService] - ;-) <TestDoubles::MakesLatteAction> has de
534
678
  I, [DATE] INFO -- : [LightService] - context message: Can't make a latte with a fatty milk like that!
535
679
  ```
536
680
 
681
+ You can specify the logger on the organizer level, so the organizer does not use the global logger.
682
+
683
+ ```ruby
684
+ class FooOrganizer
685
+ extend LightService::Organizer
686
+ log_with Logger.new("/my/special.log")
687
+ end
688
+ ```
689
+
537
690
  ## Error Codes
538
691
  You can add some more structure to your error handling by taking advantage of error codes in the context.
539
692
  Normally, when something goes wrong in your actions, you fail the process by setting the context to failure:
@@ -610,7 +763,28 @@ Using the `rolled_back` macro is optional for the actions in the chain. You shou
610
763
 
611
764
  The actions are rolled back in reversed order from the point of failure starting with the action that triggered it.
612
765
 
613
- See [this](spec/acceptance/rollback_spec.rb) acceptance test to learn more about this functionality.
766
+ See [this acceptance test](spec/acceptance/rollback_spec.rb) to learn more about this functionality.
767
+
768
+ You may find yourself directly using an action that can roll back by calling `.execute` instead of using it from within an Organizer.
769
+ If this action fails and attempts a rollback, a `FailWithRollbackError` exception will be raised. This is so that the organizer can
770
+ rollback the actions one by one. If you don't want to wrap your call to the action with a `begin, rescue FailWithRollbackError`
771
+ block, you can introspect the context like so, and keep your usage of the action clean:
772
+
773
+ ```ruby
774
+ class FooAction
775
+ extend LightService::Action
776
+
777
+ executed do |context|
778
+ # context.organized_by will be nil if run from an action,
779
+ # or will be the class name if run from an organizer
780
+ if context.organized_by.nil?
781
+ context.fail!
782
+ else
783
+ context.fail_with_rollback!
784
+ end
785
+ end
786
+ end
787
+ ```
614
788
 
615
789
  ## Localizing Messages
616
790
  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.
@@ -689,9 +863,13 @@ end
689
863
 
690
864
  To get the value of a `fail!` or `succeed!` message, simply call `#message` on the returned context.
691
865
 
692
- ## Orchestrator Logic in Organizers
866
+ ## Orchestrating Logic in Organizers
693
867
 
694
- 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:
868
+ 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.
869
+
870
+ In the past, this was solved using Orchestrators. As of [Version 0.9.0 Orchestrators have been deprecated](https://github.com/adomokos/light-service/pull/132). All their functionality is now usable directly within Organizers. Read on to understand how to orchestrate workflows from within a single Organizer.
871
+
872
+ Let's look at a piece of code that does basic data transformations:
695
873
 
696
874
  ```ruby
697
875
  class ExtractsTransformsLoadsData
@@ -745,13 +923,15 @@ end
745
923
 
746
924
  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.
747
925
 
748
- The 5 different orchestrator constructs an organizer can have:
926
+ The 7 different orchestrator constructs an organizer can have:
749
927
 
750
928
  1. `reduce_until`
751
929
  2. `reduce_if`
752
930
  3. `iterate`
753
931
  4. `execute`
754
932
  5. `with_callback`
933
+ 6. `add_to_context`
934
+ 7. `add_aliases`
755
935
 
756
936
  `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.
757
937
 
@@ -763,6 +943,10 @@ To take advantage of another organizer or action, you might need to tweak the co
763
943
 
764
944
  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.
765
945
 
946
+ `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. Keys are also made available as accessors on the context object and can be used just like methods exposed via `expects` and `promises`. [This test](spec/acceptance/organizer/add_to_context_spec.rb) describes its functionality.
947
+
948
+ 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.
949
+
766
950
  ## ContextFactory for Faster Action Testing
767
951
 
768
952
  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.
@@ -816,28 +1000,59 @@ This context then can be passed to the action under test, freeing you up from th
816
1000
 
817
1001
  In case your organizer has more logic in its `call` method, you could create your own test organizer in your specs like you can see it in this [acceptance test](spec/acceptance/testing/context_factory_spec.rb#L4-L11). This is reusable in all your action tests.
818
1002
 
819
- ## Requirements
1003
+ ## Rails support
1004
+
1005
+ LightService includes Rails generators for creating both Organizers and Actions along with corresponding tests. Currently only RSpec is
1006
+ supported ([PR's for supporting MiniTest are welcome](https://github.com/adomokos/light-service/pulls))
820
1007
 
821
- This gem requires ruby 2.x
1008
+ Note: Generators are namespaced to `light_service` not `light-service` due to Rake name constraints.
822
1009
 
823
- ## Installation
824
- Add this line to your application's Gemfile:
1010
+ ### Organizer generation
825
1011
 
826
- gem 'light-service'
1012
+ ```shell
1013
+ rails generate light_service:organizer My::SuperFancy::Organizer
1014
+ # -- or
1015
+ rails generate light_service:organizer my/super_fancy/organizer
1016
+ ```
827
1017
 
828
- And then execute:
1018
+ Options for this generator are:
829
1019
 
830
- $ bundle
1020
+ * `--dir=<SOME_DIR>`. `<SOME_DIR>` defaults to `organizers`. Will write organizers to `/app/organizers`, and specs to `/spec/organizers`
1021
+ * `--no-tests`. Default is `--tests`. Will generate a test file matching the namespace you've supplied.
831
1022
 
832
- Or install it yourself as:
1023
+ ### Action generation
1024
+
1025
+ ```shell
1026
+ rails generate light_service:action My::SuperFancy::Action
1027
+ # -- or
1028
+ rails generate light_service:action my/super_fancy/action
1029
+ ```
1030
+
1031
+ Options for this generator are:
1032
+
1033
+ * `--dir=<SOME_DIR>`. `<SOME_DIR>` defaults to `actions`. Will write actions to `/app/actions`, and specs to `/spec/actions`
1034
+ * `--no-tests`. Defaults is `--tests`. Will generate a test file matching the namespace you've supplied.
1035
+ * `--no-roll-back`. Default is `--roll-back`. Will generate a `rolled_back` block for you to implement with [roll back functionality](#action-rollback).
1036
+
1037
+ ### Advanced action generation
1038
+
1039
+ You are able to optionally specify `expects` and/or `promises` keys during generation
1040
+
1041
+ ```shell
1042
+ rails generate light_service:action CrankWidget expects:one_fish,two_fish promises:red_fish,blue_fish
1043
+ ```
1044
+
1045
+ When specifying `expects`, convenience variables will be initialized in the `executed` block so that you don't have to call
1046
+ them through the context. A stub context will be created in the test file using these keys too.
833
1047
 
834
- $ gem install light-service
1048
+ When specifying `promises`, specs will be created testing for their existence after executing the action.
835
1049
 
836
- ## Usage
837
- Based on the refactoring example above, just create an organizer object that calls the
838
- actions in order and write code for the actions. That's it.
1050
+ ## Other implementations
839
1051
 
840
- For further examples, please visit the project's [Wiki](https://github.com/adomokos/light-service/wiki).
1052
+ | Language | Repo | Author |
1053
+ | :------- |:------------------------------------------------------------------| :------------------------------------------------------|
1054
+ | Python | [pyservice](https://github.com/adomokos/pyservice) | [@adomokos](https://github.com/adomokos) |
1055
+ | PHP | [light-service](https://github.com/douglasgreyling/light-service) | [@douglasgreyling](https://github.com/douglasgreyling) |
841
1056
 
842
1057
  ## Contributing
843
1058
  1. Fork it