typed_operation 1.0.0.pre2 → 1.0.0.pre3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1337560f2bffbfd45574c1df5f007c951dba43293324c309c3409aab18e57f0c
4
- data.tar.gz: 63deedbca3cde1fadb88668af46ec21652e17f9600d23c0e13c8b22429c05e0c
3
+ metadata.gz: c16e335815316d0cc264b143755c63556fa1a016403198e83aeeeeb067307d4a
4
+ data.tar.gz: ddd2475fd3bb2898245b9430136dd881b3ef52fe92abbd7ffe4230e243398aad
5
5
  SHA512:
6
- metadata.gz: 0a8fdc89c772282de6c654365e18d7397532b84aebd738285499998c04c32906dc1e63961f5b47d139dfff532a458461dbe07a29bdfcda9f29d8132d645599df
7
- data.tar.gz: cee03eb2c1e2d9100e657d98a1b70f6889b0e276251dd839f343586ff89f17287853224a487357343fc421ff6723686a2bf672c7ad53a058246668bb1d80a926
6
+ metadata.gz: 1134e7f162febf03703dc2b36a60f799779a9170f6bf75888df7ffe58245b73b3f66b3b434509d0444537ed818c7eb5d3946df61cbe0cf485e4cd02b4bdbc2de
7
+ data.tar.gz: 1ceee6f8f37b538cf25b9011e188c79d49be4618f23b12b314fbb4295affffbf25173d61dcbfebf20589c2aad2a85a8531636adf3e4870286ab8b790351322d7
data/README.md CHANGED
@@ -49,16 +49,28 @@ class ShelveBookOperation < ::TypedOperation::Base
49
49
 
50
50
  named_param :category, String, default: "unknown".freeze
51
51
 
52
- # to setup (optional)
52
+ # optional hook called when the operation is initialized, and after the parameters have been set
53
53
  def prepare
54
54
  raise ArgumentError, "ISBN is invalid" unless valid_isbn?
55
55
  end
56
56
 
57
- # The 'work' of the operation
58
- def call
57
+ # optionally hook in before execution ... and call super to allow subclasses to hook in too
58
+ def before_execute_operation
59
+ # ...
60
+ super
61
+ end
62
+
63
+ # The 'work' of the operation, this is the main body of the operation and must be implemented
64
+ def perform
59
65
  "Put away '#{title}' by author ID #{author_id}#{shelf_code ? " on shelf #{shelf_code}" : "" }"
60
66
  end
61
67
 
68
+ # optionally hook in after execution ... and call super to allow subclasses to hook in too
69
+ def after_execute_operation(result)
70
+ # ...
71
+ super
72
+ end
73
+
62
74
  private
63
75
 
64
76
  def valid_isbn?
@@ -100,7 +112,7 @@ class TestOperation < ::TypedOperation::Base
100
112
  param :bar, String
101
113
  param :baz, String, &:to_s
102
114
 
103
- def call = "It worked! (#{foo}, #{bar}, #{baz})"
115
+ def perform = "It worked! (#{foo}, #{bar}, #{baz})"
104
116
  end
105
117
 
106
118
  # Invoking the operation directly
@@ -162,12 +174,32 @@ Create an operation by subclassing `TypedOperation::Base` or `TypedOperation::Im
162
174
  - `TypedOperation::ImmutableBase` (uses `Literal::Data`) is the parent class for an operation where the arguments are immutable (frozen on initialization),
163
175
  thus giving a somewhat stronger immutability guarantee (ie that the operation does not mutate its arguments).
164
176
 
165
- The subclass must implement the `#call` method which is where the operations main work is done.
177
+ The subclass must implement the `#perform` method which is where the operations main work is done.
166
178
 
167
179
  The operation can also implement:
168
180
 
169
181
  - `#prepare` - called when the operation is initialized, and after the parameters have been set
