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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +6 -0
- data/.travis.yml +12 -11
- data/Appraisals +4 -4
- data/Gemfile +0 -2
- data/README.md +240 -34
- data/RELEASES.md +20 -1
- data/gemfiles/activesupport_4.gemfile +0 -1
- data/gemfiles/activesupport_5.gemfile +0 -1
- data/gemfiles/{activesupport_3.gemfile → activesupport_6.gemfile} +1 -2
- data/lib/generators/light_service/action_generator.rb +90 -0
- data/lib/generators/light_service/generator_utils.rb +45 -0
- data/lib/generators/light_service/organizer_generator.rb +66 -0
- data/lib/generators/light_service/templates/action_spec_template.erb +31 -0
- data/lib/generators/light_service/templates/action_template.erb +30 -0
- data/lib/generators/light_service/templates/organizer_spec_template.erb +20 -0
- data/lib/generators/light_service/templates/organizer_template.erb +22 -0
- data/lib/light-service.rb +1 -0
- data/lib/light-service/context.rb +6 -2
- data/lib/light-service/localization_adapter.rb +1 -1
- data/lib/light-service/organizer.rb +18 -0
- data/lib/light-service/organizer/with_reducer.rb +11 -6
- data/lib/light-service/organizer/with_reducer_factory.rb +11 -7
- data/lib/light-service/organizer/with_reducer_log_decorator.rb +5 -2
- data/lib/light-service/version.rb +1 -1
- data/light-service.gemspec +9 -4
- data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
- data/spec/acceptance/fail_spec.rb +42 -16
- data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
- data/spec/acceptance/organizer/add_to_context_spec.rb +30 -0
- data/spec/acceptance/organizer/execute_spec.rb +1 -1
- data/spec/acceptance/organizer/iterate_spec.rb +7 -0
- data/spec/acceptance/organizer/reduce_if_spec.rb +38 -0
- data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
- data/spec/action_spec.rb +8 -0
- data/spec/lib/generators/action_generator_advanced_spec.rb +43 -0
- data/spec/lib/generators/action_generator_simple_spec.rb +37 -0
- data/spec/lib/generators/full_generator_test_blobs.rb +193 -0
- data/spec/lib/generators/organizer_generator_advanced_spec.rb +37 -0
- data/spec/lib/generators/organizer_generator_simple_spec.rb +37 -0
- data/spec/organizer_spec.rb +42 -14
- data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -2
- data/spec/test_doubles.rb +77 -0
- metadata +104 -15
- data/gemfiles/activesupport_3.gemfile.lock +0 -76
- data/gemfiles/activesupport_4.gemfile.lock +0 -82
- data/gemfiles/activesupport_5.gemfile.lock +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f400829da41766f56e7f0cb9cfe95aa028ef8ab34bf0bc5794bfafcff3cdcfb1
|
4
|
+
data.tar.gz: 41dbd504ac39a0804eb445b4af086dbcf9986264077994277e17fde98b7bdc3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9a1148b82b6e1a0813400b0d23b013ff6f06965f3204bb3fa95101adc8a749223dd334887f7543622b4a829fd17f190c21dd5f601df3aecde8d8451459a76af
|
7
|
+
data.tar.gz: f0aec690e94896dcc13d6caa774003e49ddfa6b74c5acf9272fb7b9d3710d28db4a29601e725e3729deba820013c62b740739d5c6052c4c5ae93fd885cec6fda
|
data/.gitignore
CHANGED
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,15 @@ env:
|
|
4
4
|
- RUN_COVERAGE_REPORT=true
|
5
5
|
|
6
6
|
rvm:
|
7
|
-
- 2.
|
8
|
-
- 2.
|
9
|
-
- 2.
|
10
|
-
- 2.
|
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
|
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.
|
31
|
-
gemfile: gemfiles/
|
32
|
-
- rvm: 2.
|
33
|
-
gemfile: gemfiles/
|
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
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.
|
5
|
-
[![
|
6
|
-
[![
|
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
|
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
|
-
|
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
|
-
|
304
|
-
|
305
|
-
|
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
|
-
|
355
|
-
|
356
|
-
|
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
|
-
|
456
|
-
|
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)
|
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
|
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
|
-
##
|
1002
|
+
## Rails support
|
821
1003
|
|
822
|
-
|
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
|
-
|
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
|
-
|
1009
|
+
### Organizer generation
|
828
1010
|
|
829
|
-
|
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
|
-
|
1017
|
+
Options for this generator are:
|
832
1018
|
|
833
|
-
|
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
|
-
|
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
|
-
|
838
|
-
|
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
|
-
|
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
|