1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
|
require 'sixarm_ruby_unaccent'
module ISO3166
UNSEARCHABLE_METHODS = [:translations].freeze
def self::Country(country_data_or_country)
case country_data_or_country
when ISO3166::Country
country_data_or_country
when String, Symbol
ISO3166::Country.search(country_data_or_country)
else
raise TypeError, "can't convert #{country_data_or_country.class.name} into ISO3166::Country"
end
end
module CountryClassMethods
FIND_BY_REGEX = /^find_(all_)?(country_|countries_)?by_(.+)/
SEARCH_TERM_FILTER_REGEX = /\(|\)|\[\]|,/
def new(country_data)
super if country_data.is_a?(Hash) || codes.include?(country_data.to_s.upcase)
end
def codes
ISO3166::Data.codes
end
def all(&blk)
blk ||= proc { |_alpha2, d| ISO3166::Country.new(d) }
ISO3166::Data.cache.map(&blk)
end
alias countries all
def all_translated(locale = 'en')
translations(locale).values
end
def all_names_with_codes(locale = 'en')
Country.all.map do |c|
lc = (c.translation(locale) || c.iso_short_name)
[lc.respond_to?('html_safe') ? lc.html_safe : lc, c.alpha2]
end.sort
end
def pluck(*attributes)
all.map do |country|
attributes.map { |attribute| country.data.fetch(attribute.to_s) }
end
end
def translations(locale = 'en')
i18n_data_countries = I18nData.countries(locale.upcase)
custom_countries = (ISO3166::Data.codes - i18n_data_countries.keys).map do |code|
country = ISO3166::Country[code]
translation = country.translations[locale] || country.iso_short_name
[code, translation]
end.to_h
i18n_data_countries.merge(custom_countries)
end
def search(query)
country = new(query.to_s.upcase)
country && country.valid? ? country : nil
end
def [](query)
search(query)
end
def method_missing(method_name, *arguments)
matches = method_name.to_s.match(FIND_BY_REGEX)
return_all = matches[1]
super unless matches
if matches[3] == 'names'
if RUBY_VERSION =~ /^3\.\d\.\d/
warn "DEPRECATION WARNING: 'find_by_name' and 'find_*_by_name' methods are deprecated, please refer to the README file for more information on this change.", uplevel: 1, category: :deprecated
else
warn "DEPRECATION WARNING: 'find_by_name' and 'find_*_by_name' methods are deprecated, please refer to the README file for more information on this change.", uplevel: 1
end
end
countries = find_by(matches[3], arguments[0], matches[2])
return_all ? countries : countries.last
end
def respond_to_missing?(method_name, include_private = false)
matches = method_name.to_s.match(FIND_BY_REGEX)
if matches && matches[3]
instance_methods.include?(matches[3].to_sym)
else
super
end
end
def find_all_by(attribute, val)
attributes, lookup_value = parse_attributes(attribute, val)
ISO3166::Data.cache.select do |_, v|
country = Country.new(v)
attributes.any? do |attr|
Array(country.send(attr)).any? do |n|
lookup_value === cached(n) { parse_value(n) }
end
end
end
end
def subdivisions(alpha2)
@subdivisions ||= {}
@subdivisions[alpha2] ||= create_subdivisions(subdivision_data(alpha2))
end
def create_subdivisions(subdivision_data)
subdivision_data.each_with_object({}) do |(k, v), hash|
data = v.merge('code' => k.to_s)
hash[k] = Subdivision.new(data)
end
end
protected
def strip_accents(v)
if v.is_a?(Regexp)
Regexp.new(v.source.unaccent, 'i')
else
v.to_s.unaccent.downcase
end
end
def parse_attributes(attribute, val)
raise "Invalid attribute name '#{attribute}'" unless searchable_attribute?(attribute.to_sym)
attributes = Array(attribute.to_s)
if attribute.to_s == 'name'
if RUBY_VERSION =~ /^3\.\d\.\d/
warn "DEPRECATION WARNING: 'find_by_name' and 'find_*_by_name' methods are deprecated, please refer to the README file for more information on this change.", uplevel: 1, category: :deprecated
else
warn "DEPRECATION WARNING: 'find_by_name' and 'find_*_by_name' methods are deprecated, please refer to the README file for more information on this change.", uplevel: 1
end
# 'find_by_name' and 'find_*_by_name' will be changed for 5.0
# The addition of 'iso_short_name' here ensures the behaviour of 4.1 is kept for 4.2
attributes = %w[iso_short_name unofficial_names translated_names]
end
[attributes, parse_value(val)]
end
def searchable_attribute?(attribute)
searchable_attributes.include?(attribute.to_sym)
end
def searchable_attributes
instance_methods - UNSEARCHABLE_METHODS
end
def find_by(attribute, value, obj = nil)
find_all_by(attribute.downcase, value).map do |country|
obj.nil? ? country : new(country.last)
end
end
def parse_value(value)
value = value.gsub(SEARCH_TERM_FILTER_REGEX, '') if value.respond_to?(:gsub)
strip_accents(value)
end
def subdivision_data(alpha2)
file = subdivision_file_path(alpha2)
File.exist?(file) ? YAML.load_file(file) : {}
end
def subdivision_file_path(alpha2)
File.join(File.dirname(__FILE__), '..', 'data', 'subdivisions', "#{alpha2}.yaml")
end
# Some methods like parse_value are expensive in that they
# create a large number of objects internally. In order to reduce the
# object creations and save the GC, we can cache them in an class instance
# variable. This will make subsequent parses O(1) and will stop the
# creation of new String object instances.
#
# NB: We only want to use this cache for values coming from the JSON
# file or our own code, caching user-generated data could be dangerous
# since the cache would continually grow.
def cached(value)
@_parsed_values_cache ||= {}
return @_parsed_values_cache[value] if @_parsed_values_cache[value]
@_parsed_values_cache[value] = yield
end
end
end
|