plumb 0.0.7 → 0.0.8

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: 82cbbcfabbe2240d11a1957c7d06f59e314d8013c1459ecdd6339404f0c3208e
4
- data.tar.gz: a17828a33c296ba50bffbb629a806f732bc3a9f73d974617e4526bfd74065d42
3
+ metadata.gz: 7e5d0ac088968506a61d63da75f4010b050579c873d79604a25dfe0b133d3726
4
+ data.tar.gz: d559c1a3964544184258043e14d0bf9e3b41ff743a8117eccee09be2c22b084a
5
5
  SHA512:
6
- metadata.gz: 28ce9c69dcfa1f5d1129745301164400211c1df5707b851a2457f4361b59ed0ad73472851ae2993267f3d74389a6477ded4a60ce3eaed70472c041fad03df7bd
7
- data.tar.gz: 45e8f649c646b3236c6e7e5c3c86ed7493b88fc544585e5dc168675c69660b7ca217b5508a834b2dfa1820f97ca87b4edd9b988d17effb82697da28273545e2d
6
+ metadata.gz: 1207b06d90baa9833cf39737f2697fb6daf9423d94ca531a99307de561b70415bb6c9ec7fb2394af6c1f8b3f86015996ebfde5db6dc3fa2aadd5284fb1867cb2
7
+ data.tar.gz: 19131a0a289b8c5082ceb3a9cd716cdabb2d12466bba76ecc5d20c2efd6db3fbb9c04d13c8706996225189de50d81c5d8325fc5527ecc8395c492430fd17393d
data/README.md CHANGED
@@ -294,6 +294,22 @@ NotEmail.parse('hello') # "hello"
294
294
  NotEmail.parse('hello@server.com') # error
295
295
  ```
296
296
 
297
+ `#not` can also be given a type as argument, which might read better:
298
+
299
+ ```ruby
300
+ Types::Any.not(nil)
301
+ Types::Any.not(Types::Email)
302
+ ```
303
+
304
+ Finally, you can use `Types::Not` for the same effect.
305
+
306
+ ```ruby
307
+ NotNil = Types::Not[nil]
308
+ NotNil.parse(1) # 1
309
+ NotNil.parse('hello') # 'hello'
310
+ NotNil.parse(nil) # error
311
+ ```
312
+
297
313
  #### `#options`
298
314
 
299
315
  Sets allowed options for value.
@@ -506,7 +522,7 @@ So, normally you'd only use this attached to primitive types without further pro
506
522
  Passing a proc will evaluate the proc on every invocation. Use this for generated values.
507
523
 
508
524
  ```ruby
509
- random_number = Types::Numeric.static { rand }
525
+ random_number = Types::Numeric.generate { rand }
510
526
  random_number.parse # 0.32332
511
527
  random_number.parse('foo') # 0.54322 etc
512
528
  ```
@@ -514,7 +530,7 @@ random_number.parse('foo') # 0.54322 etc
514
530
  Note that the type of generated value must match the initial step's type, validated at invocation.
515
531
 
516
532
  ```ruby
517
- random_number = Types::String.static { rand } # this won't raise an error here
533
+ random_number = Types::String.generate { rand } # this won't raise an error here
518
534
  random_number.parse # raises Plumb::ParseError because `rand` is not a String
519
535
  ```
520
536
 
@@ -1200,6 +1216,30 @@ Payload = Types::Hash[
1200
1216
  ]
1201
1217
  ```
1202
1218
 
1219
+ #### Attribute writers
1220
+
1221
+ By default `Types::Data` classes are inmutable, but you can define attribute writers to allow for mutation using the `writer: true` option.
1222
+
1223
+ ```ruby
1224
+ class DBConfig < Types::Data
1225
+ attribute :host, Types::String.default('localhost'), writer: true
1226
+ end
1227
+
1228
+ class Config < Types::Data
1229
+ attribute :host, Types::Forms::URI::HTTP, writer: true
1230
+ attribute :port, Types::Integer.default(80), writer: true
1231
+
1232
+ # Nested structs can have writers too
1233
+ attribute :db, DBConfig.default(DBConfig.new)
1234
+ end
1235
+
1236
+ config = Config.new
1237
+ config.host = 'http://localhost'
1238
+ config.db.host = 'db.local'
1239
+ config.valid? # true
1240
+ config.errors # {}
1241
+ ```
1242
+
1203
1243
  #### Recursive struct definitions
1204
1244
 
1205
1245
  You can use `#defer`. See [recursive types](#recursive-types).
@@ -117,6 +117,7 @@ module Plumb
117
117
 
118
118
  def initialize(attrs = {})
119
119
  assign_attributes(attrs)
120
+ freeze
120
121
  end
121
122
 
122
123
  def ==(other)
