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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6e6edbbb2c8dc709f36d3cfcdd63bab007411ad71106549f9a25986588d1d0af
4
- data.tar.gz: 0fde381de860f1aa66dd3e35df16f611ea28cafbab83e45c75eae19b5452f0a4
3
+ metadata.gz: 7583440f28dfcf7be606ffade7d58fe0fac29e783c4b79eeb15467ff6b63c654
4
+ data.tar.gz: 27da3385c06793c20d7ece1872d740ebc9cc431dc795c9a3141de4a5d64314ee
5
5
  SHA512:
6
- metadata.gz: 11393c3bd16005e8fca81b0b9e6483a8708f2bd7274f4f01eb70c0761ff2480492efd65bc332963b6c5c4c25698c9d9533ba2030407778e7ecc83279afe3185f
7
- data.tar.gz: 233646bf141f872a84a011090fd3802678b88439662ec78e536fa08ead8c637aa203b22022f3f7e400fe7dc19c44a0565da5d40fe29a1381cfd22811ab45b8e0
6
+ metadata.gz: 1886d2dabab95dc8d07ba7285991b73e70bd6083d81280c0e2ec6dd3238e20889d4a186edc012d8e381523906b6d730f2782e285112ce17385c0db33e5003594
7
+ data.tar.gz: 4e6f489907102ca633736d9109725a96bcae2d677b25feca5daead2d3f69e32368410006325c3776c6e6196ea5dd0e9ff7b3126d5fd3776dc5f07e3d747a4051
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.0'
6
6
  spec.authors = ["Anton"]
7
7
  spec.email = ["anton.estum@gmail.com"]
8
8
 
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
@@ -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.2
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-26 00:00:00.000000000 Z
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: