dry-types-tuple 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +3 -0
- data/README.md +77 -0
- data/dry-types-tuple.gemspec +1 -1
- data/lib/dry/tuple.rb +142 -0
- data/lib/dry/types/tuple.rb +6 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7583440f28dfcf7be606ffade7d58fe0fac29e783c4b79eeb15467ff6b63c654
|
4
|
+
data.tar.gz: 27da3385c06793c20d7ece1872d740ebc9cc431dc795c9a3141de4a5d64314ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1886d2dabab95dc8d07ba7285991b73e70bd6083d81280c0e2ec6dd3238e20889d4a186edc012d8e381523906b6d730f2782e285112ce17385c0db33e5003594
|
7
|
+
data.tar.gz: 4e6f489907102ca633736d9109725a96bcae2d677b25feca5daead2d3f69e32368410006325c3776c6e6196ea5dd0e9ff7b3126d5fd3776dc5f07e3d747a4051
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -58,6 +58,83 @@ Dry::Types['tuple'].of \
|
|
58
58
|
[Dry::Types['float'] | Dry::Types['integer']]
|
59
59
|
```
|
60
60
|
|
61
|
+
### Class interface mixins
|
62
|
+
|
63
|
+
The extra feature of the gem is tuple class interfaces mixins under the `Dry::Tuple` namespace. It isn't loaded by default,
|
64
|
+
so it's dependent on `require "dry/tuple"`.
|
65
|
+
|
66
|
+
#### Class interface for PORO-classes
|
67
|
+
|
68
|
+
The interfaces provide a way to use a tuple in constructors to match proper class by input.
|
69
|
+
The behaviour is similar to the one provided by `Dry::Struct::ClassInterface` and
|
70
|
+
allows to combine classes into sums but with positional input validation & coercion.
|
71
|
+
|
72
|
+
To do so, extend the class with `Dry::Tuple::ClassInterface` mixin and then assign an
|
73
|
+
explicit tuple type by calling `tuple(type_declation)` method.
|
74
|
+
|
75
|
+
With a couple of extended classes you will be able to compose a summing object, which
|
76
|
+
could be used to match incoming values by tuples. When input matches any of summed
|
77
|
+
tuples, it yields thru the type, performing coercions if need, but a top-level structure
|
78
|
+
of input keeps to be an Array. There are two abstract class methods — `coerce_tuple` and
|
79
|
+
`new_from_tuple`, — that could be redefined when, i.e., you need to splat an incoming
|
80
|
+
array into arguments due to avoid breaking the existing interface.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class Example
|
84
|
+
extend Dry::Tuple::ClassInterface
|
85
|
+
tuple Types.Tuple(Types.Value(:example) << Types::Coercible::Symbol, Types::String)
|
86
|
+
|
87
|
+
def initializer(left, right)
|
88
|
+
@left, @right = left, right
|
89
|
+
end
|
90
|
+
|
91
|
+
# @note Used by {StructClassInterface} under the hood.
|
92
|
+
# @param input [Array] after sub types coercion
|
93
|
+
# @return [Any] args acceptable by {#initializer}.
|
94
|
+
# def self.coerce_tuple(input)
|
95
|
+
# input
|
96
|
+
# end
|
97
|
+
|
98
|
+
# @param input [Any] after {.coerce_tuple}
|
99
|
+
# @return [self] instantiated object with the given arguments
|
100
|
+
def self.new_from_tuple(input)
|
101
|
+
new(*input)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class OtherExample < Example
|
106
|
+
tuple Types.Tuple(Types.Value(:other_example) << Types::Coercible::Symbol, [Types::Any])
|
107
|
+
|
108
|
+
def initializer(left, right, *rest)
|
109
|
+
super(left, right)
|
110
|
+
@rest = rest
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
ExampleSum = Example | OtherExample
|
115
|
+
ExampleSum[['example', 'foo']]
|
116
|
+
# => #<Example @left = :example, @right = 'foo'>
|
117
|
+
|
118
|
+
ExampleSum[['other_example', 1, '2', {}]].class
|
119
|
+
# => #<Example @left = :other_example, @right = 1, @rest = ['2', {}]>
|
120
|
+
```
|
121
|
+
|
122
|
+
#### Class interface for Dry::Struct classes.
|
123
|
+
|
124
|
+
And, the initial target of this gem, — let `Dry::Struct` classes to take both the key-value and
|
125
|
+
the tuple inputs. Extend `Dry::Struct` classes with the `Dry::Tuple::StructClassInterface`,
|
126
|
+
ensure keys order with helper method `auto_tuple *keys` (it will auto-declare the tuple from the struct's schema)…
|
127
|
+
Profit!
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
class SomeStruct < Dry::Struct
|
131
|
+
attribute :some, Types::Integer
|
132
|
+
attribute :with, Types::Hash
|
133
|
+
extend ::Dry::Tuple::StructClassInterface
|
134
|
+
auto_tuple :some, :with
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
61
138
|
## Development
|
62
139
|
|
63
140
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/dry-types-tuple.gemspec
CHANGED
data/lib/dry/tuple.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/types/tuple'
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
# The namespace contains mixins for class decoration with tuple type.
|
7
|
+
# The interfaces provide a way to use a tuple in constructors to match proper class by input.
|
8
|
+
# The behaviour is similar to the one provided by `Dry::Struct::ClassInterface` and
|
9
|
+
# allows to combine classes into sums but with positional input validation & coercion.
|
10
|
+
#
|
11
|
+
# @example Splat input
|
12
|
+
# class Example
|
13
|
+
# extend Dry::Tuple::ClassInterface
|
14
|
+
# tuple Types.Tuple(Types::Integer, Types::String)
|
15
|
+
#
|
16
|
+
# def initializer(a1, a2)
|
17
|
+
# # ...
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def self.new_from_tuple(input)
|
21
|
+
# new(*input)
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
module Tuple
|
25
|
+
# Pure class type decorator mixin.
|
26
|
+
module ClassDecorator
|
27
|
+
include Types::Decorator
|
28
|
+
|
29
|
+
# @return [@tuple]
|
30
|
+
def type
|
31
|
+
@tuple
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param input [Array] the result of {#coerce_tuple}
|
35
|
+
# @return [self]
|
36
|
+
# @abstract
|
37
|
+
# It is designed to be redefined for example to splat arguments on constructor.
|
38
|
+
# @note
|
39
|
+
# Will be invoked on input that was passed thru tuple validation & coercion.
|
40
|
+
def new_from_tuple(input)
|
41
|
+
defined?(super) ? super(input) : new(input)
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param input [Array] the result of tuple#call
|
45
|
+
# @return [Array] coerced input
|
46
|
+
# @note
|
47
|
+
# Will be invoked on input that was passed thru tuple validation & coercion.
|
48
|
+
def coerce_tuple(input)
|
49
|
+
defined?(super) ? super(input) : input
|
50
|
+
end
|
51
|
+
|
52
|
+
# @api private
|
53
|
+
def call_safe(input, &block)
|
54
|
+
if defined?(super)
|
55
|
+
resolve_tuple_safe(input) { |output| super(output) }
|
56
|
+
elsif input.is_a?(self)
|
57
|
+
input
|
58
|
+
else
|
59
|
+
resolve_tuple_safe(input, &block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @api private
|
64
|
+
def call_unsafe(input)
|
65
|
+
if defined?(super)
|
66
|
+
resolve_tuple_safe(input) { |output| super(output) }
|
67
|
+
elsif input.is_a?(self)
|
68
|
+
input
|
69
|
+
else
|
70
|
+
resolve_tuple_unsafe(input)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
# @api private
|
77
|
+
def resolve_tuple_safe(input)
|
78
|
+
input = tuple.call_safe(input) do |output = input|
|
79
|
+
output = yield(output) if block_given?
|
80
|
+
return output
|
81
|
+
end
|
82
|
+
new_from_tuple(coerce_tuple(input))
|
83
|
+
end
|
84
|
+
|
85
|
+
# @api private
|
86
|
+
def resolve_tuple_unsafe(input)
|
87
|
+
input = tuple.call_unsafe(input)
|
88
|
+
new_from_tuple(coerce_tuple(input))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
module HookExtendObject
|
93
|
+
# Makes the module's features to be prepended instead of appended to the target class when extended.
|
94
|
+
# Also defines the `tuple` class attribute.
|
95
|
+
# @api private
|
96
|
+
private def extend_object(base)
|
97
|
+
base.singleton_class.prepend(self)
|
98
|
+
base.defines :tuple
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
module ClassInterface
|
103
|
+
include Core::ClassAttributes
|
104
|
+
include Types::Type
|
105
|
+
include Types::Builder
|
106
|
+
include ClassDecorator
|
107
|
+
extend HookExtendObject
|
108
|
+
end
|
109
|
+
|
110
|
+
# Extracted due to make it possible to use this feature within {Dry::Struct} classes.
|
111
|
+
# @example extending Dry::Struct subclass
|
112
|
+
#
|
113
|
+
# class SomeStruct < Dry::Struct
|
114
|
+
# attribute :some, Types::Integer
|
115
|
+
# attribute :with, Types::Hash
|
116
|
+
# extend ::Dry::Tuple::StructClassInterface
|
117
|
+
# auto_tuple :some, :with
|
118
|
+
# end
|
119
|
+
module StructClassInterface
|
120
|
+
include ClassDecorator
|
121
|
+
extend HookExtendObject
|
122
|
+
|
123
|
+
class << self
|
124
|
+
private def extend_object(base)
|
125
|
+
super
|
126
|
+
base.defines :keys_order
|
127
|
+
base.keys_order []
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def auto_tuple(*keys)
|
132
|
+
keys_order(keys_order | keys)
|
133
|
+
index = schema.keys.map { |t| [t.name, t.type] }.to_h
|
134
|
+
tuple Dry::Types::Tuple.build_unsplat(index.values_at(*keys_order))
|
135
|
+
end
|
136
|
+
|
137
|
+
def coerce_tuple(input)
|
138
|
+
keys_order.zip(input).to_h
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
data/lib/dry/types/tuple.rb
CHANGED
@@ -18,6 +18,11 @@ module Dry
|
|
18
18
|
# @see build_index
|
19
19
|
# @return [Dry::Types::Tuple]
|
20
20
|
def self.build(*types)
|
21
|
+
build_unsplat(types)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
def self.build_unsplat(types)
|
21
26
|
new(::Array, types_index: build_index(types))
|
22
27
|
end
|
23
28
|
|
@@ -41,7 +46,7 @@ module Dry
|
|
41
46
|
end
|
42
47
|
|
43
48
|
if types[-1].size > 1
|
44
|
-
raise ArgumentError, "rest_type should be an Array with single element to apply to the rest of items"
|
49
|
+
raise ArgumentError, "rest_type should be an Array with single element to apply to the rest of items: #{types[-1]}"
|
45
50
|
end
|
46
51
|
|
47
52
|
types.pop[0]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-types-tuple
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anton
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-types
|
@@ -96,6 +96,7 @@ files:
|
|
96
96
|
- README.md
|
97
97
|
- Rakefile
|
98
98
|
- dry-types-tuple.gemspec
|
99
|
+
- lib/dry/tuple.rb
|
99
100
|
- lib/dry/types/tuple.rb
|
100
101
|
homepage: https://github.com/estum/dry-types-tuple
|
101
102
|
licenses:
|