typed_operation 1.0.0.pre1 → 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: ff925e2cc5ce03a696a209e0ca61130bff94ddb6ce28f2670ebaaae18f1968ba
4
- data.tar.gz: b14a0952694653f918135a57180c8c7746aeffcb14385d17aeef7a1283726e99
3
+ metadata.gz: c16e335815316d0cc264b143755c63556fa1a016403198e83aeeeeb067307d4a
4
+ data.tar.gz: ddd2475fd3bb2898245b9430136dd881b3ef52fe92abbd7ffe4230e243398aad
5
5
  SHA512:
6
- metadata.gz: 0cb23aed491058bdefba04d75661ec4cb5110a2491ba9d4b3afc929d41e1863a09b9ac09b41bdbd6306856af6d5552f2c86c3057b53df3076d0674a9c1feafda
7
- data.tar.gz: 143f6ba5a00d5cabadec3f7f326dc7a724fdd2666ca0dffa326cacf9f9af2f4dce797f77a727468cd919eb42a4b6de530105d6866e49797997c10ff5a36db9fe
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?
@@ -92,13 +104,15 @@ shelve.call(author_id: "1", isbn: false)
92
104
 
93
105
  ### Partially applying parameters
94
106
 
107
+ Operations can also be partially applied and curried:
108
+
95
109
  ```ruby
96
110
  class TestOperation < ::TypedOperation::Base
97
111
  param :foo, String, positional: true
98
112
  param :bar, String
99
113
  param :baz, String, &:to_s
100
114
 
101
- def call = "It worked! (#{foo}, #{bar}, #{baz})"
115
+ def perform = "It worked! (#{foo}, #{bar}, #{baz})"
102
116
  end
103
117
 
104
118
  # Invoking the operation directly
@@ -151,21 +165,76 @@ TestOperation.with("1").with(bar: "2").with(baz: 3).operation
151
165
 
152
166
  ## Documentation
153
167
 
154
- ### Create an operation (subclass `TypedOperation::Base`)
168
+ ### Create an operation (subclass `TypedOperation::Base` or `TypedOperation::ImmutableBase`)
169
+
170
+ Create an operation by subclassing `TypedOperation::Base` or `TypedOperation::ImmutableBase` and specifying the parameters the operation requires.
155
171
 
156
- Create an operation by subclassing `TypedOperation::Base` and specifying the parameters the operation requires.
172
+ - `TypedOperation::Base` (uses `Literal::Struct`) is the parent class for an operation where the arguments are potentially mutable (ie not frozen).
173
+ No attribute writer methods are defined, so the arguments can not be changed after initialization, but the values passed in are not guaranteed to be frozen.
174
+ - `TypedOperation::ImmutableBase` (uses `Literal::Data`) is the parent class for an operation where the arguments are immutable (frozen on initialization),
175
+ thus giving a somewhat stronger immutability guarantee (ie that the operation does not mutate its arguments).
157
176
 
158
- 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.
159
178
 
160
179
  The operation can also implement:
161
180
 
162
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
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
+ ```
163
203
 
164
204
  ### Specifying parameters (using `.param`)
165
205
 
166
206
  Parameters are specified using the provided class methods (`.positional_param` and `.named_param`),
167
207
  or using the underlying `.param` method.
168
208
 
209
+ Types are specified using the `literal` gem. In many cases this simply means providing the class of the
210
+ expected type, but there are also some other useful types provided by `literal` (eg `Union`).
211
+
212
+ These can be either accessed via the `Literal` module, eg `Literal::Types::BooleanType`:
213
+
214
+ ```ruby
215
+ class MyOperation < ::TypedOperation::Base
216
+ param :name, String
217
+ param :age, Integer, optional: true
218
+ param :choices, Literal::Types::ArrayType.new(String)
219
+ param :chose, Literal::Types::BooleanType
220
+ end
221
+
222
+ MyOperation.new(name: "bob", choices: ["st"], chose: true)
223
+ ```
224
+
225
+ or by including the `Literal::Types` module into your operation class, and using the aliases provided:
226
+
227
+ ```ruby
228
+ class MyOperation < ::TypedOperation::Base
229
+ include Literal::Types
230
+
231
+ param :name, String
232
+ param :age, _Nilable(Integer) # optional can also be specifed using `.optional`
233
+ param :choices, _Array(String)
234
+ param :chose, _Boolean
235
+ end
236
+ ```
237
+
169
238
  Type constraints can be modified to make the parameter optional using `.optional`.
170
239
 
171
240
  #### Your own aliases
