effective_addresses 0.1 → 1.0.0

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
+ SHA1:
3
+ metadata.gz: c3c2f49eca2125797231243b395e2e1b9c6d7ea2
4
+ data.tar.gz: d660100feb8bb23c00736de9c854ab0742566b36
5
+ SHA512:
6
+ metadata.gz: 6743fe38958a99b09216fd1c6578dce108198eaa88b621015b6f37e54b14f3a08a87a423dc12299578f9fdeaf25128097940845eb4dc4b02c230d2aee07b680d
7
+ data.tar.gz: 0d31cf09f163e7a84b3b0e7425a7bc83c2182226c714af93d9242013c18f4afbacf820e8036737660b3c705e2d38483a0cd70cd812c12903ebc37bd0e7ab6de6
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2013 Code and Effect Inc.
1
+ Copyright 2014 Code and Effect Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,14 +1,16 @@
1
1
  # Effective Addresses
2
2
 
3
- Provides helper methods for dealing with a has_many :addresses relationship as a single method.
4
- Such as @user.billing_address and @user.billing_address=
3
+ Extend any ActiveRecord object to have one or more named addresses. Includes a geographic region-aware custom form input backed by Carmen.
5
4
 
6
- Includes full validations for addresses with multiple categories.
5
+ Creates methods such as `user.billing_address` and `user.billing_address=` for dealing with a has_many :addresses relationship as a single method.
7
6
 
8
- Includes a formtastic helper method to create/update the address of a parent object.
9
- Uses the Carmen gem so when a Country is selected, an AJAX request populates the State/Province fields as appropriate.
7
+ Adds region-aware validations for provinces/states and postal codes.
10
8
 
11
- Rails >= 3.2.x, Ruby >= 1.9.x. Has not been tested/developed for Rails4.
9
+ Includes a Formtastic and SimpleForm one-liner method to drop in address fields to any form.
10
+
11
+ Uses the Carmen gem internally so when a Country is selected, an AJAX request populates the province/state fields as appropriate.
12
+
13
+ Rails 3.2.x and Rails 4 Support
12
14
 
13
15
  ## Getting Started
14
16
 
@@ -32,7 +34,7 @@ rails generate effective_addresses:install
32
34
 
33
35
  The generator will install an initializer which describes all configuration options and creates a database migration.
34
36
 
35
- If you want to tweak the table name (to use something other than the default 'addresses'), manually adjust both the configuration file and the migration now.
37
+ If you want to tweak the table name (to use something other than the default `addresses`), manually adjust both the configuration file and the migration now.
36
38
 
37
39
  Then migrate the database:
38
40
 
@@ -40,7 +42,7 @@ Then migrate the database:
40
42
  rake db:migrate
41
43
  ```
42
44
 
43
- If you'd like to use the form helper method, require the javascript in your application.js
45
+ And require the javascript in your application.js:
44
46
 
45
47
  ```ruby
46
48
  //= require effective_addresses
@@ -51,101 +53,155 @@ If you'd like to use the form helper method, require the javascript in your appl
51
53
 
52
54
  ### Model
53
55
 
54
- To use without any validations, just add the mixin to your existing model:
56
+ Add the mixin to your existing model and specify the name or names of the addresses it should respond to:
55
57
 
56
58
  ```ruby
57
59
  class User
58
- acts_as_addressable
60
+ acts_as_addressable :billing
59
61
  end
60
62
  ```
61
63
 
62
- This adds the following getters, along with the setters:
64
+ This adds the following getters and setters:
63
65
 
64
66
  ```ruby
65
- @user.address
66
67
  @user.billing_address
67
- @user.shipping_address
68
- @user.primary_address
69
- @user.secondary_address
68
+ @user.billing_address=
69
+ @user.billing_addresses
70
70
  ```
71
71
 
72
- You can also define validations as follows:
72
+ Calling `user.billing_address` will return a single `Effective::Address` object. Calling `user.billing_addresses` will return an array of `Effective:Address` objects.
73
+
74
+ You can also specify a `:presence` validation as follows:
73
75
 
74
76
  ```ruby
75
77
  class User
76
- acts_as_addressable :require_billing => true, :require_shipping => true
78
+ acts_as_addressable :billing => true, :shipping => false
77
79
  end
78
80
  ```
79
81
 
80
- This means when a User is created, it will not be valid unless a billing_address and shipping_address exist and are valid.
81
-
82
- ### Multiple Addresses
83
-
84
- Everytime an address is changed, an additional address record is created. The latest address will always be returned by:
82
+ or
85
83
 
86
84
  ```ruby
87
- @user.billing_address
85
+ class User
86
+ acts_as_addressable :billing => {:presence => true, :use_full_name => false}
87
+ end
88
88
  ```
89
89
 
90
- You can find all past addresses (including the current one) by:
90
+ or
91
91
 
92
92
  ```ruby
93
- @user.billing_addresses
93
+ class User
94
+ acts_as_addressable :billing
95
+
96
+ validates_presence_of :billing_address
97
+ end
94
98
  ```
95
99
 
96
- ### Form Helper
100
+ This means when a User is created, it will not be valid unless a billing_address exist and is valid.
97
101
 
98
- Use the helper in a formtastic form to quickly create the address fields 'f.inputs'. This example is in HAML:
99
102
 
100
- ```ruby
101
- = semantic_form_for @user do |f|
102
- = f.inputs :name => "Your Information" do
103
- = f.input :email
104
- = f.input :name
103
+ ### Full Name
105
104
 
106
- = effective_address_fields(f, :category => 'billing') # 'shipping', 'primary', 'secondary'
105
+ Sometimes you want to collect a `full_name` field with your addresses, such as in the case of a mailing address; other times, it's an unnecessary field.
107
106
 
108
- = f.action :submit
109
- ```
107
+ When you specify the config option `config.use_full_name = true` all `acts_as_addressable` defined addresses will use `use_full_name => true` by default.
110
108
 
111
- Currently only supports Formtastic.
109
+ This can be overridden on a per-address basis when declared in the model.
112
110
 
113
- Assuming the javascript has been properly required (as above), when you select a country from the dropdown
114
- an AJAX GET request will be made to '/effective/address/subregions/:country_code' and populate the state dropdown with the appropriate states or provinces
111
+ When `use_full_name == true`, any calls to `effective_address_fields` form helper will display the full_name input, and the model will `validate_presence_of :full_name`.
115
112
 
116
113
 
117
- ## License
114
+ ### Address Validations
118
115
 
119
- MIT License. Copyright Code and Effect Inc. http://www.codeandeffect.com
116
+ Address1, City, Country and Postal/Zip Code are all required fields.
120
117
 
121
- You are not granted rights or licenses to the trademarks of Code and Effect
118
+ If the selected Country has provinces/states (not all countries do), then the province/state field will be required.
122
119
 
123
- ## Notes
120
+ If the country is Canada or United States, then the postal/zip code formatting will be enforced.
124
121
 
125
- This is a work in progress gem. It needs smarter validations, dynamic methods and google maps integration
122
+ This validation on the postal code/zip format can be disabled via the config/initializers `validate_postal_code_format` setting
126
123
 
127
- ### Testing
124
+ Canadian postal codes will be automatically upcased and formatted to a consistent format: `T5Z 3A4` (capitalized with a single space).
128
125
 
129
- The test suite for this gem is unfortunately not yet complete.
130
126
 
131
- Run tests by:
127
+ ### Multiple Addresses
128
+
129
+ Everytime an address is changed, an additional address record is created. The latest address will always be returned by:
132
130
 
133
131
  ```ruby
134
- rake spec
132
+ @user.billing_address
135
133
  ```
136
134
 
135
+ You can find all past addresses (including the current one) by:
137
136
 
137
+ ```ruby
138
+ @user.billing_addresses
139
+ ```
140
+
141
+ ### Strong Parameters
142
+
143
+ Make your controller aware of the `acts_as_addressable` passed parameters:
144
+
145
+ ```ruby
146
+ def permitted_params
147
+ params.require(:base_object).permit(
148
+ :billing_address => EffectiveAddresses.permitted_params,
149
+ :shipping_address => EffectiveAddresses.permitted_params
150
+ )
151
+ end
152
+ ```
153
+
154
+ The actual permitted parameters are:
155
+
156
+ ```ruby
157
+ [:full_name, :address1, :address2, :city, :country_code, :state_code, :postal_code]
158
+ ```
159
+
160
+ ### Form Helpers
138
161
 
