class GtinValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) unless valid?(value, options) record.errors[attribute] << (options[:message] || I18n.t('flash_validators.errors.messages.gtin')) end end private def includes_key?(value, options) value.any? { |key| options.keys.include?(key) } end def valid_format?(value, options) if options[:strict] value =~ /^[0-9]+$/ else value =~ /^[0-9 ]+$/ end end def valid_length?(value, options) value_size = value.size case options[:format] when :ean_8, :gtin_8, :ucc_8 value_size == 8 when :gtin_12, :upc, :upc_a value_size == 12 when :ean, :ean_13, :gtin_13, :ucc_13 value_size == 13 when :gtin_14, :ucc_14 value_size == 14 else [8, 12, 13, 14].include?(value_size) end end def valid_checksum?(value) reversed_values = value.reverse odd = even = 0 (1..(reversed_values.length - 1)).each do |i| i.even? ? (even += reversed_values[i].chr.to_i) : (odd += reversed_values[i].chr.to_i) end reversed_values[0].chr.to_i == (10 - ((odd * 3) + even) % 10) end def valid?(value, options) striped_value = value.to_s.gsub(/\D/, '') valid_format?(value, options) && valid_length?(striped_value, options) && valid_checksum?(striped_value) end end