Class: Arrow::FormValidator
- Inherits:
-
FormValidator
- Object
- FormValidator
- Arrow::FormValidator
- Extended by:
- Forwardable
- Includes:
- Loggable
- Defined in:
- lib/arrow/formvalidator.rb
Overview
A FormValidator variant that adds some convenience methods and additional validations.
Usage
require 'arrow/formvalidator'
# Profile specifies validation criteria for input profile = {
:required => :name,
:optional => [:email, :description],
:filters => [:strip, :squeeze],
:untaint_all_constraints => true,
:descriptions => {
:email => "Customer Email",
:description => "Issue Description",
:name => "Customer Name",
},
:constraints => {
:email => :email,
:name => /^[\x20-\x7f]+$/,
:description => /^[\x20-\x7f]+$/,
},
}
# Create a validator object and pass in a hash of request parameters and the # profile hash.
validator = Arrow::FormValidator.new
validator.validate( req_params, profile )
# Now if there weren’t any errors, send the success page if validator.okay? return success_template
# Otherwise fill in the error template with auto-generated error messages # and return that instead. else failure_template.errors( validator.error_messages ) return failure_template end
VCS Id
$Id: formvalidator.rb,v 1b8226c06192 2010/08/09 17:50:38 ged $
Authors
-
Michael Granger <[email protected]>
Please see the file LICENSE in the top-level directory for licensing details.
Portions of this file are from Ruby on Rails’ CGIMethods class from the action_controller:
Copyright (c) 2004 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
–
Please see the file COPYRIGHT in the ‘docs’ directory for licensing details.
Constant Summary collapse
- Defaults =
{ :descriptions => {}, }
- RFC822_EMAIL_ADDRESS =
RFC822 Email Address Regex
Originally written by Cal Henderson c.f. iamcal.com/publish/articles/php/parsing_email/
Translated to Ruby by Tim Fletcher, with changes suggested by Dan Kubb.
Licensed under a Creative Commons Attribution-ShareAlike 2.5 License creativecommons.org/licenses/by-sa/2.5/
begin qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]' dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]' atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-' + '\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+' quoted_pair = '\\x5c[\\x00-\\x7f]' domain_literal = "\\x5b(?:#{dtext}|#{quoted_pair})*\\x5d" quoted_string = "\\x22(?:#{qtext}|#{quoted_pair})*\\x22" domain_ref = atom sub_domain = "(?:#{domain_ref}|#{domain_literal})" word = "(?:#{atom}|#{quoted_string})" domain = "#{sub_domain}(?:\\x2e#{sub_domain})*" local_part = "#{word}(?:\\x2e#{word})*" addr_spec = "#{local_part}\\x40#{domain}" /\A#{addr_spec}\z/ end
- RFC1738Hostname =
begin alphadigit = /[a-z0-9]/i # toplabel = alpha | alpha *[ alphadigit | "-" ] alphadigit toplabel = /[a-z]((#{alphadigit}|-)*#{alphadigit})?/i # domainlabel = alphadigit | alphadigit *[ alphadigit | "-" ] alphadigit domainlabel = /#{alphadigit}((#{alphadigit}|-)*#{alphadigit})?/i # hostname = *[ domainlabel "." ] toplabel hostname = /\A(#{domainlabel}\.)*#{toplabel}\z/ end
Instance Attribute Summary collapse
-
#raw_form ⇒ Object
readonly
Returns the value of attribute raw_form.
Instance Method Summary collapse
-
#[](key) ⇒ Object
Index operator; fetch the validated value for form field
key
. -
#[]=(key, val) ⇒ Object
Index assignment operator; set the validated value for form field
key
to the specifiedval
. -
#apply_hash_constraint(key, constraint) ⇒ Object
Apply a constraint given as a Hash to the value/s corresponding to the specified
key
:. -
#apply_proc_constraint(key, constraint, *params) ⇒ Object
Apply a constraint that was specified as a Proc to the value for the given
key
. -
#apply_regexp_constraint(key, constraint) ⇒ Object
Applies regexp constraint to form.
-
#apply_string_constraint(key, constraint) ⇒ Object
Applies a builtin constraint to form.
-
#args? ⇒ Boolean
Returns
true
if there were arguments given. -
#check_profile_syntax(profile) ⇒ Object
Overridden to remove the check for extra keys.
-
#descriptions ⇒ Object
Hash of field descriptions.
-
#descriptions=(new_descs) ⇒ Object
Set hash of field descriptions.
-
#do_constraint(key, constraints) ⇒ Object
Apply one or more
constraints
to the field value/s corresponding tokey
. -
#empty? ⇒ Boolean
Returns
true
if there were no arguments given. -
#error_fields ⇒ Object
Return an array of field names which had some kind of error associated with them.
-
#error_messages(include_unknown = false) ⇒ Object
Return an error message for each missing or invalid field; if
includeUnknown
istrue
, also include messages for unknown fields. -
#errors? ⇒ Boolean
(also: #has_errors?)
Returns
true
if any fields are missing or contain invalid values. -
#filters ⇒ Object
Formvalidator hack: The formvalidator filters method has a bug where he assumes an array when it is in fact a string for multiple values (ie anytime you have a text-area with newlines in it).
-
#get_description(field) ⇒ Object
Get the description for the specified field.
-
#initialize(profile, params = nil) ⇒ FormValidator
constructor
Create a new Arrow::FormValidator object.
-
#match_alpha(val) ⇒ Object
Constrain a value to alpha characters (a-z, case-insensitive).
-
#match_alphanumeric(val) ⇒ Object
Constrain a value to alpha characters (a-z, case-insensitive and 0-9).
-
#match_boolean(val) ⇒ Object
Constrain a value to
true
(oryes
) andfalse
(orno
). -
#match_date(val) ⇒ Object
Constrain a value to a parseable Date.
-
#match_email(val) ⇒ Object
Override the parent class’s definition to (not-sloppily) match email addresses.
-
#match_float(val) ⇒ Object
Contrain a value to a Float.
-
#match_hostname(val) ⇒ Object
Match valid hostnames according to the rules of the URL RFC.
-
#match_integer(val) ⇒ Object
Constrain a value to an integer.
-
#match_printable(val) ⇒ Object
Constrain a value to any printable characters.
-
#match_uri(val) ⇒ Object
Match valid URIs.
-
#missing ⇒ Object
Returns a distinct list of missing fields.
-
#okay? ⇒ Boolean
Return
true
if all required fields were present and validated correctly. -
#set_form_value(key, value, constraint) ⇒ Object
Set the form value for the given
key
. -
#to_s ⇒ Object
Stringified description of the validator.
-
#unknown ⇒ Object
Returns a distinct list of unknown fields.
-
#untaint?(field) ⇒ Boolean
Returns
true
if the givenfield
is one that should be untainted. -
#valid ⇒ Object
Returns the valid fields after expanding Rails-style ‘customer[street]’ variables into multi-level hashes.
-
#validate(params, additional_profile = nil) ⇒ Object
Validate the input in
params
.
Constructor Details
#initialize(profile, params = nil) ⇒ FormValidator
Create a new Arrow::FormValidator object.
104 105 106 107 |
# File 'lib/arrow/formvalidator.rb', line 104 def initialize( profile, params=nil ) @profile = Defaults.merge( profile ) validate( params ) if params end |
Instance Attribute Details
#raw_form ⇒ Object (readonly)
Returns the value of attribute raw_form.
114 115 116 |
# File 'lib/arrow/formvalidator.rb', line 114 def raw_form @raw_form end |
Instance Method Details
#[](key) ⇒ Object
Index operator; fetch the validated value for form field key
.
160 161 162 |
# File 'lib/arrow/formvalidator.rb', line 160 def []( key ) @form[ key.to_s ] end |
#[]=(key, val) ⇒ Object
Index assignment operator; set the validated value for form field key
to the specified val
.
167 168 169 |
# File 'lib/arrow/formvalidator.rb', line 167 def []=( key, val ) @form[ key.to_s ] = val end |
#apply_hash_constraint(key, constraint) ⇒ Object
Apply a constraint given as a Hash to the value/s corresponding to the specified key
:
- constraint
-
A builtin constraint (as a Symbol; e.g., :email), a Regexp, or a Proc.
- name
-
A description of the constraint should it fail and be listed in #invalid.
- params
-
If
constraint
is a Proc, this field should contain a list of other fields to send to the Proc.
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 |
# File 'lib/arrow/formvalidator.rb', line 462 def apply_hash_constraint( key, constraint ) action = constraint["constraint"] rval = case action when String self.apply_string_constraint( key, action ) when Regexp self.apply_regexp_constraint( key, action ) when Proc if args = constraint["params"] args.collect! {|field| @form[field] } self.apply_proc_constraint( key, action, *args ) else self.apply_proc_constraint( key, action ) end end # If the validation failed, and there's a name for this constraint, replace # the name in @invalid_fields with the name if !rval && constraint["name"] @invalid_fields[key] = constraint["name"] end return rval end |
#apply_proc_constraint(key, constraint, *params) ⇒ Object
Apply a constraint that was specified as a Proc to the value for the given key
491 492 493 494 495 496 497 498 499 500 501 |
# File 'lib/arrow/formvalidator.rb', line 491 def apply_proc_constraint( key, constraint, *params ) value = nil unless params.empty? value = constraint.call( *params ) else value = constraint.call( @form[key] ) end self.set_form_value( key, value, constraint ) end |
#apply_regexp_constraint(key, constraint) ⇒ Object
Applies regexp constraint to form
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 |
# File 'lib/arrow/formvalidator.rb', line 505 def apply_regexp_constraint( key, constraint ) self.log.debug "Validating '%p' via regexp %p" % [@form[key], constraint] if match = constraint.match( @form[key].to_s ) self.log.debug " matched %p" % [match[0]] if match.captures.empty? self.log.debug " no captures, using whole match: %p" % [match[0]] self.set_form_value( key, match[0], constraint ) elsif match.captures.length == 1 self.log.debug " extracting one capture: %p" % [match.captures.first] self.set_form_value( key, match.captures.first, constraint ) else self.log.debug " extracting multiple captures: %p" % [match.captures] self.set_form_value( key, match.captures, constraint ) end else self.set_form_value( key, nil, constraint ) end end |
#apply_string_constraint(key, constraint) ⇒ Object
Applies a builtin constraint to form.
443 444 445 446 447 448 449 |
# File 'lib/arrow/formvalidator.rb', line 443 def apply_string_constraint( key, constraint ) # FIXME: multiple elements rval = self.__send__( "match_#{constraint}", @form[key].to_s ) self.log.debug "Tried a string constraint: %p: %p" % [ @form[key].to_s, rval ] self.set_form_value( key, rval, constraint ) end |
#args? ⇒ Boolean
Returns true
if there were arguments given.
179 180 181 |
# File 'lib/arrow/formvalidator.rb', line 179 def args? return !@form.empty? end |
#check_profile_syntax(profile) ⇒ Object
Overridden to remove the check for extra keys.
155 156 |
# File 'lib/arrow/formvalidator.rb', line 155 def check_profile_syntax( profile ) end |
#descriptions ⇒ Object
Hash of field descriptions
128 129 130 |
# File 'lib/arrow/formvalidator.rb', line 128 def descriptions @profile[:descriptions] end |
#descriptions=(new_descs) ⇒ Object
Set hash of field descriptions
134 135 136 |
# File 'lib/arrow/formvalidator.rb', line 134 def descriptions=( new_descs ) @profile[:descriptions] = new_descs end |
#do_constraint(key, constraints) ⇒ Object
Apply one or more constraints
to the field value/s corresponding to key
.
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
# File 'lib/arrow/formvalidator.rb', line 424 def do_constraint( key, constraints ) constraints.each do |constraint| case constraint when String apply_string_constraint( key, constraint ) when Hash apply_hash_constraint( key, constraint ) when Proc apply_proc_constraint( key, constraint ) when Regexp apply_regexp_constraint( key, constraint ) else raise "unknown constraint type %p" % [constraint] end end end |
#empty? ⇒ Boolean
Returns true
if there were no arguments given.
173 174 175 |
# File 'lib/arrow/formvalidator.rb', line 173 def empty? return @form.empty? end |
#error_fields ⇒ Object
Return an array of field names which had some kind of error associated with them.
215 216 217 |
# File 'lib/arrow/formvalidator.rb', line 215 def error_fields return self.missing | self.invalid.keys end |
#error_messages(include_unknown = false) ⇒ Object
Return an error message for each missing or invalid field; if includeUnknown
is true
, also include messages for unknown fields.
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/arrow/formvalidator.rb', line 235 def ( include_unknown=false ) self.log.debug "Building error messages from descriptions: %p" % [ @profile[:descriptions] ] msgs = [] self.missing.each do |field| msgs << "Missing value for '%s'" % self.get_description( field ) end self.invalid.each do |field, constraint| msgs << "Invalid value for '%s'" % self.get_description( field ) end if include_unknown self.unknown.each do |field| msgs << "Unknown parameter '%s'" % self.get_description( field ) end end return msgs end |
#errors? ⇒ Boolean Also known as: has_errors?
Returns true
if any fields are missing or contain invalid values.
185 186 187 |
# File 'lib/arrow/formvalidator.rb', line 185 def errors? return !self.okay? end |
#filters ⇒ Object
Formvalidator hack: The formvalidator filters method has a bug where he assumes an array
when it is in fact a string for multiple values (ie anytime you have a
text-area with newlines in it).
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 |
# File 'lib/arrow/formvalidator.rb', line 559 def filters @filters_array = Array(@profile[:filters]) unless(@filters_array) @filters_array.each do |filter| if respond_to?( "filter_#{filter}" ) @form.keys.each do |field| # If a key has multiple elements, apply filter to each element @field_array = Array( @form[field] ) if @field_array.length > 1 @field_array.each_index do |i| elem = @field_array[i] @field_array[i] = self.send("filter_#{filter}", elem) end else if not @form[field].to_s.empty? @form[field] = self.send("filter_#{filter}", @form[field].to_s) end end end end end @form end |
#get_description(field) ⇒ Object
Get the description for the specified field.
221 222 223 224 225 226 227 228 229 230 |
# File 'lib/arrow/formvalidator.rb', line 221 def get_description( field ) return @profile[:descriptions][ field.to_s ] if @profile[:descriptions].key?( field.to_s ) desc = field.to_s. gsub( /.*\[(\w+)\]/, "\\1" ). gsub( /_(.)/ ) {|m| " " + m[1,1].upcase }. gsub( /^(.)/ ) {|m| m.upcase } return desc end |
#match_alpha(val) ⇒ Object
Constrain a value to alpha characters (a-z, case-insensitive)
324 325 326 327 328 329 330 |
# File 'lib/arrow/formvalidator.rb', line 324 def match_alpha( val ) if val =~ /^([a-z]+)$/i return $1 else return nil end end |
#match_alphanumeric(val) ⇒ Object
Constrain a value to alpha characters (a-z, case-insensitive and 0-9)
334 335 336 337 338 339 340 |
# File 'lib/arrow/formvalidator.rb', line 334 def match_alphanumeric( val ) if val =~ /^([a-z0-9]+)$/i return $1 else return nil end end |
#match_boolean(val) ⇒ Object
Constrain a value to true
(or yes
) and false
(or no
).
293 294 295 296 297 298 299 300 301 302 |
# File 'lib/arrow/formvalidator.rb', line 293 def match_boolean( val ) rval = nil if ( val =~ /^(t(?:rue)?|y(?:es)?)|1$/i ) rval = true elsif ( val =~ /^(no?|f(?:alse)?)|0$/i ) rval = false end return rval end |
#match_date(val) ⇒ Object
Constrain a value to a parseable Date
318 319 320 |
# File 'lib/arrow/formvalidator.rb', line 318 def match_date( val ) return Date.parse( val ) rescue nil end |
#match_email(val) ⇒ Object
Override the parent class’s definition to (not-sloppily) match email addresses.
385 386 387 388 389 390 |
# File 'lib/arrow/formvalidator.rb', line 385 def match_email( val ) match = RFC822_EMAIL_ADDRESS.match( val ) self.log.debug "Validating an email address %p: %p" % [ val, match ] return match ? match[0] : nil end |
#match_float(val) ⇒ Object
Contrain a value to a Float
312 313 314 |
# File 'lib/arrow/formvalidator.rb', line 312 def match_float( val ) return Float( val ) rescue nil end |
#match_hostname(val) ⇒ Object
Match valid hostnames according to the rules of the URL RFC.
404 405 406 407 |
# File 'lib/arrow/formvalidator.rb', line 404 def match_hostname( val ) match = RFC1738Hostname.match( val ) return match ? match[0] : nil end |
#match_integer(val) ⇒ Object
Constrain a value to an integer
306 307 308 |
# File 'lib/arrow/formvalidator.rb', line 306 def match_integer( val ) return Integer( val ) rescue nil end |
#match_printable(val) ⇒ Object
Constrain a value to any printable characters
344 345 346 347 348 349 350 |
# File 'lib/arrow/formvalidator.rb', line 344 def match_printable( val ) if val =~ /^([[:print:][:space:]]{0,255})$/ return val else return nil end end |
#match_uri(val) ⇒ Object
Match valid URIs
411 412 413 414 415 416 417 418 419 |
# File 'lib/arrow/formvalidator.rb', line 411 def match_uri( val ) return URI.parse( val ) rescue URI::InvalidURIError => err self.log.error "Error trying to parse URI %p: %s" % [ val, err. ] return nil rescue NoMethodError self.log.debug "Ignoring bug in URI#parse" return nil end |
#missing ⇒ Object
Returns a distinct list of missing fields. Overridden to eliminate the “undefined method ‘<=>’ for :foo:Symbol” error.
259 260 261 |
# File 'lib/arrow/formvalidator.rb', line 259 def missing @missing_fields.uniq.sort_by {|f| f.to_s} end |
#okay? ⇒ Boolean
Return true
if all required fields were present and validated correctly.
193 194 195 |
# File 'lib/arrow/formvalidator.rb', line 193 def okay? self.missing.empty? && self.invalid.empty? end |
#set_form_value(key, value, constraint) ⇒ Object
Set the form value for the given key
. If value
is false, add it to the list of invalid fields with a description derived from the specified constraint
.
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 |
# File 'lib/arrow/formvalidator.rb', line 530 def set_form_value( key, value, constraint ) key.untaint if !value.nil? self.log.debug "Setting form value for %p to %p (constraint was %p)" % [ key, value, constraint ] @form[key] = value @form[key].untaint if self.untaint?( key ) return true else self.log.debug "Clearing form value for %p (constraint was %p)" % [ key, constraint ] @form.delete( key ) @invalid_fields ||= {} @invalid_fields[ key ] ||= [] unless @invalid_fields[ key ].include?( constraint ) @invalid_fields[ key ].push( constraint ) end return false end end |
#to_s ⇒ Object
Stringified description of the validator
123 124 125 |
# File 'lib/arrow/formvalidator.rb', line 123 def to_s "" end |
#unknown ⇒ Object
Returns a distinct list of unknown fields.
264 265 266 |
# File 'lib/arrow/formvalidator.rb', line 264 def unknown (@unknown_fields - @invalid_fields.keys).uniq.sort_by {|f| f.to_s} end |
#untaint?(field) ⇒ Boolean
Returns true
if the given field
is one that should be untainted.
199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/arrow/formvalidator.rb', line 199 def untaint?( field ) self.log.debug "Checking to see if %p should be untainted." % [field] rval = ( @untaint_all || @untaint_fields.include?(field) ) if rval self.log.debug " ...yep it should." else self.log.debug " ...nope." end return rval end |
#valid ⇒ Object
Returns the valid fields after expanding Rails-style ‘customer[street]’ variables into multi-level hashes.
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/arrow/formvalidator.rb', line 271 def valid if @parsed_params.nil? @parsed_params = {} valid = super() for key, value in valid value = [value] if key =~ /.*\[\]$/ unless key.include?( '[' ) @parsed_params[ key ] = value else build_deep_hash( value, @parsed_params, get_levels(key) ) end end end return @parsed_params end |
#validate(params, additional_profile = nil) ⇒ Object
Validate the input in params
. If the optional additional_profile
is given, merge it with the validator’s default profile before validating.
141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/arrow/formvalidator.rb', line 141 def validate( params, additional_profile=nil ) @raw_form = params.dup profile = @profile if additional_profile self.log.debug "Merging additional profile %p" % [additional_profile] profile = @profile.merge( additional_profile ) end super( params, profile ) end |