/activemodel/lib/active_model/validations/format.rb

https://github.com/eparreno/rails · Ruby · 113 lines · 52 code · 9 blank · 52 comment · 10 complexity · 4a48bdc309f6d39f485923f359cc7374 MD5 · raw file

  1. module ActiveModel
  2. module Validations
  3. class FormatValidator < EachValidator # :nodoc:
  4. def validate_each(record, attribute, value)
  5. if options[:with]
  6. regexp = option_call(record, :with)
  7. record_error(record, attribute, :with, value) if value.to_s !~ regexp
  8. elsif options[:without]
  9. regexp = option_call(record, :without)
  10. record_error(record, attribute, :without, value) if regexp.match?(value.to_s)
  11. end
  12. end
  13. def check_validity!
  14. unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
  15. raise ArgumentError, "Either :with or :without must be supplied (but not both)"
  16. end
  17. check_options_validity :with
  18. check_options_validity :without
  19. end
  20. private
  21. def option_call(record, name)
  22. option = options[name]
  23. option.respond_to?(:call) ? option.call(record) : option
  24. end
  25. def record_error(record, attribute, name, value)
  26. record.errors.add(attribute, :invalid, options.except(name).merge!(value: value))
  27. end
  28. def check_options_validity(name)
  29. if option = options[name]
  30. if option.is_a?(Regexp)
  31. if options[:multiline] != true && regexp_using_multiline_anchors?(option)
  32. raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \
  33. "which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \
  34. ":multiline => true option?"
  35. end
  36. elsif !option.respond_to?(:call)
  37. raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}"
  38. end
  39. end
  40. end
  41. def regexp_using_multiline_anchors?(regexp)
  42. source = regexp.source
  43. source.start_with?("^") || (source.end_with?("$") && !source.end_with?("\\$"))
  44. end
  45. end
  46. module HelperMethods
  47. # Validates whether the value of the specified attribute is of the correct
  48. # form, going by the regular expression provided. You can require that the
  49. # attribute matches the regular expression:
  50. #
  51. # class Person < ActiveRecord::Base
  52. # validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create
  53. # end
  54. #
  55. # Alternatively, you can require that the specified attribute does _not_
  56. # match the regular expression:
  57. #
  58. # class Person < ActiveRecord::Base
  59. # validates_format_of :email, without: /NOSPAM/
  60. # end
  61. #
  62. # You can also provide a proc or lambda which will determine the regular
  63. # expression that will be used to validate the attribute.
  64. #
  65. # class Person < ActiveRecord::Base
  66. # # Admin can have number as a first letter in their screen name
  67. # validates_format_of :screen_name,
  68. # with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
  69. # end
  70. #
  71. # Note: use <tt>\A</tt> and <tt>\z</tt> to match the start and end of the
  72. # string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
  73. #
  74. # Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass
  75. # the <tt>multiline: true</tt> option in case you use any of these two
  76. # anchors in the provided regular expression. In most cases, you should be
  77. # using <tt>\A</tt> and <tt>\z</tt>.
  78. #
  79. # You must pass either <tt>:with</tt> or <tt>:without</tt> as an option.
  80. # In addition, both must be a regular expression or a proc or lambda, or
  81. # else an exception will be raised.
  82. #
  83. # Configuration options:
  84. # * <tt>:message</tt> - A custom error message (default is: "is invalid").
  85. # * <tt>:with</tt> - Regular expression that if the attribute matches will
  86. # result in a successful validation. This can be provided as a proc or
  87. # lambda returning regular expression which will be called at runtime.
  88. # * <tt>:without</tt> - Regular expression that if the attribute does not
  89. # match will result in a successful validation. This can be provided as
  90. # a proc or lambda returning regular expression which will be called at
  91. # runtime.
  92. # * <tt>:multiline</tt> - Set to true if your regular expression contains
  93. # anchors that match the beginning or end of lines as opposed to the
  94. # beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>.
  95. #
  96. # There is also a list of default options supported by every validator:
  97. # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
  98. # See <tt>ActiveModel::Validations#validates</tt> for more information
  99. def validates_format_of(*attr_names)
  100. validates_with FormatValidator, _merge_attributes(attr_names)
  101. end
  102. end
  103. end
  104. end