has_addresses 0.0.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.
- data/CHANGELOG +19 -0
- data/MIT-LICENSE +20 -0
- data/README +111 -0
- data/Rakefile +82 -0
- data/app/models/address.rb +84 -0
- data/app/models/country.rb +114 -0
- data/app/models/region.rb +112 -0
- data/db/migrate/001_create_countries.rb +17 -0
- data/db/migrate/002_create_regions.rb +15 -0
- data/db/migrate/003_create_addresses.rb +21 -0
- data/init.rb +1 -0
- data/lib/has_addresses.rb +74 -0
- data/tasks/has_addresses_tasks.rake +23 -0
- data/test/app_root/app/models/company.rb +7 -0
- data/test/app_root/config/environment.rb +30 -0
- data/test/app_root/db/migrate/001_create_companies.rb +11 -0
- data/test/app_root/db/migrate/002_add_address_kinds.rb +9 -0
- data/test/app_root/test/fixtures/addresses.yml +47 -0
- data/test/app_root/test/fixtures/companies.yml +7 -0
- data/test/app_root/test/fixtures/countries.yml +12 -0
- data/test/app_root/test/fixtures/regions.yml +5 -0
- data/test/files/iso_3166.xml +1719 -0
- data/test/files/iso_3166_2.xml +8617 -0
- data/test/test_helper.rb +11 -0
- data/test/unit/address_test.rb +171 -0
- data/test/unit/country_test.rb +119 -0
- data/test/unit/has_addresses_test.rb +15 -0
- data/test/unit/region_test.rb +101 -0
- metadata +88 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
*SVN*
|
2
|
+
|
3
|
+
*0.0.1* (August 21st, 2007)
|
4
|
+
|
5
|
+
* Add documentation
|
6
|
+
|
7
|
+
* Add descriptive output for rake tasks
|
8
|
+
|
9
|
+
* Remove dependency on has_association_helper
|
10
|
+
|
11
|
+
* Remove default bootstrap files in favor of creating new ones through the bootstrap tasks
|
12
|
+
|
13
|
+
* Add tests for rake tests
|
14
|
+
|
15
|
+
* Fix not allowing single character abbreviations for regions
|
16
|
+
|
17
|
+
* Add countries:create_fixtures, countries:bootstrap, regions:create_fixtures, and regions:bootstrap files
|
18
|
+
|
19
|
+
* Refactor Rake code into Country/Region classes
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006-2007 Aaron Pfeifer & Neil Abraham
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
= has_addresses
|
2
|
+
|
3
|
+
has_addresses adds a base skeleton for handling countries, regions, and
|
4
|
+
addresses.
|
5
|
+
|
6
|
+
== Resources
|
7
|
+
|
8
|
+
API
|
9
|
+
|
10
|
+
* http://api.pluginaweek.org/has_addresses
|
11
|
+
|
12
|
+
Wiki
|
13
|
+
|
14
|
+
* http://wiki.pluginaweek.org/Has_addresses
|
15
|
+
|
16
|
+
Announcement
|
17
|
+
|
18
|
+
* http://www.pluginaweek.org/
|
19
|
+
|
20
|
+
Source
|
21
|
+
|
22
|
+
* http://svn.pluginaweek.org/trunk/plugins/active_record/has/has_addresses
|
23
|
+
|
24
|
+
Development
|
25
|
+
|
26
|
+
* http://dev.pluginaweek.org/browser/trunk/plugins/active_record/has/has_addresses
|
27
|
+
|
28
|
+
== Description
|
29
|
+
|
30
|
+
Countries, regions, and addresses are all simple models whose data and
|
31
|
+
functionality should be able to be standardized across multiple applications.
|
32
|
+
has_addresses adds support for countries and regions based on the ISO 3166 and
|
33
|
+
ISO 3166-2 standard. The data for these standards is obtained through the
|
34
|
+
open-source Debian package, iso-codes.
|
35
|
+
|
36
|
+
Along with the simple Country and Region models, addresses can be defined and
|
37
|
+
integrated based on the data in these models. Addresses are minimalistic in
|
38
|
+
terms of the type of data required and follows the standard U.S. format.
|
39
|
+
|
40
|
+
=== Running migrations
|
41
|
+
|
42
|
+
To migrate the tables required for has_addresses, you can either run the
|
43
|
+
migration from the command line like so:
|
44
|
+
|
45
|
+
rake db:migrate:plugins PLUGIN=has_addresses
|
46
|
+
|
47
|
+
or (more ideally) generate a migration file that will integrate into your main
|
48
|
+
application's migration path:
|
49
|
+
|
50
|
+
ruby script/generate plugin_migration MigrateHasAddressesToVersion3
|
51
|
+
|
52
|
+
=== Bootstrapping the database
|
53
|
+
|
54
|
+
has_addresses comes bundled with tasks for bootstrapping the countries and
|
55
|
+
regions table based on the ISO 3166 and ISO 3166-2 standard. You can bootstrap
|
56
|
+
the database using built-in rake tasks or by creating fixtures which contain the
|
57
|
+
bootstrap data.
|
58
|
+
|
59
|
+
To bootstrap country/region data into the current database schema:
|
60
|
+
|
61
|
+
$ rake countries:bootstrap
|
62
|
+
(in /my/project)
|
63
|
+
Downloading ISO 3166 data...
|
64
|
+
Parsing countries...
|
65
|
+
Loading countries into database...
|
66
|
+
Done!
|
67
|
+
|
68
|
+
$ rake regions:bootstrap
|
69
|
+
Downloading ISO 3166 data...
|
70
|
+
Parsing countries...
|
71
|
+
Downloading ISO 3166-2 data...
|
72
|
+
Parsing regions...
|
73
|
+
Loading regions into database...
|
74
|
+
Done!
|
75
|
+
|
76
|
+
To create fixtures for the country/region bootstrap data:
|
77
|
+
|
78
|
+
$ rake countries:create_fixtures
|
79
|
+
(in /my/project)
|
80
|
+
Downloading ISO 3166 data...
|
81
|
+
Parsing countries...
|
82
|
+
Saving countries to /my/project/config/../db/bootstrap/countries.yml...
|
83
|
+
Done!
|
84
|
+
|
85
|
+
$ rake regions:create_fixtures
|
86
|
+
(in /my/project)
|
87
|
+
Downloading ISO 3166 data...
|
88
|
+
Parsing countries...
|
89
|
+
Downloading ISO 3166-2 data...
|
90
|
+
Parsing regions...
|
91
|
+
Saving regions to /my/project/config/../db/bootstrap/regions.yml...
|
92
|
+
Done!
|
93
|
+
|
94
|
+
== Testing
|
95
|
+
|
96
|
+
Since the rake tasks for installing TinyMCE and updating the configuration
|
97
|
+
options are part of the unit tests, already-downloaded files are included with
|
98
|
+
the plugin. If you want to perform a "live" test which actually downloads the
|
99
|
+
files off the Internet (rather than using the local versions), you must set
|
100
|
+
the LIVE environment variable to true. For example,
|
101
|
+
|
102
|
+
rake test LIVE=true
|
103
|
+
|
104
|
+
The following plugins/gems must be installed before any tests can be run:
|
105
|
+
* plugin_dependencies - http://wiki.pluginaweekk.org/Plugin_dependencies
|
106
|
+
* loaded_plugins - http://wiki.pluginaweek.org/Loaded_plugins
|
107
|
+
* appable_plugins - http://wiki.pluginaweek.org/Appable_plugins
|
108
|
+
|
109
|
+
== Dependencies
|
110
|
+
|
111
|
+
This plugin does not depend on the presence of any other plugin.
|
data/Rakefile
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/contrib/sshpublisher'
|
5
|
+
|
6
|
+
# Load custom rakefile extensions
|
7
|
+
Dir["#{File.dirname(__FILE__)}/tasks/**/*.rake"].sort.each {|ext| load ext}
|
8
|
+
|
9
|
+
PKG_NAME = 'has_addresses'
|
10
|
+
PKG_VERSION = '0.0.1'
|
11
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
12
|
+
RUBY_FORGE_PROJECT = 'pluginaweek'
|
13
|
+
|
14
|
+
desc 'Default: run unit tests.'
|
15
|
+
task :default => :test
|
16
|
+
|
17
|
+
desc 'Test the has_addresses plugin.'
|
18
|
+
Rake::TestTask.new(:test) do |t|
|
19
|
+
t.libs << 'lib'
|
20
|
+
t.pattern = 'test/unit/**/*_test.rb'
|
21
|
+
t.verbose = true
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Generate documentation for the has_addresses plugin.'
|
25
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
26
|
+
rdoc.rdoc_dir = 'rdoc'
|
27
|
+
rdoc.title = 'HasAddresses'
|
28
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
29
|
+
rdoc.rdoc_files.include('README')
|
30
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
31
|
+
end
|
32
|
+
|
33
|
+
spec = Gem::Specification.new do |s|
|
34
|
+
s.name = PKG_NAME
|
35
|
+
s.version = PKG_VERSION
|
36
|
+
s.platform = Gem::Platform::RUBY
|
37
|
+
s.summary = 'Adds a base skeleton for handling countries, regions, and addresses.'
|
38
|
+
|
39
|
+
s.files = FileList['{app,db,lib,tasks,test}/**/*'].to_a + %w(CHANGELOG init.rb MIT-LICENSE Rakefile README)
|
40
|
+
s.require_path = 'lib'
|
41
|
+
s.autorequire = 'has_addresses'
|
42
|
+
s.has_rdoc = true
|
43
|
+
s.test_files = Dir['test/unit/**/*_test.rb']
|
44
|
+
|
45
|
+
s.author = 'Aaron Pfeifer, Neil Abraham'
|
46
|
+
s.email = 'info@pluginaweek.org'
|
47
|
+
s.homepage = 'http://www.pluginaweek.org'
|
48
|
+
end
|
49
|
+
|
50
|
+
Rake::GemPackageTask.new(spec) do |p|
|
51
|
+
p.gem_spec = spec
|
52
|
+
p.need_tar = true
|
53
|
+
p.need_zip = true
|
54
|
+
end
|
55
|
+
|
56
|
+
desc 'Publish the beta gem'
|
57
|
+
task :pgem => [:package] do
|
58
|
+
Rake::SshFilePublisher.new('pluginaweek@pluginaweek.org', '/home/pluginaweek/gems.pluginaweek.org/gems', 'pkg', "#{PKG_FILE_NAME}.gem").upload
|
59
|
+
end
|
60
|
+
|
61
|
+
desc 'Publish the API documentation'
|
62
|
+
task :pdoc => [:rdoc] do
|
63
|
+
Rake::SshDirPublisher.new('pluginaweek@pluginaweek.org', "/home/pluginaweek/api.pluginaweek.org/#{PKG_NAME}", 'rdoc').upload
|
64
|
+
end
|
65
|
+
|
66
|
+
desc 'Publish the API docs and gem'
|
67
|
+
task :publish => [:pdoc, :release]
|
68
|
+
|
69
|
+
desc 'Publish the release files to RubyForge.'
|
70
|
+
task :release => [:gem, :package] do
|
71
|
+
require 'rubyforge'
|
72
|
+
|
73
|
+
ruby_forge = RubyForge.new
|
74
|
+
ruby_forge.login
|
75
|
+
|
76
|
+
%w( gem tgz zip ).each do |ext|
|
77
|
+
file = "pkg/#{PKG_FILE_NAME}.#{ext}"
|
78
|
+
puts "Releasing #{File.basename(file)}..."
|
79
|
+
|
80
|
+
ruby_forge.add_release(RUBY_FORGE_PROJECT, PKG_NAME, PKG_VERSION, file)
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# Represents a mailing address
|
2
|
+
class Address < ActiveRecord::Base
|
3
|
+
belongs_to :addressable,
|
4
|
+
:polymorphic => true
|
5
|
+
belongs_to :region
|
6
|
+
belongs_to :country
|
7
|
+
|
8
|
+
validates_presence_of :addressable_id,
|
9
|
+
:addressable_type,
|
10
|
+
:street_1,
|
11
|
+
:city,
|
12
|
+
:postal_code
|
13
|
+
validates_presence_of :region_id,
|
14
|
+
:if => :known_region_required?
|
15
|
+
validates_presence_of :custom_region,
|
16
|
+
:if => :custom_region_required?
|
17
|
+
validates_format_of :postal_code,
|
18
|
+
:with => /^[0-9]{5}$/,
|
19
|
+
:allow_nil => true
|
20
|
+
|
21
|
+
before_save :ensure_exclusive_references
|
22
|
+
|
23
|
+
# Returns the region's country if the region is specified
|
24
|
+
def country_with_region_check
|
25
|
+
region ? region.country : country_without_region_check
|
26
|
+
end
|
27
|
+
alias_method_chain :country, :region_check
|
28
|
+
|
29
|
+
# Gets the name of the region that this address is for (whether it is a custom or
|
30
|
+
# stored region in the database)
|
31
|
+
def region_name
|
32
|
+
custom_region || (region ? region.name : nil)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Gets the value of the address on a single line
|
36
|
+
def single_line
|
37
|
+
multi_line.join(', ')
|
38
|
+
end
|
39
|
+
|
40
|
+
# Gets the value of the address on multiple lines
|
41
|
+
def multi_line
|
42
|
+
lines = []
|
43
|
+
lines << street_1 if street_1?
|
44
|
+
lines << street_2 if street_2?
|
45
|
+
|
46
|
+
line = ''
|
47
|
+
line << city if city?
|
48
|
+
if region_name
|
49
|
+
line << ', ' if !line.blank?
|
50
|
+
line << region_name
|
51
|
+
end
|
52
|
+
if postal_code?
|
53
|
+
line << ' ' if !line.blank?
|
54
|
+
line << postal_code
|
55
|
+
end
|
56
|
+
lines << line if !line.blank?
|
57
|
+
|
58
|
+
lines << country.name if country
|
59
|
+
lines
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def known_region_required?
|
64
|
+
country.nil? || country.regions.count != 0
|
65
|
+
end
|
66
|
+
|
67
|
+
# A custom region name is required if a known region was not specified and
|
68
|
+
# the country in which this address resides has no known regions in the
|
69
|
+
# database
|
70
|
+
def custom_region_required?
|
71
|
+
region_id.nil? && country && country.regions.count == 0
|
72
|
+
end
|
73
|
+
|
74
|
+
# Ensures that the country id/user region combo is not set at the same time as
|
75
|
+
# the region id
|
76
|
+
def ensure_exclusive_references
|
77
|
+
if known_region_required?
|
78
|
+
self.country_id = nil
|
79
|
+
self.custom_region = nil
|
80
|
+
end
|
81
|
+
|
82
|
+
true
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'cgi'
|
3
|
+
require 'rexml/document'
|
4
|
+
|
5
|
+
# Defined by the ISO 3166 standard. The ISO 3166 standard includes a
|
6
|
+
# "Country Subdivision Code", giving a code for the names of the principal
|
7
|
+
# administrative subdivisions of the countries coded in ISO 3166.
|
8
|
+
class Country < ActiveRecord::Base
|
9
|
+
# The url of the open source version of the ISO 3166 standard
|
10
|
+
ISO_3166_URL = 'http://svn.debian.org/wsvn/pkg-isocodes/trunk/iso-codes/iso_3166/iso_3166.xml?op=file'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# Creates a fixtures file containing all possible countries
|
14
|
+
def create_fixtures(output_file_path = nil)
|
15
|
+
output_file_path ||= 'db/bootstrap/countries.yml'
|
16
|
+
output_file_path = File.join(RAILS_ROOT, output_file_path)
|
17
|
+
|
18
|
+
FileUtils.mkdir_p(File.dirname(output_file_path))
|
19
|
+
output_file = File.new(output_file_path, File::CREAT|File::TRUNC|File::RDWR)
|
20
|
+
output = ''
|
21
|
+
|
22
|
+
countries = download_all
|
23
|
+
puts "Saving countries to #{output_file_path}..." if PluginAWeek::Has::Addresses.verbose
|
24
|
+
countries.each do |country|
|
25
|
+
record_name = country.name.gsub(' ', '_').gsub(/[^A-Za-z_]/, '').downcase
|
26
|
+
|
27
|
+
output << "#{record_name}:\n"
|
28
|
+
country.attributes.each do |attr, value|
|
29
|
+
output << " #{attr}: #{value}\n" if value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
output_file << output.slice(0..output.length-2)
|
34
|
+
output_file.close
|
35
|
+
|
36
|
+
puts 'Done!' if PluginAWeek::Has::Addresses.verbose
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
# Bootstraps the table by downloading the latest ISO 3166 standard and
|
41
|
+
# saving each country to the database
|
42
|
+
def bootstrap
|
43
|
+
countries = download_all
|
44
|
+
|
45
|
+
puts 'Loading countries into database...' if PluginAWeek::Has::Addresses.verbose
|
46
|
+
delete_all
|
47
|
+
countries.each {|country| country.save!}
|
48
|
+
|
49
|
+
puts 'Done!' if PluginAWeek::Has::Addresses.verbose
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
# Downloads the latest ISO 3166 standard and returns the data as a list of
|
54
|
+
# countries
|
55
|
+
def download_all
|
56
|
+
# Download the standard
|
57
|
+
puts 'Downloading ISO 3166 data...' if PluginAWeek::Has::Addresses.verbose
|
58
|
+
iso_3166 = open(ISO_3166_URL).readlines * "\n"
|
59
|
+
iso_3166 = CGI::unescapeHTML(/<pre>(.+)<\/pre>/im.match(iso_3166)[1].gsub(' ', ''))
|
60
|
+
|
61
|
+
# Parse and load the countries
|
62
|
+
puts 'Parsing countries...' if PluginAWeek::Has::Addresses.verbose
|
63
|
+
countries = []
|
64
|
+
REXML::Document.new(iso_3166).elements.each('*/iso_3166_entry') do |country|
|
65
|
+
name = country.attributes['name'].to_s.gsub("'","\\'")
|
66
|
+
official_name = country.attributes['official_name']
|
67
|
+
official_name = official_name.to_s.gsub!("'", "\\'") if official_name
|
68
|
+
alpha_2_code = country.attributes['alpha_2_code'].to_s.upcase
|
69
|
+
alpha_3_code = country.attributes['alpha_3_code'].to_s.upcase
|
70
|
+
country_id = country.attributes['numeric_code'].to_s.to_i
|
71
|
+
record_name = name.gsub(' ', '_').gsub(/[^A-Za-z_]/, '').downcase
|
72
|
+
|
73
|
+
country = new(
|
74
|
+
:name => name,
|
75
|
+
:official_name => official_name,
|
76
|
+
:alpha_2_code => alpha_2_code,
|
77
|
+
:alpha_3_code => alpha_3_code
|
78
|
+
)
|
79
|
+
country.id = country_id
|
80
|
+
countries << country
|
81
|
+
end
|
82
|
+
|
83
|
+
countries
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
has_many :regions
|
88
|
+
|
89
|
+
validates_presence_of :name,
|
90
|
+
:alpha_2_code,
|
91
|
+
:alpha_3_code
|
92
|
+
validates_uniqueness_of :name,
|
93
|
+
:alpha_2_code,
|
94
|
+
:alpha_3_code
|
95
|
+
validates_length_of :name,
|
96
|
+
:within => 2..80
|
97
|
+
validates_length_of :alpha_2_code,
|
98
|
+
:is => 2
|
99
|
+
validates_length_of :alpha_3_code,
|
100
|
+
:is => 3
|
101
|
+
|
102
|
+
alias_attribute :abbreviation_2, :alpha_2_code
|
103
|
+
alias_attribute :abbreviation_3, :alpha_3_code
|
104
|
+
|
105
|
+
# The official name of the country
|
106
|
+
def official_name
|
107
|
+
read_attribute(:official_name) || name
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns the name of the country
|
111
|
+
def to_s #:nodoc
|
112
|
+
name
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'cgi'
|
3
|
+
require 'rexml/document'
|
4
|
+
|
5
|
+
# Defined by the ISO 3166-2 standard. This is a standard that gives short codes
|
6
|
+
# for provinces, etc. within a country.
|
7
|
+
class Region < ActiveRecord::Base
|
8
|
+
# The url of the open source version of the ISO 3166-2 standard
|
9
|
+
ISO_3166_2_URL = 'http://svn.debian.org/wsvn/pkg-isocodes/trunk/iso-codes/iso_3166/iso_3166_2/iso_3166_2.xml?op=file'
|
10
|
+
|
11
|
+
class << self
|
12
|
+
# Creates a fixtures containing all possible regions
|
13
|
+
def create_fixtures(output_file_path = nil)
|
14
|
+
output_file_path ||= 'db/bootstrap/regions.yml'
|
15
|
+
output_file_path = File.join(RAILS_ROOT, output_file_path)
|
16
|
+
|
17
|
+
FileUtils.mkdir_p(File.dirname(output_file_path))
|
18
|
+
output_file = File.new(output_file_path, File::CREAT|File::TRUNC|File::RDWR)
|
19
|
+
output = ''
|
20
|
+
|
21
|
+
regions = download_all
|
22
|
+
puts "Saving regions to #{output_file_path}..." if PluginAWeek::Has::Addresses.verbose
|
23
|
+
regions.each do |region|
|
24
|
+
record_name = region.name.gsub(' ', '_').gsub(/[^A-Za-z_]/, '').downcase
|
25
|
+
|
26
|
+
output << "#{record_name}:\n"
|
27
|
+
region.attributes.each do |attr, value|
|
28
|
+
output << " #{attr}: #{value}\n" if value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
output_file << output.slice(0..output.length-2)
|
33
|
+
output_file.close
|
34
|
+
|
35
|
+
puts 'Done!' if PluginAWeek::Has::Addresses.verbose
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
# Bootstraps the table by downloading the latest ISO 3166-2 standard and
|
40
|
+
# saving each region to the database
|
41
|
+
def bootstrap
|
42
|
+
regions = download_all
|
43
|
+
|
44
|
+
puts 'Loading regions into database...' if PluginAWeek::Has::Addresses.verbose
|
45
|
+
delete_all
|
46
|
+
regions.each {|region| region.save!}
|
47
|
+
|
48
|
+
puts 'Done!' if PluginAWeek::Has::Addresses.verbose
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
# Downloads the latest ISO 3166-2 standard and returns the data as a list of
|
53
|
+
# regions
|
54
|
+
def download_all
|
55
|
+
countries = Country.download_all
|
56
|
+
alpha_2_code_to_country = Hash[*countries.collect {|c| [c.alpha_2_code, c]}.flatten]
|
57
|
+
|
58
|
+
# Download the standard
|
59
|
+
puts 'Downloading ISO 3166-2 data...' if PluginAWeek::Has::Addresses.verbose
|
60
|
+
iso_3166_2 = open(ISO_3166_2_URL).readlines * "\n"
|
61
|
+
iso_3166_2 = CGI::unescapeHTML(/<pre>(.+)<\/pre>/im.match(iso_3166_2)[1].gsub(' ', ''))
|
62
|
+
|
63
|
+
# Parse and load the regions
|
64
|
+
puts 'Parsing regions...' if PluginAWeek::Has::Addresses.verbose
|
65
|
+
region_id = 1
|
66
|
+
regions = []
|
67
|
+
REXML::Document.new(iso_3166_2).elements.each('*/iso_3166_country') do |country|
|
68
|
+
code = country.attributes['code'][0..1] # Belarius is more than 2 characters for some reason, so just get the first 2
|
69
|
+
country_record = alpha_2_code_to_country[code.upcase]
|
70
|
+
|
71
|
+
country.elements.each('iso_3166_2_entry') do |region|
|
72
|
+
name = region.attributes['name'].to_s.gsub("'","\\'").strip
|
73
|
+
abbreviation = region.attributes['code'].upcase.sub("#{code}-", '')
|
74
|
+
|
75
|
+
region = new(
|
76
|
+
:country_id => country_record.id,
|
77
|
+
:name => name,
|
78
|
+
:abbreviation => abbreviation
|
79
|
+
)
|
80
|
+
region.id = region_id
|
81
|
+
|
82
|
+
if !country_record.regions.any? {|known_region| known_region.name == region.name}
|
83
|
+
regions << region
|
84
|
+
country_record.regions << region
|
85
|
+
region_id += 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
regions
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
belongs_to :country
|
95
|
+
has_many :addresses
|
96
|
+
|
97
|
+
validates_presence_of :name,
|
98
|
+
:country_id,
|
99
|
+
:abbreviation
|
100
|
+
validates_length_of :name,
|
101
|
+
:within => 2..80
|
102
|
+
validates_length_of :abbreviation,
|
103
|
+
:within => 1..5
|
104
|
+
validates_uniqueness_of :name,
|
105
|
+
:scope => :country_id
|
106
|
+
validates_uniqueness_of :abbreviation,
|
107
|
+
:scope => :country_id
|
108
|
+
|
109
|
+
def to_s #:nodoc
|
110
|
+
name
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateCountries < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :countries do |t|
|
4
|
+
t.column :name, :string, :null => false, :limit => 80
|
5
|
+
t.column :official_name, :string, :limit => 80
|
6
|
+
t.column :alpha_2_code, :string, :null => false, :limit => 2
|
7
|
+
t.column :alpha_3_code, :string, :null => false, :limit => 3
|
8
|
+
end
|
9
|
+
add_index :countries, :name, :unique => true
|
10
|
+
add_index :countries, :alpha_2_code, :unique => true
|
11
|
+
add_index :countries, :alpha_3_code, :unique => true
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.down
|
15
|
+
drop_table :countries
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class CreateRegions < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :regions do |t|
|
4
|
+
t.column :country_id, :integer, :null => false
|
5
|
+
t.column :name, :string, :null => false, :limit => 50
|
6
|
+
t.column :abbreviation, :string, :null => false, :limit => 5
|
7
|
+
end
|
8
|
+
add_index :regions, [:name, :country_id], :unique => true
|
9
|
+
add_index :regions, [:abbreviation, :country_id], :unique => true
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.down
|
13
|
+
drop_table :regions
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class CreateAddresses < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :addresses do |t|
|
4
|
+
t.column :addressable_id, :integer, :null => false, :references => nil
|
5
|
+
t.column :addressable_type, :string, :null => false
|
6
|
+
t.column :street_1, :string, :null => false, :limit => 100
|
7
|
+
t.column :street_2, :string, :limit => 100
|
8
|
+
t.column :city, :string, :null => false, :limit => 255
|
9
|
+
t.column :region_id, :integer
|
10
|
+
t.column :custom_region, :string, :limit => 50
|
11
|
+
t.column :postal_code, :string, :null => false, :limit => 5
|
12
|
+
t.column :country_id, :integer, :default => 223
|
13
|
+
t.column :created_at, :timestamp, :null => false
|
14
|
+
t.column :updated_at, :datetime, :null => false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.down
|
19
|
+
drop_table :addresses
|
20
|
+
end
|
21
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'has_addresses'
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module PluginAWeek #:nodoc:
|
2
|
+
module Has #:nodoc:
|
3
|
+
# Adds base models for interacting with addresses, including Country,
|
4
|
+
# Region, and Address. These have the minimal attribute definitions needed
|
5
|
+
# to store addresses.
|
6
|
+
module Addresses
|
7
|
+
# Whether or not to use verbose output
|
8
|
+
mattr_accessor :verbose
|
9
|
+
@@verbose = true
|
10
|
+
|
11
|
+
def self.included(base) #:nodoc:
|
12
|
+
base.extend(MacroMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
module MacroMethods
|
16
|
+
# Creates a new association for having a single address. This takes
|
17
|
+
# the same parameters as ActiveRecord::Associations::ClassMethods#has_one.
|
18
|
+
# By default, the following associations are the same:
|
19
|
+
#
|
20
|
+
# class Person < ActiveRecord::Base
|
21
|
+
# has_address
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# and
|
25
|
+
#
|
26
|
+
# class Person < ActiveRecord::Base
|
27
|
+
# has_one :address,
|
28
|
+
# :class_name => 'Address',
|
29
|
+
# :as => :addressable,
|
30
|
+
# :dependent => :destroy
|
31
|
+
# end
|
32
|
+
def has_address(*args, &extension)
|
33
|
+
create_address_association(:one, :address, *args, &extension)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates a new association for having a multiple addresses. This takes
|
37
|
+
# the same parameters as ActiveRecord::Associations::ClassMethods#has_many.
|
38
|
+
# By default, the following associations are the same:
|
39
|
+
#
|
40
|
+
# class Person < ActiveRecord::Base
|
41
|
+
# has_addresses
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# and
|
45
|
+
#
|
46
|
+
# class Person < ActiveRecord::Base
|
47
|
+
# has_many :addresses,
|
48
|
+
# :class_name => 'Address',
|
49
|
+
# :as => :addressable,
|
50
|
+
# :dependent => :destroy
|
51
|
+
# end
|
52
|
+
def has_addresses(*args, &extension)
|
53
|
+
create_address_association(:many, :addresses, *args, &extension)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def create_address_association(cardinality, association_id, *args, &extension)
|
58
|
+
options = extract_options_from_args!(args)
|
59
|
+
options.symbolize_keys!.reverse_merge!(
|
60
|
+
:class_name => 'Address',
|
61
|
+
:as => :addressable,
|
62
|
+
:dependent => :destroy
|
63
|
+
)
|
64
|
+
|
65
|
+
send("has_#{cardinality}", args.first || association_id, options, &extension)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
ActiveRecord::Base.class_eval do
|
73
|
+
include PluginAWeek::Has::Addresses
|
74
|
+
end
|