flow 0.9.3 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +177 -245
  3. data/lib/flow.rb +2 -0
  4. data/lib/flow/concerns/transaction_wrapper.rb +10 -17
  5. data/lib/flow/custom_matchers.rb +5 -3
  6. data/lib/flow/custom_matchers/define_failure.rb +21 -0
  7. data/lib/flow/custom_matchers/define_output.rb +34 -0
  8. data/lib/flow/custom_matchers/handle_error.rb +32 -0
  9. data/lib/flow/custom_matchers/have_on_state.rb +54 -0
  10. data/lib/flow/custom_matchers/use_operations.rb +13 -11
  11. data/lib/flow/custom_matchers/wrap_in_transaction.rb +60 -0
  12. data/lib/flow/flow/callbacks.rb +1 -1
  13. data/lib/flow/flow/core.rb +1 -0
  14. data/lib/flow/flow/flux.rb +0 -2
  15. data/lib/flow/flow/status.rb +0 -4
  16. data/lib/flow/flow/transactions.rb +2 -2
  17. data/lib/flow/flow/trigger.rb +1 -1
  18. data/lib/flow/flow_base.rb +13 -15
  19. data/lib/flow/operation/accessors.rb +66 -0
  20. data/lib/flow/operation/callbacks.rb +12 -10
  21. data/lib/flow/operation/core.rb +10 -8
  22. data/lib/flow/operation/error_handler.rb +18 -12
  23. data/lib/flow/operation/errors/already_executed.rb +5 -3
  24. data/lib/flow/operation/errors/already_rewound.rb +5 -3
  25. data/lib/flow/operation/execute.rb +28 -26
  26. data/lib/flow/operation/failures.rb +48 -42
  27. data/lib/flow/operation/status.rb +18 -21
  28. data/lib/flow/operation/transactions.rb +8 -6
  29. data/lib/flow/operation_base.rb +15 -13
  30. data/lib/flow/rspec_configuration.rb +5 -0
  31. data/lib/flow/spec_helper.rb +3 -0
  32. data/lib/flow/state/errors/not_validated.rb +9 -0
  33. data/lib/flow/state/output.rb +59 -0
  34. data/lib/flow/state/status.rb +22 -0
  35. data/lib/flow/state_base.rb +9 -16
  36. data/lib/flow/version.rb +1 -1
  37. data/lib/generators/flow/application_flow/templates/application_flow.rb +1 -1
  38. data/lib/generators/flow/application_operation/templates/application_operation.rb +1 -1
  39. data/lib/generators/flow/application_state/templates/application_state.rb +1 -1
  40. data/lib/generators/flow/operation/USAGE +1 -1
  41. data/lib/generators/flow/operation/templates/operation.rb.erb +0 -5
  42. data/lib/generators/flow/state/USAGE +1 -1
  43. data/lib/generators/flow/state/templates/state.rb.erb +2 -1
  44. data/lib/generators/rspec/application_flow/templates/application_flow_spec.rb +1 -1
  45. data/lib/generators/rspec/application_operation/templates/application_operation_spec.rb +1 -9
  46. data/lib/generators/rspec/application_state/templates/application_state_spec.rb +1 -1
  47. data/lib/generators/rspec/flow/templates/flow_spec.rb.erb +0 -8
  48. data/lib/generators/rspec/operation/templates/operation_spec.rb.erb +5 -11
  49. data/lib/generators/rspec/state/templates/state_spec.rb.erb +8 -10
  50. metadata +39 -52
  51. data/lib/flow/custom_matchers/define_argument.rb +0 -19
  52. data/lib/flow/custom_matchers/define_attribute.rb +0 -19
  53. data/lib/flow/custom_matchers/define_option.rb +0 -26
  54. data/lib/flow/flow/ebb.rb +0 -28
  55. data/lib/flow/flow/revert.rb +0 -18
  56. data/lib/flow/operation/rewind.rb +0 -25
  57. data/lib/flow/state/arguments.rb +0 -30
  58. data/lib/flow/state/attributes.rb +0 -31
  59. data/lib/flow/state/callbacks.rb +0 -13
  60. data/lib/flow/state/core.rb +0 -14
  61. data/lib/flow/state/options.rb +0 -45
  62. data/lib/flow/state/string.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 300011640bd90fa1ca5ac862e617d0bd297d4653680f03a518a71ee5dcc84e03
