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