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 +7 -0
- data/MIT-LICENSE +1 -1
- data/README.md +103 -47
- data/app/assets/javascripts/effective_addresses/address_fields.js.coffee +7 -6
- data/app/controllers/effective/addresses_controller.rb +4 -4
- data/app/helpers/effective_addresses_helper.rb +37 -19
- data/app/models/concerns/acts_as_addressable.rb +58 -80
- data/app/models/effective/address.rb +64 -18
- data/app/models/validators/effective_address_full_name_presence_validator.rb +8 -0
- data/app/models/validators/effective_address_valid_validator.rb +7 -0
- data/app/views/effective/addresses/_address.html.haml +11 -0
- data/app/views/effective/addresses/_address_fields_formtastic.html.haml +32 -0
- data/app/views/effective/addresses/_address_fields_simple_form.html.haml +22 -0
- data/app/views/effective/addresses/_subregions.html.haml +1 -1
- data/config/routes.rb +7 -2
- data/lib/effective_addresses/engine.rb +1 -0
- data/lib/effective_addresses/version.rb +1 -1
- data/lib/effective_addresses.rb +8 -1
- data/lib/generators/templates/effective_addresses.rb +27 -5
- data/spec/controllers/addresses_controller_spec.rb +21 -1
- data/spec/dummy/log/test.log +0 -125
- data/spec/spec_helper.rb +1 -0
- metadata +41 -156
- data/app/models/validators/billing_address_validator.rb +0 -7
- data/app/views/effective/addresses/_address_fields.html.haml +0 -29
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +0 -103
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
data/README.md
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
# Effective Addresses
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
64
|
+
This adds the following getters and setters:
|
63
65
|
|
64
66
|
```ruby
|
65
|
-
@user.address
|
66
67
|
@user.billing_address
|
67
|
-
@user.
|
68
|
-
@user.
|
69
|
-
@user.secondary_address
|
68
|
+
@user.billing_address=
|
69
|
+
@user.billing_addresses
|
70
70
|
```
|
71
71
|
|
72
|
-
|
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 :
|
78
|
+
acts_as_addressable :billing => true, :shipping => false
|
77
79
|
end
|
78
80
|
```
|
79
81
|
|
80
|
-
|
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
|
-
|
85
|
+
class User
|
86
|
+
acts_as_addressable :billing => {:presence => true, :use_full_name => false}
|
87
|
+
end
|
88
88
|
```
|
89
89
|
|
90
|
-
|
90
|
+
or
|
91
91
|
|
92
92
|
```ruby
|
93
|
-
|
93
|
+
class User
|
94
|
+
acts_as_addressable :billing
|
95
|
+
|
96
|
+
validates_presence_of :billing_address
|
97
|
+
end
|
94
98
|
```
|
95
99
|
|
96
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
109
|
+
This can be overridden on a per-address basis when declared in the model.
|
112
110
|
|
113
|
-
|
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
|
-
|
114
|
+
### Address Validations
|
118
115
|
|
119
|
-
|
116
|
+
Address1, City, Country and Postal/Zip Code are all required fields.
|
120
117
|
|
121
|
-
|
118
|
+
If the selected Country has provinces/states (not all countries do), then the province/state field will be required.
|
122
119
|
|
123
|
-
|
120
|
+
If the country is Canada or United States, then the postal/zip code formatting will be enforced.
|
124
121
|
|
125
|
-
This
|
122
|
+
This validation on the postal code/zip format can be disabled via the config/initializers `validate_postal_code_format` setting
|
126
123
|
|
127
|
-
|
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
|
-
|
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
|
-
|
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-
|
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 = "/
|
5
|
-
state_select = $(this).
|
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.
|
9
|
-
state_select.html('<option value="">
|
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.
|
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
|
-
|
7
|
+
@subregions = Carmen::Country.coded(params[:country_code]).try(:subregions)
|
8
8
|
|
9
|
-
if
|
10
|
-
render :partial => 'effective/addresses/subregions'
|
9
|
+
if @subregions.present?
|
10
|
+
render :partial => 'effective/addresses/subregions'
|
11
11
|
else
|
12
|
-
render :text => ''
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
22
|
-
opts[:method] = :address
|
26
|
+
countries = Carmen::Country.all.select { |c| (EffectiveAddresses.country_codes || []).include?(c.code) }
|
23
27
|
end
|
24
28
|
|
25
|
-
|
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 =
|
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 =>
|
30
|
-
|
31
|
-
|
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
|
38
|
-
|
39
|
-
|
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
|
70
|
-
|
61
|
+
def effective_address(category)
|
62
|
+
effective_addresses(category).last
|
71
63
|
end
|
72
64
|
|
73
|
-
def
|
74
|
-
|
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
|
-
|
80
|
-
addresses.select { |address| address.category == 'primary' }
|
81
|
-
end
|
68
|
+
atts = HashWithIndifferentAccess.new(atts.kind_of?(Effective::Address) ? atts.attributes : atts)
|
82
69
|
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
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.
|
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'
|
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
|
-
!
|
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 =
|
83
|
-
output += "#{
|
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 +=
|
86
|
-
output +=
|
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
|