subvalid 0.0.1 → 0.1.0

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
  SHA1:
3
- metadata.gz: 19c0fec940d79b907b072f1df373e0b6add70f91
4
- data.tar.gz: f2993841ad0edc5f065280f9518ed9e0df8d1a19
3
+ metadata.gz: edca18185857cfbbc93dbb3314d3d09083c5f200
4
+ data.tar.gz: af80dce164c5388a450adaba42db36d4f414ca73
5
5
  SHA512:
6
- metadata.gz: f41942e32113e9101e847c787e14fee98ec9b8a50baa6ddaba10d5e25b6ae73bf20b18d6df5fcdcdfae27cda032d24641d19f2452c9323fcbf5ec5e64f628c38
7
- data.tar.gz: 4ecd389d43b363882c2378b89b649be4baf2e0dde5ff5df31812b13a8b28bd8b0a813462867df94ce2e2a80fb42d9c8b93c775bddec60cc7d9a59f83cb2aad81
6
+ metadata.gz: 9875098c16bcc27ce0acafb0248684536dca09adabfc199c554cd2b4c71d8a7452b4b9b3ecb9e76764e90167cfe98fa01b2fec6d86db6f62839f070b924bf9fb
7
+ data.tar.gz: e9099ec87e31f154edd3fa4448efe3e2ff7c034cc6538aa77def84fc98d02775850a1782d7ceff0936cc14cf1a438da6c0c8b5d5a57032f5748cc0996549cfa3
@@ -0,0 +1,15 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.
12
+
13
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
14
+
15
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/)
data/README.md CHANGED
@@ -7,7 +7,7 @@ you can define it in a separate class - so it's _"subjective"_. (as in **Sub**je
7
7
  **valid**ation).
8
8
 
