effective_addresses 0.1

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