fend 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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +516 -0
- data/fend.gemspec +21 -0
- data/lib/fend.rb +296 -0
- data/lib/fend/plugins/coercions.rb +442 -0
- data/lib/fend/plugins/collective_params.rb +60 -0
- data/lib/fend/plugins/data_processing.rb +212 -0
- data/lib/fend/plugins/dependencies.rb +130 -0
- data/lib/fend/plugins/external_validation.rb +98 -0
- data/lib/fend/plugins/full_messages.rb +67 -0
- data/lib/fend/plugins/validation_helpers.rb +246 -0
- data/lib/fend/plugins/validation_options.rb +116 -0
- data/lib/fend/plugins/value_helpers.rb +148 -0
- data/lib/fend/version.rb +13 -0
- metadata +86 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Fend
|
4
|
+
module Plugins
|
5
|
+
# `full_messages` plugin adds `#full_messages` method to `Result` which
|
6
|
+
# returns error messages with prependend param name
|
7
|
+
#
|
8
|
+
# class UserValidation < Fend
|
9
|
+
# plugin :full_messages
|
10
|
+
#
|
11
|
+
# # ...
|
12
|
+
# end
|
13
|
+
# result = UserValidation.call(email: "invalid", profile: "invalid", address: { })
|
14
|
+
#
|
15
|
+
# result.full_messages
|
16
|
+
# #=> { email: ["email is in invalid format"], profile: ["profile must be hash"], address: { city: ["city must be string"] } }
|
17
|
+
#
|
18
|
+
# ## Array members
|
19
|
+
#
|
20
|
+
# When validating array elements, messages are returned with prependend
|
21
|
+
# index, since array members don't have a name.
|
22
|
+
#
|
23
|
+
# { tags: { 0 => ["0 must be string"] } }
|
24
|
+
#
|
25
|
+
# In order to make full messages nicer for array elements,
|
26
|
+
# pass `:array_memeber_names` option when loading the plugin:
|
27
|
+
#
|
28
|
+
# plugin :full_messages, array_member_names: { tags: :tag }
|
29
|
+
#
|
30
|
+
# # which will produce
|
31
|
+
# { tags: { 0 => ["tag must be string"] } }
|
32
|
+
#
|
33
|
+
# `:array_member_names` options is inheritable, so it's possible to define
|
34
|
+
# it globaly by loading the plugin directly through `Fend` class.
|
35
|
+
#
|
36
|
+
# Fend.plugin :full_messages, array_member_names: { octopi: :octopus }
|
37
|
+
#
|
38
|
+
module FullMessages
|
39
|
+
def self.configure(validation, opts = {})
|
40
|
+
validation.opts[:full_messages_array_member_names] = (validation.opts[:full_messages_array_member_names] || {}).merge(opts[:array_member_names] || {})
|
41
|
+
end
|
42
|
+
|
43
|
+
module ResultMethods
|
44
|
+
def full_messages
|
45
|
+
@_full_messages ||= generate_full_messages(@errors)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
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)
|
54
|
+
|
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
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
register_plugin(:full_messages, FullMessages)
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Fend
|
4
|
+
module Plugins
|
5
|
+
# `validation_helpers` plugin provides additional `Param` methods for common
|
6
|
+
# validation cases.
|
7
|
+
#
|
8
|
+
# plugin :validation_helpers
|
9
|
+
#
|
10
|
+
# validate do |i|
|
11
|
+
# i.param(:username) do |username|
|
12
|
+
# username.validate_presence
|
13
|
+
# username.validate_max_length(20)
|
14
|
+
# username.validate_type(String)
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# You can find list of all available helpers in ParamMethods.
|
19
|
+
#
|
20
|
+
# ## Overriding default messages
|
21
|
+
#
|
22
|
+
# You can override default messages by specifying `:default_messages`
|
23
|
+
# options when loading the plugin
|
24
|
+
#
|
25
|
+
# plugin :validation_helpers, default_messages: {
|
26
|
+
# exact_length: ->(length) { I18n.t("errors.exact_length", length: length) },
|
27
|
+
# presence: "cannot be blank",
|
28
|
+
# type: ->(type) { "is not of valid type. Must be #{type.to_s.downcase}" }
|
29
|
+
# }
|
30
|
+
#
|
31
|
+
# Custom messages can be defined by passing `:message` option to validation
|
32
|
+
# helper method:
|
33
|
+
#
|
34
|
+
# username.validate_max_length(20, message: "must be shorter than 20 chars")
|
35
|
+
|
36
|
+
module ValidationHelpers
|
37
|
+
# depends on ValueHelpers plugin, which provides methods that are used in
|
38
|
+
# certain validation helpers
|
39
|
+
def self.load_dependencies(validation, *args, &block)
|
40
|
+
validation.plugin(:value_helpers)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.configure(validation, opts = {})
|
44
|
+
validation.opts[:validation_default_messages] = (validation.opts[:validation_default_messages] || {}).merge(opts[:default_messages] || {})
|
45
|
+
end
|
46
|
+
|
47
|
+
DEFAULT_MESSAGES = {
|
48
|
+
absence: -> { "must be absent" },
|
49
|
+
acceptance: -> { "must be accepted" },
|
50
|
+
equality: ->(value) { "must be equal to '#{value}'" },
|
51
|
+
exact_length: ->(length) { "length must be equal to #{length}" },
|
52
|
+
exclusion: ->(list) { "cannot be one of: #{list.join(', ')}" },
|
53
|
+
format: -> { "is in invalid format" },
|
54
|
+
greater_than: ->(value) { "must be greater than #{value}" },
|
55
|
+
greater_than_or_equal_to: ->(value) { "must be greater than or equal to #{value}" },
|
56
|
+
inclusion: ->(list) { "must be one of: #{list.join(', ')}" },
|
57
|
+
length_range: ->(range) { "length must be between #{range.min} and #{range.max}" },
|
58
|
+
less_than: ->(value) { "must be less than #{value}" },
|
59
|
+
less_than_or_equal_to: ->(value) { "must be less than or equal to #{value}" },
|
60
|
+
max_length: ->(value) { "length cannot be greater than #{value}" },
|
61
|
+
min_length: ->(value) { "length cannot be less than #{value}" },
|
62
|
+
presence: -> { "must be present" },
|
63
|
+
type: ->(type) { "must be #{type.to_s.downcase}" }
|
64
|
+
}.freeze
|
65
|
+
|
66
|
+
ACCEPTABLE = [1, "1", true, "true", "TRUE", :yes, "YES", "yes"].freeze
|
67
|
+
UNSUPPORTED_TYPE = "__unsupported_type__".freeze
|
68
|
+
|
69
|
+
module ParamClassMethods
|
70
|
+
def default_messages
|
71
|
+
@default_messages ||= DEFAULT_MESSAGES.merge(fend_class.opts[:validation_default_messages])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module ParamMethods
|
76
|
+
# Validates that param value is blank. To see what values are considered
|
77
|
+
# as blank, check ValueHelpers::ParamMethods#blank?.
|
78
|
+
#
|
79
|
+
# id.validate_absence
|
80
|
+
def validate_absence(opts = {})
|
81
|
+
add_error(:absence, opts[:message]) if present?
|
82
|
+
end
|
83
|
+
|
84
|
+
# Validates acceptance. Potential use case would be checking if Terms of
|
85
|
+
# Service has been accepted.
|
86
|
+
#
|
87
|
+
# By default, validation will pass if value is one of:
|
88
|
+
# `[1, "1", :true, true, "true", "TRUE", :yes, "YES", "yes"]`
|
89
|
+
#
|
90
|
+
# You can pass the `:as` option with custom list of acceptable values:
|
91
|
+
#
|
92
|
+
# tos.validate_acceptance(as: ["Agreed", "OK"])
|
93
|
+
def validate_acceptance(opts = {})
|
94
|
+
as = Array(opts.fetch(:as, ACCEPTABLE))
|
95
|
+
|
96
|
+
add_error(:acceptance, opts[:message]) unless as.include?(value)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Validates that param value is equal to the specified value.
|
100
|
+
#
|
101
|
+
# color.validate_equality("black")
|
102
|
+
def validate_equality(rhs, opts = {})
|
103
|
+
add_error(:equality, opts[:message], rhs) unless value.eql?(rhs)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Validates that param value length is equal to the specified value.
|
107
|
+
# Works with any object that responds to `#length` method.
|
108
|
+
#
|
109
|
+
# code.validate_exact_length(10)
|
110
|
+
def validate_exact_length(exact_length, opts = {})
|
111
|
+
value_length = value.respond_to?(:length) ? value.length : UNSUPPORTED_TYPE
|
112
|
+
|
113
|
+
return if !value_length.eql?(UNSUPPORTED_TYPE) && value_length.eql?(exact_length)
|
114
|
+
|
115
|
+
add_error(:exact_length, opts[:message], exact_length)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Validates that param value is not one of the specified values.
|
119
|
+
#
|
120
|
+
# account_type.validate_exclusion(["admin", "editor"])
|
121
|
+
def validate_exclusion(exclude_from, opts = {})
|
122
|
+
add_error(:exclusion, opts[:message], exclude_from) if exclude_from.include?(value)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Validates that param value is a match for specified regex.
|
126
|
+
#
|
127
|
+
# name.validate_format(/\A[a-z]\z/i)
|
128
|
+
def validate_format(format, opts = {})
|
129
|
+
add_error(:format, opts[:message]) if format.match(value.to_s).nil?
|
130
|
+
end
|
131
|
+
|
132
|
+
# Validates that param value is greater than specified value
|
133
|
+
#
|
134
|
+
# age.validate_greater_than(18)
|
135
|
+
def validate_greater_than(rhs, opts = {})
|
136
|
+
add_error(:greater_than, opts[:message], rhs) unless value.is_a?(Numeric) && value > rhs
|
137
|
+
end
|
138
|
+
|
139
|
+
# Validates that param value is greater than or equal to specified value
|
140
|
+
#
|
141
|
+
# age.validate_greater_than_or_equal_to(18)
|
142
|
+
#
|
143
|
+
# Aliased as `validate_gteq`
|
144
|
+
#
|
145
|
+
# age.validate_gteq(10)
|
146
|
+
def validate_greater_than_or_equal_to(rhs, opts = {})
|
147
|
+
add_error(:greater_than_or_equal_to, opts[:message], rhs) unless value.is_a?(Numeric) && value >= rhs
|
148
|
+
end
|
149
|
+
alias_method :validate_gteq, :validate_greater_than_or_equal_to
|
150
|
+
|
151
|
+
# Validates that param value is one of the specified values.
|
152
|
+
#
|
153
|
+
# account_type.validate_inclusion(["admin", "editor"])
|
154
|
+
def validate_inclusion(include_in, opts = {})
|
155
|
+
add_error(:inclusion, opts[:message], include_in) unless include_in.include?(value)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Validates that param value length is within specified range
|
159
|
+
#
|
160
|
+
# code.validate_length_range(10..15)
|
161
|
+
def validate_length_range(range, opts = {})
|
162
|
+
value_length = value.respond_to?(:length) ? value.length : UNSUPPORTED_TYPE
|
163
|
+
|
164
|
+
return if !value_length.eql?(UNSUPPORTED_TYPE) && range.include?(value_length)
|
165
|
+
|
166
|
+
add_error(:length_range, opts[:message], range)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Validates that param value is less than specified value
|
170
|
+
#
|
171
|
+
# funds.validate_less_than(100)
|
172
|
+
def validate_less_than(rhs, opts = {})
|
173
|
+
add_error(:less_than, opts[:message], rhs) unless value.is_a?(Numeric) && value < rhs
|
174
|
+
end
|
175
|
+
|
176
|
+
# Validates that param value is less than or equal to specified value
|
177
|
+
#
|
178
|
+
# funds.validate_less_than_or_equal_to(100)
|
179
|
+
#
|
180
|
+
# Aliased as `validate_lteq`
|
181
|
+
#
|
182
|
+
# funds.validate_lteq(100)
|
183
|
+
def validate_less_than_or_equal_to(rhs, opts = {})
|
184
|
+
add_error(:less_than_or_equal_to, opts[:message], rhs) unless value.is_a?(Numeric) && value <= rhs
|
185
|
+
end
|
186
|
+
alias_method :validate_lteq, :validate_less_than_or_equal_to
|
187
|
+
|
188
|
+
# Validates that param value length is not greater than specified value
|
189
|
+
#
|
190
|
+
# password.validate_max_length(15)
|
191
|
+
def validate_max_length(length, opts = {})
|
192
|
+
value_length = value.respond_to?(:length) ? value.length : UNSUPPORTED_TYPE
|
193
|
+
|
194
|
+
return if !value_length.eql?(UNSUPPORTED_TYPE) && value_length <= length
|
195
|
+
|
196
|
+
add_error(:max_length, opts[:message], length)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Validates that param value length is not less than specified value
|
200
|
+
#
|
201
|
+
# password.validate_min_length(5)
|
202
|
+
def validate_min_length(length, opts = {})
|
203
|
+
value_length = value.respond_to?(:length) ? value.length : UNSUPPORTED_TYPE
|
204
|
+
|
205
|
+
return if !value_length.eql?(UNSUPPORTED_TYPE) && value_length >= length
|
206
|
+
|
207
|
+
add_error(:min_length, opts[:message], length)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Validates that param value is present. To see what values are
|
211
|
+
# considered as present, check ValueHelpers::ParamMethods#present?
|
212
|
+
#
|
213
|
+
# name.validate_presence
|
214
|
+
def validate_presence(opts = {})
|
215
|
+
add_error(:presence, opts[:message]) if blank?
|
216
|
+
end
|
217
|
+
|
218
|
+
# Uses ValueHelpers::ParamMethods#type_of? method to validate that
|
219
|
+
# param value is of specified type.
|
220
|
+
#
|
221
|
+
# tags.validate_type(Array)
|
222
|
+
def validate_type(type, opts = {})
|
223
|
+
add_error(:type, opts[:message], type) unless type_of?(type)
|
224
|
+
end
|
225
|
+
|
226
|
+
# :nodoc:
|
227
|
+
def add_error(*args)
|
228
|
+
if args.size == 1 && args.first.is_a?(String)
|
229
|
+
super(*args)
|
230
|
+
else
|
231
|
+
@errors << error_message(*args)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
def error_message(type, message, *args)
|
238
|
+
message ||= self.class.default_messages.fetch(type)
|
239
|
+
message.is_a?(String) ? message : message.call(*args)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
register_plugin(:validation_helpers, ValidationHelpers)
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Fend
|
4
|
+
module Plugins
|
5
|
+
# Instead of calling ValidationHelpers::ParamMethods separately,
|
6
|
+
# you can use `validation_options` plugin in order to specify all
|
7
|
+
# validations as options and pass them to `Param#validate` method.
|
8
|
+
#
|
9
|
+
# plugin :validation_options
|
10
|
+
#
|
11
|
+
# validate do |i|
|
12
|
+
# i.param(:email) do |email|
|
13
|
+
# email.validate(presence: true, type: String, format: EMAIL_REGEX)
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# ## Custom error messages
|
18
|
+
#
|
19
|
+
# Custom error messages can be defined with `:message` option:
|
20
|
+
#
|
21
|
+
# email.validate(presence: { message: "cannot be blank"})
|
22
|
+
#
|
23
|
+
# ## Mandatory arguments
|
24
|
+
#
|
25
|
+
# For ValidationHelpers::ParamMethods that expect mandatory arguments, there
|
26
|
+
# are predefined option keys that you can use. To see them all check
|
27
|
+
# MANDATORY_ARG_KEYS constant.
|
28
|
+
#
|
29
|
+
# email.validate type: { of: String, message: "is not a string" }, format: { with: EMAIL_REGEX }
|
30
|
+
# account_type.validate inclusion: { in: %w(admin, moderator) }
|
31
|
+
#
|
32
|
+
# You can also use the DEFAULT_ARG_KEY (`:value`) if you find it hard to
|
33
|
+
# remember the specific ones.
|
34
|
+
#
|
35
|
+
# email.validate type: { value: String }, format: { value: EMAIL_REGEX }
|
36
|
+
#
|
37
|
+
# `validation_options` supports ExternalValidation plugin:
|
38
|
+
#
|
39
|
+
# plugin :external_validation
|
40
|
+
#
|
41
|
+
# # ...
|
42
|
+
#
|
43
|
+
# email.validate(with: CustomEmailValidator)
|
44
|
+
module ValidationOptions
|
45
|
+
NO_ARG_METHODS = [:absence, :presence, :acceptance].freeze
|
46
|
+
ARRAY_ARG_METHODS = [:exclusion, :inclusion, :length_range].freeze
|
47
|
+
|
48
|
+
DEFAULT_ARG_KEY = :value
|
49
|
+
|
50
|
+
# List of keys to use when specifying mandatory validation arguments
|
51
|
+
MANDATORY_ARG_KEYS = {
|
52
|
+
equality: :value,
|
53
|
+
exact_length: :of,
|
54
|
+
exclusion: :from,
|
55
|
+
format: :with,
|
56
|
+
greater_than: :value,
|
57
|
+
greater_than_or_equal_to: :value,
|
58
|
+
gteq: :value,
|
59
|
+
inclusion: :in,
|
60
|
+
length_range: :within,
|
61
|
+
less_than: :value,
|
62
|
+
less_than_or_equal_to: :value,
|
63
|
+
lteq: :value,
|
64
|
+
max_length: :of,
|
65
|
+
min_length: :of,
|
66
|
+
type: :of
|
67
|
+
}.freeze
|
68
|
+
|
69
|
+
# Depends on ValidationHelpers plugin
|
70
|
+
def self.load_dependencies(validation, *args, &block)
|
71
|
+
validation.plugin(:validation_helpers)
|
72
|
+
end
|
73
|
+
|
74
|
+
module ParamMethods
|
75
|
+
def validate(opts = {})
|
76
|
+
return if opts.empty?
|
77
|
+
|
78
|
+
opts.each do |validator_name, args|
|
79
|
+
method_name = "validate_#{validator_name}"
|
80
|
+
|
81
|
+
raise Error, "undefined validation method '#{validator_name}'" unless respond_to?(method_name)
|
82
|
+
|
83
|
+
if NO_ARG_METHODS.include?(validator_name)
|
84
|
+
if !!args == args
|
85
|
+
next unless args
|
86
|
+
|
87
|
+
validation_method_args = []
|
88
|
+
else
|
89
|
+
validation_method_args = [args]
|
90
|
+
end
|
91
|
+
elsif args.is_a?(Hash)
|
92
|
+
next if args[:allow_nil] == true && value.nil?
|
93
|
+
next if args[:allow_blank] == true && blank?
|
94
|
+
|
95
|
+
mandatory_arg_key = MANDATORY_ARG_KEYS[validator_name]
|
96
|
+
|
97
|
+
unless args.key?(mandatory_arg_key) || args.key?(DEFAULT_ARG_KEY)
|
98
|
+
raise Error, "missing mandatory argument for '#{validator_name}' validator"
|
99
|
+
end
|
100
|
+
|
101
|
+
mandatory_arg = args.delete(mandatory_arg_key) || args.delete(DEFAULT_ARG_KEY)
|
102
|
+
|
103
|
+
validation_method_args = [mandatory_arg, args]
|
104
|
+
else
|
105
|
+
validation_method_args = ARRAY_ARG_METHODS.include?(validator_name) ? [args] : args
|
106
|
+
end
|
107
|
+
|
108
|
+
public_send(method_name, *validation_method_args)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
register_plugin(:validation_options, ValidationOptions)
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bigdecimal"
|
4
|
+
|
5
|
+
class Fend
|
6
|
+
module Plugins
|
7
|
+
# `value_helpers` plugin provides helper methods you can use to
|
8
|
+
# check/fetch param values.
|
9
|
+
#
|
10
|
+
# plugin :value_helpers
|
11
|
+
#
|
12
|
+
# validate do |i|
|
13
|
+
# i.param(:username) do |username|
|
14
|
+
# username.present? #=> true
|
15
|
+
# username.blank? #=> false
|
16
|
+
# username.empty_string? #=> false
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# For a complete list of available methods, see ParamMethods.
|
21
|
+
module ValueHelpers
|
22
|
+
module ParamMethods
|
23
|
+
# Returns `true` when:
|
24
|
+
#
|
25
|
+
# * `value.empty?`
|
26
|
+
# * `value.nil?`
|
27
|
+
# * `value == false`
|
28
|
+
# * `value.empty_string?`
|
29
|
+
def blank?
|
30
|
+
case value
|
31
|
+
when Array, Hash
|
32
|
+
value.empty?
|
33
|
+
when NilClass, FalseClass
|
34
|
+
true
|
35
|
+
when Integer, Float, Numeric, Time, TrueClass, Symbol
|
36
|
+
false
|
37
|
+
when String
|
38
|
+
empty_string?
|
39
|
+
else
|
40
|
+
value.respond_to?(:empty?) ? !!value.empty? : !value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Enables easier fetching of nested data values.
|
45
|
+
# Works with hashes and arrays.
|
46
|
+
#
|
47
|
+
# validate do |i|
|
48
|
+
# # { user: { address: { city: "Amsterdam" } } }
|
49
|
+
# i.dig(:user, :address, :city) #=> "Amsterdam"
|
50
|
+
# i.dig(:user, :profile, :username) #=> nil
|
51
|
+
#
|
52
|
+
# # { tags: [ { id: 2, name: "JS" }, { id: 3, name: "Ruby" }] }
|
53
|
+
# i.dig(:tags, 1, :name) #=> "Ruby"
|
54
|
+
# i.dig(:tags, 5, :id) #=> nil
|
55
|
+
#
|
56
|
+
# i.param(:accounts) do |accounts|
|
57
|
+
# accounts.dig(0, :transactions, 3) #=> "$100.00"
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
def dig(*path)
|
61
|
+
result = value
|
62
|
+
|
63
|
+
path.each do |point|
|
64
|
+
break if result.is_a?(Array) && !point.is_a?(Integer)
|
65
|
+
|
66
|
+
result = result.is_a?(Enumerable) ? result[point] : nil
|
67
|
+
|
68
|
+
break if result.nil?
|
69
|
+
end
|
70
|
+
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns `true` when value is an empty string (_space_, _tab_, _newline_,
|
75
|
+
# _carriage_return_, etc...)
|
76
|
+
#
|
77
|
+
# # email.value #=> ""
|
78
|
+
# # email.value #=> " "
|
79
|
+
# # email.value #=> "\n"
|
80
|
+
# # email.value #=> "\r"
|
81
|
+
# # email.value #=> "\t"
|
82
|
+
# # email.value #=> "\n\r\t"
|
83
|
+
#
|
84
|
+
# email.empty_string? #=> true
|
85
|
+
def empty_string?
|
86
|
+
return false unless value.is_a?(String) || value.is_a?(Symbol)
|
87
|
+
|
88
|
+
regex = /\A[[:space:]]*\z/
|
89
|
+
|
90
|
+
!regex.match(value).nil?
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns `true` if value is present/not blank
|
94
|
+
def present?
|
95
|
+
!blank?
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns `true` if value is of specified type. Accepts constants, their
|
99
|
+
# string representations and symbols:
|
100
|
+
#
|
101
|
+
# email.type_of?(String)
|
102
|
+
#
|
103
|
+
# # or
|
104
|
+
#
|
105
|
+
# email.type_of?("string")
|
106
|
+
#
|
107
|
+
# # or
|
108
|
+
#
|
109
|
+
# email.type_of?(:string)
|
110
|
+
#
|
111
|
+
# Additional examples:
|
112
|
+
#
|
113
|
+
# # these are all checking the same thing
|
114
|
+
# user.type_of?(AdminUser)
|
115
|
+
# user.type_of?(:admin_user)
|
116
|
+
# user.type_of?("admin_user")
|
117
|
+
#
|
118
|
+
# Provides a convenient way for checking if value is boolean, decimal or
|
119
|
+
# nil:
|
120
|
+
#
|
121
|
+
# # true if value is TrueClass or FalseClass
|
122
|
+
# confirmed.type_of?(:boolean)
|
123
|
+
#
|
124
|
+
# # true if value is Float or BigDecimal
|
125
|
+
# amount.type_of?(:decimal)
|
126
|
+
#
|
127
|
+
# # true if value is nil/NilClass
|
128
|
+
# email.type_of?(:nil)
|
129
|
+
def type_of?(type_ref)
|
130
|
+
return value.is_a?(type_ref) unless type_ref.is_a?(String) || type_ref.is_a?(Symbol)
|
131
|
+
|
132
|
+
case type_ref.to_s
|
133
|
+
when "boolean" then !!value == value
|
134
|
+
when "decimal" then value.is_a?(Float) || value.is_a?(BigDecimal)
|
135
|
+
when "nil" then value.is_a?(NilClass)
|
136
|
+
else
|
137
|
+
camelized_type_ref = type_ref.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:\A|_)(.)/) { $1.upcase }
|
138
|
+
type_class = Object.const_get(camelized_type_ref)
|
139
|
+
|
140
|
+
value.is_a?(type_class)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
register_plugin(:value_helpers, ValueHelpers)
|
147
|
+
end
|
148
|
+
end
|