has_addresses 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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('&nbsp;', ''))
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('&nbsp;', ''))
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