Class: ActiveModel::Validations::TrackingNumberValidator

Inherits:
EachValidator
  • Object
show all
Defined in:
lib/active_validators/active_model/validations/tracking_number_validator.rb

Constant Summary collapse

UPS_REGEXES =

UPS: ups tracking codes are validated solely on their format see www.ups.com/content/us/en/tracking/help/tracking/tnh.html

[ /\A1Z[a-zA-Z0-9]{16}\z/, /\A[a-zA-Z0-9]{12}\z/, /\A[a-zA-Z0-9]{9}\z/, /\AT[a-zA-Z0-9]{10}\z/ ]
USS128_REGEX =
/\A(\d{19,21})(\d)\z/
USS39_REGEX =
/\A[a-zA-Z0-9]{2}(\d{8})(\d)US\z/
MOD10_WEIGHTS =
[3,1]
MOD11_WEIGHTS =
[8,6,4,2,3,5,9,7]

Instance Method Summary collapse

Instance Method Details

#usps_mod10(chars) ⇒ Object



46
47
48
# File 'lib/active_validators/active_model/validations/tracking_number_validator.rb', line 46

def usps_mod10(chars)
  (10 - weighted_sum(chars.reverse, MOD10_WEIGHTS) % 10) % 10
end

#usps_mod11(chars) ⇒ Object



51
52
53
54
55
56
57
58
# File 'lib/active_validators/active_model/validations/tracking_number_validator.rb', line 51

def usps_mod11(chars)
  mod = weighted_sum(chars, MOD11_WEIGHTS) % 11
  case mod
  when 0 then 5
  when 1 then 0
  else 11 - mod
  end
end

#uss228?(value) ⇒ Boolean

Returns:

  • (Boolean)


32
33
34
35
# File 'lib/active_validators/active_model/validations/tracking_number_validator.rb', line 32

def uss228?(value)
  m = value.match(USS128_REGEX)
  m.present? && (m[2].to_i == usps_mod10(m[1]))
end

#uss39?(value) ⇒ Boolean

Returns:

  • (Boolean)


38
39
40
41
42
43
# File 'lib/active_validators/active_model/validations/tracking_number_validator.rb', line 38

def uss39?(value)
  m = value.match(USS39_REGEX)
  # it appears to be valid for a USS39 barcode's checkdigit to be calculated with either the usps mod 10
  # algorithm or the usps mod 11.
  m.present? && (m[2].to_i == usps_mod10(m[1]) || m[2].to_i == usps_mod11(m[1]))
end

#valid_ups?(value) ⇒ Boolean

Returns:

  • (Boolean)


16
17
18
# File 'lib/active_validators/active_model/validations/tracking_number_validator.rb', line 16

def valid_ups?(value)
  !!UPS_REGEXES.detect { |fmt| value.match(fmt) }
end

#valid_usps?(value) ⇒ Boolean

USPS: usps tracking codes are validated based on format (one of USS228 or USS39) and a check digit (using either of the USPS’s MOD10 or MOD11 algorithms) see USPS Publications:

-  #91 (05/2008) pp. 38
-  #97 (05/2002) pp. 62-63
- #109 (09/2007) pp. 19-21

Returns:

  • (Boolean)


27
28
29
# File 'lib/active_validators/active_model/validations/tracking_number_validator.rb', line 27

def valid_usps?(value)
  uss228?(value) || uss39?(value)
end

#validate_each(record, attribute, value) ⇒ Object



4
5
6
7
8
9
10
# File 'lib/active_validators/active_model/validations/tracking_number_validator.rb', line 4

def validate_each(record, attribute, value)
  carrier = options[:carrier] || (options[:carrier_field] && record.send(options[:carrier_field]))
  raise "Carrier option required" unless carrier
  method = "valid_#{carrier.to_s}?"
  raise "Tracking number validation not supported for carrier #{carrier}" unless self.respond_to?(method)
  record.errors.add(attribute) if value.blank? || !self.send(method, value)
end

#weighted_sum(value, weights) ⇒ Object

takes a string containing digits and calculates a checksum using the provided weight array cycles the weight array if it’s not long enough



62
63
64
65
66
# File 'lib/active_validators/active_model/validations/tracking_number_validator.rb', line 62

def weighted_sum(value, weights)
  digits = value.split('').map { |d| d.to_i }
  weights = weights.cycle.take(digits.count) if weights.count < digits.count
  digits.zip(weights).inject(0) { |s,p| s + p[0] * p[1] }
end