blood_contracts-core 0.4.1 → 0.4.2
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 +4 -4
- data/CHANGELOG.md +10 -3
- data/README.md +16 -0
- data/blood_contracts-core.gemspec +1 -1
- data/lib/blood_contracts/core/tuple.rb +55 -2
- data/lib/blood_contracts/ext/symbolize_keys.rb +11 -0
- data/spec/blood_contracts/core_spec.rb +99 -6
- 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: 5a95e70463f9422ef654272494ae21d3b2440f6df9f2ec93fce292d1760bf8a5
|
4
|
+
data.tar.gz: 38cae2fd0e55b1e4a32fbbafa455659add37c78ed6c03c1f250166d426bff362
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee8f1b8795c6906757a188e9714869420c31bdab2b77a43ef8ca299a31c9e95269c4d254e63f1cd6f425ad50fccd6d6568c7f0ace662e1c29eb1e28a8e7671b3
|
7
|
+
data.tar.gz: c7e613e87f3c9038094c15f0f0eda3bee294b69d55998ceed794d9c918065d7faff6a77bdb23760ae36e8ebc3b581de74fa26cc5b2dae34fc7deb9125c661355
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
7
7
|
|
8
|
+
## master
|
9
|
+
|
10
|
+
Features:
|
11
|
+
|
12
|
+
- `BC::Tuple#argument` accepts an optional block to define the attribute class dynamically
|
13
|
+
- `BC::Tuple#match` can accept arguments as a Hash
|
14
|
+
|
8
15
|
## [0.4.1] - [2019-07-04]
|
9
16
|
|
10
17
|
Fixes:
|
@@ -16,6 +23,6 @@ key elements. It violates principlke of least surprise.
|
|
16
23
|
|
17
24
|
This is a first public release marked in change log with features extracted from production app.
|
18
25
|
Includes:
|
19
|
-
- Base class
|
20
|
-
- Meta classes
|
21
|
-
-
|
26
|
+
- Base class BloodContracts::Core::Refined to write your own validations
|
27
|
+
- Meta classes BloodContracts::Core::Pipe, BloodContracts::Core::Sum and BloodContracts::Core::Tuple for validations composition in ADT style
|
28
|
+
- BloodContracts::Core::Contract meta class as a syntactic sugar to define your own contracts with Refined validations under the hood
|
data/README.md
CHANGED
@@ -344,10 +344,26 @@ module Registration
|
|
344
344
|
# defines a reader and applies validation on `.match` call
|
345
345
|
attribute :login, Email.or_a(Phone)
|
346
346
|
attribute :password, Ascii
|
347
|
+
# defines an attribute using the anonymous type class
|
348
|
+
attribute :remember_me do
|
349
|
+
def match
|
350
|
+
value.to_s.in? ["checked", ""]
|
351
|
+
end
|
352
|
+
|
353
|
+
def mapped
|
354
|
+
value.to_s == "checked"
|
355
|
+
end
|
356
|
+
end
|
347
357
|
end
|
348
358
|
end
|
349
359
|
```
|
350
360
|
|
361
|
+
Tuple can accept either a list of arguments or a hash:
|
362
|
+
```ruby
|
363
|
+
Registration::Form.match(login, password)
|
364
|
+
Registration::Form.match(login: login, password: password)
|
365
|
+
```
|
366
|
+
|
351
367
|
And the code that you'll put in your controller is something like that:
|
352
368
|
|
353
369
|
```ruby
|
@@ -1,3 +1,7 @@
|
|
1
|
+
require_relative "../ext/symbolize_keys.rb"
|
2
|
+
|
3
|
+
using BloodContracts::SymbolizeKeys
|
4
|
+
|
1
5
|
module BloodContracts::Core
|
2
6
|
# Meta refinement type, represents product of several refinement types
|
3
7
|
class Tuple < Refined
|
@@ -20,6 +24,8 @@ module BloodContracts::Core
|
|
20
24
|
#
|
21
25
|
# rubocop:disable Style/SingleLineMethods
|
22
26
|
def new(*args, **kwargs, &block)
|
27
|
+
args = lookup_hash_args(*args, kwargs)
|
28
|
+
|
23
29
|
return super(*args, **kwargs) if @finalized
|
24
30
|
names = args.pop.delete(:names) if args.last.is_a?(Hash)
|
25
31
|
|
@@ -34,10 +40,16 @@ module BloodContracts::Core
|
|
34
40
|
# rubocop:enable Style/SingleLineMethods
|
35
41
|
|
36
42
|
# Helper which registers attribute in the Tuple, also defines a reader
|
37
|
-
def attribute(name, type)
|
38
|
-
|
43
|
+
def attribute(name, type = nil, &block)
|
44
|
+
if type.nil?
|
45
|
+
type = type_from_block(name, &block)
|
46
|
+
else
|
47
|
+
raise ArgumentError unless type < Refined
|
48
|
+
end
|
49
|
+
|
39
50
|
@attributes << type
|
40
51
|
@names << name
|
52
|
+
|
41
53
|
define_method(name) do
|
42
54
|
match.context.dig(:attributes, name)
|
43
55
|
end
|
@@ -56,6 +68,47 @@ module BloodContracts::Core
|
|
56
68
|
new_klass.failure_klass ||= TupleContractFailure
|
57
69
|
super
|
58
70
|
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Generate an anonymous type from a block
|
75
|
+
#
|
76
|
+
# @param String name of the attribute to define the type for
|
77
|
+
#
|
78
|
+
# @param Proc block which will be evaluated in a context
|
79
|
+
# of our anonymous type
|
80
|
+
#
|
81
|
+
# @return Class
|
82
|
+
#
|
83
|
+
def type_from_block(name, &block)
|
84
|
+
raise ArgumentError unless block_given?
|
85
|
+
|
86
|
+
const_set(
|
87
|
+
"InlineType_#{name.to_s.capitalize}",
|
88
|
+
Class.new(Refined) { class_eval(&block) }
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Handle arguments passed as hash with string or
|
93
|
+
# symbol keys
|
94
|
+
#
|
95
|
+
# @param [Array<Object>] *args that can contain an array of arguments
|
96
|
+
# or a single Hash we're looking for
|
97
|
+
#
|
98
|
+
# @param [Hash<Object, Object>] **kwargs hash that can be converted to
|
99
|
+
# args when args array is empty
|
100
|
+
#
|
101
|
+
# @return [Array<Object>]
|
102
|
+
#
|
103
|
+
def lookup_hash_args(*args, **kwargs)
|
104
|
+
if args.empty?
|
105
|
+
[kwargs]
|
106
|
+
elsif args.length == 1 && args.last.is_a?(Hash)
|
107
|
+
[args.first.symbolize_keys]
|
108
|
+
else
|
109
|
+
args
|
110
|
+
end
|
111
|
+
end
|
59
112
|
end
|
60
113
|
|
61
114
|
# List of values in Tuple
|
@@ -179,18 +179,111 @@ RSpec.describe BloodContracts::Core do
|
|
179
179
|
subject { Test::RegistrationInput.match(email, password) }
|
180
180
|
|
181
181
|
context "when valid input" do
|
182
|
+
shared_examples "is valid" do |options = {}|
|
183
|
+
it do
|
184
|
+
expect(subject).to be_valid
|
185
|
+
unless options[:without_attributes]
|
186
|
+
expect(subject.attributes).to match(attributes)
|
187
|
+
end
|
188
|
+
expect(subject.to_h).to match(email: email, password: password)
|
189
|
+
expect(subject.errors).to be_empty
|
190
|
+
expect(subject.attribute_errors).to be_empty
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
182
194
|
let(:email) { "admin@mail.com" }
|
183
195
|
let(:password) { "newP@ssw0rd" }
|
184
196
|
let(:attributes) do
|
185
197
|
{ email: kind_of(Test::Email), password: kind_of(Test::Ascii) }
|
186
198
|
end
|
187
199
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
200
|
+
include_examples "is valid"
|
201
|
+
|
202
|
+
context "when attributes are defined inline" do
|
203
|
+
before do
|
204
|
+
module Test
|
205
|
+
class InlineRegistrationInput < ::BC::Tuple
|
206
|
+
attribute :email do
|
207
|
+
EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
|
208
|
+
|
209
|
+
def match
|
210
|
+
context[:email] = unpack_refined(value).to_s
|
211
|
+
return if context[:email] =~ EMAIL_REGEX
|
212
|
+
|
213
|
+
failure("Not an email")
|
214
|
+
end
|
215
|
+
|
216
|
+
def mapped
|
217
|
+
context[:email]
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
attribute :password do
|
222
|
+
ASCII_REGEX = /^[[:ascii:]]+$/i
|
223
|
+
|
224
|
+
def match
|
225
|
+
context[:ascii_string] = value.to_s
|
226
|
+
return if context[:ascii_string] =~ ASCII_REGEX
|
227
|
+
|
228
|
+
failure("Not ASCII")
|
229
|
+
end
|
230
|
+
|
231
|
+
def mapped
|
232
|
+
context[:ascii_string]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
subject { Test::InlineRegistrationInput.match(email, password) }
|
240
|
+
|
241
|
+
include_examples "is valid", without_attributes: true
|
242
|
+
|
243
|
+
context "when input is invalid" do
|
244
|
+
let(:email) { "admin" }
|
245
|
+
let(:dynamic_email_type) do
|
246
|
+
Test::InlineRegistrationInput::InlineType_Email
|
247
|
+
end
|
248
|
+
let(:dynamic_password_type) do
|
249
|
+
Test::InlineRegistrationInput::InlineType_Password
|
250
|
+
end
|
251
|
+
let(:email_error) { { dynamic_email_type => ["Not an email"] } }
|
252
|
+
let(:password) { "newP@ssw0rd" }
|
253
|
+
let(:attributes) do
|
254
|
+
attribute_errors.merge(password: kind_of(dynamic_password_type))
|
255
|
+
end
|
256
|
+
let(:attribute_errors) { { email: kind_of(BC::ContractFailure) } }
|
257
|
+
let(:tuple_invalid) do
|
258
|
+
{ Test::InlineRegistrationInput => [:invalid_tuple] }
|
259
|
+
end
|
260
|
+
|
261
|
+
it do
|
262
|
+
expect(subject).to be_invalid
|
263
|
+
expect(subject.attributes).to match(attributes)
|
264
|
+
expect(subject.to_h).to match(email: email_error)
|
265
|
+
expect(subject.errors).to match_array([email_error, tuple_invalid])
|
266
|
+
expect(subject.attribute_errors).to match(attribute_errors)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
context "when input is a Hash" do
|
272
|
+
subject do
|
273
|
+
Test::RegistrationInput.match(password: password, email: email)
|
274
|
+
end
|
275
|
+
|
276
|
+
include_examples "is valid"
|
277
|
+
|
278
|
+
context "when keys are strings" do
|
279
|
+
subject do
|
280
|
+
Test::RegistrationInput.match(
|
281
|
+
"password" => password, "email" => email
|
282
|
+
)
|
283
|
+
end
|
284
|
+
|
285
|
+
include_examples "is valid"
|
286
|
+
end
|
194
287
|
end
|
195
288
|
end
|
196
289
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blood_contracts-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Dolganov (sclinede)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -117,6 +117,7 @@ files:
|
|
117
117
|
- lib/blood_contracts/core/sum.rb
|
118
118
|
- lib/blood_contracts/core/tuple.rb
|
119
119
|
- lib/blood_contracts/core/tuple_contract_failure.rb
|
120
|
+
- lib/blood_contracts/ext/symbolize_keys.rb
|
120
121
|
- spec/blood_contracts/core_spec.rb
|
121
122
|
- spec/spec_helper.rb
|
122
123
|
homepage: https://github.com/sclinede/blood_contracts-core
|