dry-types-tuple 0.0.2 → 0.1.1

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: 6e6edbbb2c8dc709f36d3cfcdd63bab007411ad71106549f9a25986588d1d0af
4
- data.tar.gz: 0fde381de860f1aa66dd3e35df16f611ea28cafbab83e45c75eae19b5452f0a4
3
+ metadata.gz: 1847f0ad55cbc51ed75bdf0ff5a49a89876be7c714dfea8778407aa76b65b051
4
+ data.tar.gz: b859c0d7f1c5110a529362a92b8ac22c136452d59a26867cbfd2daff0f30f239
5
5
  SHA512:
6
- metadata.gz: 11393c3bd16005e8fca81b0b9e6483a8708f2bd7274f4f01eb70c0761ff2480492efd65bc332963b6c5c4c25698c9d9533ba2030407778e7ecc83279afe3185f
7
- data.tar.gz: 233646bf141f872a84a011090fd3802678b88439662ec78e536fa08ead8c637aa203b22022f3f7e400fe7dc19c44a0565da5d40fe29a1381cfd22811ab45b8e0
6
+ metadata.gz: 6e03425bd2b5f380c11cac1c55664615bd212474f9233fffbec94aa4547f467885274a426c5db97b9974d003fa7c4f64d332e2744a236c254f4ea7a14f22cc26
7
+ data.tar.gz: 534cbf77bc3680c8ec72010c8b315bd28c109bcd4d82eb1674bf0ea6b0654933eb8a555d87fae84084d1f57b27f1ace203aad7e238272aa5b0314231e6eb44a7
data/Gemfile CHANGED
@@ -13,6 +13,9 @@ group :test do
13
13
  gem "rspec"
14
14
  gem "warning"
15
15
  gem "rspec-instafail", require: false
16
+ gem "dry-initializer", require: false
17
+ gem "dry-inflector"
18
+ gem "dry-struct"
16
19
  end
17
20
 
18
21
  group :tools do
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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "dry-types-tuple"
5
- spec.version = '0.0.2'
5
+ spec.version = '0.1.1'
6
6
  spec.authors = ["Anton"]
7
7
  spec.email = ["anton.estum@gmail.com"]
8
8
 
data/lib/dry/tuple.rb ADDED
@@ -0,0 +1,144 @@
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
+ case input
55
+ when self
56
+ input
57
+ when Array
58
+ resolve_tuple_safe(input, &block)
59
+ else
60
+ super
61
+ end
62
+ end
63
+
64
+ # @api private
65
+ def call_unsafe(input)
66
+ case input
67
+ when self
68
+ input
69
+ when Array
70
+ resolve_tuple_unsafe(input)
71
+ else
72
+ super
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ # @api private
79
+ def resolve_tuple_safe(input)
80
+ input = tuple.call_safe(input) do |output = input|
81
+ output = yield(output) if block_given?
82
+ return output
83
+ end
84
+ new_from_tuple(coerce_tuple(input))
85
+ end
86
+
87
+ # @api private
88
+ def resolve_tuple_unsafe(input)
89
+ input = tuple.call_unsafe(input)
90
+ new_from_tuple(coerce_tuple(input))
91
+ end
92
+ end
93
+
94
+ module HookExtendObject
95
+ # Makes the module's features to be prepended instead of appended to the target class when extended.
96
+ # Also defines the `tuple` class attribute.
97
+ # @api private
98
+ private def extend_object(base)
99
+ base.singleton_class.prepend(self)
100
+ base.defines :tuple
101
+ end
102
+ end
103
+
104
+ module ClassInterface
105
+ include Core::ClassAttributes
106
+ include Types::Type
107
+ include Types::Builder
108
+ include ClassDecorator
109
+ extend HookExtendObject
110
+ end
111
+
112
+ # Extracted due to make it possible to use this feature within {Dry::Struct} classes.
113
+ # @example extending Dry::Struct subclass
114
+ #
115
+ # class SomeStruct < Dry::Struct
116
+ # attribute :some, Types::Integer
117
+ # attribute :with, Types::Hash
118
+ # extend ::Dry::Tuple::StructClassInterface
119
+ # auto_tuple :some, :with
120
+ # end
121
+ module StructClassInterface
122
+ include ClassDecorator
123
+ extend HookExtendObject
124
+
125
+ class << self
126
+ private def extend_object(base)
127
+ super
128
+ base.defines :keys_order
129
+ base.keys_order []
130
+ end
131
+ end
132
+
133
+ def auto_tuple(*keys)
134
+ keys_order(keys_order | keys)
135
+ index = schema.keys.map { |t| [t.name, t.type] }.to_h
136
+ tuple Dry::Types::Tuple.build_unsplat(index.values_at(*keys_order))
137
+ end
138
+
139
+ def coerce_tuple(input)
140
+ keys_order.zip(input).to_h
141
+ end
142
+ end
143
+ end
144
+ end
@@ -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]
@@ -210,7 +215,7 @@ module Dry
210
215
  yield "#{header}>#{opts}"
211
216
  else
212
217
  yield header.dup << (types.map { |pos, pos_type|
213
- visit(pos_type) { |type| "0: #{type}" }
218
+ visit(pos_type) { |type| "#{pos}: #{type}" }
214
219
  } << rest).compact.join(", ") << ">#{opts}"
215
220
  end
216
221
  end
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.2
4
+ version: 0.1.1
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-26 00:00:00.000000000 Z
11
+ date: 2023-02-06 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: