light-service 0.10.3 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +6 -0
  4. data/.travis.yml +12 -11
  5. data/Appraisals +4 -4
  6. data/Gemfile +0 -2
  7. data/README.md +240 -34
  8. data/RELEASES.md +20 -1
  9. data/gemfiles/activesupport_4.gemfile +0 -1
  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/context.rb +6 -2
  21. data/lib/light-service/localization_adapter.rb +1 -1
  22. data/lib/light-service/organizer.rb +18 -0
  23. data/lib/light-service/organizer/with_reducer.rb +11 -6
  24. data/lib/light-service/organizer/with_reducer_factory.rb +11 -7
  25. data/lib/light-service/organizer/with_reducer_log_decorator.rb +5 -2
  26. data/lib/light-service/version.rb +1 -1
  27. data/light-service.gemspec +9 -4
  28. data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
  29. data/spec/acceptance/fail_spec.rb +42 -16
  30. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  31. data/spec/acceptance/organizer/add_to_context_spec.rb +30 -0
  32. data/spec/acceptance/organizer/execute_spec.rb +1 -1
  33. data/spec/acceptance/organizer/iterate_spec.rb +7 -0
  34. data/spec/acceptance/organizer/reduce_if_spec.rb +38 -0
  35. data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
  36. data/spec/action_spec.rb +8 -0
  37. data/spec/lib/generators/action_generator_advanced_spec.rb +43 -0
  38. data/spec/lib/generators/action_generator_simple_spec.rb +37 -0
  39. data/spec/lib/generators/full_generator_test_blobs.rb +193 -0
  40. data/spec/lib/generators/organizer_generator_advanced_spec.rb +37 -0
  41. data/spec/lib/generators/organizer_generator_simple_spec.rb +37 -0
  42. data/spec/organizer_spec.rb +42 -14
  43. data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
  44. data/spec/spec_helper.rb +7 -2
  45. data/spec/test_doubles.rb +77 -0
  46. metadata +104 -15
  47. data/gemfiles/activesupport_3.gemfile.lock +0 -76
  48. data/gemfiles/activesupport_4.gemfile.lock +0 -82
  49. data/gemfiles/activesupport_5.gemfile.lock +0 -82
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 35d148e9bdcc089cd30046a2a99eff468ec77fde
4
- data.tar.gz: 95b6d152ab5eca62471cfb245be0f60c2d4bfc73
2
+ SHA256:
3
+ metadata.gz: f400829da41766f56e7f0cb9cfe95aa028ef8ab34bf0bc5794bfafcff3cdcfb1
4
+ data.tar.gz: 41dbd504ac39a0804eb445b4af086dbcf9986264077994277e17fde98b7bdc3d
5
5
  SHA512:
6
- metadata.gz: 20fe035e150978da67f5cc624c9e142fc5299c7b20cc1f71d89c90a665577daa85ec73e32f695950f7d5d52ce0a4604159b08d367b195d27191819c18f497b38
7
- data.tar.gz: 829ccc813a76c16da7ceb7aaa02262d30c555b5486224212b016588a45c2f172e050e6a2c6adc0c2ebef2c28ce5e9f3968a6c791fbc30c2cf4bee2f2a336824d
6
+ metadata.gz: e9a1148b82b6e1a0813400b0d23b013ff6f06965f3204bb3fa95101adc8a749223dd334887f7543622b4a829fd17f190c21dd5f601df3aecde8d8451459a76af
7
+ data.tar.gz: f0aec690e94896dcc13d6caa774003e49ddfa6b74c5acf9272fb7b9d3710d28db4a29601e725e3729deba820013c62b740739d5c6052c4c5ae93fd885cec6fda
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:
@@ -21,13 +20,15 @@ script:
21
20
  - bundle exec rubocop
22
21
 
23
22
  gemfile:
24
- - gemfiles/activesupport_3.gemfile
25
23
  - gemfiles/activesupport_4.gemfile
