effective_addresses 0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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