fend 0.2.0 → 0.3.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
- SHA1:
3
- metadata.gz: 5d13258d558a3a951a5b468994dd43d71e5146cb
4
- data.tar.gz: 961c725b800fee07ed70e6f937fab1b8e97c42f3
2
+ SHA256:
3
+ metadata.gz: d0f5edff32bf78090df43951c3a7aa95628ac4e2417566a51db60971bef24634
4
+ data.tar.gz: 201f363db87219f57403f6c665fdcad4e0f7e07a049604775503a68810ad015d
5
5
  SHA512:
6
- metadata.gz: 78062b89d15fb139ef304be7764a1e0f3bba18f3f8f53d58a7ae70456c8740e2cbf284e36e5e2b3696cf4693db73a4219ec12bddf043b826b4333ff9ef3b037c
7
- data.tar.gz: 4849a34a6212b5495a4cad9aa3d6635564164f820669136791cdc36b5f0c1f4fd194feaabe41a6849a171c66ac5c45afc75338cf496f6c9552de5d9af539bbaa
6
+ metadata.gz: d7c9f4ac50bf03c2c6f3ba5f97e2a241b7dcf8b7e49139d4e3d5d889e4f6da6608483cb1e95b078407d9abd4b20fb53ad141e9a5d30cb9188977dbff82e1eba9
7
+ data.tar.gz: 9f4f65a5fea0b112712a86d07be79bbf852040040400ee3f4af65b30e8d8bb04cce5668cd8001f2d023ecca96f905f29a1d392f90af451e7df6c0aa008f1e8e3
data/README.md CHANGED
@@ -11,6 +11,7 @@ Fend is a small and extensible data validation toolkit.
11
11
  * [**Introduction**](#introduction)
12
12
  * [Core functionalities](#core-functionalities)
13
13
  * [Nested params](#nested-params)
14
+ * [Iterating over nested hash params](#iterating-over-nested-hash-params)
14
15
  * [Arrays](#arrays)
15
16
  * [**Plugins overview**](#plugins-overview)
16
17
  * [Value helpers](#value-helpers)
@@ -40,7 +41,7 @@ Some of the features include:
40
41
 
41
42
  ## Documentation
42
43
 
43
- For detailed documentation visit [fend.radunovic.io](http://fend.radunovic.io)
44
+ For detailed documentation visit [aradunovic.github.io/fend](https://aradunovic.github.io/fend)
44
45
 
45
46
  ## Why?
46
47
 
@@ -82,11 +83,12 @@ require "fend"
82
83
  class UserValidation < Fend
83
84
  # define validation block
84
85
  validation do |i|
85
- # specify :username param that needs to be validated
86
+ # specify username and email params that needs to be validated
86
87
  i.params(:username, :email) do |username, email|
87
88
  # append error if username value is not string
88
89
  username.add_error("must be string") unless username.value.is_a?(String)
89
90
 
91
+ # append error is email is invalid or already exists
90
92
  email.add_error("is not a valid email address") unless email.match?(EMAIL_REGEX)
91
93
  email.add_error("must be unique") if email.valid? && !unique?(email: email.value)
92
94
 
@@ -130,7 +132,7 @@ result.failure? #=> true
130
132
  result.input #=> { username: 1234, email: "invalid@email" }
131
133
 
132
134
  # get result output
133
- result.input #=> { username: 1234, email: "invalid@email" }
135
+ result.output #=> { username: 1234, email: "invalid@email" }
134
136
 
135
137
  # get error messages
136
138
  result.messages #=> { username: ["must be string"], email: ["is not a valid email address"] }
@@ -161,14 +163,43 @@ result.failure? #=> true
161
163
  result.messages #=> { address: ["must be hash"] }
162
164
  ```
163
165
 
164
- As you can see, nested param validations are **not** executed when
165
- parent param is invalid.
166
+ **NOTE:** Nested param(s) validations won't be run if parent param
167
+ is invalid.
166
168
 
167
169
  ```ruby
168
170
  result = UserValidation.call(username: "test", address: {})
169
171
  result.messages #=> { address: { city: ["must be string"], street: ["must be string"] } }
170
172
  ```
171
173
 
174
+ #### Iterating over nested hash params
175
+
176
+ In order to iterate over nested hash params while ignoring the keys/names
177
+ use `Param#each` method with `hash: true` option:
178
+
179
+ ```ruby
180
+ i.params(:emails) do |emails|
181
+
182
+ emails.each(hash: true) do |email_address|
183
+ email_address.params(:email, :confirmed) do |email, confirmed|
184
+ email.add_error("must be provided") if email.nil?
185
+ email.add_error("must be confirmed") if email.valid? && confirmed == false
186
+ end
187
+ end
188
+ end
189
+
190
+ user_params = {
191
+ emails: {
192
+ "0" => { email: "love@ruby.com", confirmed: false },
193
+ "1" => { email: "", confirmed: "" },
194
+ }
195
+ }
196
+
197
+ result = UserValidation.call(user_params)
198
+
199
+ result.messages
200
+ #=> { emails: { "0" => { email: ["must be confirmed"] }, "1" => { email: ["must be provided"] } } }
201
+ ```
202
+
172
203
  ### Arrays
173
204
 
174
205
  Validating array members is done by passing a block to `Param#each` method:
@@ -188,7 +219,7 @@ result = UserValidation.call(tags: [1, 2])
188
219
  result.messages #=> { tags: { 0 => ["must be string"], 1 => ["must be string"] } }
189
220
  ```
190
221
 
191
- Needless to say, member validation won't be run if `tags` is not an array.
222
+ **NOTE:** Member validations won't be run if `tags` is not an array.
192
223
 
193
224
  Fend makes it possible to validate specific array members, since `#each` method
194
225
  also provides an `index`:
@@ -205,7 +236,7 @@ end
205
236
 
206
237
  ## Plugins overview
207
238
 
208
- For complete plugins documentation, go to [fend.radunovic.io](http://fend.radunovic.io).
239
+ For complete plugins documentation, go to [aradunovic.github.io/fend](https://aradunovic.github.io/fend).
209
240
 
210
241
  ### Value helpers
211
242
 
@@ -252,7 +283,7 @@ validation do |i|
252
283
 
253
284
  address.validate_type(Hash)
254
285
 
255
- address.param(:city) do |city|
286
+ address.params(:city) do |city|
256
287
  city.validate_presence
257
288
  city.validate_type(String)
258
289
  end
@@ -362,14 +393,16 @@ class UserValidation < Fend
362
393
  end
363
394
 
364
395
  validate do |i|
365
- i.param(:username) { |username| username.value #=> "john" }
366
- i.param(:foo) { |foo| foo.value #=> "foo" }
396
+ i.params(:username, :foo) do |username, foo|
397
+ username.value #=>"john"
398
+ foo.value #=> "foo"
399
+ end
367
400
  end
368
401
  end
369
402
 
370
403
  result = UserValidation.call(username: "john")
371
404
 
372
- result.input #=> { username: "john" }
405
+ result.input #=> { username: "john", foo: "foo" }
373
406
  result.output #=> { username: "john", timestamp: 2018-01-01 00:00:00 UTC }
374
407
  ```
375
408
 
@@ -4,11 +4,11 @@ Gem::Specification.new do |gem|
4
4
  gem.name = "fend"
5
5
  gem.version = Fend.version
6
6
  gem.authors = ["Aleksandar Radunovic"]
7
- gem.email = ["aleksandar@radunovic.io"]
7
+ gem.email = ["a.radunovic@pm.me"]
8
8
 
9
9
  gem.summary = "Small and extensible data validation toolkit"
10
10
  gem.description = gem.summary
11
- gem.homepage = "https://fend.radunovic.io"
11
+ gem.homepage = "https://aradunovic.github.io/fend"
12
12
  gem.license = "MIT"
13
13
 
14
14
  gem.files = Dir["README.md", "LICENSE.txt", "lib/**/*.rb", "fend.gemspec", "doc/*.md"]
@@ -186,20 +186,6 @@ class Fend
186
186
  @value.fetch(name, nil) if @value.respond_to?(:fetch)
187
187
  end
188
188
 
189
- # Define child param and execute validation block
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
-
193
- return if flat? && invalid?
194
-
195
- value = self[name]
196
- param = _build_param(name, value)
197
-
198
- yield(param)
199
-
200
- _nest_errors(name, param.errors) if param.invalid?
201
- end
202
-
203
189
  # Define child params and execute validation block
204
190
  def params(*names, &block)
205
191
  return if flat? && invalid?
@@ -214,16 +200,21 @@ class Fend
214
200
  params.each { |name, param| _nest_errors(name, param.errors) if param.invalid? }
215
201
  end
216
202
 
217
- # Define array member param and execute validation block
218
- def each(&block)
203
+ # Define enumerable param member and execute validation block
204
+ def each(opts = {}, &block)
219
205
  return if (flat? && invalid?) || !@value.is_a?(Enumerable)
220
206
 
207
+ is_hash = opts[:hash].eql?(true)
208
+
209
+ return if is_hash && !@value.is_a?(Hash)
210
+
221
211
  @value.each_with_index do |value, index|
222
- param = _build_param(index, value)
212
+ param_name, param_value = is_hash ? value : [index, value]
213
+ param = _build_param(param_name, param_value)
223
214
 
224
215
  yield(param, index)
225
216
 
226
- _nest_errors(index, param.errors) if param.invalid?
217
+ _nest_errors(param.name, param.errors) if param.invalid?
227
218
  end
228
219
  end
229
220
 
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Fend
4
+ module Plugins
5
+ # `base_errors` plugin allows you to add validation errors which are
6
+ # not related to a specific param, but to validation input as a whole.
7
+ #
8
+ # class AuthValidation < Fend
9
+ # plugin :base_errors
10
+ #
11
+ # validate do |i|
12
+ # i.params(:email, :password) do |email, password|
13
+ # # ...
14
+ #
15
+ # if email.invalid? || password.invalid?
16
+ # add_base_error("Invalid email or password")
17
+ # end
18
+ # end
19
+ # end
20
+ # end
21
+ #
22
+ # Messages are available under `:base` key by default.
23
+ #
24
+ # AuthValidation.call(email: nil, password: nil).messages
25
+ # #=> { base: ["Invalid email or password"] }
26
+ #
27
+ # You can specify custom key when loading the plugin:
28
+ #
29
+ # plugin :base_errors, key: :general
30
+ module BaseErrors
31
+ DEFAULT_KEY = :base
32
+
33
+ def self.configure(validation, opts = {})
34
+ validation.opts[:base_errors_key] = opts[:key] || validation.opts[:base_errors_key] || DEFAULT_KEY
35
+ end
36
+
37
+ module InstanceMethods
38
+ def add_base_error(message)
39
+ messages = @_input_param.errors
40
+ key = self.class.opts[:base_errors_key]
41
+
42
+ if messages.is_a?(Hash) && messages.key?(key)
43
+ messages[key] << message
44
+ else
45
+ @_input_param.params(key) { |base| base.add_error(message) }
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ register_plugin(:base_errors, BaseErrors)
52
+ end
53
+ end
@@ -9,15 +9,7 @@ class Fend
9
9
  #
10
10
  # ## Registering dependencies
11
11
  #
12
- # There are two types of dependencies:
13
- #
14
- # 1. **Inheritable dependencies** - available in current validation class
15
- # and in subclasses
16
- # 2. *[DEPRECATED] **Local dependencies** - available only in current validation class
17
- #
18
- # ### Inheritable dependencies
19
- #
20
- # Inheritable dependencies can be registered when plugin is loaded:
12
+ # Dependencies can be registered when plugin is loaded:
21
13
  #
22
14
  # plugin :dependencies, user_class: User
23
15
  #
@@ -30,27 +22,6 @@ class Fend
30
22
  #
31
23
  # Now, all `Fend` subclasses will be able to resolve `address_checker`
32
24
  #
33
- # ### Local dependencies *[DEPRECATED]
34
- #
35
- # Local dependencies can be registered in `deps` registry, on instance level.
36
- # Recommended place to do this is the initializer.
37
- #
38
- # class UserValidation < Fend
39
- # plugin :dependencies
40
- #
41
- # def initialize(model)
42
- # # you can pass a dependency on initialization
43
- # deps[:model] = model
44
- #
45
- # # or
46
- #
47
- # # hardcode it yourself
48
- # deps[:address_checker] = AddressChecker.new
49
- # end
50
- # end
51
- #
52
- # user_validation = UserValidation.new(User)
53
- #
54
25
  # ## Resolving dependencies
55
26
  #
56
27
  # To resolve dependencies, `:inject` option needs to be provided to the
@@ -73,19 +44,13 @@ class Fend
73
44
  # end
74
45
  # end
75
46
  #
76
- # ## Overriding inheritable dependencies
47
+ # ## Overriding dependencies
77
48
  #
78
- # To override inheritable dependency, just load the plugin again in a
79
- # subclass, or define local dependency with the same name.
49
+ # To override global dependency, just load the plugin again in a subclass
50
+ # and specify new dependecy value.
80
51
  #
81
52
  # plugin :dependencies, user_model: SpecialUser
82
53
  #
83
- # # or
84
- #
85
- # def initialize
86
- # deps[:user_model] = SpecialUser
87
- # end
88
- #
89
54
  # ## Example usage
90
55
  #
91
56
  # Here's an example of email uniqueness validation:
@@ -115,16 +80,10 @@ class Fend
115
80
  end
116
81
 
117
82
  module InstanceMethods
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
-
121
- @_deps ||= self.class.opts[:dependencies].dup
122
- end
123
-
124
83
  def validate(&block)
125
84
  super if self.class.specified_dependencies.nil?
126
85
 
127
- dependencies = deps.values_at(*self.class.specified_dependencies)
86
+ dependencies = self.class.opts[:dependencies].values_at(*self.class.specified_dependencies)
128
87
 
129
88
  yield(@_input_param, *dependencies) if block_given?
130
89
  end
@@ -3,7 +3,7 @@
3
3
  class Fend
4
4
  module Plugins
5
5
  # `full_messages` plugin adds `#full_messages` method to `Result` which
6
- # returns error messages with prependend param name
6
+ # returns error messages with prependend param name.
7
7
  #
8
8
  # class UserValidation < Fend
9
9
  # plugin :full_messages
@@ -23,7 +23,7 @@ class Fend
23
23
  # { tags: { 0 => ["0 must be string"] } }
24
24
  #
25
25
  # In order to make full messages nicer for array elements,
26
- # pass `:array_memeber_names` option when loading the plugin:
26
+ # pass `:array_member_names` option when loading the plugin:
27
27
  #
28
28
  # plugin :full_messages, array_member_names: { tags: :tag }
29
29
  #
@@ -35,30 +35,78 @@ class Fend
35
35
  #
36
36
  # Fend.plugin :full_messages, array_member_names: { octopi: :octopus }
37
37
  #
38
+ # ## Base errors
39
+ #
40
+ # Full messages are **not** generated for errors added with `base_errors`
41
+ # plugin, since those messages are not connected to specific param(s).
38
42
  module FullMessages
39
43
  def self.configure(validation, opts = {})
40
44
  validation.opts[:full_messages_array_member_names] = (validation.opts[:full_messages_array_member_names] || {}).merge(opts[:array_member_names] || {})
45
+ validation.const_set(:FullMessagesGenerator, Generator) unless validation.const_defined?(:FullMessagesGenerator)
41
46
  end
42
47
 
43
48
  module ResultMethods
44
49
  def full_messages
45
- @_full_messages ||= generate_full_messages(@errors)
50
+ @_full_messages ||= full_messages_generator.call(@errors)
46
51
  end
47
52
 
48
53
  private
49
54
 
50
- def generate_full_messages(errors, array_param_name = nil)
51
- errors.each_with_object({}) do |(param, messages), result|
52
- result[param] = if messages.is_a?(Hash)
53
- param_is_array = messages.first[0].is_a?(Integer)
55
+ def full_messages_generator
56
+ self.fend_class::FullMessagesGenerator.new(
57
+ array_member_names: fend_class.opts[:full_messages_array_member_names],
58
+ skip: [fend_class.opts[:base_errors_key]]
59
+ )
60
+ end
61
+ end
62
+
63
+ class Generator
64
+ def initialize(opts = {})
65
+ @array_params = opts.fetch(:array_member_names, {})
66
+ @skip_list = opts[:skip]
67
+ end
68
+
69
+ def call(errors)
70
+ errors.each_with_object({}) do |(param_name, messages), result|
71
+ result[param_name] = if @skip_list.include?(param_name)
72
+ messages
73
+ else
74
+ messages_for(param_name, messages)
75
+ end
76
+ end
77
+ end
78
+
79
+ private
54
80
 
55
- generate_full_messages(messages, param_is_array ? param : nil)
56
- else
57
- param_name = fend_class.opts[:full_messages_array_member_names].fetch(array_param_name, param)
58
- messages.map { |message| "#{param_name} #{message}"}
59
- end
81
+ def messages_for(param, messages)
82
+ if messages.is_a?(Hash)
83
+ process_hash_messages(param, messages)
84
+ else
85
+ full_messages_for(param, messages)
60
86
  end
61
87
  end
88
+
89
+ def process_hash_messages(param, messages)
90
+ param_is_array = messages.first[0].is_a?(Integer)
91
+
92
+ messages.each_with_object({}) do |(_param, msgs), result|
93
+ param_name = if param_is_array
94
+ @array_params.fetch(param, _param)
95
+ else
96
+ _param
97
+ end
98
+
99
+ result[_param] = messages_for(param_name, msgs)
100
+ end
101
+ end
102
+
103
+ def full_messages_for(param_name, messages)
104
+ messages.map { |message| build_full_message(param_name, message) }
105
+ end
106
+
107
+ def build_full_message(param_name, message)
108
+ "#{param_name} #{message}"
109
+ end
62
110
  end
63
111
  end
64
112
 
@@ -5,7 +5,7 @@ class Fend
5
5
 
6
6
  module VERSION
7
7
  MAJOR = 0
8
- MINOR = 2
8
+ MINOR = 3
9
9
  PATCH = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].compact.join(".")
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.2.0
4
+ version: 0.3.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-05-02 00:00:00.000000000 Z
11
+ date: 2018-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -40,7 +40,7 @@ dependencies:
40
40
  version: '0'
41
41
  description: Small and extensible data validation toolkit
42
42
  email:
43
- - aleksandar@radunovic.io
43
+ - a.radunovic@pm.me
44
44
  executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
@@ -49,8 +49,8 @@ files:
49
49
  - README.md
50
50
  - fend.gemspec
51
51
  - lib/fend.rb
52
+ - lib/fend/plugins/base_errors.rb
52
53
  - lib/fend/plugins/coercions.rb
53
- - lib/fend/plugins/collective_params.rb
54
54
  - lib/fend/plugins/contexts.rb
55
55
  - lib/fend/plugins/data_processing.rb
56
56
  - lib/fend/plugins/dependencies.rb
@@ -61,7 +61,7 @@ files:
61
61
  - lib/fend/plugins/validation_options.rb
62
62
  - lib/fend/plugins/value_helpers.rb
63
63
  - lib/fend/version.rb
64
- homepage: https://fend.radunovic.io
64
+ homepage: https://aradunovic.github.io/fend
65
65
  licenses:
66
66
  - MIT
67
67
  metadata: {}
@@ -81,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
81
  version: '0'
82
82
  requirements: []
83
83
  rubyforge_project:
84
- rubygems_version: 2.6.8
84
+ rubygems_version: 2.7.6
85
85
  signing_key:
86
86
  specification_version: 4
87
87
  summary: Small and extensible data validation toolkit
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Fend
4
- module Plugins
5
- # *[DEPRECATED] `collective_params` plugin allows you to specify multiple params at once,
6
- # instead of defining each one separately.
7
- #
8
- # Example:
9
- #
10
- # plugin :collective_params
11
- # plugin :validation_helpers # just to make the example more concise
12
- #
13
- # validate do |i|
14
- # i.params(:name, :email, :address) do |name, email, address|
15
- # name.validate_presence
16
- #
17
- # email.validate_format(EMAIL_REGEX)
18
- #
19
- # address.params(:city, :street, :zip) do |city, street, zip|
20
- # # ...
21
- # end
22
- # end
23
- # end
24
- #
25
- # Since all params are then available in the same scope, you can add custom
26
- # validations more easily:
27
- #
28
- # validate do |i|
29
- # i.params(:started_at, :ended_at) do |started_at, ended_at|
30
- # started_at.validate_presence
31
- # started_at.validate_type(Time)
32
- #
33
- # ended_at.validate_presence
34
- # ended_at.validate_type(Time)
35
- #
36
- # if started_at.valid? && ended_at.valid? && started_at > ended_at
37
- # started_at.add_error("must happen before ended_at")
38
- # end
39
- # end
40
- # end
41
- module CollectiveParams
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
-
45
- def params(*names, &block)
46
- return if flat? && invalid?
47
-
48
- params = names.each_with_object({}) do |name, result|
49
- param = _build_param(name, self[name])
50
- result[name] = param
51
- end
52
-
53
- yield(*params.values)
54
-
55
- params.each { |name, param| _nest_errors(name, param.errors) if param.invalid? }
56
- end
57
- end
58
- end
59
-
60
- register_plugin(:collective_params, CollectiveParams)
61
- end
62
- end