civitas 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 28037fc6995f149ecd6fcd77de06bb73314f350543cff25588566d14e4a1b5be
4
+ data.tar.gz: c3491d5efd566a0935fb25fb806b7664e597998d0f2dbdeedd2a39534f9fd3c6
5
+ SHA512:
6
+ metadata.gz: 904e5eb9643e640adfdebcce66fe1e30515c7bd97874dda3dbf387d10c4e6a8b60fa3fbe0bce858770807ff8567e72d6b4cb79672ca60c099a6407ceac337763
7
+ data.tar.gz: 9e77e35ec9febda0aed5284b6acdb4a4550742e05e84f1dbd76ffb993da7635decc31b31b815af6c12b6b1e9a3309e39d65f0960c483894848180b31a9b2ca37
data/CHANGELOG.md ADDED
@@ -0,0 +1,38 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.1.0] - 2023-08-14
9
+
10
+ ### Added
11
+ - Ruby 3 support.
12
+ - Development dependencies: `rspec 3.10`.
13
+ - Unit tests with RSpec.
14
+
15
+ ### Removed
16
+ - Ruby 2.5 support. Minimum Ruby version is now 2.6.0.
17
+ - `Gemfile.lock` from version control.
18
+
19
+ ### Changed
20
+ - Update bundled MaxMind database.
21
+ - Upgrade runtime dependencies: minimum Rake is 11.0, minimum Rubyzip is 2.3.
22
+
23
+ ## [0.1.0] - 2020-03-25
24
+
25
+ ### Added
26
+ - Methods `set_license_key(license_key)` and `set_maxmind_zip_url(url)`.
27
+
28
+ ### Removed
29
+ - Rails-specific code for broader Ruby compatibility.
30
+
31
+ ### Changed
32
+ - Improve methods for renaming and adding missing cities.
33
+ - Update bundled MaxMind database.
34
+ - Upgrade dependencies for security and compatibility.
35
+
36
+ ### Fixed
37
+ - Duplicated city entries. `CS.cities(:CA, :US)` multiple `Burbank` entries.
38
+ - Calling `CS.cities(nil)` returns random values.
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2023 Daniel Loureiro
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # Civitas Ruby Gem
2
+
3
+ The `civitas` gem offers a straightforward way to retrieve lists of states for any given country and cities for any state. It's built on the MaxMind database, making it a reliable source for such data.
4
+
5
+ ## Compatibility
6
+
7
+ This branch (`main`) is compatible with **Ruby 3 and higher**. If you are using Ruby 2, please refer to the `v0` branch.
8
+
9
+ ### Ruby 3 and Higher
10
+
11
+ - This is the primary development branch.
12
+ - New features and improvements will be added here.
13
+ - Ensure you have Ruby 3 or higher to use the versions from this branch.
14
+
15
+ ### Ruby 2 Support
16
+
17
+ - For Ruby 2 support, please refer to the `v0` branch.
18
+ - The `v0` branch is in maintenance mode, which means it will only receive bug fixes and will not get any new features.
19
+
20
+ ## Installation
21
+
22
+ Add the gem to your Gemfile:
23
+
24
+ ```ruby
25
+ gem 'civitas'
26
+ ```
27
+
28
+ Then, run:
29
+
30
+ ```bash
31
+ $ bundle install
32
+ ```
33
+
34
+ ## Listing States:
35
+
36
+ Retrieve a list of states for a specified country:
37
+
38
+ ```ruby
39
+ CS.states(:US)
40
+ ```
41
+ **Note:** The gem is case-insensitive. You can use variations like `:US`, `:us`, `:Us`, `"us"`, and `"US"`.
42
+
43
+ ## Listing Cities:
44
+
45
+ Retrieve a list of cities for a specified state and country:
46
+
47
+ ```ruby
48
+ CS.cities(:AK, :US)
49
+ ```
50
+
51
+ You can also specify the country, though it's optional. The gem remembers the last country you used:
52
+
53
+ ```ruby
54
+ CS.states(:BR)
55
+
56
+ CS.cities(:TO) # This will use Brazil (BR) as the country
57
+ ```
58
+
59
+ Miscellaneous Notes:
60
+ - The country is an optional argument. The gem always uses the last country that you used.
61
+
62
+ ## Listing Countries:
63
+
64
+ ```ruby
65
+ CS.countries
66
+ ```
67
+
68
+ ## Missing cities and wrong names
69
+ To add missing cities or to rename wrong ones, create these files in your project folder:
70
+ `db/cities-lookup.yml` and `db/states-lookup.yml` and `db/countries-lookup.yml`:
71
+
72
+ ### Renaming a country - `US` to `America`:
73
+
74
+ ```yaml
75
+ # db/countries-lookup.yml
76
+ US: "America"
77
+ ```
78
+
79
+ ### Renaming a state - `California` to `Something Else`:
80
+
81
+ ```yaml
82
+ # db/states-lookup.yml
83
+ US:
84
+ CA: Something Else
85
+ ```
86
+
87
+ ### Renaming a city:
88
+
89
+ ```yaml
90
+ # db/cities-lookup.yml
91
+ US:
92
+ CA:
93
+ "Burbank": "Bur Bank"
94
+ ```
95
+
96
+ ### Adding a missing city:
97
+
98
+ ```yaml
99
+ # db/cities-lookup.yml
100
+ US:
101
+ CA:
102
+ "My Town": "My Town"
103
+ ```
104
+
105
+ ### Suppressing a city (set it as a blank line):
106
+ ```yaml
107
+ # db/cities-lookup.yml
108
+ US:
109
+ CA:
110
+ "Burbank": ""
111
+ ```
112
+
113
+ ### To use a different file instead of `db\cities-lookup.yml`:
114
+
115
+ ```ruby
116
+ CS.set_cities_lookup_file('new-city-names.yml')
117
+
118
+ CS.set_states_lookup_file('new-state-names.yml')
119
+
120
+ CS.set_countries_lookup_file('new-country-names.yml')
121
+ ```
122
+
123
+ ## Updating MaxMind database
124
+ MaxMind update their databases weekly on Tuesdays.
125
+
126
+ Since Dec 30, 2019, MaxMind requires a license key (for free) to get download updates.
127
+
128
+ To get the license key:
129
+ 1. Sign up for a MaxMind account: https://www.maxmind.com/en/geolite2/signup
130
+ 2. Create a license key: https://www.maxmind.com/en/accounts/current/license-key
131
+ 3. There's no need to download anything.
132
+
133
+ To update:
134
+
135
+ ```ruby
136
+ CS.set_license_key('MY_KEY')
137
+
138
+ CS.update
139
+ ```
140
+ **Note:** Replace `MY_KEY` with your actual license key.
141
+
142
+ ## Manually setting a database file:
143
+
144
+ You can use an alternative database file instead of downloading from MaxMind servers:
145
+
146
+ ```ruby
147
+ CS.set_maxmind_zip_url('/home/daniel/GeoLite2-City-CSV_20200324.zip')
148
+
149
+ CS.update
150
+ ```
151
+
152
+ or
153
+
154
+ ```ruby
155
+ CS.set_maxmind_zip_url('https://example.com/GeoLite2-City-CSV_20200324.zip')
156
+
157
+ CS.update
158
+ ```
159
+
160
+ The file has to be a ZIP file. And it has to contain a CVS file named `GeoLite2-City-Locations-en.csv`. This file must be in MaxMind's GeoLite2 City's format.
161
+
162
+ ## Changelog
163
+ See [CHANGELOG.md](CHANGELOG.md)
164
+
165
+ ## How the original `city-state` gem was created
166
+ https://learnwithdaniel.com/2015/02/citystate-list-of-countries-cities-and-states-ruby/
167
+
168
+ ## civitas License
169
+ **civitas** is a open source project forked from `city-state` by Daniel Loureiro with a MIT license. Also, it uses MaxMind open source database.
170
+
171
+ ## MaxMind License
172
+ Database and Contents Copyright (c) 2020 MaxMind, Inc.
173
+ This work is licensed under the Creative Commons Attribution 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/civitas.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'civitas/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "civitas"
8
+ spec.version = CS::VERSION
9
+ spec.authors = ["Daniel Loureiro"]
10
+ spec.email = ["loureirorg@gmail.com"]
11
+ spec.summary = %q{Simple list of cities and states of the world}
12
+ spec.description = %q{Useful to make forms and validations. It uses MaxMind database.}
13
+ spec.homepage = "https://github.com/duduribeiro/civitas"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = [
17
+ 'Rakefile',
18
+ 'README.md',
19
+ 'LICENSE.txt',
20
+ 'CHANGELOG.md',
21
+ 'civitas.gemspec'
22
+ ] + Dir["lib/**/*"] + Dir["spec/**/*"]
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.required_ruby_version = '>= 2.6.0'
26
+
27
+ spec.add_runtime_dependency "rubyzip", ">= 2.3"
28
+
29
+ spec.add_development_dependency "bundler", ">= 1.7"
30
+ spec.add_development_dependency "rake", ">= 11.0"
31
+ spec.add_development_dependency 'rspec', '~> 3.10'
32
+ end
@@ -0,0 +1,3 @@
1
+ module CS
2
+ VERSION = "1.1.1"
3
+ end
data/lib/civitas.rb ADDED
@@ -0,0 +1,353 @@
1
+ require 'uri'
2
+ require "civitas/version"
3
+ require 'yaml'
4
+
5
+ module CS
6
+ # CS constants
7
+ FILES_FOLDER = File.expand_path('../db', __FILE__)
8
+ MAXMIND_DB_FN = File.join(FILES_FOLDER, "GeoLite2-City-Locations-en.csv")
9
+ COUNTRIES_FN = File.join(FILES_FOLDER, "countries.yml")
10
+ DEFAULT_CITIES_LOOKUP_FN = 'db/cities-lookup.yml'
11
+ DEFAULT_STATES_LOOKUP_FN = 'db/states-lookup.yml'
12
+ DEFAULT_COUNTRIES_LOOKUP_FN = 'db/countries-lookup.yml'
13
+
14
+ @countries, @states, @cities = [{}, {}, {}]
15
+ @current_country = nil # :US, :BR, :GB, :JP, ...
16
+ @maxmind_zip_url = nil
17
+ @license_key = nil
18
+
19
+ # lookup tables for state/cities renaming
20
+ @cities_lookup_fn = nil
21
+ @cities_lookup = nil
22
+ @states_lookup_fn = nil
23
+ @states_lookup = nil
24
+ @countries_lookup_fn = nil
25
+ @countries_lookup = nil
26
+
27
+ def self.set_maxmind_zip_url(maxmind_zip_url)
28
+ @maxmind_zip_url = maxmind_zip_url
29
+ end
30
+
31
+ def self.set_license_key(license_key)
32
+ url = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City-CSV&license_key=#{license_key}&suffix=zip"
33
+ @license_key = license_key
34
+ self.set_maxmind_zip_url(url)
35
+ end
36
+
37
+ def self.update_maxmind
38
+ require "open-uri"
39
+ require "zip"
40
+
41
+ # get zipped file
42
+ return false if !@maxmind_zip_url
43
+ f_zipped = URI.open(@maxmind_zip_url)
44
+
45
+ # unzip file:
46
+ # recursively searches for "GeoLite2-City-Locations-en"
47
+ Zip::File.open(f_zipped) do |zip_file|
48
+ zip_file.each do |entry|
49
+ if self.present?(entry.name["GeoLite2-City-Locations-en"])
50
+ fn = entry.name.split("/").last
51
+ entry.extract(File.join(FILES_FOLDER, fn)) { true } # { true } is to overwrite
52
+ break
53
+ end
54
+ end
55
+ end
56
+ true
57
+ end
58
+
59
+ def self.update
60
+ self.update_maxmind # update via internet
61
+ Dir[File.join(FILES_FOLDER, "states.*")].each do |state_fn|
62
+ self.install(state_fn.split(".").last.upcase.to_sym) # reinstall country
63
+ end
64
+ @countries, @states, @cities = [{}, {}, {}] # invalidades cache
65
+ File.delete COUNTRIES_FN # force countries.yml to be generated at next call of CS.countries
66
+ true
67
+ end
68
+
69
+ # constants: CVS position
70
+ ID = 0
71
+ COUNTRY = 4
72
+ COUNTRY_LONG = 5
73
+ STATE = 6
74
+ STATE_LONG = 7
75
+ CITY = 10
76
+
77
+ def self.install(country)
78
+ # get CSV if doesn't exists
79
+ update_maxmind unless File.exist? MAXMIND_DB_FN
80
+
81
+ # normalize "country"
82
+ country = country.to_s.upcase
83
+
84
+ # some state codes are empty: we'll use "states-replace" in these cases
85
+ states_replace_fn = File.join(FILES_FOLDER, "states-replace.yml")
86
+ states_replace = self.symbolize_keys(YAML::load_file(states_replace_fn))
87
+ states_replace = states_replace[country.to_sym] || {} # we need just this country
88
+ states_replace_inv = states_replace.invert # invert key with value, to ease the search
89
+
90
+ # read CSV line by line
91
+ cities = {}
92
+ states = {}
93
+ File.foreach(MAXMIND_DB_FN) do |line|
94
+ rec = line.split(",")
95
+ next if rec[COUNTRY] != country
96
+ next if (self.blank?(rec[STATE]) && self.blank?(rec[STATE_LONG])) || self.blank?(rec[CITY])
97
+
98
+ # some state codes are empty: we'll use "states-replace" in these cases
99
+ rec[STATE] = states_replace_inv[rec[STATE_LONG]] if self.blank?(rec[STATE])
100
+ rec[STATE] = rec[STATE_LONG] if self.blank?(rec[STATE]) # there's no correspondent in states-replace: we'll use the long name as code
101
+
102
+ # some long names are empty: we'll use "states-replace" to get the code
103
+ rec[STATE_LONG] = states_replace[rec[STATE]] if self.blank?(rec[STATE_LONG])
104
+
105
+ # normalize
106
+ rec[STATE] = rec[STATE].to_sym
107
+ rec[CITY].gsub!(/\"/, "") # sometimes names come with a "\" char
108
+ rec[STATE_LONG].gsub!(/\"/, "") # sometimes names come with a "\" char
109
+
110
+ # cities list: {TX: ["Texas City", "Another", "Another 2"]}
111
+ cities.merge!({rec[STATE] => []}) if ! states.has_key?(rec[STATE])
112
+ cities[rec[STATE]] << rec[CITY]
113
+
114
+ # states list: {TX: "Texas", CA: "California"}
115
+ if ! states.has_key?(rec[STATE])
116
+ state = {rec[STATE] => rec[STATE_LONG]}
117
+ states.merge!(state)
118
+ end
119
+ end
120
+
121
+ # sort
122
+ cities = Hash[cities.sort]
123
+ states = Hash[states.sort]
124
+ cities.each { |k, v| cities[k].sort! }
125
+
126
+ # save to states.us and cities.us
127
+ states_fn = File.join(FILES_FOLDER, "states.#{country.downcase}")
128
+ cities_fn = File.join(FILES_FOLDER, "cities.#{country.downcase}")
129
+ File.open(states_fn, "w") { |f| f.write states.to_yaml }
130
+ File.open(cities_fn, "w") { |f| f.write cities.to_yaml }
131
+ File.chmod(0666, states_fn, cities_fn) # force permissions to rw_rw_rw_ (issue #3)
132
+ true
133
+ end
134
+
135
+ def self.current_country
136
+ return @current_country if self.present?(@current_country)
137
+
138
+ # we don't have used this method yet: discover by the file extension
139
+ fn = Dir[File.join(FILES_FOLDER, "cities.*")].last
140
+ @current_country = self.blank?(fn) ? nil : fn.split(".").last
141
+
142
+ # there's no files: we'll install and use :US
143
+ if self.blank?(@current_country)
144
+ @current_country = :US
145
+ self.install(@current_country)
146
+
147
+ # we find a file: normalize the extension to something like :US
148
+ else
149
+ @current_country = @current_country.to_s.upcase.to_sym
150
+ end
151
+
152
+ @current_country
153
+ end
154
+
155
+ def self.current_country=(country)
156
+ @current_country = country.to_s.upcase.to_sym
157
+ end
158
+
159
+ def self.cities(state, country = nil)
160
+ self.current_country = country if self.present?(country) # set as current_country
161
+ country = self.current_country
162
+ state = state.to_s.upcase.to_sym
163
+
164
+ # load the country file
165
+ if self.blank?(@cities[country])
166
+ cities_fn = File.join(FILES_FOLDER, "cities.#{country.to_s.downcase}")
167
+ self.install(country) if ! File.exist? cities_fn
168
+ @cities[country] = self.symbolize_keys(YAML::load_file(cities_fn))
169
+
170
+ # Remove duplicated cities
171
+ @cities[country].each do |key, value|
172
+ @cities[country][key] = value.uniq || []
173
+ end
174
+
175
+ # Process lookup table
176
+ lookup = get_cities_lookup(country)
177
+ if ! lookup.nil?
178
+ lookup.each do |state, new_values|
179
+ new_values.each do |old_value, new_value|
180
+ if new_value.nil? || self.blank?(new_value)
181
+ @cities[country][state].delete(old_value)
182
+ else
183
+ index = @cities[country][state].index(old_value)
184
+ if index.nil?
185
+ @cities[country][state] << new_value
186
+ else
187
+ @cities[country][state][index] = new_value
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ @cities[country][state] = @cities[country][state].sort # sort it alphabetically
194
+ end
195
+ end
196
+
197
+ # Return list
198
+ @cities[country][state]
199
+ end
200
+
201
+ def self.set_cities_lookup_file(filename)
202
+ @cities_lookup_fn = filename
203
+ @cities_lookup = nil
204
+ end
205
+
206
+ def self.set_states_lookup_file(filename)
207
+ @states_lookup_fn = filename
208
+ @states_lookup = nil
209
+ end
210
+
211
+ def self.set_countries_lookup_file(filename)
212
+ @countries_lookup_fn = filename
213
+ @countries_lookup = nil
214
+ end
215
+
216
+ def self.get_cities_lookup(country)
217
+ # lookup file not loaded
218
+ if @cities_lookup.nil?
219
+ @cities_lookup_fn = DEFAULT_CITIES_LOOKUP_FN if @cities_lookup_fn.nil?
220
+ @cities_lookup_fn = File.expand_path(@cities_lookup_fn)
221
+ return nil if ! File.exist?(@cities_lookup_fn)
222
+ @cities_lookup = self.symbolize_keys(YAML::load_file(@cities_lookup_fn)) # force countries to be symbols
223
+ @cities_lookup.each { |key, value| @cities_lookup[key] = self.symbolize_keys(value) } # force states to be symbols
224
+ end
225
+
226
+ return nil if ! @cities_lookup.key?(country)
227
+ @cities_lookup[country]
228
+ end
229
+
230
+ def self.get_states_lookup(country)
231
+ # lookup file not loaded
232
+ if @states_lookup.nil?
233
+ @states_lookup_fn = DEFAULT_STATES_LOOKUP_FN if @states_lookup_fn.nil?
234
+ @states_lookup_fn = File.expand_path(@states_lookup_fn)
235
+ return nil if ! File.exist?(@states_lookup_fn)
236
+ @states_lookup = self.symbolize_keys(YAML::load_file(@states_lookup_fn)) # force countries to be symbols
237
+ @states_lookup.each { |key, value| @states_lookup[key] = self.symbolize_keys(value) } # force states to be symbols
238
+ end
239
+
240
+ return nil if ! @states_lookup.key?(country)
241
+ @states_lookup[country]
242
+ end
243
+
244
+ def self.get_countries_lookup
245
+ # lookup file not loaded
246
+ if @countries_lookup.nil?
247
+ @countries_lookup_fn = DEFAULT_COUNTRIES_LOOKUP_FN if @countries_lookup_fn.nil?
248
+ @countries_lookup_fn = File.expand_path(@countries_lookup_fn)
249
+ return nil if ! File.exist?(@countries_lookup_fn)
250
+ @countries_lookup = self.symbolize_keys(YAML::load_file(@countries_lookup_fn)) # force countries to be symbols
251
+ end
252
+
253
+ @countries_lookup
254
+ end
255
+
256
+ def self.states(country)
257
+ # Bugfix: https://github.com/loureirorg/city-state/issues/24
258
+ return {} if country.nil?
259
+
260
+ # Set it as current_country
261
+ self.current_country = country # set as current_country
262
+ country = self.current_country # normalized
263
+
264
+ # Load the country file
265
+ if self.blank?(@states[country])
266
+ states_fn = File.join(FILES_FOLDER, "states.#{country.to_s.downcase}")
267
+ self.install(country) if ! File.exist? states_fn
268
+ @states[country] = self.symbolize_keys(YAML::load_file(states_fn))
269
+
270
+ # Process lookup table
271
+ lookup = get_states_lookup(country)
272
+ if ! lookup.nil?
273
+ lookup.each do |key, value|
274
+ if value.nil? || self.blank?(value)
275
+ @states[country].delete(key)
276
+ else
277
+ @states[country][key] = value
278
+ end
279
+ end
280
+ @states[country] = @states[country].sort.to_h # sort it alphabetically
281
+ end
282
+ end
283
+
284
+ # Return list
285
+ @states[country] || {}
286
+ end
287
+
288
+ # list of all countries of the world (countries.yml)
289
+ def self.countries
290
+ if ! File.exist? COUNTRIES_FN
291
+ # countries.yml doesn't exists, extract from MAXMIND_DB
292
+ update_maxmind unless File.exist? MAXMIND_DB_FN
293
+
294
+ # reads CSV line by line
295
+ File.foreach(MAXMIND_DB_FN) do |line|
296
+ rec = line.split(",")
297
+ next if self.blank?(rec[COUNTRY]) || self.blank?(rec[COUNTRY_LONG]) # jump empty records
298
+ country = rec[COUNTRY].to_s.upcase.to_sym # normalize to something like :US, :BR
299
+ if self.blank?(@countries[country])
300
+ long = rec[COUNTRY_LONG].gsub(/\"/, "") # sometimes names come with a "\" char
301
+ @countries[country] = long
302
+ end
303
+ end
304
+
305
+ # sort and save to "countries.yml"
306
+ @countries = Hash[@countries.sort]
307
+ File.open(COUNTRIES_FN, "w") { |f| f.write @countries.to_yaml }
308
+ File.chmod(0666, COUNTRIES_FN) # force permissions to rw_rw_rw_ (issue #3)
309
+ else
310
+ # countries.yml exists, just read it
311
+ @countries = self.symbolize_keys(YAML::load_file(COUNTRIES_FN))
312
+ end
313
+
314
+ # Applies `countries-lookup.yml` if exists
315
+ lookup = self.get_countries_lookup()
316
+ if ! lookup.nil?
317
+ lookup.each do |key, value|
318
+ if value.nil? || self.blank?(value)
319
+ @countries.delete(key)
320
+ else
321
+ @countries[key] = value
322
+ end
323
+ end
324
+ @countries = @countries.sort.to_h # sort it alphabetically
325
+ end
326
+
327
+ # Return countries list
328
+ @countries
329
+ end
330
+
331
+ # get is a method to simplify the use of city-state
332
+ # get = countries, get(country) = states(country), get(country, state) = cities(state, country)
333
+ def self.get(country = nil, state = nil)
334
+ return self.countries if country.nil?
335
+ return self.states(country) if state.nil?
336
+ return self.cities(state, country)
337
+ end
338
+
339
+ # Emulates Rails' `blank?` method
340
+ def self.blank?(obj)
341
+ obj.respond_to?(:empty?) ? !!obj.empty? : !obj
342
+ end
343
+
344
+ # Emulates Rails' `present?` method
345
+ def self.present?(obj)
346
+ !self.blank?(obj)
347
+ end
348
+
349
+ # Emulates Rails' `symbolize_keys` method
350
+ def self.symbolize_keys(obj)
351
+ obj.transform_keys { |key| key.to_sym rescue key }
352
+ end
353
+ end