目前我有一个美国邮政编码的正则表达式:
validates :zip,
presence: true,
format: { with: /\A\d{5}(-\d{4})?\z/ }
我想在同一个邮政编码上对其他国家/地区使用不同的正则表达式,因此应根据国家/地区使用正则表达式:
有人可以建议我如何满足我的要求?
答案 0 :(得分:3)
你可以给一个返回Regexp的lambda作为格式验证器(see :with
)的:with
选项,这样可以保持良好和干净:
ZIP_COUNTRY_FORMATS = {
'US' => /\A\d{5}(-\d{4})?\z/,
'Australia' => /\A\d{4}\z/,
# ...
}
validates :zip, presence: true,
format: { with: ->(record){ ZIP_COUNTRY_FORMATS.fetch(record.country) } }
请注意,使用Hash#fetch
代替Hash#[]
,这样如果给出了不存在的country
,它将引发KeyError,就像进行健全性检查一样。或者,您可以返回与任何内容匹配的默认Regexp:
ZIP_COUNTRY_FORMATS.fetch(record.country, //)
......或者什么都没有:
ZIP_COUNTRY_FORMATS.fetch(record.country, /.\A/)
...取决于您想要的行为。
答案 1 :(得分:2)
您可能希望编写一种方法来帮助您:
validates :zip, presence: true, with: :zip_validator
def zip_validator
case country
when 'AU'
# some regex or fail
when 'CA'
# some other regex or fail
when 'UK'
# some other regex or fail
else
# should this fail?
end
end
答案 2 :(得分:1)
假设我们以散列形式给出每个国家的有效邮政编码示例,如下所示。
example_pcs = {
US: ["", "98230", "98230-1346"],
CAN: ["*", "V8V 3A2"],
OZ: ["!*", "NSW 1130", "ACT 0255", "VIC 3794", "QLD 4000", "SA 5664",
"WA 6500", "TAS 7430", "NT 0874"]
}
其中每个数组的第一个元素是一串代码,稍后将对此进行解释。
我们可以根据这些信息为每个国家构建一个正则表达式。 (这些信息无疑会在实际应用中有所不同,但我只是提出了一般性的想法。)对于每个国家,我们为每个示例邮政编码构建一个正则表达式,部分使用上述代码。然后,我们将这些正则表达式联合起来,为该国家获得单一的正则表达式。这是构建示例邮政编码的正则表达式的一种方式。
def make_regex(str, codes='')
rstr = str.each_char.chunk do |c|
case c
when /\d/ then :DIGIT
when /[[:alpha:]]/ then :ALPHA
when /\s/ then :WHITE
else :OTHER
end
end.
map do |type, arr|
case type
when :ALPHA
if codes.include?('!')
arr
elsif arr.size == 1
"[[:alpha:]]"
else "[[:alpha:]]\{#{arr.size}\}"
end
when :DIGIT
(arr.size == 1) ? "\\d" : "\\d\{#{arr.size}\}"
when :WHITE
case codes
when /\*/ then "\\s*"
when /\+/ then "\\s+"
else (arr.size == 1) ? "\\s" : "\\s\{#{arr.size}\}"
end
when :OTHER
arr
end
end.
join
Regexp.new("\\A" << rstr << "\\z")
end
我已经使正则表达式对字母不区分大小写,但这当然可以改变。此外,对于某些国家/地区,可能必须手动调整所生成的正则表达式和/或可能需要对邮政编码字符串进行一些预处理或后处理。例如,某些组合可能具有正确的格式,但仍然不是有效的邮政编码。例如,在澳大利亚,每个地区代码后面的四位数字必须落在因地区而异的指定范围内。
以下是一些例子。
make_regex("12345")
#=> /\A\d{5}\z/
make_regex("12345-1234")
#=> /\A\d{5}-\d{4}\z/
Regexp.union(make_regex("12345"), make_regex("12345-1234"))
#=> /(?-mix:\A\d{5}\z)|(?-mix:\A\d{5}-\d{4}\z)/
make_regex("V8V 3A2", "*")
#=> /\A[[:alpha:]]\d[[:alpha:]]\s*\d[[:alpha:]]\d\z/
make_regex("NSW 1130", "!*")
# => /\ANSW\s*\d{4}\z/
然后,对于每个国家/地区,我们为每个示例邮政编码采用正则表达式的并集,将这些结果保存为哈希,其键是国家/地区代码。
h = example_pcs.each_with_object({}) { |(country, (codes, *examples)), h|
h[country] = Regexp.union(examples.map { |s| make_regex(s, codes) }.uniq) }
#=> {:US=>/(?-mix:\A\d{5}\z)|(?-mix:\A\d{5}-\d{4}\z)/,
# :CAN=>/\A[[:alpha:]]\d[[:alpha:]]\s*\d[[:alpha:]]\d\z/,
# :OZ=>/(?-mix:\ANSW\s*\d{4}\z)|(?-mix:\AACT\s*\d{4}\z)|(?-mix:\AVIC\s*\d{4}\z)|(?-mix:\AQLD\s*\d{4}\z)|(?-mix:\ASA\s*\d{4}\z)|(?-mix:\AWA\s*\d{4}\z)|(?-mix:\ATAS\s*\d{4}\z)|(?-mix:\ANT\s*\d{4}\z)/}
"12345" =~ h[:US]
#=> 0
"12345-1234" =~ h[:US]
#=> 0
"1234" =~ h[:US]
#=> nil
"12345 1234" =~ h[:US]
#=> nil
"V8V 3A2" =~ h[:CAN]
#=> 0
"V8V 3A2" =~ h[:CAN]
#=> 0
"V8v3a2" =~ h[:CAN]
#=> 0
"3A2 V8V" =~ h[:CAN]
#=> nil
"NSW 1132" =~ h[:OZ]
#=> 0
"NSW 1132" =~ h[:OZ]
#=> 0
"NSW1132" =~ h[:OZ]
#=> 0
"NSW113" =~ h[:OZ]
#=> nil
"QLD" =~ h[:OZ]
#=> nil
"CAT 1132" =~ h[:OZ]
#=> nil
make_regex
为
str = "V8V 3A2"
codes = "*+"
如下。
e = str.each_char.chunk do |c|
case c
when /\d/ then :DIGIT
when /[[:alpha:]]/ then :ALPHA
when /\s/ then :WHITE
else :OTHER
end
end
#=> #<Enumerator: #<Enumerator::Generator:0x007f9ff201a330>:each>
我们可以看到这个枚举器通过将它转换为数组而生成的值。
e.to_a
#=> [[:ALPHA, ["V"]], [:DIGIT, ["8"]], [:ALPHA, ["V"]], [:WHITE, [" "]],
# [:DIGIT, ["3"]], [:ALPHA, ["A"]], [:DIGIT, ["2"]]]
继续,
a = e.map do |type, arr|
case type
when :ALPHA
if codes.include?('!')
arr
elsif arr.size == 1
"[[:alpha:]]"
else "[[:alpha:]]\{#{arr.size}\}"
end
when :DIGIT
(arr.size == 1) ? "\\d" : "\\d\{#{arr.size}\}"
when :WHITE
case codes
when /\*/ then "\\s*"
when /\+/ then "\\s+"
else (arr.size == 1) ? "\\s" : "\\s\{#{arr.size}\}"
end
when :OTHER
arr
end
end
#=> ["[[:alpha:]]", "\\d", "[[:alpha:]]", "\\s*", "\\d", "[[:alpha:]]", "\\d"]
rstr = a.join
#=> "[[:alpha:]]\\d[[:alpha:]]\\s*\\d[[:alpha:]]\\d"
t = "\\A" << rstr << "\\z"
#=> "\\A[[:alpha:]]\\d[[:alpha:]]\\s*\\d[[:alpha:]]\\d\\z"
puts t
#=> \A[[:alpha:]]\d[[:alpha:]]\s*\d[[:alpha:]]\d\z
Regexp.new(t)
#=> /\A[[:alpha:]]\d[[:alpha:]]\s*\d[[:alpha:]]\d\z/