26
24
  - gemfiles/activesupport_5.gemfile
25
+ - gemfiles/activesupport_6.gemfile
27
26
 
28
27
  matrix:
29
28
  exclude:
30
- - rvm: 2.1.8
31
- gemfile: gemfiles/activesupport_5.gemfile
32
- - rvm: 2.2.2
33
- gemfile: gemfiles/activesupport_5.gemfile
29
+ - rvm: 2.4.2
30
+ gemfile: gemfiles/activesupport_6.gemfile
31
+ - rvm: 2.7.0
32
+ gemfile: gemfiles/activesupport_3.gemfile
33
+ - rvm: 2.7.0
34
+ 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,12 +1,13 @@
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
+ [![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)
7
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)
8
9
 
9
- <br><br>
10
+ <br>
10
11
 
11
12
  ![Orchestrators-Deprecated](resources/orchestrators_deprecated.svg)
12
13
  <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.
@@ -15,6 +16,11 @@
15
16
 
16
17
  ## Table of Content
17
18
  * [Why LightService?](#why-lightservice)
19
+ * [Getting Started](#getting-started)
20
+ * [Requirements](#requirements)
21
+ * [Installation](#installation)
22
+ * [Your first action](#your-first-action)
23
+ * [Your first organizer](#your-first-organizer)
18
24
  * [Stopping the Series of Actions](#stopping-the-series-of-actions)
19
25
  * [Failing the Context](#failing-the-context)
20
26
  * [Skipping the Rest of the Actions](#skipping-the-rest-of-the-actions)
@@ -27,7 +33,7 @@
27
33
  * [Localizing Messages](#localizing-messages)
28
34
  * [Orchestrator Logic in Organizers](#orchestrator-logic-in-organizers)
29
35
  * [ContextFactory for Faster Action Testing](#contextfactory-for-faster-action-testing)
30
-
36
+ * [Rails support](#rails-support)
31
37
 
32
38
  ## Why LightService?
33
39
 
@@ -79,7 +85,7 @@ Wouldn't it be nice to see this instead?
79
85
  (
80
86
  LooksUpTaxPercentage,
81
87
  CalculatesOrderTax,
82
- ChecksFreeShipping
88
+ ProvidesFreeShipping
83
89
  )
84
90
  ```
85
91
 
@@ -175,7 +181,143 @@ end
175
181
  I gave a [talk at RailsConf 2013](http://www.adomokos.com/2013/06/simple-and-elegant-rails-code-with.html) on
176
182
  simple and elegant Rails code where I told the story of how LightService was extracted from the projects I had worked on.
177
183
 
184
+ ## Getting started
185
+
186
+ ### Requirements
187
+
188
+ 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
189
+ Rails versions as old as 3.2)
190
+
191
+ ### Installation
192
+
193
+ In your Gemfile:
194
+
195
+ ```ruby
196
+ gem 'light-service'
197
+ ```
198
+
199
+ And then
200
+
201
+ ```shell
202
+ bundle install
203
+ ```
204
+
205
+ Or install it yourself as:
206
+
207
+ ```shell
208
+ gem install light-service
209
+ ```
210
+
211
+ ### Your first action
212
+
213
+ LightService's building blocks are actions that are normally composed within an organizer, but can be run independently.
214
+ Let's make a simple greeter action. Each action can take an optional list of expected inputs and promised outputs. If
215
+ these are specified and missing at action start and stop respectively, an exception will be thrown.
216
+
217
+ ```ruby
218
+ class GreetsPerson
219
+ extend ::LightService::Action
220
+
221
+ expects :name
222
+ promises :greeting
223
+
224
+ executed do |context|
225
+ context.greeting = "Hey there, #{name}. You enjoying LightService so far?"
226
+ end
227
+ end
228
+ ```
178
229
 
230
+ When an action is run, you have access to its returned context, and the status of the action. You can invoke an
231
+ action by calling `.execute` on its class with `key: value` arguments, and inspect its status and context like so:
232
+
233
+ ```ruby
234
+ outcome = GreetsPerson.execute(name: "Han")
235
+
236
+ if outcome.success?
237
+ puts outcome.greeting # which was a promised context value
238
+ elsif outcome.failure?
239
+ puts "Rats... I can't say hello to you"
240
+ end
241
+ ```
242
+
243
+ You will notice that actions are set up to promote simplicity, i.e. they either succeed or fail, and they have
244
+ 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.
245
+
246
+ ### Your first organizer
247
+
248
+ LightService provides a facility to compose actions using organizers. This is great when you have a business process
249
+ to execute that has multiple steps. By composing actions that do exactly one thing, you can sequence simple
250
+ actions together to perform complex multi-step business processes in a clear manner that is very easy
251
+ to reason about.
252
+
253
+ There are advanced ways to sequence actions that can be found later in the README, but we'll keep this simple for now.
254
+ First, let's add a second action that we can sequence to run after the `GreetsPerson` action from above:
255
+
256
+ ```ruby
257
+ class RandomlyAwardsPrize
258
+ extend ::LightService::Action
259
+
260
+ expects :name, :greeting
261
+ promises :did_i_win
262
+
263
+ executed do |context|
264
+ prize_num = "#{context.name}__#{context.greeting}".length
265
+ prizes = ["jelly beans", "ice cream", "pie"]
266
+ did_i_win = rand((1..prize_num)) % 7 == 0
267
+ did_i_lose = rand((1..prize_num)) % 13 == 0
268
+
269
+ if did_i_lose
270
+ # When failing, send a message as an argument, readable from the return context
271
+ context.fail!("you are exceptionally unlucky")
272
+ else
273
+ # You can specify 'optional' context items by treating context like a hash.
274
+ # Useful for when you may or may not be returning extra data. Ideally, selecting
275
+ # a prize should be a separate action that is only run if you win.
276
+ context[:prize] = "lifetime supply of #{prizes.sample}" if did_i_win
277
+ context.did_i_win = did_i_win
278
+ end
279
+ end
280
+ end
281
+ ```
282
+
283
+ And here's the organizer that ties the two together. You implement a `call` class method that takes some arguments and
284
+ from there sends them to `with` in `key: value` format which forms the initial state of the context. From there, chain
285
+ `reduce` to `with` and send it a list of action class names in sequence. The organizer will call each action, one
286
+ after the other, and build up the context as it goes along.
287
+
288
+ ```ruby
289
+ class WelcomeAPotentiallyLuckyPerson
290
+ extend LightService::Organizer
291
+
292
+ def self.call(name)
293
+ with(:name => name).reduce(GreetsPerson, RandomlyAwardsPrize)
294
+ end
295
+ end
296
+ ```
297
+
298
+ When an organizer is run, you have access to the context as it passed through all actions, and the overall status
299
+ of the organized execution. You can invoke an organizer by calling `.call` on the class with the expected arguments,
300
+ and inspect its status and context just like you would an action:
301
+
302
+ ```ruby
303
+ outcome = WelcomeAPotentiallyLuckyPerson.call("Han")
304
+
305
+ if outcome.success?
306
+ puts outcome.greeting # which was a promised context value
307
+
308
+ if outcome.did_i_win
309
+ puts "And you've won a prize! Lucky you. Please see the front desk for your #{outcome.prize}."
310
+ end
311
+ else # outcome.failure? is true, and we can pull the failure message out of the context for feedback to the user.
312
+ puts "Rats... I can't say hello to you, because #{outcome.message}."
313
+ end
314
+ ```
315
+
316
+ Because organizers generally run through complex business logic, and every action has the potential to cause a failure,
317
+ testing an organizer is functionally equivalent to an integration test.
318
+
319
+ For further examples, please visit the project's [Wiki](https://github.com/adomokos/light-service/wiki) and review
320
+ the ["Why LightService" section](#why-lightservice) above.
179
321
 
180
322
  ## Stopping the Series of Actions
181
323
  When nothing unexpected happens during the organizer's call, the returned `context` will be successful. Here is how you can check for this:
@@ -295,14 +437,16 @@ Consider this code:
295
437
  class SomeOrganizer
296
438
  extend LightService::Organizer
297
439
 
298
- def call(ctx)
440
+ def self.call(ctx)
299
441
  with(ctx).reduce(actions)
300
442
  end
301
443
 
302
- def actions
303
- OneAction,
304
- TwoAction,
305
- ThreeAction
444
+ def self.actions
445
+ [
446
+ OneAction,
447
+ TwoAction,
448
+ ThreeAction
449
+ ]
306
450
  end
307
451
  end
308
452
 
@@ -346,14 +490,16 @@ class SomeOrganizer
346
490
  end
347
491
  end)
348
492
 
349
- def call(ctx)
493
+ def self.call(ctx)
350
494
  with(ctx).reduce(actions)
351
495
  end
352
496
 
353
- def actions
354
- OneAction,
355
- TwoAction,
356
- ThreeAction
497
+ def self.actions
498
+ [
499
+ OneAction,
500
+ TwoAction,
501
+ ThreeAction
502
+ ]
357
503
  end
358
504
  end
359
505
 
@@ -452,9 +598,9 @@ class AnOrganizer
452
598
 
453
599
  def self.call(order)
454
600
  with(:order => order).reduce(
455
- AnAction,
456
- AnotherAction,
457
- )
601
+ AnAction,
602
+ AnotherAction,
603
+ )
458
604
  end
459
605
  end
460
606
 
@@ -535,6 +681,15 @@ I, [DATE] INFO -- : [LightService] - ;-) <TestDoubles::MakesLatteAction> has de
535
681
  I, [DATE] INFO -- : [LightService] - context message: Can't make a latte with a fatty milk like that!
536
682
  ```
537
683
 
684
+ You can specify the logger on the organizer level, so the organizer does not use the global logger.
685
+
686
+ ```ruby
687
+ class FooOrganizer
688
+ extend LightService::Organizer
689
+ log_with Logger.new("/my/special.log")
690
+ end
691
+ ```
692
+
538
693
  ## Error Codes
539
694
  You can add some more structure to your error handling by taking advantage of error codes in the context.
540
695
  Normally, when something goes wrong in your actions, you fail the process by setting the context to failure:
@@ -611,7 +766,28 @@ Using the `rolled_back` macro is optional for the actions in the chain. You shou
611
766
 
612
767
  The actions are rolled back in reversed order from the point of failure starting with the action that triggered it.
613
768
 
614
- See [this](spec/acceptance/rollback_spec.rb) acceptance test to learn more about this functionality.
769
+ See [this acceptance test](spec/acceptance/rollback_spec.rb) to learn more about this functionality.
770
+
771
+ You may find yourself directly using an action that can roll back by calling `.execute` instead of using it from within an Organizer.
772
+ If this action fails and attempts a rollback, a `FailWithRollbackError` exception will be raised. This is so that the organizer can
773
+ rollback the actions one by one. If you don't want to wrap your call to the action with a `begin, rescue FailWithRollbackError`
774
+ block, you can introspect the context like so, and keep your usage of the action clean:
775
+
776
+ ```ruby
777
+ class FooAction
778
+ extend LightService::Action
779
+
780
+ executed do |context|
781
+ # context.organized_by will be nil if run from an action,
782
+ # or will be the class name if run from an organizer
783
+ if context.organized_by.nil?
784
+ context.fail!
785
+ else
786
+ context.fail_with_rollback!
787
+ end
788
+ end
789
+ end
790
+ ```
615
791
 
616
792
  ## Localizing Messages
617
793
  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.
@@ -746,13 +922,15 @@ end
746
922
 
747
923
  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
924
 
749
- The 5 different orchestrator constructs an organizer can have:
925
+ The 7 different orchestrator constructs an organizer can have:
750
926
 
751
927
  1. `reduce_until`
752
928
  2. `reduce_if`
753
929
  3. `iterate`
754
930
  4. `execute`
755
931
  5. `with_callback`
932
+ 6. `add_to_context`
933
+ 7. `add_aliases`
756
934
 
757
935
  `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
936
 
@@ -764,6 +942,10 @@ To take advantage of another organizer or action, you might need to tweak the co
764
942
 
765
943
  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
944
 
945
+ `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.
946
+
947
+ 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.
948
+
767
949
  ## ContextFactory for Faster Action Testing
768
950
 
769
951
  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.
@@ -817,28 +999,52 @@ This context then can be passed to the action under test, freeing you up from th
817
999
 
818
1000
  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.
819
1001
 
820
- ## Requirements
1002
+ ## Rails support
821
1003
 
822
- This gem requires ruby 2.x
1004
+ LightService includes Rails generators for creating both Organizers and Actions along with corresponding tests. Currently only RSpec is
1005
+ supported ([PR's for supporting MiniTest are welcome](https://github.com/adomokos/light-service/pulls))
823
1006
 
824
- ## Installation
825
- Add this line to your application's Gemfile:
1007
+ Note: Generators are namespaced to `light_service` not `light-service` due to Rake name constraints.
826
1008
 
827
- gem 'light-service'
1009
+ ### Organizer generation
828
1010
 
829
- And then execute:
1011
+ ```shell
1012
+ rails generate light_service:organizer My::SuperFancy::Organizer
1013
+ # -- or
1014
+ rails generate light_service:organizer my/super_fancy/organizer
1015
+ ```
830
1016
 
831
- $ bundle
1017
+ Options for this generator are:
832
1018
 
833
- Or install it yourself as:
1019
+ * `--dir=<SOME_DIR>`. `<SOME_DIR>` defaults to `organizers`. Will write organizers to `/app/organizers`, and specs to `/spec/organizers`
1020
+ * `--no-tests`. Default is `--tests`. Will generate a test file matching the namespace you've supplied.
1021
+
1022
+ ### Action generation
834
1023
 
835
- $ gem install light-service
1024
+ ```shell
1025
+ rails generate light_service:action My::SuperFancy::Action
1026
+ # -- or
1027
+ rails generate light_service:action my/super_fancy/action
1028
+ ```
1029
+
1030
+ Options for this generator are:
1031
+
1032
+ * `--dir=<SOME_DIR>`. `<SOME_DIR>` defaults to `actions`. Will write actions to `/app/actions`, and specs to `/spec/actions`
1033
+ * `--no-tests`. Defaults is `--tests`. Will generate a test file matching the namespace you've supplied.
1034
+ * `--no-roll-back`. Default is `--roll-back`. Will generate a `rolled_back` block for you to implement with [roll back functionality](#action-rollback).
1035
+
1036
+ ### Advanced action generation
1037
+
1038
+ You are able to optionally specify `expects` and/or `promises` keys during generation
1039
+
1040
+ ```shell
1041
+ rails generate light_service:action CrankWidget expects:one_fish,two_fish promises:red_fish,blue_fish
1042
+ ```
836
1043
 
837
- ## Usage
838
- Based on the refactoring example above, just create an organizer object that calls the
839
- actions in order and write code for the actions. That's it.
1044
+ When specifying `expects`, convenience variables will be initialized in the `executed` block so that you don't have to call
1045
+ them through the context. A stub context will be created in the test file using these keys too.
840
1046
 
841
- For further examples, please visit the project's [Wiki](https://github.com/adomokos/light-service/wiki).
1047
+ When specifying `promises`, specs will be created testing for their existence after executing the action.
842
1048
 
843
1049
  ## Contributing
844
1050
  1. Fork it