182
+ - `#before_execute_operation` - optionally hook in before execution ... and call super to allow subclasses to hook in too
183
+ - `#after_execute_operation` - optionally hook in after execution ... and call super to allow subclasses to hook in too
184
+
185
+ ```ruby
186
+ # optionally hook in before execution...
187
+ def before_execute_operation
188
+ # Remember to call super
189
+ super
190
+ end
191
+
192
+ def perform
193
+ # ... implement me!
194
+ end
170
195
 
196
+ # optionally hook in after execution...
197
+ def after_execute_operation(result)
198
+ # Remember to call super, note the result is passed in and the return value of this method is the result of the operation
199
+ # thus allowing you to modify the result if you wish
200
+ super
201
+ end
202
+ ```
171
203
 
172
204
  ### Specifying parameters (using `.param`)
173
205
 
@@ -260,7 +292,7 @@ class MyOperation < ::TypedOperation::Base
260
292
  # Or alternatively => `param :name, String, positional: true`
261
293
  positional_param :age, Integer, default: -> { 0 }
262
294
 
263
- def call
295
+ def perform
264
296
  puts "Hello #{name} (#{age})"
265
297
  end
266
298
  end
@@ -291,7 +323,7 @@ class MyOperation < ::TypedOperation::Base
291
323
  # Or alternatively => `param :name, String`
292
324
  named_param :age, Integer, default: -> { 0 }
293
325
 
294
- def call
326
+ def perform
295
327
  puts "Hello #{name} (#{age})"
296
328
  end
297
329
  end
@@ -312,7 +344,7 @@ class MyOperation < ::TypedOperation::Base
312
344
  positional_param :name, String
313
345
  named_param :age, Integer, default: -> { 0 }
314
346
 
315
- def call
347
+ def perform
316
348
  puts "Hello #{name} (#{age})"
317
349
  end
318
350
  end
@@ -403,9 +435,10 @@ An operation can be invoked by:
403
435
 
404
436
  - instantiating it with at least required params and then calling the `#call` method on the instance
405
437
  - once a partially applied operation has been prepared (all required parameters have been set), the call
406
- method on TypedOperation::Prepared can be used to instantiate and call the operation.
438
+ method on `TypedOperation::Prepared` can be used to instantiate and call the operation.
407
439
  - once an operation is curried, the `#call` method on last TypedOperation::Curried in the chain will invoke the operation
408
440
  - calling `#call` on a partially applied operation and passing in any remaining required parameters
441
+ - calling `#execute_operation` on an operation instance (this is the method that is called by `#call`)
409
442
 
410
443
  See the many examples in this document.
411
444
 
@@ -524,6 +557,26 @@ class ApplicationOperation < ::TypedOperation::Base
524
557
  end
