typed_operation 1.0.0.pre1 → 1.0.0.pre2
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 +4 -4
- data/README.md +44 -8
- data/Rakefile +15 -1
- data/lib/typed_operation/base.rb +13 -95
- data/lib/typed_operation/immutable_base.rb +13 -0
- data/lib/typed_operation/operations/attribute_builder.rb +75 -0
- data/lib/typed_operation/operations/callable.rb +27 -0
- data/lib/typed_operation/operations/deconstruct.rb +16 -0
- data/lib/typed_operation/operations/introspection.rb +38 -0
- data/lib/typed_operation/operations/lifecycle.rb +11 -0
- data/lib/typed_operation/operations/parameters.rb +31 -0
- data/lib/typed_operation/operations/partial_application.rb +16 -0
- data/lib/typed_operation/version.rb +1 -1
- data/lib/typed_operation.rb +10 -2
- metadata +10 -4
- data/lib/typed_operation/attribute_builder.rb +0 -73
- data/lib/typed_operation/nilable_type.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1337560f2bffbfd45574c1df5f007c951dba43293324c309c3409aab18e57f0c
|
4
|
+
data.tar.gz: 63deedbca3cde1fadb88668af46ec21652e17f9600d23c0e13c8b22429c05e0c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a8fdc89c772282de6c654365e18d7397532b84aebd738285499998c04c32906dc1e63961f5b47d139dfff532a458461dbe07a29bdfcda9f29d8132d645599df
|
7
|
+
data.tar.gz: cee03eb2c1e2d9100e657d98a1b70f6889b0e276251dd839f343586ff89f17287853224a487357343fc421ff6723686a2bf672c7ad53a058246668bb1d80a926
|
data/README.md
CHANGED
@@ -92,6 +92,8 @@ shelve.call(author_id: "1", isbn: false)
|
|
92
92
|
|
93
93
|
### Partially applying parameters
|
94
94
|
|
95
|
+
Operations can also be partially applied and curried:
|
96
|
+
|
95
97
|
```ruby
|
96
98
|
class TestOperation < ::TypedOperation::Base
|
97
99
|
param :foo, String, positional: true
|
@@ -151,9 +153,14 @@ TestOperation.with("1").with(bar: "2").with(baz: 3).operation
|
|
151
153
|
|
152
154
|
## Documentation
|
153
155
|
|
154
|
-
### Create an operation (subclass `TypedOperation::Base`)
|
156
|
+
### Create an operation (subclass `TypedOperation::Base` or `TypedOperation::ImmutableBase`)
|
157
|
+
|
158
|
+
Create an operation by subclassing `TypedOperation::Base` or `TypedOperation::ImmutableBase` and specifying the parameters the operation requires.
|
155
159
|
|
156
|
-
|
160
|
+
- `TypedOperation::Base` (uses `Literal::Struct`) is the parent class for an operation where the arguments are potentially mutable (ie not frozen).
|
161
|
+
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.
|
162
|
+
- `TypedOperation::ImmutableBase` (uses `Literal::Data`) is the parent class for an operation where the arguments are immutable (frozen on initialization),
|
163
|
+
thus giving a somewhat stronger immutability guarantee (ie that the operation does not mutate its arguments).
|
157
164
|
|
158
165
|
The subclass must implement the `#call` method which is where the operations main work is done.
|
159
166
|
|
@@ -161,11 +168,41 @@ The operation can also implement:
|
|
161
168
|
|
162
169
|
- `#prepare` - called when the operation is initialized, and after the parameters have been set
|
163
170
|
|
171
|
+
|
164
172
|
### Specifying parameters (using `.param`)
|
165
173
|
|
166
174
|
Parameters are specified using the provided class methods (`.positional_param` and `.named_param`),
|
167
175
|
or using the underlying `.param` method.
|
168
176
|
|
177
|
+
Types are specified using the `literal` gem. In many cases this simply means providing the class of the
|
178
|
+
expected type, but there are also some other useful types provided by `literal` (eg `Union`).
|
179
|
+
|
180
|
+
These can be either accessed via the `Literal` module, eg `Literal::Types::BooleanType`:
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
class MyOperation < ::TypedOperation::Base
|
184
|
+
param :name, String
|
185
|
+
param :age, Integer, optional: true
|
186
|
+
param :choices, Literal::Types::ArrayType.new(String)
|
187
|
+
param :chose, Literal::Types::BooleanType
|
188
|
+
end
|
189
|
+
|
190
|
+
MyOperation.new(name: "bob", choices: ["st"], chose: true)
|
191
|
+
```
|
192
|
+
|
193
|
+
or by including the `Literal::Types` module into your operation class, and using the aliases provided:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
class MyOperation < ::TypedOperation::Base
|
197
|
+
include Literal::Types
|
198
|
+
|
199
|
+
param :name, String
|
200
|
+
param :age, _Nilable(Integer) # optional can also be specifed using `.optional`
|
201
|
+
param :choices, _Array(String)
|
202
|
+
param :chose, _Boolean
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
169
206
|
Type constraints can be modified to make the parameter optional using `.optional`.
|
170
207
|
|
171
208
|
#### Your own aliases
|
@@ -319,7 +356,7 @@ You can specify a block after a parameter definition to coerce the argument valu
|
|
319
356
|
|
320
357
|
```ruby
|
321
358
|
param :name, String, &:to_s
|
322
|
-
param :choice,
|
359
|
+
param :choice, Literal::Types::BooleanType do |v|
|
323
360
|
v == "y"
|
324
361
|
end
|
325
362
|
```
|
@@ -498,28 +535,27 @@ class MyOperation < ::TypedOperation::Base
|
|
498
535
|
|
499
536
|
def call
|
500
537
|
create_account.bind do |account|
|
501
|
-
associate_owner(account).
|
502
|
-
account
|
503
|
-
end
|
538
|
+
associate_owner(account).map { account }
|
504
539
|
end
|
505
540
|
end
|
506
541
|
|
507
542
|
private
|
508
543
|
|
509
544
|
def create_account
|
510
|
-
#
|
545
|
+
# ...
|
546
|
+
# Literal::Failure.new(:cant_create_account)
|
511
547
|
Literal::Success.new(account_name)
|
512
548
|
end
|
513
549
|
|
514
550
|
def associate_owner(account)
|
515
551
|
# ...
|
516
552
|
Literal::Failure.new(:cant_associate_owner)
|
553
|
+
# Literal::Success.new("ok")
|
517
554
|
end
|
518
555
|
end
|
519
556
|
|
520
557
|
MyOperation.new(account_name: "foo", owner: "bar").call
|
521
558
|
# => Literal::Failure(:cant_associate_owner)
|
522
|
-
|
523
559
|
```
|
524
560
|
|
525
561
|
### Using with `Dry::Monads`
|
data/Rakefile
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
-
|
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]
|
data/lib/typed_operation/base.rb
CHANGED
@@ -1,106 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "literal"
|
4
|
-
|
5
3
|
module TypedOperation
|
6
|
-
class Base < Literal::
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
4
|
+
class Base < Literal::Struct
|
5
|
+
extend Operations::Introspection
|
6
|
+
extend Operations::Parameters
|
7
|
+
extend Operations::PartialApplication
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
|
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::Callable
|
10
|
+
include Operations::Lifecycle
|
11
|
+
include Operations::Deconstruct
|
55
12
|
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
13
|
+
class << self
|
14
|
+
def attribute(name, type, special = nil, reader: :public, writer: :public, positional: false, default: nil)
|
15
|
+
super(name, type, special, reader:, writer: false, positional:, default:)
|
82
16
|
end
|
83
17
|
end
|
84
18
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
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
|
19
|
+
def with(...)
|
20
|
+
# copy to new operation with new attrs
|
21
|
+
self.class.new(**attributes.merge(...))
|
104
22
|
end
|
105
23
|
end
|
106
24
|
end
|
@@ -0,0 +1,13 @@
|
|
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::Callable
|
10
|
+
include Operations::Lifecycle
|
11
|
+
include Operations::Deconstruct
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,75 @@
|
|
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
|
+
@typed_operation.attribute(
|
21
|
+
@name,
|
22
|
+
@signature,
|
23
|
+
default: default_value_for_literal,
|
24
|
+
positional: @positional,
|
25
|
+
reader: @reader,
|
26
|
+
&converter
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def prepare_type_signature_for_literal
|
33
|
+
@signature = Literal::Types::NilableType.new(@signature) if needs_to_be_nilable?
|
34
|
+
union_with_nil_to_support_nil_default
|
35
|
+
validate_positional_order_params! if @positional
|
36
|
+
end
|
37
|
+
|
38
|
+
# If already wrapped in a Nilable then don't wrap again
|
39
|
+
def needs_to_be_nilable?
|
40
|
+
@optional && !type_nilable?
|
41
|
+
end
|
42
|
+
|
43
|
+
def type_nilable?
|
44
|
+
@signature.is_a?(Literal::Types::NilableType)
|
45
|
+
end
|
46
|
+
|
47
|
+
def union_with_nil_to_support_nil_default
|
48
|
+
@signature = Literal::Union.new(@signature, NilClass) if has_default_value_nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_default_value_nil?
|
52
|
+
default_provided? && @default.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_positional_order_params!
|
56
|
+
# Optional ones can always be added after required ones, or before any others, but required ones must be first
|
57
|
+
unless type_nilable? || @typed_operation.optional_positional_parameters.empty?
|
58
|
+
raise ParameterError, "Cannot define required positional parameter '#{@name}' after optional positional parameters"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def default_provided?
|
63
|
+
@default_key
|
64
|
+
end
|
65
|
+
|
66
|
+
def default_value_for_literal
|
67
|
+
if has_default_value_nil? || type_nilable?
|
68
|
+
-> {}
|
69
|
+
else
|
70
|
+
@default
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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
|
+
|
22
|
+
def call
|
23
|
+
raise InvalidOperationError, "You must implement #call"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
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,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,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
|
data/lib/typed_operation.rb
CHANGED
@@ -2,11 +2,19 @@ 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/
|
8
|
-
require "typed_operation/
|
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"
|
9
16
|
require "typed_operation/curried"
|
17
|
+
require "typed_operation/immutable_base"
|
10
18
|
require "typed_operation/base"
|
11
19
|
require "typed_operation/partially_applied"
|
12
20
|
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.
|
4
|
+
version: 1.0.0.pre2
|
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-
|
11
|
+
date: 2023-08-22 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,16 @@ 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/
|
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/introspection.rb
|
40
|
+
- lib/typed_operation/operations/lifecycle.rb
|
41
|
+
- lib/typed_operation/operations/parameters.rb
|
42
|
+
- lib/typed_operation/operations/partial_application.rb
|
37
43
|
- lib/typed_operation/partially_applied.rb
|
38
44
|
- lib/typed_operation/prepared.rb
|
39
45
|
- 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
|