effective_addresses 0.1

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.
Files changed (60) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +151 -0
  3. data/Rakefile +23 -0
  4. data/app/assets/javascripts/effective_addresses.js +1 -0
  5. data/app/assets/javascripts/effective_addresses/address_fields.js.coffee +13 -0
  6. data/app/controllers/effective/addresses_controller.rb +16 -0
  7. data/app/helpers/effective_addresses_helper.rb +27 -0
  8. data/app/models/concerns/acts_as_addressable.rb +107 -0
  9. data/app/models/effective/address.rb +93 -0
  10. data/app/models/validators/billing_address_validator.rb +7 -0
  11. data/app/views/effective/addresses/_address_fields.html.haml +29 -0
  12. data/app/views/effective/addresses/_subregions.html.haml +1 -0
  13. data/config/routes.rb +4 -0
  14. data/db/migrate/01_create_effective_addresses.rb.erb +24 -0
  15. data/lib/effective_addresses.rb +16 -0
  16. data/lib/effective_addresses/engine.rb +28 -0
  17. data/lib/effective_addresses/version.rb +3 -0
  18. data/lib/generators/effective_addresses/install_generator.rb +32 -0
  19. data/lib/generators/templates/README +1 -0
  20. data/lib/generators/templates/effective_addresses.rb +12 -0
  21. data/lib/tasks/effective_addresses_tasks.rake +4 -0
  22. data/spec/controllers/addresses_controller_spec.rb +10 -0
  23. data/spec/dummy/README.rdoc +261 -0
  24. data/spec/dummy/Rakefile +7 -0
  25. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  26. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  27. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  28. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  29. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  30. data/spec/dummy/config.ru +4 -0
  31. data/spec/dummy/config/application.rb +65 -0
  32. data/spec/dummy/config/boot.rb +10 -0
  33. data/spec/dummy/config/database.yml +25 -0
  34. data/spec/dummy/config/environment.rb +5 -0
  35. data/spec/dummy/config/environments/development.rb +37 -0
  36. data/spec/dummy/config/environments/production.rb +67 -0
  37. data/spec/dummy/config/environments/test.rb +37 -0
  38. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  39. data/spec/dummy/config/initializers/inflections.rb +15 -0
  40. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  41. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  42. data/spec/dummy/config/initializers/session_store.rb +8 -0
  43. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  44. data/spec/dummy/config/locales/en.yml +5 -0
  45. data/spec/dummy/config/routes.rb +58 -0
  46. data/spec/dummy/db/development.sqlite3 +0 -0
  47. data/spec/dummy/db/schema.rb +34 -0
  48. data/spec/dummy/db/test.sqlite3 +0 -0
  49. data/spec/dummy/log/development.log +103 -0
  50. data/spec/dummy/log/test.log +125 -0
  51. data/spec/dummy/public/404.html +26 -0
  52. data/spec/dummy/public/422.html +26 -0
  53. data/spec/dummy/public/500.html +25 -0
  54. data/spec/dummy/public/favicon.ico +0 -0
  55. data/spec/dummy/script/rails +6 -0
  56. data/spec/dummy/spec_link +3 -0
  57. data/spec/models/address_spec.rb +52 -0
  58. data/spec/spec_helper.rb +39 -0
  59. data/spec/support/factories.rb +13 -0
  60. metadata +339 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Code and Effect Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # Effective Addresses
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=
5
+
6
+ Includes full validations for addresses with multiple categories.
7
+
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.
10
+
11
+ Rails >= 3.2.x, Ruby >= 1.9.x. Has not been tested/developed for Rails4.
12
+
13
+ ## Getting Started
14
+
15
+ Add to your Gemfile:
16
+
17
+ ```ruby
18
+ gem 'effective_addresses'
19
+ ```
20
+
21
+ Run the bundle command to install it:
22
+
23
+ ```console
24
+ bundle install
25
+ ```
26
+
27
+ Then run the generator:
28
+
29
+ ```ruby
30
+ rails generate effective_addresses:install
31
+ ```
32
+
33
+ The generator will install an initializer which describes all configuration options and creates a database migration.
34
+
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.
36
+
37
+ Then migrate the database:
38
+
39
+ ```ruby
40
+ rake db:migrate
41
+ ```
42
+
43
+ If you'd like to use the form helper method, require the javascript in your application.js
44
+
45
+ ```ruby
46
+ //= require effective_addresses
47
+ ```
48
+
49
+
50
+ ## Usage
51
+
52
+ ### Model
53
+
54
+ To use without any validations, just add the mixin to your existing model:
55
+
56
+ ```ruby
57
+ class User
58
+ acts_as_addressable
59
+ end
60
+ ```
61
+
62
+ This adds the following getters, along with the setters:
63
+
64
+ ```ruby
65
+ @user.address
66
+ @user.billing_address
67
+ @user.shipping_address
68
+ @user.primary_address
69
+ @user.secondary_address
70
+ ```
71
+
72
+ You can also define validations as follows:
73
+
74
+ ```ruby
75
+ class User
76
+ acts_as_addressable :require_billing => true, :require_shipping => true
77
+ end
78
+ ```
79
+
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:
85
+
86
+ ```ruby
87
+ @user.billing_address
88
+ ```
89
+
90
+ You can find all past addresses (including the current one) by:
91
+
92
+ ```ruby
93
+ @user.billing_addresses
94
+ ```
95
+
96
+ ### Form Helper
97
+
98
+ Use the helper in a formtastic form to quickly create the address fields 'f.inputs'. This example is in HAML:
99
+
100
+ ```ruby
101
+ = semantic_form_for @user do |f|
102
+ = f.inputs :name => "Your Information" do
103
+ = f.input :email
104
+ = f.input :name
105
+
106
+ = effective_address_fields(f, :category => 'billing') # 'shipping', 'primary', 'secondary'
107
+
108
+ = f.action :submit
109
+ ```
110
+
111
+ Currently only supports Formtastic.
112
+
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
115
+
116
+
117
+ ## License
118
+
119
+ MIT License. Copyright Code and Effect Inc. http://www.codeandeffect.com
120
+
121
+ You are not granted rights or licenses to the trademarks of Code and Effect
122
+
123
+ ## Notes
124
+
125
+ This is a work in progress gem. It needs smarter validations, dynamic methods and google maps integration
126
+
127
+ ### Testing
128
+
129
+ The test suite for this gem is unfortunately not yet complete.
130
+
131
+ Run tests by:
132
+
133
+ ```ruby
134
+ rake spec
135
+ ```
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ # Our tasks
9
+ load 'lib/tasks/effective_addresses_tasks.rake'
10
+
11
+ # Testing tasks
12
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
13
+ load 'rails/tasks/engine.rake'
14
+
15
+ Bundler::GemHelper.install_tasks
16
+
17
+ require 'rspec/core'
18
+ require 'rspec/core/rake_task'
19
+
20
+ desc "Run all specs in spec directory (excluding plugin specs)"
21
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
22
+
23
+ task :default => :spec
@@ -0,0 +1 @@
1
+ //= require_tree ./effective_addresses
@@ -0,0 +1,13 @@
1
+ $(document).on 'change', "select[data-behavior='address-country']", (event) ->
2
+ country_code = $(this).val()
3
+
4
+ url = "/effective/address/subregions/#{country_code}"
5
+ state_select = $(this).parent().parent().find("li > select[data-behavior='address-state']").first()
6
+
7
+ if country_code.length == 0
8
+ state_select.attr('disabled', 'disabled')
9
+ state_select.html('<option value="">please select a country</option>')
10
+ else
11
+ state_select.removeAttr('disabled')
12
+ state_select.find('option').first().text('loading...')
13
+ state_select.load(url)
@@ -0,0 +1,16 @@
1
+ module Effective
2
+ class AddressesController < ApplicationController
3
+ skip_authorization_check if defined?(CanCan)
4
+ respond_to :json
5
+
6
+ def subregions
7
+ country_code = params[:country_code]
8
+
9
+ if country_code.present?
10
+ render :partial => 'effective/addresses/subregions', :locals => {:country_code => country_code}
11
+ else
12
+ render :text => '', :status => :unprocessable_entity
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ require 'carmen-rails'
2
+
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
20
+ else
21
+ opts[:name] = 'Address'
22
+ opts[:method] = :address
23
+ end
24
+
25
+ render :partial => 'effective/addresses/address_fields', :locals => opts
26
+ end
27
+ end
@@ -0,0 +1,107 @@
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
+
18
+ module ActsAsAddressable
19
+ extend ActiveSupport::Concern
20
+
21
+ 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)
24
+ include ::ActsAsAddressable
25
+ end
26
+ end
27
+
28
+ 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]
32
+ end
33
+
34
+ module ClassMethods
35
+ end
36
+
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' }
67
+ end
68
+
69
+ def billing_address
70
+ billing_addresses.last
71
+ end
72
+
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
78
+
79
+ def primary_addresses
80
+ addresses.select { |address| address.category == 'primary' }
81
+ end
82
+
83
+ def primary_address
84
+ primary_addresses.last
85
+ end
86
+
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)
91
+ end
92
+
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
+ end
107
+
@@ -0,0 +1,93 @@
1
+ require 'carmen'
2
+
3
+ module Effective
4
+ class Address < ActiveRecord::Base
5
+ self.table_name = EffectiveAddresses.addresses_table_name.to_s
6
+
7
+ belongs_to :addressable, :polymorphic => true, :touch => true
8
+
9
+ structure do
10
+ category :string, :validates => [:presence, :inclusion => { :in => %w(billing shipping primary secondary address)}]
11
+
12
+ full_name :string, :validates => [:presence]
13
+ address1 :string, :validates => [:presence]
14
+ address2 :string
15
+ city :string, :validates => [:presence]
16
+ state_code :string, :validates => [:presence]
17
+ country_code :string, :validates => [:presence]
18
+ postal_code :string, :validates => [:presence]
19
+ timestamps
20
+ end
21
+
22
+ default_scope order(:updated_at)
23
+
24
+ scope :billing_addresses, -> { where(:category => 'billing') }
25
+ scope :shipping_addresses, -> { where(:category => 'shipping') }
26
+
27
+ def first_name
28
+ full_name.split(' ').first rescue full_name
29
+ end
30
+
31
+ def last_name
32
+ full_name.gsub(first_name, '').strip rescue full_name
33
+ end
34
+
35
+ def country
36
+ Carmen::Country.coded(country_code).name rescue ''
37
+ end
38
+
39
+ def state
40
+ Carmen::Country.coded(country_code).subregions.coded(state_code).name rescue ''
41
+ end
42
+
43
+ def province
44
+ Carmen::Country.coded(country_code).subregions.coded(state_code).name rescue ''
45
+ end
46
+
47
+ def country=(country_string)
48
+ value = Carmen::Country.named(country_string) || Carmen::Country.coded(country_string.try(:upcase))
49
+ self.country_code = value.code if value.present?
50
+ end
51
+
52
+ def state=(state_string)
53
+ 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))
55
+ self.state_code = value.code if value.present?
56
+ else
57
+ Rails.logger.info 'No country set. Try calling country= before state='
58
+ puts 'No country set. Try calling country= before state='
59
+ end
60
+ end
61
+
62
+ 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
64
+ end
65
+
66
+ def ==(other_address)
67
+ self_attrs = self.attributes
68
+ other_attrs = other_address.respond_to?(:attributes) ? other_address.attributes : {}
69
+
70
+ [self_attrs, other_attrs].each { |attrs| attrs.except!('id', 'created_at', 'updated_at', 'addressable_type', 'addressable_id', 'address2') }
71
+
72
+ self_attrs == other_attrs
73
+ end
74
+
75
+ # An address may be set with a category but nothing else
76
+ # This is considered empty
77
+ 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?
79
+ end
80
+
81
+ def to_s
82
+ output = "#{full_name}\n"
83
+ output += "#{address1}\n"
84
+ output += "#{address2}\n" if address2.present?
85
+ output += "#{city}, #{state}\n"
86
+ output += "#{country}, #{postal_code}"
87
+ end
88
+
89
+ def to_html
90
+ to_s.gsub(/\n/, '<br>').html_safe
91
+ end
92
+ end
93
+ end