@@ -223,7 +292,7 @@ class MyOperation < ::TypedOperation::Base
223
292
  # Or alternatively => `param :name, String, positional: true`
224
293
  positional_param :age, Integer, default: -> { 0 }
225
294
 
226
- def call
295
+ def perform
227
296
  puts "Hello #{name} (#{age})"
228
297
  end
229
298
  end
@@ -254,7 +323,7 @@ class MyOperation < ::TypedOperation::Base
254
323
  # Or alternatively => `param :name, String`
255
324
  named_param :age, Integer, default: -> { 0 }
256
325
 
257
- def call
326
+ def perform
258
327
  puts "Hello #{name} (#{age})"
259
328
  end
260
329
  end
@@ -275,7 +344,7 @@ class MyOperation < ::TypedOperation::Base
275
344
  positional_param :name, String
276
345
  named_param :age, Integer, default: -> { 0 }
277
346
 
278
- def call
347
+ def perform
279
348
  puts "Hello #{name} (#{age})"
280
349
  end
281
350
  end
@@ -319,7 +388,7 @@ You can specify a block after a parameter definition to coerce the argument valu
319
388
 
320
389
  ```ruby
321
390
  param :name, String, &:to_s
322
- param :choice, Union(FalseClass, TrueClass) do |v|
391
+ param :choice, Literal::Types::BooleanType do |v|
323
392
  v == "y"
324
393
  end
325
394
  ```
@@ -366,9 +435,10 @@ An operation can be invoked by:
366
435
 
367
436
  - instantiating it with at least required params and then calling the `#call` method on the instance
368
437
  - once a partially applied operation has been prepared (all required parameters have been set), the call
369
- 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.
370
439
  - once an operation is curried, the `#call` method on last TypedOperation::Curried in the chain will invoke the operation
371
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`)
372
442
 
373
443
  See the many examples in this document.
374
444
 
@@ -487,6 +557,26 @@ class ApplicationOperation < ::TypedOperation::Base
487
557
  end
488
558
  ```
489
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
+ ```
490
580
  ### Using with `literal` monads
491
581
 
492
582
  You can use the `literal` gem to provide a `Result` type for your operations.
@@ -496,30 +586,29 @@ class MyOperation < ::TypedOperation::Base
496
586
  param :account_name, String
497
587
  param :owner, String
498
588
 
499
- def call
589
+ def perform
500
590
  create_account.bind do |account|
501
- associate_owner(account).bind do
502
- account
503
- end
591
+ associate_owner(account).map { account }
504
592
  end
505
593
  end
506
594
 
507
595
  private
508
596
 
509
597
  def create_account
510
- # returns Literal::Success(account) or Literal::Failure(:cant_create)
598
+ # ...
599
+ # Literal::Failure.new(:cant_create_account)
511
600
  Literal::Success.new(account_name)
512
601
  end
513
602
 
514
603
  def associate_owner(account)
515
604
  # ...
516
605
  Literal::Failure.new(:cant_associate_owner)
606
+ # Literal::Success.new("ok")
517
607
  end
518
608
  end
519
609
 
520
610
  MyOperation.new(account_name: "foo", owner: "bar").call
521
611
  # => Literal::Failure(:cant_associate_owner)
522
-
523
612
  ```
524
613
 
525
614
  ### Using with `Dry::Monads`
@@ -534,7 +623,7 @@ class MyOperation < ::TypedOperation::Base
534
623
  param :account_name, String
535
624
  param :owner, ::Owner
536
625
 
537
- def call
626
+ def perform
538
627
  account = yield create_account(account_name)
539
628
  yield associate_owner(account, owner)
540
629
 
data/Rakefile CHANGED
@@ -1,3 +1,17 @@
1
- require "bundler/setup"
1
+ # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
+ require "bundler/setup"
5
+ require "rake/testtask"
6
+
7
+ ENV["NO_RAILS"] = "true"
8
+
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << "test"
11
+ t.libs << "lib"
12
+ t.test_files = FileList["test/typed_operation/**/*_test.rb"]
13
+ end
14
+
15
+ require "standard/rake"
16
+
17
+ task default: %i[test standard]
@@ -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
@@ -1,106 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "literal"
4
-
5
3
  module TypedOperation
6
- class Base < Literal::Data
7
- class << self
8
- def call(...)
9
- new(...).call
10
- end
4
+ class Base < Literal::Struct
5
+ extend Operations::Introspection
6
+ extend Operations::Parameters
7
+ extend Operations::PartialApplication
11
8
 