9
9
  Subvalid was extracted from a project at [Envato](http://envato.com) which
10
- required complex validation logic at each stage of the object's life cycle:
10
+ requires complex validation logic at each stage of an object's life cycle:
11
11
  - Users upload videos. The videos are validated to make sure an actual
12
12
  video was uploaded (and not someone's university Powerpoint slides), that
13
13
  framerate is good, resolution and codec is acceptable etc. Failure here would
@@ -21,6 +21,10 @@ just leave it as _"incomplete"_, and allow the user to come back later and
21
21
  complete it. Once this passes, the item is ready, and we submit it for review to
22
22
  our internal review team.
23
23
 
24
+ All these steps are done asynchronously, so we need to capture the errors, save
25
+ them to a different field on the item, and carry on to report results back to
26
+ the user.
27
+
24
28
  While
25
29
  [ActiveModel::Validations](http://api.rubyonrails.org/classes/ActiveModel/Validations.html)
26
30
  is great if you've got simple validation logic, it doesn't cut it for something
@@ -54,6 +58,8 @@ anything. A key design goal is to **not** pollute the objects being validated at
54
58
  all
55
59
  - Supports nested validation on nested object structures - and nicely handles
56
60
  nested errors.
61
+ - DSL and API inspired by ActiveModel::Validations - just simplified and more
62
+ consistent.
57
63
 
58
64
  ## Development Status [![travis ci build](https://api.travis-ci.org/envato/subvalid.svg)](https://travis-ci.org/envato/subvalid)
59
65
 
@@ -77,12 +83,115 @@ Or install it yourself as:
77
83
 
78
84
  ## Usage
79
85
 
80
- TODO: Write usage instructions here
86
+ Say you've got some object:
87
+ ```ruby
88
+ Person = Struct.new(:name)
89
+ madlep = Person.new("madlep")
90
+ ```
91
+
92
+ You can validate it with Subvalid like this:
93
+ ```ruby
94
+ require 'subvalid'
95
+
96
+ class PersonValidator
97
+ include Subvalid::Validator
98
+
99
+ validates :name, presence: true
100
+ end
101
+
102
+ PersonValidator.validate(madlep).valid? # => true
103
+ ```
104
+
105
+ `validate` returns a validation result. You can check if it is `#valid?` or if
106
+ it has `errors` on an attribute
107
+ ```ruby
108
+ result = PersonValidator.validate(Person.new(nil))
109
+ result.valid? # => false
110
+ result.errors[:name] # => ["is not present"]
111
+ ```
112
+
113
+ Of course, because Subvalid only cares about duck-types, and not any particular
114
+ modelling framework, this validator works equally well with any type of object -
115
+ so long as it responds to `name`
116
+
117
+ ```ruby
118
+ class Person < ActiveRecord::Base
119
+ end
120
+
121
+ madlepAR = Person.create(name: "madlep")
122
+
123
+ PersonValidator.validate(madlepAR).valid? # => true
124
+ ```
125
+
126
+ And you can validate nested data structures
127
+ ```ruby
128
+ Video = Struct.new(:title, :length, :author)
129
+
130
+ class VideoValidator
131
+ include Subvalid::Validator
132
+
133
+ validates :title, presence: true
134
+ validates :length, presence: true
135
+ validates :author do
136
+ validates :name, presence: true
137
+ end
138
+ end
139
+
140
+ invalid_video = Video.new(nil, nil, Person.new(nil))
141
+ result = VideoValidator.validate(video)
142
+ result.to_h # => {:title=>{:errors=>["is not present"]}, :length=>{:errors=>["is not present"]}, :author=>{:name=>{:errors=>["is not present"]}}}
143
+ ```
144
+
145
+
146
+ Or you can DRY up your validation code by composing validators together
147
+ ```ruby
148
+ class VideoValidator
149
+ include Subvalid::Validator
150
+
151
+ validates :title, presence: true
152
+ validates :length, presence: true
153
+ validates :author, with: PersonValidator
154
+ end
155
+ ```
156
+
157
+ Validator execution on specific fields can be run or skipped at validation time
158
+ by passing an `if` validator proc, which decides if the validation should run
159
+ ```ruby
160
+ class PersonValidator
161
+ include Subvalid::Validator
162
+
163
+ validates :postcode, presence: true, if: -> (person) { person.country == "US" }
164
+ end
165
+ ```
166
+
167
+ ## Contact
168
+
169
+ - [github project](https://github.com/envato/subvalid)
170
+ - Bug reports and feature requests are via [github issues](https://github.com/envato/subvalid/issues)
171
+
172
+ ## Maintainers
173
+
174
+ - [Julian Doherty](https://github.com/madlep)
175
+
176
+ ## License
177
+
178
+ `Subvalid` uses MIT license. See
179
+ [`LICENSE.txt`](https://github.com/envato/subvalid/blob/master/LICENSE.txt) for
180
+ details.
181
+
182
+ ## Code of conduct
183
+
184
+ We welcome contribution from everyone. Read more about it in
185
+ [`CODE_OF_CONDUCT.md`](https://github.com/envato/subvalid/blob/master/CODE_OF_CONDUCT.md)
81
186
 
82
187
  ## Contributing
83
188
 
84
- 1. Fork it ( https://github.com/[my-github-username]/subvalid/fork )
85
- 2. Create your feature branch (`git checkout -b my-new-feature`)
86
- 3. Commit your changes (`git commit -am 'Add some feature'`)
87
- 4. Push to the branch (`git push origin my-new-feature`)
189
+ For bug fixes, documentation changes, and small features:
190
+
191
+ 1. Fork it ( https://github.com/subvalid/subvalid/fork )
192
+ 2. Create your feature branch (git checkout -b my-new-feature)
193
+ 3. Commit your changes (git commit -am 'Add some feature')
194
+ 4. Push to the branch (git push origin my-new-feature)
88
195
  5. Create a new Pull Request
196
+
197
+ For larger new features: Do everything as above, but first also make contact with the project maintainers to be sure your change fits with the project direction and you won't be wasting effort going in the wrong direction
@@ -9,6 +9,8 @@ module Subvalid
9
9
 
10
10
  module DSL
11
11
 
12
+ MODIFIERS = [:if]
13
+
12
14
  def validates(*attributes, **validators, &block)
13
15
  if validators.empty? && !block
14
16
  raise "no validations or block specified"
@@ -20,20 +22,30 @@ module Subvalid
20
22
  end
21
23
 
22
24
  private
23
- ValidatorEntry = Struct.new(:validator, :args)
25
+ ValidatorEntry = Struct.new(:validator, :modifiers, :args)
24
26
  def validations
25
27
  @validations ||= Hash.new{|vals,attribute| vals[attribute] = [] }
26
28
  end
27
29
 
28
30
  def add_validations(attributes, validators, block)
31
+ modifiers, validators = extract_modifiers(validators)
29
32
  attributes.each do |attribute|
30
33
  validators.each do |validator_key, args|
31
34
  validator = ValidatorRegistry[validator_key]
32
- validations[attribute] << ValidatorEntry.new(validator, args)
35
+ validations[attribute] << ValidatorEntry.new(validator, modifiers, args)
33
36
  end
34
- validations[attribute] << ValidatorEntry.new(BlockValidator, block) if block
37
+ validations[attribute] << ValidatorEntry.new(BlockValidator, modifiers, block) if block
35
38
  end
36
39
  end
40
+
41
+ def extract_modifiers(validators)
42
+ modifiers = MODIFIERS.inject([]){|acc, mod_key|
43
+ modifier_proc = validators.delete(mod_key)
44
+ acc << modifier_proc if modifier_proc
45
+ acc
46
+ }
47
+ [modifiers, validators]
48
+ end
37
49
  end
38
50
 
39
51
  module API
@@ -46,13 +58,15 @@ module Subvalid
46
58
  end
47
59
 
48
60
  validators.each do |validator_entry|
49
- validator = validator_entry.validator
50
- if attribute == :base
51
- validator.validate(object, attribute_result, *validator_entry.args)
52
- elsif object
53
- validator.validate(object.send(attribute), attribute_result, *validator_entry.args)
54
- else
55
- # no-op if we 're asked to validate an attribute for a nil value - that needs to be caught by a user defined `presence` validation instead
61
+ if validator_entry.modifiers.map{|m| m.call(object)}.all?
62
+ validator = validator_entry.validator
63
+ if attribute == :base
64
+ validator.validate(object, attribute_result, *validator_entry.args)
65
+ elsif object
66
+ validator.validate(object.send(attribute), attribute_result, *validator_entry.args)
67
+ else
68
+ # no-op if we 're asked to validate an attribute for a nil value - that needs to be caught by a user defined `presence` validation instead
69
+ end
56
70
  end
57
71
  end
58
72
 
@@ -2,7 +2,7 @@ module Subvalid
2
2
  class ValidatorRegistry
3
3
  class << self
4
4
  def [](key)
5
- validators[key] or raise "no validator with key=#{key}"
5
+ validators[key] or raise ArgumentError.new("no validator with key=#{key}")
6
6
  end
7
7
 
8
8
  def register(key, validator)
@@ -1,3 +1,3 @@
1
1
  module Subvalid
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -13,7 +13,7 @@ describe Subvalid::ValidatorRegistry do
13
13
 
14
14
  context "when validator doesn't exist" do
15
15
  it "raises an error" do
16
- expect { described_class[:bad_key] }.to raise_error
16
+ expect { described_class[:bad_key] }.to raise_error(ArgumentError)
17
17
  end
18
18
  end
19
19
  end
@@ -11,7 +11,7 @@ describe Subvalid::Validator do
11
11
 
12
12
 
13
13
  validates with: STUB_VALIDATOR
14
- validates :foo, with: STUB_VALIDATOR
14
+ validates :foo, with: STUB_VALIDATOR, if: -> (record) { record.some_predicate? }
15
15
  validates :child do
16
16
  validates :boz, with: STUB_VALIDATOR
17
17
  end
@@ -24,10 +24,14 @@ describe Subvalid::Validator do
24
24
  end
25
25
  Subvalid::ValidatorRegistry.register(:test, TestValidator)
26
26
 
27
- Poro = Struct.new(:foo, :bar, :child) do
27
+ Poro = Struct.new(:foo, :bar, :child, :some_predicate) do
28
28
  def to_s
29
29
  "I'M A PORO"
30
30
  end
31
+
32
+ def some_predicate?
33
+ some_predicate
34
+ end
31
35
  end
32
36
 
33
37
  PoroChild = Struct.new(:baz, :boz) do
@@ -36,8 +40,16 @@ describe Subvalid::Validator do
36
40
  end
37
41
  end
38
42
 
39
- let(:poro) { Poro.new("foo", "bar",
40
- PoroChild.new("baz", "boz")) }
43
+ let(:poro) {
44
+ Poro.new(
45
+ "foo",
46
+ "bar",
47
+ PoroChild.new("baz", "boz"),
48
+ some_predicate
49
+ )
50
+ }
51
+
52
+ let(:some_predicate) { true }
41
53
 
42
54
  describe "#validate" do
43
55
  subject { FooValidator.validate(poro) }
@@ -56,6 +68,17 @@ describe Subvalid::Validator do
56
68
  it "validates child" do
57
69
  expect(subject[:child][:boz].errors).to eq(["testing boz"])
58
70
  end
71
+
72
+ context "when `if` modifier returns false" do
73
+ let(:some_predicate) { false }
74
+ it "doesn't execute validator" do
75
+ expect(subject[:foo].errors).to eq([])
76
+ end
77
+
78
+ it "executes other validators" do
79
+ expect(subject[:child][:boz].errors).to eq(["testing boz"])
80
+ end
81
+ end
59
82
  end
60
83
 
61
84
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: subvalid
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
  - Julian Doherty
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-12 00:00:00.000000000 Z
11
+ date: 2016-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -79,6 +79,7 @@ files:
79
79
  - ".gitignore"
80
80
  - ".rspec"
81
81
  - ".travis.yml"
82
+ - CODE_OF_CONDUCT.md
82
83
  - Gemfile
83
84
  - LICENSE.txt
84
85
  - README.md
@@ -121,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
122
  version: '0'
122
123
  requirements: []
123
124
  rubyforge_project:
124
- rubygems_version: 2.4.5
125
+ rubygems_version: 2.5.1
125
126
  signing_key:
126
127
  specification_version: 4
127
128
  summary: Subjective validation for Plain Old Ruby Objects