fend 0.1.0 → 0.2.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 +106 -78
- data/fend.gemspec +1 -1
- data/lib/fend.rb +38 -9
- data/lib/fend/plugins/collective_params.rb +4 -2
- data/lib/fend/plugins/contexts.rb +86 -0
- data/lib/fend/plugins/dependencies.rb +11 -5
- data/lib/fend/plugins/external_validation.rb +0 -2
- data/lib/fend/plugins/object_validation.rb +59 -0
- data/lib/fend/plugins/validation_helpers.rb +1 -1
- data/lib/fend/plugins/validation_options.rb +21 -4
- data/lib/fend/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d13258d558a3a951a5b468994dd43d71e5146cb
|
4
|
+
data.tar.gz: 961c725b800fee07ed70e6f937fab1b8e97c42f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78062b89d15fb139ef304be7764a1e0f3bba18f3f8f53d58a7ae70456c8740e2cbf284e36e5e2b3696cf4693db73a4219ec12bddf043b826b4333ff9ef3b037c
|
7
|
+
data.tar.gz: 4849a34a6212b5495a4cad9aa3d6635564164f820669136791cdc36b5f0c1f4fd194feaabe41a6849a171c66ac5c45afc75338cf496f6c9552de5d9af539bbaa
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Fend [](https://travis-ci.org/aradunovic/fend)
|
1
|
+
# Fend [](https://badge.fury.io/rb/fend) [](https://travis-ci.org/aradunovic/fend)
|
2
2
|
|
3
3
|
Fend is a small and extensible data validation toolkit.
|
4
4
|
|
@@ -16,12 +16,13 @@ Fend is a small and extensible data validation toolkit.
|
|
16
16
|
* [Value helpers](#value-helpers)
|
17
17
|
* [Validation helpers](#validation-helpers)
|
18
18
|
* [Validation options](#validation-options)
|
19
|
-
* [
|
19
|
+
* [Contexts](#contexts)
|
20
20
|
* [Data processing](#data-processing)
|
21
21
|
* [Dependencies](#dependencies)
|
22
22
|
* [Coercions](#coercions)
|
23
23
|
* [External validation](#external-validation)
|
24
24
|
* [Full messages](#full-messages)
|
25
|
+
* [Object validation](#object-validation)
|
25
26
|
* [**Code of Conduct**](#code-of-conduct)
|
26
27
|
* [**License**](#license)
|
27
28
|
|
@@ -34,6 +35,8 @@ Some of the features include:
|
|
34
35
|
* Dependency management
|
35
36
|
* Custom/external validation support
|
36
37
|
* Data processing
|
38
|
+
* Contextual validations
|
39
|
+
* Object validation
|
37
40
|
|
38
41
|
## Documentation
|
39
42
|
|
@@ -80,14 +83,31 @@ class UserValidation < Fend
|
|
80
83
|
# define validation block
|
81
84
|
validation do |i|
|
82
85
|
# specify :username param that needs to be validated
|
83
|
-
i.
|
86
|
+
i.params(:username, :email) do |username, email|
|
84
87
|
# append error if username value is not string
|
85
88
|
username.add_error("must be string") unless username.value.is_a?(String)
|
86
89
|
|
90
|
+
email.add_error("is not a valid email address") unless email.match?(EMAIL_REGEX)
|
91
|
+
email.add_error("must be unique") if email.valid? && !unique?(email: email.value)
|
92
|
+
|
87
93
|
username.valid? #=> false
|
88
|
-
|
94
|
+
email.invalid? #=> true
|
89
95
|
end
|
90
96
|
end
|
97
|
+
|
98
|
+
# you have full access to the constructor
|
99
|
+
def initialize(user_model)
|
100
|
+
@user_model = user_model
|
101
|
+
end
|
102
|
+
|
103
|
+
# custom methods are available in validation block
|
104
|
+
def unique?(args)
|
105
|
+
user_model.exists?(args)
|
106
|
+
end
|
107
|
+
|
108
|
+
def user_model
|
109
|
+
@user_model
|
110
|
+
end
|
91
111
|
end
|
92
112
|
```
|
93
113
|
|
@@ -98,7 +118,7 @@ Let's run the validation:
|
|
98
118
|
|
99
119
|
```ruby
|
100
120
|
# run the validation and store the result
|
101
|
-
result = UserValidation.call(username: 1234)
|
121
|
+
result = UserValidation.new(User).call(username: 1234, email: "invalid@email")
|
102
122
|
|
103
123
|
# check if result is a success
|
104
124
|
result.success? #=> false
|
@@ -107,13 +127,13 @@ result.success? #=> false
|
|
107
127
|
result.failure? #=> true
|
108
128
|
|
109
129
|
# get validation input
|
110
|
-
result.input #=> { username: 1234 }
|
130
|
+
result.input #=> { username: 1234, email: "invalid@email" }
|
111
131
|
|
112
132
|
# get result output
|
113
|
-
result.input #=> { username: 1234 }
|
133
|
+
result.input #=> { username: 1234, email: "invalid@email" }
|
114
134
|
|
115
135
|
# get error messages
|
116
|
-
result.messages #=> { username: ["must be string"] }
|
136
|
+
result.messages #=> { username: ["must be string"], email: ["is not a valid email address"] }
|
117
137
|
```
|
118
138
|
|
119
139
|
`result` is an instance of `Result` class.
|
@@ -123,14 +143,11 @@ result.messages #=> { username: ["must be string"] }
|
|
123
143
|
Nested params are defined in the same way as regular params:
|
124
144
|
|
125
145
|
```ruby
|
126
|
-
i.
|
146
|
+
i.params(:address) do |address|
|
127
147
|
address.add_error("must be hash") unless address.value.is_a?(Hash)
|
128
148
|
|
129
|
-
address.
|
149
|
+
address.params(:city, :street) do |city, :street|
|
130
150
|
city.add_error("must be string") unless city.value.is_a?(String)
|
131
|
-
end
|
132
|
-
|
133
|
-
address.param(:street) do |street|
|
134
151
|
street.add_error("must be string") unless street.value.is_a?(String)
|
135
152
|
end
|
136
153
|
end
|
@@ -196,8 +213,6 @@ The `value_helpers` plugin provides additional `Param` methods that can be used
|
|
196
213
|
check or fetch param values.
|
197
214
|
|
198
215
|
```ruby
|
199
|
-
plugin :collective_params
|
200
|
-
|
201
216
|
plugin :value_helpers
|
202
217
|
|
203
218
|
validate do |i|
|
@@ -231,21 +246,17 @@ The `validation_helpers` plugin provides methods for some common validation case
|
|
231
246
|
plugin :validation_helpers
|
232
247
|
|
233
248
|
validation do |i|
|
234
|
-
i.
|
249
|
+
i.params(:username, :address, :tags) do |username, address, tags|
|
235
250
|
username.validate_presence
|
236
251
|
username.validate_type(String)
|
237
|
-
end
|
238
252
|
|
239
|
-
i.param(:address) do |address|
|
240
253
|
address.validate_type(Hash)
|
241
254
|
|
242
255
|
address.param(:city) do |city|
|
243
256
|
city.validate_presence
|
244
257
|
city.validate_type(String)
|
245
258
|
end
|
246
|
-
end
|
247
259
|
|
248
|
-
i.param(:tags) do |tags|
|
249
260
|
tags.validate_type(Array)
|
250
261
|
tags.validate_min_length(1)
|
251
262
|
|
@@ -266,17 +277,13 @@ can be used in order to specify all validations as options.
|
|
266
277
|
plugin :validation_options
|
267
278
|
|
268
279
|
validation do |i|
|
269
|
-
i.
|
280
|
+
i.params(:username, :address, :tags) do |username, address, tags|
|
281
|
+
username.validate(presence: true, type: String)
|
270
282
|
|
271
|
-
i.param(:address) do |address|
|
272
283
|
address.validate_type(Hash)
|
284
|
+
address.params(:city) { |city| city.validate(presence: true, type: String) }
|
273
285
|
|
274
|
-
address.param(:city) { |city| city.validate(presence: true, type: String) }
|
275
|
-
end
|
276
|
-
|
277
|
-
i.param(:tags) do |tags|
|
278
286
|
tags.validate(type: Array, min_length: 1)
|
279
|
-
|
280
287
|
tags.each do |tag|
|
281
288
|
tag.validate(type: String,
|
282
289
|
inclusion: { in: %w(ruby js elixir), message: "#{tag.value} is not a valid tag" })
|
@@ -285,35 +292,45 @@ validation do |i|
|
|
285
292
|
end
|
286
293
|
```
|
287
294
|
|
288
|
-
|
295
|
+
`:allow_nil` and `:allow_blank` options are also supported:
|
296
|
+
|
297
|
+
```ruby
|
298
|
+
username.validate(type: String, allow_nil: true)
|
299
|
+
```
|
300
|
+
|
301
|
+
### Contexts
|
289
302
|
|
290
|
-
|
291
|
-
|
292
|
-
using `#params` method, instead of `#param`:
|
303
|
+
`contexts` plugin adds support for contextual validation, which basically
|
304
|
+
means you can branch validation logic depending on provided context.
|
293
305
|
|
294
306
|
```ruby
|
295
|
-
|
296
|
-
plugin :
|
307
|
+
class UserValidation < Fend
|
308
|
+
plugin :contexts
|
297
309
|
|
298
|
-
|
299
|
-
|
300
|
-
|
310
|
+
validate do |i|
|
311
|
+
i.params(:account_type) do |acc_type|
|
312
|
+
context(:admin) do
|
313
|
+
acc_type.validate_equality("admin")
|
314
|
+
end
|
301
315
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
street.validate(presence: true, type: String)
|
306
|
-
end
|
316
|
+
context(:editor) do
|
317
|
+
acc_type.validate_equality("editor")
|
318
|
+
end
|
307
319
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
320
|
+
# you can check context against multiple values
|
321
|
+
context(:visitor, :demo) do
|
322
|
+
acc_type.validate_equality(nil)
|
323
|
+
end
|
312
324
|
end
|
313
325
|
end
|
314
326
|
end
|
327
|
+
|
328
|
+
user_validation = UserValidation.new(context: :editor)
|
329
|
+
user_validation.call(account_type: "invalid").messages #=> { account_type: ["must be equal to 'editor'"] }
|
315
330
|
```
|
316
331
|
|
332
|
+
If no context is provided, context will be set to `:default`.
|
333
|
+
|
317
334
|
### Data processing
|
318
335
|
|
319
336
|
With `data_processing` plugin you can process input/output data.
|
@@ -358,51 +375,29 @@ result.output #=> { username: "john", timestamp: 2018-01-01 00:00:00 UTC }
|
|
358
375
|
|
359
376
|
### Dependencies
|
360
377
|
|
361
|
-
The `dependencies` plugin enables you to register and resolve dependencies.
|
362
|
-
|
363
|
-
There are two types of dependencies:
|
364
|
-
|
365
|
-
1. Inheritable - Available in subclasses also
|
366
|
-
2. Local - registered under a key in `deps` registry. Available only in the current
|
367
|
-
class
|
378
|
+
The `dependencies` plugin enables you to register and resolve global dependencies.
|
368
379
|
|
369
380
|
To resolve dependencies, pass `:inject` option with dependency list
|
370
381
|
to `.validate` method:
|
371
382
|
|
372
383
|
```ruby
|
373
|
-
plugin :collective_params
|
374
384
|
plugin :validation_options
|
375
|
-
|
376
385
|
plugin :dependencies, user_model: User
|
377
386
|
|
378
|
-
validate(inject: [:user_model
|
379
|
-
|
387
|
+
validate(inject: [:user_model]) do |i, user_model|
|
380
388
|
i.params(:email, :password, :password_confirmation) do |email, password, password_confirmation|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
if context == :password_change
|
385
|
-
password.validate(type: String, min_length: 6)
|
386
|
-
if password.valid?
|
387
|
-
password.add_error("must be confirmed") unless password.value == password_confirmation.value
|
388
|
-
end
|
389
|
+
if email.present? && !user_model.exists?(email: email.value)
|
390
|
+
email.add_error("not found")
|
389
391
|
end
|
390
392
|
end
|
391
393
|
end
|
392
|
-
|
393
|
-
def initialize(context)
|
394
|
-
deps[:context] = context
|
395
|
-
end
|
396
394
|
```
|
397
395
|
|
398
|
-
Here, `:user_model` is an inheritable dependency,
|
399
|
-
|
400
|
-
As you can see, we're expecting for `context` to be passed in the initializer:
|
396
|
+
Here, `:user_model` is an inheritable dependency, which means it will be
|
397
|
+
available in subclasses also. Global dependencies can be defined on `Fend` directly:
|
401
398
|
|
402
399
|
```ruby
|
403
|
-
|
404
|
-
|
405
|
-
result.messages #=> { email: ["not found"], password: ["must be string", "must be confirmed"] }
|
400
|
+
Fend.plugin :dependencies, user_model: User
|
406
401
|
```
|
407
402
|
|
408
403
|
### Coercions
|
@@ -411,8 +406,6 @@ result.messages #=> { email: ["not found"], password: ["must be string", "must b
|
|
411
406
|
By default, incoercible values are returned unmodified.
|
412
407
|
|
413
408
|
```ruby
|
414
|
-
plugin :collective_params
|
415
|
-
|
416
409
|
plugin :coercions
|
417
410
|
|
418
411
|
coerce username: :string,
|
@@ -458,7 +451,6 @@ end
|
|
458
451
|
|
459
452
|
class AddressValidation < Fend
|
460
453
|
plugin :validation_options
|
461
|
-
plugin :collective_params
|
462
454
|
|
463
455
|
validate do |i|
|
464
456
|
i.params(:city, :street) do |city, street|
|
@@ -469,7 +461,6 @@ class AddressValidation < Fend
|
|
469
461
|
end
|
470
462
|
|
471
463
|
class UserValidation < Fend
|
472
|
-
plugin :collective_params
|
473
464
|
plugin :external_validation
|
474
465
|
|
475
466
|
validate do |i|
|
@@ -504,6 +495,43 @@ result.full_messages
|
|
504
495
|
# }
|
505
496
|
```
|
506
497
|
|
498
|
+
### Object validation
|
499
|
+
|
500
|
+
`object_validation` plugin adds support for validating object attributes
|
501
|
+
and methods.
|
502
|
+
|
503
|
+
```ruby
|
504
|
+
class UserModelValidation < Fend
|
505
|
+
plugin :object_validation
|
506
|
+
plugin :validation_options
|
507
|
+
|
508
|
+
validate do |user|
|
509
|
+
# use #attrs when validating object attributes/methods
|
510
|
+
user.attrs(:username, :email, :address) do |username, email, address|
|
511
|
+
username.validate(presence: true, max_length: 20, type: String)
|
512
|
+
email.validate(presence: true, format: EMAIL_REGEX, type: String)
|
513
|
+
|
514
|
+
# keep using #params if attribute value is expected to be hash
|
515
|
+
address.params(:city, :street) do |city, street|
|
516
|
+
city.validate(presence: true)
|
517
|
+
street.validate(presence: true)
|
518
|
+
end
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
user = User.new(username: "", email: "invalid@email", address: {})
|
524
|
+
validation = UserModelValidation.call(user)
|
525
|
+
|
526
|
+
validation.success? #=> false
|
527
|
+
validation.messages
|
528
|
+
#=> {
|
529
|
+
# username: ["must be present"],
|
530
|
+
# email: ["is in invalid format"],
|
531
|
+
# address: { city: ["must be present"], street: ["must be present"] }
|
532
|
+
# }
|
533
|
+
```
|
534
|
+
|
507
535
|
## Code of Conduct
|
508
536
|
|
509
537
|
Everyone interacting in the Fend project’s codebases, issue trackers, chat rooms
|
data/fend.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |gem|
|
|
11
11
|
gem.homepage = "https://fend.radunovic.io"
|
12
12
|
gem.license = "MIT"
|
13
13
|
|
14
|
-
gem.files = Dir["README.md", "LICENSE.txt", "lib/**/*.rb", "fend.gemspec"]
|
14
|
+
gem.files = Dir["README.md", "LICENSE.txt", "lib/**/*.rb", "fend.gemspec", "doc/*.md"]
|
15
15
|
gem.require_path = "lib"
|
16
16
|
|
17
17
|
gem.required_ruby_version = ">= 2.0"
|
data/lib/fend.rb
CHANGED
@@ -85,7 +85,7 @@ class Fend
|
|
85
85
|
# Store validation block for later execution:
|
86
86
|
#
|
87
87
|
# validate do |i|
|
88
|
-
# i.
|
88
|
+
# i.params(:foo) do |foo|
|
89
89
|
# # foo validation logic
|
90
90
|
# end
|
91
91
|
# end
|
@@ -96,6 +96,11 @@ class Fend
|
|
96
96
|
def call(input)
|
97
97
|
new.call(input)
|
98
98
|
end
|
99
|
+
|
100
|
+
# Prints a deprecation warning to standard error.
|
101
|
+
def deprecation(message)
|
102
|
+
warn "FEND DEPRECATION WARNING: #{message}"
|
103
|
+
end
|
99
104
|
end
|
100
105
|
|
101
106
|
module InstanceMethods
|
@@ -116,7 +121,7 @@ class Fend
|
|
116
121
|
@_raw_data = raw_data
|
117
122
|
@_input_data = process_input(raw_data) || raw_data
|
118
123
|
@_output_data = process_output(@_input_data) || @_input_data
|
119
|
-
@_input_param
|
124
|
+
@_input_param = param_class.new(:input, @_input_data)
|
120
125
|
end
|
121
126
|
|
122
127
|
# Returns validation block set on class level
|
@@ -140,9 +145,9 @@ class Fend
|
|
140
145
|
# Process output data
|
141
146
|
def process_output(output); end
|
142
147
|
|
143
|
-
#
|
148
|
+
# Execute validation block
|
144
149
|
def validate(&block)
|
145
|
-
|
150
|
+
instance_exec(@_input_param, &block) if block_given?
|
146
151
|
end
|
147
152
|
|
148
153
|
# Instantiate and return result
|
@@ -160,37 +165,61 @@ class Fend
|
|
160
165
|
# Get param value
|
161
166
|
attr_reader :value
|
162
167
|
|
168
|
+
# Get param name
|
169
|
+
attr_reader :name
|
170
|
+
|
163
171
|
# Get param validation errors
|
164
172
|
attr_reader :errors
|
165
173
|
|
166
|
-
def initialize(value)
|
174
|
+
def initialize(name, value)
|
175
|
+
@name = name
|
167
176
|
@value = value
|
168
177
|
@errors = []
|
169
178
|
end
|
170
179
|
|
171
180
|
# Fetch nested value
|
172
181
|
def [](name)
|
182
|
+
fetch(name)
|
183
|
+
end
|
184
|
+
|
185
|
+
def fetch(name)
|
173
186
|
@value.fetch(name, nil) if @value.respond_to?(:fetch)
|
174
187
|
end
|
175
188
|
|
176
189
|
# Define child param and execute validation block
|
177
190
|
def param(name, &block)
|
191
|
+
Fend.deprecation("Calling Param#param to specify params is deprecated and will not be supported in Fend 0.3.0. Use Param#params method instead.")
|
192
|
+
|
178
193
|
return if flat? && invalid?
|
179
194
|
|
180
195
|
value = self[name]
|
181
|
-
param = _build_param(value)
|
196
|
+
param = _build_param(name, value)
|
182
197
|
|
183
198
|
yield(param)
|
184
199
|
|
185
200
|
_nest_errors(name, param.errors) if param.invalid?
|
186
201
|
end
|
187
202
|
|
203
|
+
# Define child params and execute validation block
|
204
|
+
def params(*names, &block)
|
205
|
+
return if flat? && invalid?
|
206
|
+
|
207
|
+
params = names.each_with_object({}) do |name, result|
|
208
|
+
param = _build_param(name, self[name])
|
209
|
+
result[name] = param
|
210
|
+
end
|
211
|
+
|
212
|
+
yield(*params.values)
|
213
|
+
|
214
|
+
params.each { |name, param| _nest_errors(name, param.errors) if param.invalid? }
|
215
|
+
end
|
216
|
+
|
188
217
|
# Define array member param and execute validation block
|
189
218
|
def each(&block)
|
190
|
-
return if (flat? && invalid?) || !@value.is_a?(
|
219
|
+
return if (flat? && invalid?) || !@value.is_a?(Enumerable)
|
191
220
|
|
192
221
|
@value.each_with_index do |value, index|
|
193
|
-
param = _build_param(value)
|
222
|
+
param = _build_param(index, value)
|
194
223
|
|
195
224
|
yield(param, index)
|
196
225
|
|
@@ -218,7 +247,7 @@ class Fend
|
|
218
247
|
end
|
219
248
|
|
220
249
|
def to_s
|
221
|
-
"#{fend_class.inspect}::Param"
|
250
|
+
"#{fend_class.inspect}::Param #{super}"
|
222
251
|
end
|
223
252
|
|
224
253
|
# Return Fend class under which Param class is namespaced
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class Fend
|
4
4
|
module Plugins
|
5
|
-
# `collective_params` plugin allows you to specify multiple params at once,
|
5
|
+
# *[DEPRECATED] `collective_params` plugin allows you to specify multiple params at once,
|
6
6
|
# instead of defining each one separately.
|
7
7
|
#
|
8
8
|
# Example:
|
@@ -40,11 +40,13 @@ class Fend
|
|
40
40
|
# end
|
41
41
|
module CollectiveParams
|
42
42
|
module ParamMethods
|
43
|
+
warn("collective_params plugin is deprecated and will be removed in Fend 0.3.0. #params method is now provided out of the box.")
|
44
|
+
|
43
45
|
def params(*names, &block)
|
44
46
|
return if flat? && invalid?
|
45
47
|
|
46
48
|
params = names.each_with_object({}) do |name, result|
|
47
|
-
param = _build_param(self[name])
|
49
|
+
param = _build_param(name, self[name])
|
48
50
|
result[name] = param
|
49
51
|
end
|
50
52
|
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Fend
|
4
|
+
module Plugins
|
5
|
+
# `contexts` plugin adds support for contextual validation, which basically
|
6
|
+
# means you can branch validation logic depending on provided context.
|
7
|
+
#
|
8
|
+
# class UserValidation < Fend
|
9
|
+
# plugin :contexts
|
10
|
+
#
|
11
|
+
# validate do |i|
|
12
|
+
# i.params(:account_type) do |acc_type|
|
13
|
+
# context(:admin) do
|
14
|
+
# acc_type.validate_equality("admin")
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# context(:editor) do
|
18
|
+
# acc_type.validate_equality("editor")
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # you can check context against multiple values
|
22
|
+
# context(:visitor, :demo) do
|
23
|
+
# acc_type.validate_equality(nil)
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# You can pass the context on initialization
|
30
|
+
#
|
31
|
+
# user_validation = UserValidation.new(context: :editor)
|
32
|
+
# user_validation.call(account_type: "invalid").messages
|
33
|
+
# #=> { account_type: ["must be equal to 'editor'"] }
|
34
|
+
#
|
35
|
+
# `#context` can be called anywhere in the validation block. You can also
|
36
|
+
# specify contextual params
|
37
|
+
#
|
38
|
+
# validate do |i|
|
39
|
+
# context(:admin) do
|
40
|
+
# i.params(:admin_specific_param) do |asp|
|
41
|
+
# # ...
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# context(:editor) do
|
46
|
+
# i.params(:editor_specific_param) do |esp|
|
47
|
+
# # ...
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# ## Default context
|
53
|
+
#
|
54
|
+
# If no context is provided, context will be set to `:default`
|
55
|
+
#
|
56
|
+
# context(:default) do
|
57
|
+
# # default validation logic
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# ## Overriding constructor
|
61
|
+
#
|
62
|
+
# Since context value is set in the constructor, you should always call
|
63
|
+
# `super` when/if overriding it.
|
64
|
+
module Contexts
|
65
|
+
module InstanceMethods
|
66
|
+
def initialize(*args)
|
67
|
+
opts = if (_opts = args.last) && _opts.is_a?(Hash)
|
68
|
+
_opts
|
69
|
+
else
|
70
|
+
{}
|
71
|
+
end
|
72
|
+
|
73
|
+
@_context = opts.fetch(:context, :default)
|
74
|
+
end
|
75
|
+
|
76
|
+
def context(*values, &block)
|
77
|
+
values = Array(values)
|
78
|
+
|
79
|
+
yield if values.include?(@_context)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
register_plugin(:contexts, Contexts)
|
85
|
+
end
|
86
|
+
end
|
@@ -13,7 +13,7 @@ class Fend
|
|
13
13
|
#
|
14
14
|
# 1. **Inheritable dependencies** - available in current validation class
|
15
15
|
# and in subclasses
|
16
|
-
# 2. **Local dependencies** - available only in current validation class
|
16
|
+
# 2. *[DEPRECATED] **Local dependencies** - available only in current validation class
|
17
17
|
#
|
18
18
|
# ### Inheritable dependencies
|
19
19
|
#
|
@@ -30,7 +30,7 @@ class Fend
|
|
30
30
|
#
|
31
31
|
# Now, all `Fend` subclasses will be able to resolve `address_checker`
|
32
32
|
#
|
33
|
-
# ### Local dependencies
|
33
|
+
# ### Local dependencies *[DEPRECATED]
|
34
34
|
#
|
35
35
|
# Local dependencies can be registered in `deps` registry, on instance level.
|
36
36
|
# Recommended place to do this is the initializer.
|
@@ -59,13 +59,17 @@ class Fend
|
|
59
59
|
# class UserValidation < Fend
|
60
60
|
# plugin :dependencies, user_model: User
|
61
61
|
#
|
62
|
-
# validate(inject: [:user_model
|
62
|
+
# validate(inject: [:user_model]) do |i, user_model|
|
63
63
|
# user_model #=> User
|
64
64
|
# address_checker #=> #<AddressChecker ...>
|
65
65
|
# end
|
66
66
|
#
|
67
67
|
# def initialize(address_checker)
|
68
|
-
#
|
68
|
+
# @address_checker = address_checker
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# def address_checker
|
72
|
+
# @address_checker
|
69
73
|
# end
|
70
74
|
# end
|
71
75
|
#
|
@@ -87,7 +91,7 @@ class Fend
|
|
87
91
|
# Here's an example of email uniqueness validation:
|
88
92
|
#
|
89
93
|
# validate(inject: [:user_model]) do |i, user_model|
|
90
|
-
# i.
|
94
|
+
# i.params(:email) do |email|
|
91
95
|
# email.add_error("must be unique") if user_model.exists?(email: email.value)
|
92
96
|
# end
|
93
97
|
# end
|
@@ -112,6 +116,8 @@ class Fend
|
|
112
116
|
|
113
117
|
module InstanceMethods
|
114
118
|
def deps
|
119
|
+
Fend.deprecation("Local dependencies are deprecated and will not be supported in Fend 0.3.0. Instead, you can set attributes or define custom methods which will be available in validation block.")
|
120
|
+
|
115
121
|
@_deps ||= self.class.opts[:dependencies].dup
|
116
122
|
end
|
117
123
|
|
@@ -30,7 +30,6 @@ class Fend
|
|
30
30
|
#
|
31
31
|
# class AddressValidation < Fend
|
32
32
|
# plugin :validation_options
|
33
|
-
# plugin :collective_params
|
34
33
|
#
|
35
34
|
# validate do |i|
|
36
35
|
# i.params(:city, :street) do |city, street|
|
@@ -42,7 +41,6 @@ class Fend
|
|
42
41
|
#
|
43
42
|
# class UserValidation < Fend
|
44
43
|
# plugin :external_validation
|
45
|
-
# plugin :collective_params
|
46
44
|
#
|
47
45
|
# validate do |i|
|
48
46
|
# i.params(:email, :address) do |email, address|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Fend
|
4
|
+
module Plugins
|
5
|
+
# `object_validation` plugin adds support for validating object attributes
|
6
|
+
# and methods.
|
7
|
+
#
|
8
|
+
# class UserModelValidation < Fend
|
9
|
+
# plugin :object_validation
|
10
|
+
# plugin :validation_options
|
11
|
+
#
|
12
|
+
# validate do |user|
|
13
|
+
# user.attrs(:username, :email) do |username, email|
|
14
|
+
# username.validate(presence: true, max_length: 20, type: String)
|
15
|
+
# email.validate(presence: true, format: EMAIL_REGEX, type: String)
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# user = User.new(username: "", email: "invalid@email")
|
21
|
+
# validation = UserModelValidation.call(user)
|
22
|
+
#
|
23
|
+
# validation.success? #=> false
|
24
|
+
# validation.messages #=> { username: ["must be present"], email: ["is in invalid format"] }
|
25
|
+
#
|
26
|
+
# As the example shows, the only change is that instread of the `#params` you
|
27
|
+
# should use `#attrs` method.
|
28
|
+
#
|
29
|
+
# ## Handling hash values
|
30
|
+
#
|
31
|
+
# If attribute value should be a hash, you can still use the `#params`
|
32
|
+
# method:
|
33
|
+
#
|
34
|
+
# # user.address #=> { city: "My city", street: "My street" }
|
35
|
+
# user.attrs(:address) do |address|
|
36
|
+
# address.params(:city, :street) do |city, street|
|
37
|
+
# # ...
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
module ObjectValidation
|
41
|
+
module ParamMethods
|
42
|
+
def attrs(*names, &block)
|
43
|
+
return if flat? && invalid?
|
44
|
+
|
45
|
+
attrs = names.each_with_object({}) do |name, result|
|
46
|
+
attr = _build_param(name, @value.public_send(name))
|
47
|
+
result[name] = attr
|
48
|
+
end
|
49
|
+
|
50
|
+
yield(*attrs.values)
|
51
|
+
|
52
|
+
attrs.each { |name, attr| _nest_errors(name, attr.errors) if attr.invalid? }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
register_plugin(:object_validation, ObjectValidation)
|
58
|
+
end
|
59
|
+
end
|
@@ -9,7 +9,7 @@ class Fend
|
|
9
9
|
# plugin :validation_options
|
10
10
|
#
|
11
11
|
# validate do |i|
|
12
|
-
# i.
|
12
|
+
# i.params(:email) do |email|
|
13
13
|
# email.validate(presence: true, type: String, format: EMAIL_REGEX)
|
14
14
|
# end
|
15
15
|
# end
|
@@ -34,6 +34,20 @@ class Fend
|
|
34
34
|
#
|
35
35
|
# email.validate type: { value: String }, format: { value: EMAIL_REGEX }
|
36
36
|
#
|
37
|
+
# ## Allowing nil and blank values
|
38
|
+
#
|
39
|
+
# You can skip validation if param value is `nil` or blank by passing
|
40
|
+
# `:allow_nil` or `:allow_blank` options:
|
41
|
+
#
|
42
|
+
# # will skip type validation if name.value.nil?
|
43
|
+
# name.validate(type: String, allow_nil: true)
|
44
|
+
#
|
45
|
+
# # will skip type validation if email.blank?
|
46
|
+
# email.validate(type: String, allow_blank: true)
|
47
|
+
#
|
48
|
+
# To see what values are considered as blank,
|
49
|
+
# check ValueHelpers::ParamMethods#blank?.
|
50
|
+
#
|
37
51
|
# `validation_options` supports ExternalValidation plugin:
|
38
52
|
#
|
39
53
|
# plugin :external_validation
|
@@ -41,6 +55,7 @@ class Fend
|
|
41
55
|
# # ...
|
42
56
|
#
|
43
57
|
# email.validate(with: CustomEmailValidator)
|
58
|
+
|
44
59
|
module ValidationOptions
|
45
60
|
NO_ARG_METHODS = [:absence, :presence, :acceptance].freeze
|
46
61
|
ARRAY_ARG_METHODS = [:exclusion, :inclusion, :length_range].freeze
|
@@ -75,6 +90,11 @@ class Fend
|
|
75
90
|
def validate(opts = {})
|
76
91
|
return if opts.empty?
|
77
92
|
|
93
|
+
allow_nil = opts.delete(:allow_nil)
|
94
|
+
allow_blank = opts.delete(:allow_blank)
|
95
|
+
|
96
|
+
return if (allow_nil == true && value.nil?) || (allow_blank == true && blank?)
|
97
|
+
|
78
98
|
opts.each do |validator_name, args|
|
79
99
|
method_name = "validate_#{validator_name}"
|
80
100
|
|
@@ -89,9 +109,6 @@ class Fend
|
|
89
109
|
validation_method_args = [args]
|
90
110
|
end
|
91
111
|
elsif args.is_a?(Hash)
|
92
|
-
next if args[:allow_nil] == true && value.nil?
|
93
|
-
next if args[:allow_blank] == true && blank?
|
94
|
-
|
95
112
|
mandatory_arg_key = MANDATORY_ARG_KEYS[validator_name]
|
96
113
|
|
97
114
|
unless args.key?(mandatory_arg_key) || args.key?(DEFAULT_ARG_KEY)
|
data/lib/fend/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fend
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aleksandar Radunovic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -51,10 +51,12 @@ files:
|
|
51
51
|
- lib/fend.rb
|
52
52
|
- lib/fend/plugins/coercions.rb
|
53
53
|
- lib/fend/plugins/collective_params.rb
|
54
|
+
- lib/fend/plugins/contexts.rb
|
54
55
|
- lib/fend/plugins/data_processing.rb
|
55
56
|
- lib/fend/plugins/dependencies.rb
|
56
57
|
- lib/fend/plugins/external_validation.rb
|
57
58
|
- lib/fend/plugins/full_messages.rb
|
59
|
+
- lib/fend/plugins/object_validation.rb
|
58
60
|
- lib/fend/plugins/validation_helpers.rb
|
59
61
|
- lib/fend/plugins/validation_options.rb
|
60
62
|
- lib/fend/plugins/value_helpers.rb
|