12
- def with(...)
13
- PartiallyApplied.new(self, ...).with
14
- end
15
- alias_method :[], :with
16
-
17
- def curry
18
- Curried.new(self)
19
- end
20
-
21
- def to_proc
22
- method(:call).to_proc
23
- end
24
-
25
- # Method to define parameters for your operation.
26
-
27
- # Parameter for keyword argument, or a positional argument if you use positional: true
28
- # Required, but you can set a default or use optional: true if you want optional
29
- def param(name, signature = :any, **options, &converter)
30
- AttributeBuilder.new(self, name, signature, options).define(&converter)
31
- end
32
-
33
- # Alternative DSL
34
-
35
- # Parameter for positional argument
36
- def positional_param(name, signature = :any, **options, &converter)
37
- param(name, signature, **options.merge(positional: true), &converter)
38
- end
39
-
40
- # Parameter for a keyword or named argument
41
- def named_param(name, signature = :any, **options, &converter)
42
- param(name, signature, **options.merge(positional: false), &converter)
43
- end
44
-
45
- # Wrap a type signature in a NilableType meaning it is optional to TypedOperation
46
- def optional(type_signature)
47
- NilableType.new(type_signature)
48
- end
49
-
50
- # Introspection methods
51
-
52
- def positional_parameters
53
- literal_attributes.filter_map { |name, attribute| name if attribute.positional? }
54
- end
9
+ include Operations::Lifecycle
10
+ include Operations::Callable
11
+ include Operations::Deconstruct
12
+ include Operations::Executable
55
13
 
56
- def keyword_parameters
57
- literal_attributes.filter_map { |name, attribute| name unless attribute.positional? }
58
- end
59
-
60
- def required_positional_parameters
61
- required_parameters.filter_map { |name, attribute| name if attribute.positional? }
62
- end
63
-
64
- def required_keyword_parameters
65
- required_parameters.filter_map { |name, attribute| name unless attribute.positional? }
66
- end
67
-
68
- def optional_positional_parameters
69
- positional_parameters - required_positional_parameters
70
- end
71
-
72
- def optional_keyword_parameters
73
- keyword_parameters - required_keyword_parameters
74
- end
75
-
76
- private
77
-
78
- def required_parameters
79
- literal_attributes.filter do |name, attribute|
80
- attribute.default.nil? # Any optional parameters will have a default value/proc in their Literal::Attribute
81
- end
14
+ class << self
15
+ def attribute(name, type, special = nil, reader: :public, writer: :public, positional: false, default: nil)
16
+ super(name, type, special, reader:, writer: false, positional:, default:)
82
17
  end
83
18
  end
84
19
 
85
- def after_initialization
86
- prepare if respond_to?(:prepare)
87
- end
88
-
89
- def call
90
- raise InvalidOperationError, "You must implement #call"
91
- end
92
-
93
- def to_proc
94
- method(:call).to_proc
95
- end
96
-
97
- def deconstruct
98
- attributes.values
99
- end
100
-
101
- def deconstruct_keys(keys)
102
- h = attributes.to_h
103
- keys ? h.slice(*keys) : h
20
+ def with(...)
21
+ # copy to new operation with new attrs
22
+ self.class.new(**attributes.merge(...))
104
23
  end
105
24
  end