4
- data.tar.gz: ca376a90cb8981bf509c90f4e1ed666add065cb8a952743904af0546b8844b40
3
+ metadata.gz: 8e881d33993ef70f023c82f8c595d77780487a67625c050849e1688c02dc4ccd
4
+ data.tar.gz: 7624fed517717e114ba494ce5ae93f252329ee704730a7579f02d4e20f7a53c7
5
5
  SHA512:
6
- metadata.gz: 5cb710f38dd83e6b7ddb44f2c90235f3aea87f4270e2938a8ef4325ff72e2e281592d92663b51036d95d2617897d9c9eb05932245aa8486ef317ea4cc48fb415
7
- data.tar.gz: 98d96900e861b15e13a3f30364628d1f0c8c55584e9e34cb400cd84b5f93c754325166370729997d324054d43aa3b721dc810d734c13850e7833ec36e286d2de
6
+ metadata.gz: 6a07515e6214de6df0e568d18d3a04692d59d99aa50d8b3fb2ed5f89d394ed4ee0d78dcff05aabd86780df8484f240ca3cb2213bb37ad4e4bc5f8a17ec48454e
7
+ data.tar.gz: 2d09e0ca74029c270ae49a73287eef064669a5924cfec2fcd8d72ccf959edc6fa49f9625a4aad393213f24a39d498f990ddb3b451d6e08e84e67699c9c1afe9e
data/README.md CHANGED
@@ -13,7 +13,7 @@
13
13
  * [Operations](#operations)
14
14
  * [States](#states)
15
15
  * [Input](#input)
16
- * [Mutable Data](#mutable-data)
16
+ * [Output](#output)
17
17
  * [Derivative Data](#derivative-data)
18
18
  * [State Concerns](#state-concerns)
19
19
  * [Validations](#validations)
@@ -21,9 +21,6 @@
21
21
  * [Exceptions](#exceptions)
22
22
  * [Failures](#failures)
23
23
  * [Callback Events](#callback-events)
24
- * [Reverting a Flow](#reverting-a-flow)
25
- * [Undoing Operations](#undoing-operations)
26
- * [Manual Revert](#manual-revert)
27
24
  * [Transactions](#transactions)
28
25
  * [Around a Flow](#around-a-flow)
29
26
  * [Around an Operation](#around-an-operation)
@@ -275,6 +272,23 @@ ExampleFlow.trigger(foo: :foo) # => ArgumentError (Missing argument: bar)
275
272
  ExampleFlow.trigger(foo: :foo, bar: :bar) # => #<ExampleFlow:0x00007ff7b7d92ae0 ...>
276
273
  ```
277
274
 
275
+ By default, nil is a valid argument:
276
+
277
+ ```ruby
278
+ ExampleFlow.trigger(foo: nil, bar: nil) # => #<ExampleFlow:0x10007ff7b7d92ae0 ...>
279
+ ```
280
+
281
+ If you want to require a non-nil value for your argument, set the `allow_nil` option (`true` by default):
282
+
283
+ ```ruby
284
+ class ExampleState < ApplicationState
285
+ argument :foo
286
+ argument :bar, allow_nil: false
287
+ end
288
+
289
+ ExampleFlow.trigger(foo: nil, bar: nil) # => ArgumentError (Missing argument: bar)
290
+ ```
291
+
278
292
  **Options** describe input which may be provided to define or override the initial state.
279
293
 
280
294
  Options can optionally define a default value.
@@ -303,28 +317,57 @@ state.favorite_color # => "1a1f1e"
303
317
  state.favorite_foods # => ["avocado", "hummus" ,"nutritional_yeast"]
304
318
  ```
305
319
 
306
- #### Mutable Data
307
-
308
- States can define objects specifically to be populated by operations as they run.
320
+ #### Output
321
+
322
+ Output data is created by Operations during runtime and CANNOT be validated or provided as part of the input. It can only be written once the state has been validated successfully, otherwise an error is raised.
323
+
324
+ ```ruby
325
+ class ExampleState < ApplicationState
326
+ argument :name
327
+
328
+ validates :name, length: { minimum: 3 }
329
+ output :foo
330
+ end
331
+
332
+ state = ExampleState.new(name: "fe")
333
+ state.foo # => raises Flow::State::Errors::NotValidated
334
+ state.foo = :something # => raises Flow::State::Errors::NotValidated
335
+
336
+ state.valid? # => false
337
+ state.foo # => raises Flow::State::Errors::NotValidated
338
+ state.foo = :something # => raises Flow::State::Errors::NotValidated
309
339
 
310
- Mutable operation data is not technically distinct from other operation data.
340
+ state.name = "fefifofum"
341
+ state.valid? # => true
342
+ state.foo # => nil
343
+ state.foo = :something # => :something
344
+ state.outputs.foo # :something
345
+ ```
346
+
347
+ Outputs can optionally define a default value.
348
+
349
+ If no default is specified, the value will be `nil`.
311
350
 
312
- This section is really just a heads up that you can do things like this:
351
+ If the default value is static, it can be specified in the class definition.
352
+
353
+ If the default value is dynamic, you may provide a block to compute the default value.
354
+
355
+ ⚠️‍ **Heads Up**: The default value blocks **DO NOT** provide access to the state or it's other variables!
313
356
 
314
357
  ```ruby
315
358
  class ExampleState < ApplicationState
316
- option :string_buffer, default: []
359
+ output :story, default: []
317
360
  end
318
361
 
319
362
  class AskAQuestion < ApplicationOperation
320
363
  def behavior
321
- state.string_buffer << "Bah Bah, Black Sheep. Have you any wool?"
364
+ state.story << "Bah Bah, Black Sheep. Have you any wool?"
322
365
  end
323
366
  end
324
367
 
325
368
  class GiveAnAnswer < ApplicationOperation
326
369
  def behavior
327
- state.string_buffer << "Yes sir, yes sir! Three bags full!"
370
+ state.story << "Yes sir, yes sir! Three bags full!"
328
371
  end
329
372
  end
330
373
 
@@ -332,75 +375,64 @@ class ExampleFlow < ApplicationFlow
332
375
  operations AskAQuestion, GiveAnAnswer
333
376
  end
334
377
 
335
- result = ExampleFlow.trigger(string_buffer: ["A conversation, for your consideration:"])
336
- result.state.string_buffer.join("\n")
337
- # A conversation, for your consideration:
378
+ result = ExampleFlow.trigger
379
+ result.outputs.story.join("\n")
338
380
  # Bah Bah, Black Sheep. Have you any wool?
339
381
  # Yes sir, yes sir! Three bags full!
340
382
  ```
341
383
 
342
- If you are planning to create some object during your operation at runtime, use `attribute`:
384
+ If you are creating something in your operation it's usually best practice to use [Transactions](#transactions) on either the Flow or Operation.
385
+
386
+ 🙅‍ *Don't Make This Mistake*: Output is meant to capture data generated at runtime. Do not define data that COULD have been fetched in a standard state method into output:
343
387
 
344
388
  ```ruby
345
- class ExampleState < ApplicationState
346
- attribute :the_foo
389
+ class BadState < ApplicationState
390
+ argument :foo
391
+ output :bar
347
392
  end
348
393
 
349
- class CreateFoo < ApplicationOperation
394
+ class BadOperation < ApplicationOperation
350
395
  def behavior
351
- state.the_foo = Foo.create!
352
- end
353
-
354
- def undo
355
- state.the_foo.destroy!
356
- state.the_foo = nil
396
+ state.foo = Bar.where(foo: foo)
357
397
  end
358
398
  end
359
399
  ```
360
400
 
361
- If your attribute should have a default value, you can use a hook to define that default:
401
+ Instead, always define data retrieval within the state and use output for created / generated data:
362
402
 
363
403
  ```ruby
364
- class ExampleState < ApplicationState
365
- attribute :the_foo
366
- set_callback(:initialize, :after) { self.the_foo = [] }
404
+ class GoodState < ApplicationState
405
+ argument :foo
406
+
407
+ output :gaz
408
+
409
+ def bar
410
+ Bar.where(foo: foo)
411
+ end
412
+ memoize :bar
367
413
  end
368
- ```
369
-
370
- Under the hood `attribute` uses `attr_accessor` so you could override the default reader instead:
371
414
 
372
- ```ruby
373
- class ExampleState < ApplicationState
374
- attribute :the_foo
375
-
376
- def the_foo
377
- @the_foo ||= []
415
+ class GoodOperation < ApplicationOperation
416
+ def behavior
417
+ state.gaz = Gaz.create!(name: foo.name, count: bar.count)
378
418
  end
379
419
  end
380
420
  ```
381
421
 
382
- Use whatever method seems more readable to you or appropriate to your use case!
422
+ 🚨 *Don't Make This Mistake Either*: You may feel you want to "combine" some of you input data so it shows up in the output hash. **Well don't!** Output narrowly refers to a specific type of runtime created data. It's **not** some kind "result" hash. (Technically, it's not even a hash at all, it's a [Struct](https://ruby-doc.org/core-2.6.2/Struct.html)!)
383
423
 
384
- 💁‍ *Pro Tip*: You don't need to use `attribute` for mutable data, but you are highly encouraged to! If you do not use it, and opt instead of a simple `attr_accessor`, the value will not be output in the string.
424
+ If you want to define an explicit results payload, do so explicitly:
385
425
 
386
426
  ```ruby
387
- class AccessorState < ApplicationState
388
- attr_accessor :foo
389
- end
390
-
391
- class AttributeState < ApplicationState
392
- attribute :foo
427
+ class GoodState < ApplicationState
428
+ argument :foo
429
+ option :bar, default: :nar
430
+ output :gaz
431
+
432
+ def results
433
+ outputs.to_h.dup.merge(foo: foo, bar: bar)
434
+ end
393
435
  end
394
-
395
- accr_state = AccessorState.new
396
- accr_state.foo = "some value!"
397
- accr_state.to_s # => #<AccessorState >
398
- accr_state.foo # => "some value!"
399
-
400
- attr_state = AttributeState.new
401
- attr_state.foo = "some value!"
402
- attr_state.to_s # => #<AttributeState foo="some value!">
403
- attr_state.foo # => "some value!"
404
436
  ```
405
437
 
406
438
  #### Derivative Data
@@ -675,6 +707,21 @@ operation_failure11.problem # => :too_generous
675
707
  operation_failure11.details.disappointment_level # => :wow_very_disappoint
676
708
  ```
677
709
 
710
+ You can also specify `if:` / `unless:` options to proactively trigger failures as guard clauses:
711
+
712
+ ```ruby
713
+ class PassBottlesAround < ApplicationOperation
714
+ failure :too_dangerous, if: -> { state.bottle_of == "tequila" }
715
+ failure :not_dangerous_enough, unless: :drink_dangerous?
716
+
717
+ private
718
+
719
+ def drink_dangerous?
720
+ %[water juice soda].exclude? state.bottle_of
721
+ end
722
+ end
723
+ ```
724
+
678
725
  ### Callback Events
679
726
 
680
727
  Operations feature error events which are triggered when a problem occurs.
@@ -715,119 +762,6 @@ class OperationThree < ApplicationOperation
715
762
  end
716
763
  ```
717
764
 
718
- ## Reverting a Flow
719
-
720
- ![Flow Revert](docs/images/revert.png)
721
-
722
- When something goes wrong in Flow `#revert` is called.
723
-
724
- This calls `#rewind` on Operations to `#undo` their behavior.
725
-
726
- Reverting a Flow rewinds all its executed operations in reverse order (referred to as an **ebb**).
727
-
728
- Reverting is automatic and happens by default. You cannot opt out of the revert process, but you can choose to not define any `#undo` methods on your Operations.
729
-
730
- ```ruby
731
- class ExampleState < ApplicationState; end
732
-
733
- class GenericOperation < ApplicationOperation
734
- def behavior
735
- puts "#{self.class.name}#behavior"
736
- end
737
-
738
- def undo
739
- puts "#{self.class.name}#undo"
740
- end
741
- end
742
-
743
- class ExampleFlow < ApplicationFlow
744
- operations OperationOne, OperationTwo, OperationThree, OperationFour
745
- end
746
-
747
- class OperationOne < GenericOperation; end
748
- class OperationTwo < GenericOperation; end
749
- class OperationThree < GenericOperation
750
- failure :bad_stuff
751
-
752
- def behavior
753
- super
754
- bad_stuff_failure!
755
- end
756
- end
757
- class OperationFour < GenericOperation; end
758
-
759
- ExampleFlow.trigger
760
-
761
- # Prints:
762
- # OperationOne#behavior
763
- # OperationTwo#behavior
764
- # OperationThree#behavior
765
- # OperationTwo#undo
766
- # OperationOne#undo
767
- ```
768
-
769
- ⚠️ **Heads Up**: For the Operation that failed, `#undo` is **NOT** called. Only operations which execute successfully can be undone.
770
-
771
- ### Undoing Operations
772
-
773
- ```ruby
774
- class ReserveQuantity < ApplicationOperation
775
- delegate :product, :quantity, to: :state
776
- delegate :available_inventory_count, to: :product
777
-
778
- def behavior
779
- product.update!(available_inventory_count: available_inventory_count - quantity)
780
- end
781
-
782
- def undo
783
- product.update!(available_inventory_count: available_inventory_count + quantity)
784
- end
785
- end
786
- ```
787
-
788
- 💁‍ *Note*: If you omit the `#undo`, a revert will essentially pass over that Operation.
789
-
790
- If your Operation should not be undone and you want it to halt reverting, call a defined failure in `#undo`.
791
-
792
- ```ruby
793
- class ExampleOperation < ApplicationOperation
794
- failure :irreversible_behavior
795
-
796
- def behavior
797
- PurchaseService.charge_customer(state.customer)
798
- end
799
-
800
- def undo
801
- irreversible_behavior_failure!
802
- end
803
- end
804
- ```
805
-
806
- ### Manual Revert
807
-
808
- Flows in which an error occur are reverted automatically.
809
-
810
- You can also manually revert a completed flow, even if it was fully successful.
811
-
812
- ```ruby
813
- class ExampleFlow < ApplicationFlow
814
- operations OperationOne, OperationTwo, OperationThree, OperationFour
815
- end
816
-
817
- flow = SomeExampleFlow.trigger
818
- # OperationOne#behavior
819
- # OperationTwo#behavior
820
- # OperationThree#behavior
821
- # OperationFour#behavior
822
- flow.success? # => true
823
- flow.revert
824
- # OperationFour#undo
825
- # OperationThree#undo
826
- # OperationTwo#undo
827
- # OperationOne#undo
828
- flow.reverted? # => true
829
- ```
830
-
831
765
  ## Transactions
832
766
 
833
767
  ![Flow Transactions](docs/images/transaction.png)
@@ -836,8 +770,6 @@ Flow features a callback driven approach to wrap business logic within database
836
770
 
837
771
  Both **Flows** and **Operations** can be wrapped with a transaction.
838
772
 
839
- 🚨 *Be Aware*: Unless otherwise specified, transactions apply to **both** success **and** failure cases. You can pass `only:` or `except:` options to `wrap_in_transaction` to alter this behavior.
840
-
841
773
  ### Around a Flow
842
774
 
843
775
  Flows where no operation should be persisted unless all are successful should use a transaction.
@@ -850,18 +782,6 @@ class ExampleFlow < ApplicationFlow
850
782
  end
851
783
  ```
852
784
 
853
- Flows can transaction wrap `:flux` (caused by `#trigger`) or `:ebb` (caused by `#revert`).
854
-
855
- ```ruby
856
- class ExampleFlow < ApplicationFlow
857
- wrap_in_transaction only: :flux
858
- end
859
-
860
- class ExampleFlow < ApplicationFlow
861
- wrap_in_transaction except: :ebb
862
- end
863
- ```
864
-
865
785
  ### Around an Operation
866
786
 
867
787
  Operations which modify several persisted objects together should use a transaction.
@@ -876,32 +796,32 @@ class OperationTwo < ApplicationFlow
876
796
  end
877
797
  ```
878
798
 
879
- Operations can transaction wrap `:behavior` or `:undo`.
799
+ ## Statuses
880
800
 
881
- ```ruby
882
- class ExampleOperation < ApplicationOperation
883
- wrap_in_transaction only: :behavior
884
- end
801
+ Flows, Operations, and States all have a set of predicate methods to describe their current status.
885
802
 
886
- class ExampleOperation < ApplicationOperation
887
- wrap_in_transaction except: :undo
888
- end
889
- ```
803
+ ### Flows
890
804
 
891
- ## Statuses
805
+ | Status | Description |
806
+ | ------------ | ------------------------- |
807
+ | `pending?` | `#trigger` not called. |
808
+ | `triggered?` | `#trigger` was called. |
809
+ | `failed?` | Some operation failed. |
810
+ | `success?` | All operations succeeded. |
892
811
 
893
- Flows and Operations each have a set of predicate methods to describe their current status.
812
+ ### Operations
813
+
814
+ | Status | Description |
815
+ | ------------ | ------------------------- |
816
+ | `executed?` | `#execute` was called. |
817
+ | `failed?` | Execution failed. |
818
+ | `success?` | Execution succeeded. |
894
819
 
895
- | Object | Status | Description |
896
- | --------- | ------------ | ------------------------- |
897
- | Operation | `executed?` | `#execute` was called. |
898
- | Operation | `failed?` | Execution failed. |
899
- | Operation | `success?` | Execution succeeded. |
900
- | Flow | `pending?` | `#trigger` not called. |
901
- | Flow | `triggered?` | `#trigger` was called. |
902
- | Flow | `failed?` | Some operation failed. |
903
- | Flow | `success?` | All operations succeeded. |
904
- | Flow | `reverted?` | `#revert` was called. |
820
+ ### States
821
+
822
+ | Status | Description |
823
+ | ------------ | ------------------------- |
824
+ | `validated?` | `#vaild?` returned true. |
905
825
 
906
826
  ## Utilities
907
827
 
@@ -912,7 +832,7 @@ Flow offers a number of utilities which allow you to tap into and extend it's fu
912
832
  Flows, Operations, and States all make use of [ActiveSupport::Callbacks](https://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html) to compose advanced functionality.
913
833
 
914
834
  ```ruby
915
- class TakeBottlesDown < OperationBase
835
+ class TakeBottlesDown < ApplicationOperation
916
836
  set_callback(:execute, :before) { bottle_count_term }
917
837
  set_callback(:execute, :after) { state.output.push("You take #{bottle_count_term} down.") }
918
838
 
@@ -934,13 +854,9 @@ The callbacks which are available on each class are:
934
854
  | Flow | `:initialize` | When a new flow is being constructed. |
935
855
  | Flow | `:trigger` | When `#trigger` is called on a flow. |
936
856
  | Flow | `:flux` | When `#trigger` is called on a flow. |
937
- | Flow | `:revert` | When `#revert` is called on a flow. |
938
- | Flow | `:ebb` | When `#revert` is called on a flow. |
939
857
  | State | `:initialize` | When a new state is being constructed. |
940
858
  | Operation | `:execute` | When `#execute` is called. |
941
859
  | Operation | `:behavior` | When `#execute` is called. |
942
- | Operation | `:rewind` | When `#rewind` is called. |
943
- | Operation | `:undo` | When `#rewind` is called. |
944
860
  | Operation | `:failure` | When any type of error occurs. |
945
861
  | Operation | `$problem` | When an error of type $problem occurs. |
946
862
 
@@ -951,7 +867,7 @@ Flow includes the very awesome [ShortCircuIt](https://github.com/Freshly/spicera
951
867
  To leverage it, just add `memoize :method_name` to your Flows, Operations, or States.
952
868
 
953
869
  ```ruby
954
- class TakeBottlesDown < OperationBase
870
+ class TakeBottlesDown < ApplicationOperation
955
871
  def bottle_count_term
956
872
  return "it" if state.bottles.number_on_the_wall == 1
957
873
  return "one" if state.taking_down_one?
@@ -979,7 +895,7 @@ The gems adds methods to Flows, Operations, and States which share names with lo
979
895
  | `fatal` | Highly actionable and critical issues. |
980
896
 
981
897
  ```ruby
982
- class ExampleOperation < OperationBase
898
+ class ExampleOperation < ApplicationOperation
983
899
  def behavior
984
900
  warn(:nothing_to_do, { empty_object: obj }) and return if obj.empty?
985
901
 
@@ -1091,10 +1007,15 @@ end
1091
1007
  ```
1092
1008
 
1093
1009
  This will allow you to use the following custom matchers:
1094
- * [define_argument](lib/flow/custom_matchers/define_argument.rb)
1095
- * [define_attribute](lib/flow/custom_matchers/define_attribute.rb)
1096
- * [define_option](lib/flow/custom_matchers/define_option.rb)
1097
- * [use_operations](lib/flow/custom_matchers/use_operations.rb)
1010
+ * [define_argument](lib/flow/custom_matchers/define_argument.rb) tests usage of `ApplicationState.argument`
1011
+ * [define_attribute](lib/flow/custom_matchers/define_attribute.rb) tests usage of `ApplicationState.attribute`
1012
+ * [define_failure](lib/flow/custom_matchers/define_failure.rb) tests usage of `ApplicationOperation.failure`
1013
+ * [define_option](lib/flow/custom_matchers/define_option.rb) tests usage of `ApplicationState.option`
1014
+ * [define_output](lib/flow/custom_matchers/define_output.rb) tests usage of `ApplicationState.output`
1015
+ * [handle_error](lib/flow/custom_matchers/handle_error.rb) tests usage of `ApplicationOperation.handle_error`
1016
+ * [use_operations](lib/flow/custom_matchers/use_operations.rb) tests usage of `ApplicationFlow.operations`
1017
+ * [wrap_in_transaction](lib/flow/custom_matchers/wrap_in_transaction.rb) tests usage of `.wrap_in_transaction` for `ApplicationFlow` or `ApplicationOperation`
1018
+ * [have_on_state](lib/flow/custom_matchers/have_on_state.rb) tests for data on State after a `ApplicationFlow` or `ApplicationOperation` has been run
1098
1019
 
1099
1020
  ### Testing Flows
1100
1021
 
@@ -1124,14 +1045,6 @@ RSpec.describe FooFlow, type: :flow do
1124
1045
 
1125
1046
  pending "describe the effects of a successful `Flow#flux` (or delete) #{__FILE__}"
1126
1047
  end
1127
-
1128
- describe "#revert" do
1129
- before { flow.trigger! }
1130
-
1131
- subject(:revert) { flow.revert }
1132
-
1133
- pending "describe the effects of a successful `Flow#ebb` (or delete) #{__FILE__}"
1134
- end
1135
1048
  end
1136
1049
  ```
1137
1050
 
@@ -1151,11 +1064,13 @@ require "rails_helper"
1151
1064
  RSpec.describe MakeTheThingDoTheStuff, type: :operation do
1152
1065
  subject(:operation) { described_class.new(state) }
1153
1066
 
1154
- let(:state) { example_state_class.new(**state_input) }
1067
+ let(:state) { example_state_class.new(**state_input).tap(&:validate) }
1155
1068
  let(:example_state_class) do
1156
1069
  Class.new(ApplicationState) do
1157
1070
  # argument :foo
1158
1071
  # option :bar
1072
+ # attribute :baz
1073
+ # output :gaz
1159
1074
  end
1160
1075
  end
1161
1076
  let(:state_input) do
@@ -1164,19 +1079,11 @@ RSpec.describe MakeTheThingDoTheStuff, type: :operation do
1164
1079
 
1165
1080
  it { is_expected.to inherit_from ApplicationOperation }
1166
1081
 
1167
- describe "#execute!" do
1168
- subject(:execute!) { operation.execute! }
1082
+ describe "#execute" do
1083
+ subject(:execute) { operation.execute }
1169
1084
 
1170
1085
  pending "describe `Operation#behavior` (or delete) #{__FILE__}"
1171
1086
  end
1172
-
1173
- describe "#rewind" do
1174
- before { operation.execute! }
1175
-
1176
- subject(:execute!) { operation.rewind }
1177
-
1178
- pending "describe `Operation#undo` (or delete) #{__FILE__}"
1179
- end
1180
1087
  end
1181
1088
  ```
1182
1089
 
@@ -1189,6 +1096,8 @@ let(:example_state_class) do
1189
1096
  Class.new(ApplicationState) do
1190
1097
  # argument :foo
1191
1098
  # option :bar
1099
+ # attribute :baz
1100
+ # output :gaz
1192
1101
  end
1193
1102
  end
1194
1103
  ```
@@ -1211,6 +1120,7 @@ let(:example_state_class) do
1211
1120
  argument :foo
1212
1121
  option :bar
1213
1122
  attribute :baz
1123
+ output :gaz
1214
1124
  end
1215
1125
 
1216
1126
  let(:state_input) do
@@ -1220,7 +1130,31 @@ let(:example_state_class) do
1220
1130
  let(:foo) { ... }
1221
1131
  let(:bar) { ... }
1222
1132
  end
1223
- ```
1133
+ ```
1134
+
1135
+ You are encouraged to use `execute`, rather than `execute!` in testing. You can trust that if an `operation_failure` is present, the operation _would_ have raised an `Operation::Failures::OperationFailure` if you used `execute!`.
1136
+
1137
+ Doing so will allow you to make assertions on the failure without having to expect errors:
1138
+ ```ruby
1139
+ class SomeOperation < ApplicationOperation
1140
+ failure :somethings_invalid
1141
+
1142
+ def behavior
1143
+ somethings_invalid_failure! baz: "relevant data" if state.foo == "something invalid"
1144
+ end
1145
+ end
1146
+ ```
1147
+
1148
+ ```ruby
1149
+ let(:state_input) { foo: "something invalid" }
1150
+
1151
+ before { operation.execute }
1152
+
1153
+ it "fails with the expected data" do
1154
+ expect(operation.operation_failure.problem).to eq :somethings_invalid
1155
+ expect(operation.operation_failure.details.baz).to eq "relevant data"
1156
+ end
1157
+ ```
1224
1158
 
1225
1159
  ### Testing States
1226
1160
 
@@ -1234,19 +1168,17 @@ States are generated with the following RSPec template:
1234
1168
  require "rails_helper"
1235
1169
 
1236
1170
  RSpec.describe FooState, type: :state do
1237
- subject(:state) { described_class.new(**input) }
1238
-
1239
- let(:input) do
1240
- {}
1241
- end
1171
+ subject(:state) { described_class }
1242
1172
 
1243
1173
  it { is_expected.to inherit_from ApplicationState }
1244
- # it { is_expected.to define_argument :foo }
1245
- # it { is_expected.to define_option(:foo) }
1246
- # it { is_expected.to define_option(:foo).with_default_value(:bar) }
1247
- # it { is_expected.to define_option(:foo).with_default_value_block }
1174
+ # it { is_expected.to define_argument :required_input }
1175
+ # it { is_expected.to define_argument :necessary_input, allow_nil: false }
1176
+ # it { is_expected.to define_option(:optional_input) }
1177
+ # it { is_expected.to define_option(:option_with_default, default: :default_static_value) }
1178
+ # it { is_expected.to define_option(:option_with_default_from_block, default: default_block_value) }
1248
1179
  # it { is_expected.to validate_presence_of ... }
1249
- # it { is_expected.to define_attribute :foo }
1180
+ # it { is_expected.to define_output :foo }
1181
+ # it { is_expected.to define_output :foo, default: :bar }
1250
1182
  end
1251
1183
  ```
1252
1184