525
558
  ```
526
559
 
560
+ ### Using with Action Policy (`action_policy` gem)
561
+
562
+ Base you `ApplicationOperation` on the following:
563
+
564
+ ```ruby
565
+ class ApplicationOperation < ::TypedOperation::Base
566
+ # ...
567
+ include ActionPolicy::Behaviour
568
+
569
+ # ...
570
+
571
+ param :initiator, ::User
572
+ authorize :initiator
573
+ # Or
574
+ # param :initiator, optional(::User)
575
+ # authorize :initiator, through: {optional: true}
576
+
577
+ # ...
578
+ end
579
+ ```
527
580
  ### Using with `literal` monads
528
581
 
529
582
  You can use the `literal` gem to provide a `Result` type for your operations.
@@ -533,7 +586,7 @@ class MyOperation < ::TypedOperation::Base
533
586
  param :account_name, String
534
587
  param :owner, String
535
588
 
536
- def call
589
+ def perform
537
590
  create_account.bind do |account|
538
591
  associate_owner(account).map { account }
539
592
  end
@@ -570,7 +623,7 @@ class MyOperation < ::TypedOperation::Base
570
623
  param :account_name, String
571
624
  param :owner, ::Owner
572
625
 
573
- def call
626
+ def perform
574
627
  account = yield create_account(account_name)
575
628
  yield associate_owner(account, owner)
576
629
 
@@ -14,7 +14,7 @@ module <%= namespace_name %>
14
14
  # Prepare...
15
15
  end
16
16
 
17
- def call
17
+ def perform
18
18
  # Perform...
19
19
  "Hello World!"
20
20
  end
@@ -33,7 +33,7 @@ class <%= name %> < ::ApplicationOperation
33
33
  # Prepare...
34
34
  end
35
35
 
36
- def call
36
+ def perform
37
37
  # Perform...
38
38
  "Hello World!"
39
39
  end
@@ -6,9 +6,10 @@ module TypedOperation
6
6
  extend Operations::Parameters
7
7
  extend Operations::PartialApplication
8
8
 
9
- include Operations::Callable
10
9
  include Operations::Lifecycle
10
+ include Operations::Callable
11
11
  include Operations::Deconstruct
12
+ include Operations::Executable
12
13
 
13
14
  class << self
14
15
  def attribute(name, type, special = nil, reader: :public, writer: :public, positional: false, default: nil)
@@ -6,8 +6,9 @@ module TypedOperation
6
6
  extend Operations::Parameters
7
7
  extend Operations::PartialApplication
8
8
 
9
- include Operations::Callable
10
9
  include Operations::Lifecycle
10
+ include Operations::Callable
11
11
  include Operations::Deconstruct
12
+ include Operations::Executable
12
13
  end
13
14
  end
@@ -17,13 +17,19 @@ module TypedOperation
17
17
  end
18
18
 
19
19
  def define(&converter)
20
+ # If nilable, then converter should not attempt to call the type converter block if the value is nil
21
+ coerce_by = if type_nilable? && converter
22
+ ->(v) { (v == Literal::Null || v.nil?) ? v : converter.call(v) }
23
+ else
24
+ converter
25
+ end
20
26
  @typed_operation.attribute(
21
27
  @name,
22
28
  @signature,
23
29
  default: default_value_for_literal,
24
30
  positional: @positional,
25
31
  reader: @reader,
26
- &converter
32
+ &coerce_by
27
33
  )
28
34
  end
29
35
 
@@ -18,10 +18,6 @@ module TypedOperation
18
18
  end
19
19
 
20
20
  include CallableMethods
21
-
22
- def call
23
- raise InvalidOperationError, "You must implement #call"
24
- end
25
21
  end
26
22
  end
27
23
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedOperation
4
+ module Operations
5
+ module Executable
6
+ def call
7
+ execute_operation
8
+ end
9
+
10
+ def execute_operation
11
+ before_execute_operation
12
+ retval = perform
13
+ after_execute_operation(retval)
14
+ end
15
+
16
+ def before_execute_operation
17
+ # noop
18
+ end
19
+
20
+ def after_execute_operation(retval)
21
+ retval
22
+ end
23
+
24
+ def perform
25
+ raise InvalidOperationError, "Operation #{self.class} does not implement #perform"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -3,6 +3,7 @@
3
3
  module TypedOperation
4
4
  module Operations
5
5
  module Lifecycle
6
+ # This is called by Literal on initialization of underlying Struct/Data
6
7
  def after_initialization
7
8
  prepare if respond_to?(:prepare)
8
9
  end
@@ -1,3 +1,3 @@
1
1
  module TypedOperation
2
- VERSION = "1.0.0.pre2"
2
+ VERSION = "1.0.0.pre3"
3
3
  end
@@ -13,6 +13,7 @@ require "typed_operation/operations/callable"
13
13
  require "typed_operation/operations/lifecycle"
14
14
  require "typed_operation/operations/deconstruct"
15
15
  require "typed_operation/operations/attribute_builder"
16
+ require "typed_operation/operations/executable"
16
17
  require "typed_operation/curried"
17
18
  require "typed_operation/immutable_base"
18
19
  require "typed_operation/base"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typed_operation
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre2
4
+ version: 1.0.0.pre3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Ierodiaconou
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-22 00:00:00.000000000 Z
11
+ date: 2023-08-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: TypedOperation is a command pattern implementation where inputs can be
14
14
  defined with runtime type checks. Operations can be partially applied.
@@ -36,6 +36,7 @@ files:
36
36
  - lib/typed_operation/operations/attribute_builder.rb
37
37
  - lib/typed_operation/operations/callable.rb
38
38
  - lib/typed_operation/operations/deconstruct.rb
39
+ - lib/typed_operation/operations/executable.rb
39
40
  - lib/typed_operation/operations/introspection.rb
40
41
  - lib/typed_operation/operations/lifecycle.rb
41
42
  - lib/typed_operation/operations/parameters.rb