106
25
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedOperation
4
+ class ImmutableBase < Literal::Data
5
+ extend Operations::Introspection
6
+ extend Operations::Parameters
7
+ extend Operations::PartialApplication
8
+
9
+ include Operations::Lifecycle
10
+ include Operations::Callable
11
+ include Operations::Deconstruct
12
+ include Operations::Executable
13
+ end
14
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedOperation
4
+ module Operations
5
+ class AttributeBuilder
6
+ def initialize(typed_operation, parameter_name, type_signature, options)
7
+ @typed_operation = typed_operation
8
+ @name = parameter_name
9
+ @signature = type_signature
10
+ @optional = options[:optional]
11
+ @positional = options[:positional]
12
+ @reader = options[:reader] || :public
13
+ @default_key = options.key?(:default)
14
+ @default = options[:default]
15
+
16
+ prepare_type_signature_for_literal
17
+ end
18
+
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
26
+ @typed_operation.attribute(
27
+ @name,
28
+ @signature,
29
+ default: default_value_for_literal,
30
+ positional: @positional,
31
+ reader: @reader,
32
+ &coerce_by
33
+ )
34
+ end
35
+
36
+ private
37
+
38
+ def prepare_type_signature_for_literal
39
+ @signature = Literal::Types::NilableType.new(@signature) if needs_to_be_nilable?
40
+ union_with_nil_to_support_nil_default
41
+ validate_positional_order_params! if @positional
42
+ end
43
+
44
+ # If already wrapped in a Nilable then don't wrap again
45
+ def needs_to_be_nilable?
46
+ @optional && !type_nilable?
47
+ end
48
+
49
+ def type_nilable?
50
+ @signature.is_a?(Literal::Types::NilableType)
51
+ end
52
+
53
+ def union_with_nil_to_support_nil_default
54
+ @signature = Literal::Union.new(@signature, NilClass) if has_default_value_nil?
55
+ end
56
+
57
+ def has_default_value_nil?
58
+ default_provided? && @default.nil?
59
+ end
60
+
61
+ def validate_positional_order_params!
62
+ # Optional ones can always be added after required ones, or before any others, but required ones must be first
63
+ unless type_nilable? || @typed_operation.optional_positional_parameters.empty?
64
+ raise ParameterError, "Cannot define required positional parameter '#{@name}' after optional positional parameters"
65
+ end
66
+ end
67
+
68
+ def default_provided?
69
+ @default_key
70
+ end
71
+
72
+ def default_value_for_literal
73
+ if has_default_value_nil? || type_nilable?
74
+ -> {}
75
+ else
76
+ @default
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedOperation
4
+ module Operations
5
+ module Callable
6
+ def self.included(base)
7
+ base.extend(CallableMethods)
8
+ end
9
+
10
+ module CallableMethods
11
+ def call(...)
12
+ new(...).call
13
+ end
14
+
15
+ def to_proc
16
+ method(:call).to_proc
17
+ end
18
+ end
19
+
20
+ include CallableMethods
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedOperation
4
+ module Operations
5
+ module Deconstruct
6
+ def deconstruct
7
+ attributes.values
8
+ end
9
+
10
+ def deconstruct_keys(keys)
11
+ h = attributes.to_h
12
+ keys ? h.slice(*keys) : h
13
+ end
14
+ end
15
+ end
16
+ 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
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedOperation
4
+ module Operations
5
+ # Introspection methods
6
+ module Introspection
7
+ def positional_parameters
8
+ literal_attributes.filter_map { |name, attribute| name if attribute.positional? }
9
+ end
10
+
11
+ def keyword_parameters
12
+ literal_attributes.filter_map { |name, attribute| name unless attribute.positional? }
13
+ end
14
+
15
+ def required_parameters
16
+ literal_attributes.filter do |name, attribute|
17
+ attribute.default.nil? # Any optional parameters will have a default value/proc in their Literal::Attribute
18
+ end
19
+ end
20
+
21
+ def required_positional_parameters
22
+ required_parameters.filter_map { |name, attribute| name if attribute.positional? }
23
+ end
24
+
25
+ def required_keyword_parameters
26
+ required_parameters.filter_map { |name, attribute| name unless attribute.positional? }
27
+ end
28
+
29
+ def optional_positional_parameters
30
+ positional_parameters - required_positional_parameters
31
+ end
32
+
33
+ def optional_keyword_parameters
34
+ keyword_parameters - required_keyword_parameters
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedOperation
4
+ module Operations
5
+ module Lifecycle
6
+ # This is called by Literal on initialization of underlying Struct/Data
7
+ def after_initialization
8
+ prepare if respond_to?(:prepare)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedOperation
4
+ module Operations
5
+ # Method to define parameters for your operation.
6
+ module Parameters
7
+ # Parameter for keyword argument, or a positional argument if you use positional: true
8
+ # Required, but you can set a default or use optional: true if you want optional
9
+ def param(name, signature = :any, **options, &converter)
10
+ AttributeBuilder.new(self, name, signature, options).define(&converter)
11
+ end
12
+
13
+ # Alternative DSL
14
+
15
+ # Parameter for positional argument
16
+ def positional_param(name, signature = :any, **options, &converter)
17
+ param(name, signature, **options.merge(positional: true), &converter)
18
+ end
19
+
20
+ # Parameter for a keyword or named argument
21
+ def named_param(name, signature = :any, **options, &converter)
22
+ param(name, signature, **options.merge(positional: false), &converter)
23
+ end
24
+
25
+ # Wrap a type signature in a NilableType meaning it is optional to TypedOperation
26
+ def optional(type_signature)
27
+ Literal::Types::NilableType.new(type_signature)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TypedOperation
4
+ module Operations
5
+ module PartialApplication
6
+ def with(...)
7
+ PartiallyApplied.new(self, ...).with
8
+ end
9
+ alias_method :[], :with
10
+
11
+ def curry
12
+ Curried.new(self)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module TypedOperation
2
- VERSION = "1.0.0.pre1"
2
+ VERSION = "1.0.0.pre3"
3
3
  end