162
+ Use the helper in a Formtastic or SimpleForm form to quickly create the address fields. This example is in HAML:
139
163
 
164
+ ```ruby
165
+ = semantic_form_for @user do |f|
166
+ %h3 Billing Address
167
+ = effective_address_fields(f, :billing_address)
168
+
169
+ = f.action :submit
170
+
171
+ = simple_form_for @user do |f|
172
+ %h3 Billing Address
173
+ = effective_address_fields(f, :billing_address)
174
+
175
+ = f.submit 'Save'
176
+ ```
140
177
 
178
+ Currently only supports Formtastic and SimpleForm.
141
179
 
180
+ When you select a country from the select input an AJAX GET request will be made to `effective_addresses.address_subregions_path` (`/addresses/subregions/:country_code`) which populates the province/state dropdown with the selected country's states or provinces.
142
181
 
143
182
 
183
+ ## License
184
+
185
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
144
186
 
187
+ Code and Effect is the product arm of [AgileStyle](http://www.agilestyle.com/), an Edmonton-based shop that specializes in building custom web applications with Ruby on Rails.
145
188
 
146
189
 
190
+ ## Testing
147
191
 
192
+ The test suite for this gem is unfortunately not yet complete.
148
193
 
194
+ Run tests by:
149
195
 
196
+ ```ruby
197
+ rake spec
198
+ ```
150
199
 
200
+ ## Contributing
151
201
 
202
+ 1. Fork it
203
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
204
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
205
+ 4. Push to the branch (`git push origin my-new-feature`)
206
+ 5. Bonus points for test coverage
207
+ 6. Create new Pull Request
@@ -1,13 +1,14 @@
1
- $(document).on 'change', "select[data-behavior='address-country']", (event) ->
1
+ $(document).on 'change', "select[data-effective-address-country]", (event) ->
2
2
  country_code = $(this).val()
3
+ uuid = $(this).data('effective-address-country')
3
4
 
4
- url = "/effective/address/subregions/#{country_code}"
5
- state_select = $(this).parent().parent().find("li > select[data-behavior='address-state']").first()
5
+ url = "/addresses/subregions/#{country_code}"
6
+ state_select = $(this).closest('form').find("select[data-effective-address-state='#{uuid}']").first()
6
7
 
7
8
  if country_code.length == 0
8
- state_select.attr('disabled', 'disabled')
9
- state_select.html('<option value="">please select a country</option>')
9
+ state_select.prop('disabled', true).parent('.form-group').addClass('disabled')
10
+ state_select.html('<option value="">Please choose a country first</option>')
10
11
  else
11
- state_select.removeAttr('disabled')
12
+ state_select.prop('disabled', false).parent('.form-group').removeClass('disabled')
12
13
  state_select.find('option').first().text('loading...')
13
14
  state_select.load(url)
@@ -4,12 +4,12 @@ module Effective
4
4
  respond_to :json
5
5
 
6
6
  def subregions
7
- country_code = params[:country_code]
7
+ @subregions = Carmen::Country.coded(params[:country_code]).try(:subregions)
8
8
 
9
- if country_code.present?
10
- render :partial => 'effective/addresses/subregions', :locals => {:country_code => country_code}
9
+ if @subregions.present?
10
+ render :partial => 'effective/addresses/subregions'
11
11
  else
12
- render :text => '', :status => :unprocessable_entity
12
+ render :text => "<option value=''>None Available</option>"
13
13
  end
14
14
  end
15
15
  end
@@ -1,27 +1,45 @@
1
1
  require 'carmen-rails'
2
2
 
3
3
  module EffectiveAddressesHelper
4
- def effective_address_fields(form, options = {})
5
- opts = {:f => form, :category => 'address', :skip_full_name => false}.merge(options)
6
-
7
- case opts[:category]
8
- when 'shipping'
9
- opts[:name] = 'Shipping Address'
10
- opts[:method] = :shipping_address
11
- when 'billing'
12
- opts[:name] = 'Billing Address'
13
- opts[:method] = :billing_address
14
- when 'primary'
15
- opts[:name] = 'Primary Address'
16
- opts[:method] = :primary_address
17
- when 'secondary'
18
- opts[:name] = 'Secondary Address'
19
- opts[:method] = :secondary_address
4
+ def effective_address_fields(form, method = 'billing', options = {})
5
+ method = (method.to_s.include?('_address') ? method.to_s : "#{method}_address")
6
+
7
+ required = (form.object._validators[method.to_sym].any? { |v| v.kind_of?(ActiveRecord::Validations::PresenceValidator) && (v.options[:if].blank? || (v.options[:if].respond_to?(:call) ? f.object.instance_exec(&v.options[:if]) : v.options[:if])) } rescue true)
8
+
9
+ opts = {:f => form, :method => method, :required => required}.merge(options)
10
+
11
+ if form.class.name == 'SimpleForm::FormBuilder'
12
+ render :partial => 'effective/addresses/address_fields_simple_form', :locals => opts
13
+ elsif form.class.name == 'Formtastic::FormBuilder'
14
+ render :partial => 'effective/addresses/address_fields_formtastic', :locals => opts
15
+ else
16
+ raise 'Unsupported FormBuilder. You must use formtastic or simpleform. Sorry.'
17
+ end
18
+ end
19
+
20
+ def region_options_for_simple_form_select(regions = nil)
21
+ if regions.present?
22
+ countries = regions
23
+ elsif EffectiveAddresses.country_codes == :all
24
+ countries = Carmen::Country.all
20
25
  else
21
- opts[:name] = 'Address'
22
- opts[:method] = :address
26
+ countries = Carmen::Country.all.select { |c| (EffectiveAddresses.country_codes || []).include?(c.code) }
23
27
  end
24
28
 
25
- render :partial => 'effective/addresses/address_fields', :locals => opts
29
+ collection = countries.map { |c| [c.name, c.code] }.sort! { |a, b| a.first <=> b.first }
30
+
31
+ if regions.blank? && EffectiveAddresses.country_codes_priority.present?
32
+ collection.insert(0, ['---------------------', '', :disabled])
33
+
34
+ EffectiveAddresses.country_codes_priority.reverse.each do |code|
35
+ if (country = countries.coded(code))
36
+ collection.insert(0, [country.name, country.code])
37
+ end
38
+ end
39
+ end
40
+
41
+ collection
26
42
  end
43
+
27
44
  end
45
+
@@ -1,107 +1,85 @@
1
1
  # ActsAsAddressable
2
- #
3
- # This module creates .billing_address, .shipping_address and .address methods
4
- # it tricks a has_many relationship into singleton methods
5
- #
6
- # Mark your model with 'acts_as_addressable'
7
- #
8
- # there are no additional migrations needed for this module to work.
9
- #
10
- # By default, addresses are NOT required
11
- #
12
- # If you want to validate presence, please mark your model
13
- # acts_as_addressable :require_billing => true, :require_address => true, :require_shipping => true
14
- #
15
- # Please see app/views/admin/users/_form.html.haml for an example of how to use this in a formtastic form
16
- #
17
2
 
18
3
  module ActsAsAddressable
19
4
  extend ActiveSupport::Concern
20
5
 
21
6
  module ActiveRecord
22
- def acts_as_addressable(options = {})
23
- @acts_as_addressable_opts = {:require_shipping => false, :require_billing => false, :require_primary => false, :require_secondary => false, :require_address => false}.merge(options)
7
+ def acts_as_addressable(*options)
8
+ @acts_as_addressable_opts = options || []
24
9
  include ::ActsAsAddressable
25
10
  end
26
11
  end
27
12
 
28
13
  included do
29
- has_many :addresses, :as => :addressable, :class_name => "Effective::Address", :dependent => :delete_all
30
-
31
- validates_with BillingAddressValidator if @acts_as_addressable_opts[:require_billing]
14
+ has_many :addresses, :as => :addressable, :class_name => 'Effective::Address', :dependent => :delete_all
15
+
16
+ # Setup validations and methods
17
+ categories = @acts_as_addressable_opts.try(:flatten) || []
18
+
19
+ if categories.first.kind_of?(Hash) # We were passed some validation requirements
20
+ categories = categories.first
21
+
22
+ categories.each do |category, validation| # billing, shipping
23
+ category = category.to_s.gsub('_address', '')
24
+
25
+ self.send(:define_method, "#{category}_address") { effective_address(category) }
26
+ self.send(:define_method, "#{category}_addresses") { effective_addresses(category) }
27
+ self.send(:define_method, "#{category}_address=") { |atts| set_effective_address(category, atts) }
28
+
29
+ validates "#{category}_address", :effective_address_valid => true
30
+
31
+ if validation.kind_of?(Hash)
32
+ validates "#{category}_address", :presence => validation.fetch(:presence, false)
33
+ validates "#{category}_address", :effective_address_full_name_presence => validation.fetch(:use_full_name, EffectiveAddresses.use_full_name)
34
+ elsif validation == true
35
+ validates "#{category}_address", :presence => true
36
+ validates "#{category}_address", :effective_address_full_name_presence => EffectiveAddresses.use_full_name
37
+ end
38
+ end
39
+ else # No validation requirements passed
40
+ categories.each do |category|
41
+ category = category.to_s.gsub('_address', '')
42
+
43
+ self.send(:define_method, "#{category}_address") { effective_address(category) }
44
+ self.send(:define_method, "#{category}_addresses") { effective_addresses(category) }
45
+ self.send(:define_method, "#{category}_address=") { |atts| set_effective_address(category, atts) }
46
+
47
+ validates "#{category}_address", :effective_address_valid => true
48
+ validates "#{category}_address", :effective_address_full_name_presence => EffectiveAddresses.use_full_name
49
+ end
50
+ end
32
51
  end
33
52
 
34
53
  module ClassMethods
35
54
  end
36
55
 
37
- def single_addresses
38
- addresses.select { |address| address.category == 'address' }
39
- end
40
-
41
- def address
42
- single_addresses.last
43
- end
44
-
45
- def address=(address_atts)
46
- add = (address_atts.kind_of?(Effective::Address) ? address_atts.dup : Effective::Address.new(address_atts))
47
- add.category = 'address'
48
- addresses << add if !add.empty? and !(add == address)
49
- end
50
-
51
- def shipping_addresses
52
- addresses.select { |address| address.category == 'shipping' }
53
- end
54
-
55
- def shipping_address
56
- shipping_addresses.last
57
- end
58
-
59
- def shipping_address=(address_atts)
60
- add = (address_atts.kind_of?(Effective::Address) ? address_atts.dup : Effective::Address.new(address_atts))
61
- add.category = 'shipping'
62
- addresses << add if !add.empty? and !(add == shipping_address)
63
- end
64
-
65
- def billing_addresses
66
- addresses.select { |address| address.category == 'billing' }
56
+ def effective_addresses(category)
57
+ category = category.to_s
58
+ addresses.select { |address| address.category == category }
67
59
  end
68
60
 
69
- def billing_address
70
- billing_addresses.last
61
+ def effective_address(category)
62
+ effective_addresses(category).last
71
63
  end
72
64
 
73
- def billing_address=(address_atts)
74
- add = (address_atts.kind_of?(Effective::Address) ? address_atts.dup : Effective::Address.new(address_atts))
75
- add.category = 'billing'
76
- addresses << add if !add.empty? and !(add == billing_address)
77
- end
65
+ def set_effective_address(category, atts)
66
+ raise ArgumentError.new("Effective::Address #{category}_address= expecting an Effective::Address or Hash of attributes") unless (atts.kind_of?(Effective::Address) || atts.kind_of?(Hash) || atts == nil)
78
67
 
79
- def primary_addresses
80
- addresses.select { |address| address.category == 'primary' }
81
- end
68
+ atts = HashWithIndifferentAccess.new(atts.kind_of?(Effective::Address) ? atts.attributes : atts)
82
69
 
83
- def primary_address
84
- primary_addresses.last
85
- end
70
+ add = Effective::Address.new(
71
+ :category => category,
72
+ :full_name => atts[:full_name],
73
+ :address1 => atts[:address1],
74
+ :address2 => atts[:address2],
75
+ :city => atts[:city],
76
+ :state_code => atts[:state_code],
77
+ :country_code => atts[:country_code],
78
+ :postal_code => atts[:postal_code]
79
+ )
86
80
 
87
- def primary_address=(address_atts)
88
- add = (address_atts.kind_of?(Effective::Address) ? address_atts.dup : Effective::Address.new(address_atts))
89
- add.category = 'primary'
90
- addresses << add if !add.empty? and !(add == primary_address)
81
+ self.addresses << add unless (add.empty? || add == effective_address(category))
91
82
  end
92
83
 
93
- def secondary_addresses
94
- addresses.select { |address| address.category == 'secondary' }
95
- end
96
-
97
- def secondary_address
98
- secondary_addresses.last
99
- end
100
-
101
- def secondary_address=(address_atts)
102
- add = (address_atts.kind_of?(Effective::Address) ? address_atts.dup : Effective::Address.new(address_atts))
103
- add.category = 'secondary'
104
- addresses << add if !add.empty? and !(add == secondary_address)
105
- end
106
84
  end
107
85
 
@@ -4,25 +4,45 @@ module Effective
4
4
  class Address < ActiveRecord::Base
5
5
  self.table_name = EffectiveAddresses.addresses_table_name.to_s
6
6
 
7
+ POSTAL_CODE_CA = /\A[A-Z]{1}\d{1}[A-Z]{1}\ \d{1}[A-Z]{1}\d{1}\z/ # Matches 'T5Z 2B1'
8
+ POSTAL_CODE_US = /\A\d{5}\z/ # Matches 5 digits
9
+
7
10
  belongs_to :addressable, :polymorphic => true, :touch => true
8
11
 
9
12
  structure do
10
- category :string, :validates => [:presence, :inclusion => { :in => %w(billing shipping primary secondary address)}]
13
+ category :string, :validates => [:presence]
14
+
15
+ full_name :string
11
16
 
12
- full_name :string, :validates => [:presence]
13
17
  address1 :string, :validates => [:presence]
14
18
  address2 :string
19
+
15
20
  city :string, :validates => [:presence]
16
- state_code :string, :validates => [:presence]
21
+
22
+ state_code :string
17
23
  country_code :string, :validates => [:presence]
24
+
18
25
  postal_code :string, :validates => [:presence]
26
+
19
27
  timestamps
20
28
  end
21
29
 
22
- default_scope order(:updated_at)
30
+ validates_presence_of :state_code, :if => Proc.new { |address| address.country_code.blank? || Carmen::Country.coded(address.country_code).try(:subregions).present? }
31
+
32
+ validates_format_of :postal_code, :with => POSTAL_CODE_CA,
33
+ :if => proc { |address| Array(EffectiveAddresses.validate_postal_code_format).include?('CA') && address.country_code == 'CA' },
34
+ :allow_blank => true, # We're already checking for presence above
35
+ :message => 'is an invalid Canadian postal code'
36
+
37
+ validates_format_of :postal_code, :with => POSTAL_CODE_US,
38
+ :if => proc { |address| Array(EffectiveAddresses.validate_postal_code_format).include?('US') && address.country_code == 'US' },
39
+ :allow_blank => true, # We're already checking for presence above
40
+ :message => 'is an invalid United States zip code'
23
41
 
24
- scope :billing_addresses, -> { where(:category => 'billing') }
25
- scope :shipping_addresses, -> { where(:category => 'shipping') }
42
+ default_scope -> { order(:updated_at) }
43
+
44
+ scope :billing, -> { where(:category => 'billing') }
45
+ scope :shipping, -> { where(:category => 'shipping') }
26
46
 
27
47
  def first_name
28
48
  full_name.split(' ').first rescue full_name
@@ -39,10 +59,7 @@ module Effective
39
59
  def state
40
60
  Carmen::Country.coded(country_code).subregions.coded(state_code).name rescue ''
41
61
  end
42
-
43
- def province
44
- Carmen::Country.coded(country_code).subregions.coded(state_code).name rescue ''
45
- end
62
+ alias_method :province, :state
46
63
 
47
64
  def country=(country_string)
48
65
  value = Carmen::Country.named(country_string) || Carmen::Country.coded(country_string.try(:upcase))
@@ -51,23 +68,49 @@ module Effective
51
68
 
52
69
  def state=(state_string)
53
70
  if country.present?
54
- value = Carmen::Country.coded(country_code).subregions.named(state_string) || Carmen::Country.coded(country_code).subregions.coded(state_string.try(:upcase))
71
+ subregions = (Carmen::Country.coded(country_code).subregions rescue nil)
72
+
73
+ if subregions.present?
74
+ value = subregions.named(state_string) || subregions.coded(state_string.try(:upcase))
75
+ else
76
+ value = nil
77
+ end
78
+
55
79
  self.state_code = value.code if value.present?
56
80
  else
57
81
  Rails.logger.info 'No country set. Try calling country= before state='
58
82
  puts 'No country set. Try calling country= before state='
59
83
  end
60
84
  end
85
+ alias_method :province=, :state=
86
+
87
+ # If the country is Canada or US, enforce the correct postal code/zip code format
88
+ def postal_code=(code)
89
+ if code.presence.kind_of?(String)
90
+ if country_code == 'CA'
91
+ code = code.upcase.gsub(/[^A-Z0-9]/, '')
92
+ code = code.insert(3, ' ') if code.length == 6
93
+ elsif country_code == 'US'
94
+ code = code.gsub(/[^0-9]/, '')
95
+ end
96
+ end
97
+
98
+ super(code)
99
+ end
61
100
 
62
101
  def postal_code_looks_canadian?
63
- postal_code.gsub(/ /, '').strip.match(/^\D{1}\d{1}\D{1}\-?\d{1}\D{1}\d{1}$/).present? rescue false # Matches T5T2T1 or T5T-2T1
102
+ postal_code.to_s.match(POSTAL_CODE_CA).present?
103
+ end
104
+
105
+ def postal_code_looks_american?
106
+ postal_code.to_s.match(POSTAL_CODE_US).present?
64
107
  end
65
108
 
66
109
  def ==(other_address)
67
110
  self_attrs = self.attributes
68
111
  other_attrs = other_address.respond_to?(:attributes) ? other_address.attributes : {}
69
112
 
70
- [self_attrs, other_attrs].each { |attrs| attrs.except!('id', 'created_at', 'updated_at', 'addressable_type', 'addressable_id', 'address2') }
113
+ [self_attrs, other_attrs].each { |attrs| attrs.except!('id', 'created_at', 'updated_at', 'addressable_type', 'addressable_id') }
71
114
 
72
115
  self_attrs == other_attrs
73
116
  end
@@ -75,15 +118,18 @@ module Effective
75
118
  # An address may be set with a category but nothing else
76
119
  # This is considered empty
77
120
  def empty?
78
- !full_name.present? and !address1.present? and !address2.present? and !city.present? and !state_code.present? and !country_code.present? and !postal_code.present?
121
+ !address1.present? && !address2.present? && !city.present? && !state_code.present? && !country_code.present? && !postal_code.present?
79
122
  end
80
123
 
81
124
  def to_s
82
- output = "#{full_name}\n"
83
- output += "#{address1}\n"
125
+ output = ''
126
+ output += "#{full_name}\n" if full_name.present?
127
+ output += "#{address1}\n" if address1.present?
84
128
  output += "#{address2}\n" if address2.present?
85
- output += "#{city}, #{state}\n"
86
- output += "#{country}, #{postal_code}"
129
+ output += [city.presence, state.presence].compact.join(', ')
130
+ output += '\n' if city.present? || state.present?
131
+ output += [country.presence, postal_code.presence].compact.join(', ')
132
+ output
87
133
  end
88
134
 
89
135
  def to_html