typed_operation 1.0.0.pre1 → 1.0.0.pre3

Sign up to get free protection for your applications and to get access to all the features.
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