@@ -140,13 +141,18 @@ module Plumb
140
141
 
141
142
  # @return [Hash]
142
143
  def to_h
143
- attributes.transform_values do |value|
144
- case value
145
- when ::Array
146
- value.map { |v| v.respond_to?(:to_h) ? v.to_h : v }
147
- else
148
- value.respond_to?(:to_h) ? value.to_h : value
149
- end
144
+ self.class._schema._schema.keys.each.with_object({}) do |key, memo|
145
+ key = key.to_sym
146
+ value = attributes[key]
147
+ val = case value
148
+ when ::Array
149
+ value.map { |v| v.respond_to?(:to_h) ? v.to_h : v }
150
+ when ::NilClass
151
+ nil
152
+ else
153
+ value.respond_to?(:to_h) ? value.to_h : value
154
+ end
155
+ memo[key] = val
150
156
  end
151
157
  end
152
158
 
@@ -158,7 +164,7 @@ module Plumb
158
164
  def assign_attributes(attrs = BLANK_HASH)
159
165
  raise ArgumentError, 'Must be a Hash of attributes' unless attrs.respond_to?(:to_h)
160
166
 
161
- @errors = BLANK_HASH
167
+ @errors = {}
162
168
  result = self.class._schema.resolve(attrs.to_h)
163
169
  @attributes = prepare_attributes(result.value)
164
170
  @errors = result.errors unless result.valid?
@@ -211,7 +217,7 @@ module Plumb
211
217
  # attribute(:friends, Types::Array[Person])
212
218
  # attribute(:friends, [Person])
213
219
  #
214
- def attribute(name, type = Types::Any, &block)
220
+ def attribute(name, type = Types::Any, writer: false, &block)
215
221
  key = Key.wrap(name)
216
222
  name = key.to_sym
217
223
  type = Composable.wrap(type)
@@ -234,13 +240,30 @@ module Plumb
234
240
  end
235
241
 
236
242
  @_schema = _schema + { key => type }
237
- __plumb_define_attribute_method__(name)
243
+ __plumb_define_attribute_reader_method__(name)
244
+ return name unless writer
245
+
246
+ __plumb_define_attribute_writer_method__(name)
238
247
  end
239
248
 
240
- def __plumb_define_attribute_method__(name)
249
+ def __plumb_define_attribute_reader_method__(name)
241
250
  define_method(name) { @attributes[name] }
242
251
  end
243
252
 
253
+ def __plumb_define_attribute_writer_method__(name)
254
+ define_method("#{name}=") do |value|
255
+ type = self.class._schema.at_key(name)
256
+ result = type.resolve(value)
257
+ @attributes[name] = result.value
258
+ if result.valid?
259
+ @errors.delete(name)
260
+ else
261
+ @errors.merge!(name => result.errors)
262
+ end
263
+ result.value
264
+ end
265
+ end
266
+
244
267
  def attribute?(name, *args, &block)
245
268
  attribute(Key.new(name, optional: true), *args, &block)
246
269
  end
data/lib/plumb/not.rb CHANGED
@@ -8,13 +8,19 @@ module Plumb
8
8
 
9
9
  attr_reader :children, :errors
10
10
 
11
- def initialize(step, errors: nil)
12
- @step = step
13
- @errors = errors
11
+ def initialize(step = nil, errors: nil)
12
+ @step = Composable.wrap(step)
13
+ @errors = errors || "must not be #{step.inspect}"
14
14
  @children = [step].freeze
15
15
  freeze
16
16
  end
17
17
 
18
+ # @param step [Object]
19
+ # @return [Not]
20
+ def [](step)
21
+ self.class.new(step)
22
+ end
23
+
18
24
  private def _inspect
19
25
  %(Not(#{@step.inspect}))
20
26
  end
data/lib/plumb/types.rb CHANGED
@@ -159,6 +159,7 @@ module Plumb
159
159
  Stream = StreamClass.new
160
160
  Tuple = TupleClass.new
161
161
  Hash = HashClass.new
162
+ Not = Plumb::Not.new
162
163
  Interface = InterfaceClass.new
163
164
  Email = String[URI::MailTo::EMAIL_REGEXP].as_node(:email)
164
165
  Date = Any[::Date]
data/lib/plumb/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Plumb
4
- VERSION = '0.0.7'
4
+ VERSION = '0.0.8'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plumb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismael Celis
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-25 00:00:00.000000000 Z
11
+ date: 2024-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -115,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
115
  - !ruby/object:Gem::Version
116
116
  version: '0'
117
117
  requirements: []
118
- rubygems_version: 3.5.11
118
+ rubygems_version: 3.5.21
119
119
  signing_key:
120
120
  specification_version: 4
121
121
  summary: Data validation and transformation library.