poro_validator 0.0.1 → 0.1.0
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/README.md +8 -3
- data/lib/poro_validator/utils/deep_fetch.rb +38 -0
- data/lib/poro_validator/validators/base_class.rb +20 -4
- data/lib/poro_validator/version.rb +1 -1
- data/lib/poro_validator.rb +3 -0
- data/poro_validator.gemspec +3 -0
- data/spec/features/validate_hash_object_spec.rb +92 -0
- data/spec/lib/poro_validator/utils/deep_fetch_spec.rb +93 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c80c300291ed684162f3d39b73486788444c542e
|
4
|
+
data.tar.gz: c174bca416c79b2daad6503aa1d0f212238e107e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee06bdb65a1513e9850a2ee6f6732698870bce95751224aff49f853a270b77dd38331a5ead47c2dedecfc81a42513114e58e91331184d5edd41b14f8aa93d076
|
7
|
+
data.tar.gz: 48b39457c43c797397c946723e4a7880b17af87c5f839df66534ce855add51668543012a21e9cd3ffd6f82fd1d8be441597ee67da40e350e4991779fe60168ed
|
data/README.md
CHANGED
@@ -31,6 +31,7 @@ you want to validate we define it in a seperate class making it *subjective*.
|
|
31
31
|
## Features ##
|
32
32
|
- **Familiar, simple and consistent API.**
|
33
33
|
- **Framework agnostic, all you need is a PORO**.
|
34
|
+
- **Validate Hash objects! Good for params, json objects what have you!**
|
34
35
|
- **Validation logic is decoupled from business logic by creating seperate *validator* classes
|
35
36
|
which allows easy testing for the validator class**.
|
36
37
|
- **No magic, caller is in control.**
|
@@ -64,7 +65,7 @@ $ gem install poro_validator
|
|
64
65
|
|
65
66
|
## Usage ##
|
66
67
|
|
67
|
-
### Creating and using
|
68
|
+
### Creating and using a validator ###
|
68
69
|
```ruby
|
69
70
|
# Create a validator
|
70
71
|
class CustomerValidator
|
@@ -75,11 +76,15 @@ class CustomerValidator
|
|
75
76
|
validates :age, numeric: { min: 18 }
|
76
77
|
end
|
77
78
|
|
78
|
-
|
79
|
+
validator = CustomerValidator.new
|
79
80
|
|
80
81
|
# Validate entity
|
82
|
+
customer = CustomerDetail.new
|
83
|
+
validator.valid?(customer) # => false
|
84
|
+
validator.errors.full_messages # => ["last name is not present", "..."]
|
81
85
|
|
82
|
-
|
86
|
+
# Validate hash
|
87
|
+
customer = {}
|
83
88
|
validator.valid?(customer) # => false
|
84
89
|
validator.errors.full_messages # => ["last name is not present", "..."]
|
85
90
|
```
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module PoroValidator
|
2
|
+
module Utils
|
3
|
+
# Searches a deeply nested datastructure for a key path, and returns
|
4
|
+
# the associated value.
|
5
|
+
#
|
6
|
+
# @param args [Array] collection of keys/attributes
|
7
|
+
# @param block [Proc] block to set value if nil
|
8
|
+
#
|
9
|
+
# @return associated value or if nil value from passed block.
|
10
|
+
#
|
11
|
+
# @example fetches a value from a hash or deeply nested hash
|
12
|
+
# options = { user: { location: { address: '123 Street' } } }
|
13
|
+
# options.deep_fetch(:user, :location, :address) #=> '123 Street'
|
14
|
+
#
|
15
|
+
# options.deep_fetch(:user, :non_existent_key) do
|
16
|
+
# 'a value'
|
17
|
+
# end #=> 'a value'
|
18
|
+
module DeepFetch
|
19
|
+
class UndefinedPathError < StandardError; end
|
20
|
+
|
21
|
+
def deep_fetch(*args, &block)
|
22
|
+
args.reduce(self) do |obj, arg|
|
23
|
+
begin
|
24
|
+
arg = Integer(arg) if obj.is_a?(Array)
|
25
|
+
obj.fetch(arg)
|
26
|
+
rescue ArgumentError, IndexError, NoMethodError => e
|
27
|
+
break block.call(arg) if block
|
28
|
+
raise(
|
29
|
+
UndefinedPathError,
|
30
|
+
"Could not fetch path (#{args.join(' > ')}) at #{arg}",
|
31
|
+
e.backtrace
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -17,7 +17,7 @@ module PoroValidator
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def errors
|
20
|
-
@errors
|
20
|
+
@errors
|
21
21
|
end
|
22
22
|
|
23
23
|
def context
|
@@ -35,17 +35,33 @@ module PoroValidator
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def value
|
38
|
+
if entity_is_hash = context.entity.is_a?(::Hash)
|
39
|
+
context.entity.extend(::PoroValidator::Utils::DeepFetch)
|
40
|
+
end
|
41
|
+
|
38
42
|
if nested?
|
39
|
-
|
43
|
+
if entity_is_hash
|
44
|
+
@value = context.entity.deep_fetch(*attribute.flatten) do
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
else
|
48
|
+
@value = attribute.flatten.inject(context.entity, :public_send)
|
49
|
+
end
|
40
50
|
else
|
41
|
-
|
51
|
+
if entity_is_hash
|
52
|
+
@value = context.entity.deep_fetch(attribute) do
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
else
|
56
|
+
@value = context.entity.public_send(attribute)
|
57
|
+
end
|
42
58
|
end
|
43
59
|
end
|
44
60
|
|
45
61
|
# @private
|
46
62
|
def __validate__(validator_context)
|
47
63
|
@context = validator_context
|
48
|
-
@errors =
|
64
|
+
@errors = validator_context.errors
|
49
65
|
validate(attribute, value, options)
|
50
66
|
end
|
51
67
|
end
|
data/lib/poro_validator.rb
CHANGED
@@ -50,5 +50,8 @@ require "poro_validator/validators/float_validator"
|
|
50
50
|
require "poro_validator/validators/length_validator"
|
51
51
|
require "poro_validator/validators/numeric_validator"
|
52
52
|
|
53
|
+
# Utils
|
54
|
+
require "poro_validator/utils/deep_fetch"
|
55
|
+
|
53
56
|
# Modules
|
54
57
|
require "poro_validator/validator"
|
data/poro_validator.gemspec
CHANGED
@@ -46,6 +46,7 @@ Gem::Specification.new do |spec|
|
|
46
46
|
lib/poro_validator/error_store.rb
|
47
47
|
lib/poro_validator/errors.rb
|
48
48
|
lib/poro_validator/exceptions.rb
|
49
|
+
lib/poro_validator/utils/deep_fetch.rb
|
49
50
|
lib/poro_validator/validator.rb
|
50
51
|
lib/poro_validator/validator/base_class.rb
|
51
52
|
lib/poro_validator/validator/conditions.rb
|
@@ -69,9 +70,11 @@ Gem::Specification.new do |spec|
|
|
69
70
|
spec/features/composable_validations_spec.rb
|
70
71
|
spec/features/inheritable_spec.rb
|
71
72
|
spec/features/nested_validations_spec.rb
|
73
|
+
spec/features/validate_hash_object_spec.rb
|
72
74
|
spec/lib/poro_validator/configuration_spec.rb
|
73
75
|
spec/lib/poro_validator/error_store_spec.rb
|
74
76
|
spec/lib/poro_validator/errors_spec.rb
|
77
|
+
spec/lib/poro_validator/utils/deep_fetch_spec.rb
|
75
78
|
spec/lib/poro_validator/validator/base_class_spec.rb
|
76
79
|
spec/lib/poro_validator/validator/conditions_spec.rb
|
77
80
|
spec/lib/poro_validator/validator/factory_spec.rb
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe "validates hash object", type: :feature do
|
4
|
+
context "allows nested structure validations" do
|
5
|
+
subject(:validator) do
|
6
|
+
Class.new do
|
7
|
+
include PoroValidator.validator
|
8
|
+
|
9
|
+
validates :customer_id, presence: true
|
10
|
+
|
11
|
+
validates :customer do
|
12
|
+
validates :first_name, presence: true
|
13
|
+
validates :last_name, presence: true
|
14
|
+
end
|
15
|
+
|
16
|
+
validates :address do
|
17
|
+
validates :line1, presence: true
|
18
|
+
validates :line2, presence: true
|
19
|
+
validates :city, presence: true
|
20
|
+
validates :country do
|
21
|
+
validates :iso_code, presence: true
|
22
|
+
validates :short_name, presence: true
|
23
|
+
validates :coordinates do
|
24
|
+
validates :longtitude, presence: true
|
25
|
+
validates :latitude, presence:true
|
26
|
+
validates :planet do
|
27
|
+
validates :name, presence: true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "if validation fails" do
|
36
|
+
let(:validator) { subject.new }
|
37
|
+
|
38
|
+
let(:invalid_entity) do
|
39
|
+
{
|
40
|
+
customer: {
|
41
|
+
first_name: "Man Bear",
|
42
|
+
last_name: nil,
|
43
|
+
},
|
44
|
+
address: {
|
45
|
+
line1: "175 West Jackson Boulevard",
|
46
|
+
line2: "5th Floor",
|
47
|
+
city: "Chicago",
|
48
|
+
country: {
|
49
|
+
iso_code: "33CHIC",
|
50
|
+
short_name: "CHICAGOUSA",
|
51
|
+
coordinates: {
|
52
|
+
latitude: nil,
|
53
|
+
planet: {
|
54
|
+
name: nil
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
it "validates a hash object" do
|
63
|
+
expect(invalid_entity).to be_kind_of(::Hash)
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#valid?" do
|
67
|
+
it "returns false" do
|
68
|
+
expect(validator.valid?(invalid_entity)).to be_falsey
|
69
|
+
end
|
70
|
+
|
71
|
+
context "empty hash" do
|
72
|
+
let(:invalid_entity) { {} }
|
73
|
+
|
74
|
+
it "validates an empty hash" do
|
75
|
+
expect(invalid_entity).to be_empty
|
76
|
+
end
|
77
|
+
|
78
|
+
it "returns false" do
|
79
|
+
expect(validator.valid?({})).to be_falsey
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "#errors" do
|
85
|
+
it "returns a non empty error hash" do
|
86
|
+
validator.valid?(invalid_entity)
|
87
|
+
expect(validator.errors.count).to be > 0
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe PoroValidator::Utils::DeepFetch do
|
4
|
+
subject { Class.new(Hash) { include ::PoroValidator::Utils::DeepFetch } }
|
5
|
+
let(:hash) do
|
6
|
+
{
|
7
|
+
library: {
|
8
|
+
books: [
|
9
|
+
{ title: 'Call of the Wild' },
|
10
|
+
{ title: 'Moby Dick' }
|
11
|
+
],
|
12
|
+
shelves: nil,
|
13
|
+
location: {
|
14
|
+
address: '123 Library St.'
|
15
|
+
}
|
16
|
+
}
|
17
|
+
}
|
18
|
+
end
|
19
|
+
let(:instance) { subject.new.update(hash) }
|
20
|
+
|
21
|
+
describe '#deep_fetch' do
|
22
|
+
it 'extracts a value from a nested hash' do
|
23
|
+
expect(instance.deep_fetch(:library, :location, :address)).to eq('123 Library St.')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'extracts a value from a nested array' do
|
27
|
+
expect(instance.deep_fetch(:library, :books, 1, :title)).to eq('Moby Dick')
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when one of the keys is not present' do
|
31
|
+
context 'when a block is provided' do
|
32
|
+
it 'returns the value of the block' do
|
33
|
+
value = instance.deep_fetch(:library, :unknown_key, :location) { 'block value' }
|
34
|
+
expect(value).to eq('block value')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when a block is not provided' do
|
39
|
+
context 'when the nested object is an array' do
|
40
|
+
it 'raises an UndefinedPathError' do
|
41
|
+
expect do
|
42
|
+
instance.deep_fetch(:library, :books, 2)
|
43
|
+
end.to(
|
44
|
+
raise_error(
|
45
|
+
::PoroValidator::Utils::DeepFetch::UndefinedPathError,
|
46
|
+
'Could not fetch path (library > books > 2) at 2'
|
47
|
+
)
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when the nested object is a hash' do
|
53
|
+
it 'raises a UndefinedPathError' do
|
54
|
+
expect do
|
55
|
+
instance.deep_fetch(:library, :location, :unknown_key)
|
56
|
+
end.to(
|
57
|
+
raise_error(
|
58
|
+
::PoroValidator::Utils::DeepFetch::UndefinedPathError,
|
59
|
+
'Could not fetch path (library > location > unknown_key) at unknown_key'
|
60
|
+
)
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when the nested object is missing' do
|
66
|
+
it 'raises an UndefinedPathError' do
|
67
|
+
expect do
|
68
|
+
instance.deep_fetch(:library, :unknown_key, :books)
|
69
|
+
end.to(
|
70
|
+
raise_error(
|
71
|
+
::PoroValidator::Utils::DeepFetch::UndefinedPathError,
|
72
|
+
'Could not fetch path (library > unknown_key > books) at unknown_key'
|
73
|
+
)
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when the nested object is nil' do
|
79
|
+
it 'raises an UndefinedPathError' do
|
80
|
+
expect do
|
81
|
+
instance.deep_fetch(:library, :shelves, :address)
|
82
|
+
end.to(
|
83
|
+
raise_error(
|
84
|
+
::PoroValidator::Utils::DeepFetch::UndefinedPathError,
|
85
|
+
'Could not fetch path (library > shelves > address) at address'
|
86
|
+
)
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: poro_validator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kareem Gan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -125,6 +125,7 @@ files:
|
|
125
125
|
- lib/poro_validator/error_store.rb
|
126
126
|
- lib/poro_validator/errors.rb
|
127
127
|
- lib/poro_validator/exceptions.rb
|
128
|
+
- lib/poro_validator/utils/deep_fetch.rb
|
128
129
|
- lib/poro_validator/validator.rb
|
129
130
|
- lib/poro_validator/validator/base_class.rb
|
130
131
|
- lib/poro_validator/validator/conditions.rb
|
@@ -148,9 +149,11 @@ files:
|
|
148
149
|
- spec/features/composable_validations_spec.rb
|
149
150
|
- spec/features/inheritable_spec.rb
|
150
151
|
- spec/features/nested_validations_spec.rb
|
152
|
+
- spec/features/validate_hash_object_spec.rb
|
151
153
|
- spec/lib/poro_validator/configuration_spec.rb
|
152
154
|
- spec/lib/poro_validator/error_store_spec.rb
|
153
155
|
- spec/lib/poro_validator/errors_spec.rb
|
156
|
+
- spec/lib/poro_validator/utils/deep_fetch_spec.rb
|
154
157
|
- spec/lib/poro_validator/validator/base_class_spec.rb
|
155
158
|
- spec/lib/poro_validator/validator/conditions_spec.rb
|
156
159
|
- spec/lib/poro_validator/validator/factory_spec.rb
|