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 +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
|