@@ -2,11 +2,20 @@ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2.0")
2
2
  require "polyfill-data"
3
3
  end
4
4
 
5
+ require "literal"
6
+
5
7
  require "typed_operation/version"
6
8
  require "typed_operation/railtie" if defined?(Rails::Railtie)
7
- require "typed_operation/nilable_type"
8
- require "typed_operation/attribute_builder"
9
+ require "typed_operation/operations/introspection"
10
+ require "typed_operation/operations/parameters"
11
+ require "typed_operation/operations/partial_application"
12
+ require "typed_operation/operations/callable"
13
+ require "typed_operation/operations/lifecycle"
14
+ require "typed_operation/operations/deconstruct"
15
+ require "typed_operation/operations/attribute_builder"
16
+ require "typed_operation/operations/executable"
9
17
  require "typed_operation/curried"
18
+ require "typed_operation/immutable_base"
10
19
  require "typed_operation/base"
11
20
  require "typed_operation/partially_applied"
12
21
  require "typed_operation/prepared"
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.pre1
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-20 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.
@@ -30,10 +30,17 @@ files:
30
30
  - lib/generators/typed_operation_generator.rb
31
31
  - lib/tasks/typed_operation_tasks.rake
32
32
  - lib/typed_operation.rb
33
- - lib/typed_operation/attribute_builder.rb
34
33
  - lib/typed_operation/base.rb
35
34
  - lib/typed_operation/curried.rb
36
- - lib/typed_operation/nilable_type.rb
35
+ - lib/typed_operation/immutable_base.rb
36
+ - lib/typed_operation/operations/attribute_builder.rb
37
+ - lib/typed_operation/operations/callable.rb
38
+ - lib/typed_operation/operations/deconstruct.rb
39
+ - lib/typed_operation/operations/executable.rb
40
+ - lib/typed_operation/operations/introspection.rb
41
+ - lib/typed_operation/operations/lifecycle.rb
42
+ - lib/typed_operation/operations/parameters.rb
43
+ - lib/typed_operation/operations/partial_application.rb
37
44
  - lib/typed_operation/partially_applied.rb
38
45
  - lib/typed_operation/prepared.rb
39
46
  - lib/typed_operation/railtie.rb
@@ -1,73 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TypedOperation
4
- class AttributeBuilder
5
- def initialize(typed_operation, parameter_name, type_signature, options)
6
- @typed_operation = typed_operation
7
- @name = parameter_name
8
- @signature = type_signature
9
- @optional = options[:optional]
10
- @positional = options[:positional]
11
- @reader = options[:reader] || :public
12
- @default_key = options.key?(:default)
13
- @default = options[:default]
14
-
15
- prepare_type_signature_for_literal
16
- end
17
-
18
- def define(&converter)
19
- @typed_operation.attribute(
20
- @name,
21
- @signature,
22
- default: default_value_for_literal,
23
- positional: @positional,
24
- reader: @reader,
25
- &converter
26
- )
27
- end
28
-
29
- private
30
-
31
- def prepare_type_signature_for_literal
32
- @signature = NilableType.new(@signature) if needs_to_be_nilable?
33
- union_with_nil_to_support_nil_default
34
- validate_positional_order_params! if @positional
35
- end
36
-
37
- # If already wrapped in a Nilable then don't wrap again
38
- def needs_to_be_nilable?
39
- @optional && !type_nilable?
40
- end
41
-
42
- def type_nilable?
43
- @signature.is_a?(NilableType)
44
- end
45
-
46
- def union_with_nil_to_support_nil_default
47
- @signature = Literal::Union.new(@signature, NilClass) if has_default_value_nil?
48
- end
49
-
50
- def has_default_value_nil?
51
- default_provided? && @default.nil?
52
- end
53
-
54
- def validate_positional_order_params!
55
- # Optional ones can always be added after required ones, or before any others, but required ones must be first
56
- unless type_nilable? || @typed_operation.optional_positional_parameters.empty?
57
- raise ParameterError, "Cannot define required positional parameter '#{@name}' after optional positional parameters"
58
- end
59
- end
60
-
61
- def default_provided?
62
- @default_key
63
- end
64
-
65
- def default_value_for_literal
66
- if has_default_value_nil? || type_nilable?
67
- -> {}
68
- else
69
- @default
70
- end
71
- end
72
- end
73
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "literal"
4
-
5
- module TypedOperation
6
- class NilableType < Literal::Union
7
- def initialize(*types)
8
- @types = types + [NilClass]
9
- end
10
- end
11
- end