fend 0.2.0 → 0.3.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
- 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