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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 072a8e275c97616be8b4b507107f78f01738b5bb
4
- data.tar.gz: 278dc6e7841a98f5e713e0257096ab30aa5a7658
3
+ metadata.gz: c80c300291ed684162f3d39b73486788444c542e
4
+ data.tar.gz: c174bca416c79b2daad6503aa1d0f212238e107e
5
5
  SHA512:
6
- metadata.gz: 982f54f710bf94743d207f6be12375d3a4b7a9f287c37a4962f981d6c49b172c61d436c69ede5db32df11d8c4536328d9d1039c957169a31b7f3fb4d4c1bd313
7
- data.tar.gz: 868f211a49e50d7be95c3c2d259db8d9e6f52b6c80c72a6680db16bd59808561311b1b7fcf4c648b05ca54be5a85fe4c38e1677ba61dba9b08aa159ef9902e7a
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 the validator ###
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
- customer = CustomerDetail.new
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
- validator = CustomerValidator.new
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 ||= context.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
- @value = attribute.flatten.inject(context.entity, :public_send)
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
- @value = context.entity.public_send(attribute)
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 = context.errors
64
+ @errors = validator_context.errors
49
65
  validate(attribute, value, options)
50
66
  end
51
67
  end
@@ -1,3 +1,3 @@
1
1
  module PoroValidator
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -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"
@@ -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.1
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-09 00:00:00.000000000 Z
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