blood_contracts-core 0.4.1 → 0.4.2

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: 7f3ff63364178ca281b22b2ca291904ce9a70ec47056a41f9a622dd9a00c9a55
4
- data.tar.gz: b5b000306c85eb7871fb3f4ad1c7a96d4fd8dbc8ceeab98bab49f50a78f611b0
3
+ metadata.gz: 5a95e70463f9422ef654272494ae21d3b2440f6df9f2ec93fce292d1760bf8a5
4
+ data.tar.gz: 38cae2fd0e55b1e4a32fbbafa455659add37c78ed6c03c1f250166d426bff362
5
5
  SHA512:
6
- metadata.gz: d84a0a438d5d264240e40ef8ba79f2732bcfa3f81d11533e9131b175887197c87e0ed84931126cf7aaa897c6fc7b68b7c0b17fc5ea49c03a383fec86ada6c658
7
- data.tar.gz: 614ed9eb7021fc53fc0e55384868599afcfefb84437cf52a6739cb84e615bc929a3e5ad15ead259629820b32627d2641ae0f6738e33dda05ba8aa6d6cc94a864
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 BloodContracs::Core::Refined to write your own validations
20
- - Meta classes BloodContracs::Core::Pipe, BloodContracs::Core::Sum and BloodContracs::Core::Tuple for validations composition in ADT style
21
- - BloodContracs::Core::Contract meta class as a syntactic sugar to define your own contracts with Refined validations under the hood
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,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "blood_contracts-core"
3
- gem.version = "0.4.1"
3
+ gem.version = "0.4.2"
4
4
  gem.authors = ["Sergey Dolganov (sclinede)"]
5
5
  gem.email = ["sclinede@evilmartians.com"]
6
6
 
@@ -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
- raise ArgumentError unless type < Refined
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
@@ -0,0 +1,11 @@
1
+ module BloodContracts
2
+ module SymbolizeKeys
3
+ refine Hash do
4
+ def symbolize_keys
5
+ each_with_object({}) do |(key, value), acc|
6
+ acc[key.to_sym] = value
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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
- it do
189
- expect(subject).to be_valid
190
- expect(subject.attributes).to match(attributes)
191
- expect(subject.to_h).to match(email: email, password: password)
192
- expect(subject.errors).to be_empty
193
- expect(subject.attribute_errors).to be_empty
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.1
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-07-04 00:00:00.000000000 Z
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