my_zipcode_gem 0.1.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.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ tmp/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in my_zipcode_gem.gemspec
4
+ gemspec
data/README.textile ADDED
@@ -0,0 +1,126 @@
1
+ h1. My Zipcode Gem
2
+
3
+ Simple gem to handle zipcode lookups and related functionality.
4
+
5
+ h2. Installation
6
+
7
+ Add the following line to your Gemfile:
8
+
9
+ bc. gem 'my_zipcode_gem'
10
+
11
+ Run:
12
+
13
+ bc. rake bundle install
14
+
15
+ Generate the models and populate the data:
16
+
17
+ bc. rails g my_zipcode_gem:models
18
+ rake db:migrate
19
+ rake zipcodes:update
20
+
21
+ You should now have three new tables and three new models, Zipcode, State, County.
22
+
23
+ h2. Usage
24
+
25
+ bc. zipcode = Zipcode.find_by_code '66206'
26
+ zipcode.state.abbr # => 'KS'
27
+ zipcode.city # => 'Shawnee Mission'
28
+ zipcode.county.name # => 'Johnson'
29
+ zipcode.lat.to_s # => '38.959356', it is actually a BigDecimal object converted to_s for documentation.
30
+ zipcode.lon.to_s # => '-94.716155', ditto
31
+ zipcode.is_geocoded? # => true, most if not all should be pre-geocoded.
32
+
33
+ You can also look for a zipcode from a city and state:
34
+
35
+ bc. Zipcode.find_by_city_state "Shawnee Mission", "KS"
36
+
37
+ You can use State and County objects as follows:
38
+
39
+ bc. state = State.find_by_abbr "MO"
40
+ state.cities.count # => 963
41
+ state.cities # gives you an sorted array of all cities for the state
42
+ state.zipcodes.count # => 1195
43
+ ...
44
+ county = state.counties.first
45
+ county.cities.count # => 5
46
+ county.cities # gives you an sorted array of all cities for the county
47
+ county.zipcodes.count # => 5
48
+
49
+ h3. Automatic JQuery/AJAX lookup
50
+
51
+ You can have a user enter a zipcode and automatically lookup their city, state and county.
52
+
53
+ Put something like this in your view:
54
+
55
+ bc. f.text_field :zip, :size => 5, :maxlength => 5, :class => 'zipcode_interactive'
56
+ f.text_field :city, :size => 20, :maxlength => 60, :readonly => true
57
+ f.text_field(:state, :size => 2, :maxlength => 2, :readonly => true)
58
+ f.text_field(:county, :size => 20, :maxlength => 60, :readonly => true)
59
+
60
+ Then add this to your application.js, but remember to replace [mycontrollername] with your own controller.
61
+
62
+ bc. $(document).ready(function() {
63
+ // Interactive Zipcodes
64
+ $('input.zipcode_interactive').blur(function(data) {
65
+ var elem_id = $(this).attr("id");
66
+ var base_id = elem_id.substring(0, elem_id.lastIndexOf("_"));
67
+ $.get("/mycontrollername/get_zip_data/" + this.value, {},
68
+ function(data) {
69
+ var zipcode = $.parseJSON(data);
70
+ var city = $('#' + base_id + '_city');
71
+ var state = $('#' + base_id + '_state');
72
+ var county = $('#' + base_id + '_county');
73
+ if (zipcode.err) {
74
+ alert(zipcode.err);
75
+ } else {
76
+ city.val(zipcode.city);
77
+ state.val(zipcode.state)
78
+ county.val(zipcode.county)
79
+ }
80
+ })
81
+ });
82
+ });
83
+
84
+ You will also need a controller method similar to this, which will return the data to your form:
85
+
86
+ bc. def get_zip_data
87
+ @zipcode = Zipcode.find_by_code(params[:code], :include => [:county, :state])
88
+ if @zipcode
89
+ @counties = County.find(:all, :conditions => [ "state_id = ?", @zipcode.county.state_id ])
90
+ data = {
91
+ 'state' => @zipcode.state.abbr,
92
+ 'county' => @zipcode.county.name,
93
+ 'city' => @zipcode.city.titleize
94
+ }
95
+ render :text => data.to_json
96
+ else
97
+ if params[:code].blank?
98
+ return true
99
+ else
100
+ if params[:code].is_zipcode?
101
+ data = {
102
+ 'err' => "Could not find Zipcode [#{params[:code]}]. If this is a valid zipcode please notify support <support@mydomain.com>, so we can update our database."
103
+ }
104
+ else
105
+ data = {
106
+ 'err' => "[#{params[:code]}] is not a valid Zipcode."
107
+ }
108
+ end
109
+ render :text => data.to_json
110
+ end
111
+ end
112
+ end
113
+
114
+ And define a route for the AJAX function in routes.rb:
115
+
116
+ bc. get 'mycontrollername/get_zip_data/:code', :controller => 'mycontrollername', :action => 'get_zip_data'
117
+
118
+ That's about it.
119
+
120
+ Let me know if there are any errors. I cut and pasted the code above from a working application, but there may be some gotchas that I missed.
121
+
122
+ h2. LOG
123
+
124
+ h3. 05/03/2011:
125
+
126
+ Initial Release
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'cucumber'
5
+ require 'cucumber/rake/task'
6
+ Cucumber::Rake::Task.new(:features) do |t|
7
+ t.cucumber_opts = "features --format progress"
8
+ end
9
+
10
+ task :default => :features
11
+ task :test => :features
@@ -0,0 +1,76 @@
1
+ When(/^I run "([^\"]*)"$/) do |command|
2
+ system("cd #{@current_directory} && #{command}").should be_true
3
+ end
4
+
5
+ When(/^I add "([^\"]*)" to file "([^\"]*)"$/) do |content, short_path|
6
+ path = File.join(@current_directory, short_path)
7
+ File.should exist(path)
8
+ File.open(path, 'a') { |f| f.write(content + "\n") }
9
+ end
10
+
11
+ When(/^I replace "([^\"]*)" with "([^\"]*)" in file "([^\"]*)"$/) do |old_content, new_content, short_path|
12
+ path = File.join(@current_directory, short_path)
13
+ File.should exist(path)
14
+ content = File.read(path).gsub(old_content, new_content)
15
+ File.open(path, 'w') { |f| f.write(content) }
16
+ end
17
+
18
+ When(/^I insert "([^\"]*)" into "([^\"]*)" after line (\d+)$/) do |content, short_path, after_line|
19
+ path = File.join(@current_directory, short_path)
20
+ File.should exist(path)
21
+ lines = File.read(path).split("\n")
22
+ lines[after_line.to_i, 0] = content
23
+ File.open(path, 'w') { |f| f.write(lines.join("\n")) }
24
+ end
25
+
26
+ Then(/^I should see file "([^\"]*)"$/) do |path|
27
+ File.should exist(File.join(@current_directory, path))
28
+ end
29
+
30
+ Then(/^I should see "(.*)" in file "([^\"]*)"$/) do |content, short_path|
31
+ path = File.join(@current_directory, short_path)
32
+ File.should exist(path)
33
+ File.readlines(path).join.should include(content)
34
+ end
35
+
36
+ Then(/^I should not see "(.*)" in file "([^\"]*)"$/) do |content, short_path|
37
+ path = File.join(@current_directory, short_path)
38
+ File.should exist(path)
39
+ File.readlines(path).join.should_not include(content)
40
+ end
41
+
42
+ Then(/^I should see the following files$/) do |table|
43
+ table.raw.flatten.each do |path|
44
+ File.should exist(File.join(@current_directory, path))
45
+ end
46
+ end
47
+
48
+ Then(/^I should see the following in file "([^\"]*)"$/) do |short_path, table|
49
+ path = File.join(@current_directory, short_path)
50
+ File.should exist(path)
51
+ table.raw.flatten.each do |content|
52
+ File.readlines(path).join.should include(content)
53
+ end
54
+ end
55
+
56
+ Then(/^I should successfully run "([^\"]*)"$/) do |command|
57
+ system("cd #{@current_directory} && #{command}").should be_true
58
+ end
59
+
60
+ Then(/^I should see "([^\"]*)" when running "([^\"]*)"$/) do |expected_response, command|
61
+ `cd #{@current_directory} && #{command}`.should include(expected_response)
62
+ end
63
+
64
+ Then(/^I should see (\d+) records in the "([^\"]*)" table$/) do |count, table_name|
65
+ FileUtils.chdir(@current_directory)
66
+ ActiveRecord::Base.establish_connection(
67
+ :adapter => 'sqlite3',
68
+ :database => "db/development.sqlite3",
69
+ :pool => 5,
70
+ :timeout => 5000
71
+ )
72
+ sql = "SELECT COUNT(*) FROM #{table_name}"
73
+ result = ActiveRecord::Base.connection.select_rows(sql)
74
+ puts ">>> result: [#{result.flatten.first}]"
75
+ "Record Count:#{result.flatten.first}".should == "Record Count:#{count}"
76
+ end
@@ -0,0 +1,17 @@
1
+ Given(/^a new Rails app$/) do
2
+ FileUtils.rm_rf "tmp/rails_app"
3
+ FileUtils.mkdir_p("tmp")
4
+ system("rails new tmp/rails_app").should be_true
5
+ system("ln -s ../../../lib/generators tmp/rails_app/lib/generators").should be_true
6
+ @current_directory = File.expand_path("tmp/rails_app")
7
+ end
8
+
9
+ Given %{a new migrated Rails app} do
10
+ # Don't delete the rails app
11
+ FileUtils.mkdir_p("tmp")
12
+ system("rails new tmp/rails_app").should be_true
13
+ system("ln -s ../../../lib/generators tmp/rails_app/lib/generators").should be_true
14
+ @current_directory = File.expand_path("tmp/rails_app")
15
+ When %{I run "rails g my_zipcode_gem:models"}
16
+ Then %{I should successfully run "rake db:migrate"}
17
+ end
@@ -0,0 +1,8 @@
1
+ require 'cucumber'
2
+ require 'rspec'
3
+ require 'rails'
4
+ require 'active_record'
5
+
6
+ Before do
7
+ # FileUtils.rm_rf "tmp/rails_app"
8
+ end
@@ -0,0 +1,128 @@
1
+ class String
2
+
3
+ class << self
4
+ def random(count = 6, ranges = [('a'..'z'),('A'..'Z'),('0'..'9')])
5
+ o = ranges.map{|i| i.to_a}.flatten;
6
+ string = (0..(count-1)).map{ o[rand(o.length)] }.join;
7
+ end
8
+ end
9
+
10
+ def left(count)
11
+ self.slice(0,count)
12
+ end
13
+
14
+ def right(count)
15
+ self.slice(-count,count)
16
+ end
17
+
18
+ def left_trim
19
+ # remove leading whitespace
20
+ self.gsub(/^[\t\s]+/, '')
21
+ end
22
+
23
+ def right_trim
24
+ # remove trailing whitespace
25
+ self.gsub(/[\t\s]+$/, '')
26
+ end
27
+
28
+ def trim
29
+ # remove leading and trailing whitespace
30
+ self.left_trim.right_trim
31
+ end
32
+
33
+ # html = <<-stop.here_with_pipe
34
+ # |<!-- Begin: comment -->
35
+ # |<script type="text/javascript">
36
+ # stop
37
+ def here_with_pipe(linefeeds = false)
38
+ lines = self.split("\n")
39
+ lines.map! {|c| c.sub!(/\s*\|/, '')}
40
+ new_string = lines.join(linefeeds ? "\n" : " ")
41
+ self.replace(new_string)
42
+ end
43
+
44
+ def is_alpha_numeric?
45
+ regex = /^[a-zA-Z0-9]+$/
46
+ return (self =~ regex) == 0 ? true : false
47
+ end
48
+
49
+ def is_email_address?
50
+ # //Email address
51
+ # //Use this version to seek out email addresses in random documents and texts.
52
+ # //Does not match email addresses using an IP address instead of a domain name.
53
+ # //Does not match email addresses on new-fangled top-level domains with more than 4 letters such as .museum.
54
+ # //Including these increases the risk of false positives when applying the regex to random documents.
55
+ # '\b[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b'
56
+ #
57
+ # //Email address (anchored)
58
+ # //Use this anchored version to check if a valid email address was entered.
59
+ # //Does not match email addresses using an IP address instead of a domain name.
60
+ # //Does not match email addresses on new-fangled top-level domains with more than 4 letters such as .museum.
61
+ # //Requires the "case insensitive" option to be ON.
62
+ # '^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$'
63
+ #
64
+ # //Email address (anchored; no consecutive dots)
65
+ # //Use this anchored version to check if a valid email address was entered.
66
+ # //Improves on the original email address regex by excluding addresses with consecutive dots such as john@aol...com
67
+ # //Does not match email addresses using an IP address instead of a domain name.
68
+ # //Does not match email addresses on new-fangled top-level domains with more than 4 letters such as .museum.
69
+ # //Including these increases the risk of false positives when applying the regex to random documents.
70
+ # '^[A-Z0-9._%-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}$'
71
+ #
72
+ # //Email address (no consecutive dots)
73
+ # //Use this version to seek out email addresses in random documents and texts.
74
+ # //Improves on the original email address regex by excluding addresses with consecutive dots such as john@aol...com
75
+ # //Does not match email addresses using an IP address instead of a domain name.
76
+ # //Does not match email addresses on new-fangled top-level domains with more than 4 letters such as .museum.
77
+ # //Including these increases the risk of false positives when applying the regex to random documents.
78
+ # '\b[A-Z0-9._%-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}\b'
79
+ #
80
+ # //Email address (specific TLDs)
81
+ # //Does not match email addresses using an IP address instead of a domain name.
82
+ # //Matches all country code top level domains, and specific common top level domains.
83
+ # '^[A-Z0-9._%-]+@[A-Z0-9.-]+\.(?:[A-Z]{2}|com|org|net|biz|info|name|aero|biz|info|jobs|museum|name)$'
84
+ #
85
+ # //Email address: Replace with HTML link
86
+ # '\b(?:mailto:)?([A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4})\b'
87
+
88
+ email_regex = %r{^[A-Z0-9._%-]+@[A-Z0-9.-]+\.(?:[A-Z]{2}|com|org|net|biz|info|name|aero|biz|info|jobs|museum|name)$}xi # Case insensitive
89
+
90
+ return (self =~ email_regex) == 0 ? true : false
91
+ end
92
+
93
+ def is_zipcode?
94
+ self =~ %r{^(\d{5})(-\d{4})?$}x ? true : false
95
+ end
96
+
97
+ def format_phone
98
+ '(' << slice(0..2) << ')' << slice(3..5) << '-' << slice(-4,4)
99
+ end
100
+
101
+ def is_numeric?
102
+ begin
103
+ Float(self)
104
+ rescue
105
+ false # not numeric
106
+ else
107
+ true # numeric
108
+ end
109
+ end
110
+
111
+ def sanitize
112
+ clean_string = self.gsub(/[^a-z0-9,! \-\(\)\:\;\.\&\$]+/i, '')
113
+ #p "SAN: #{clean_string}"
114
+ clean_string
115
+ end
116
+
117
+ def shorten(count = 30)
118
+ if self.length >= count
119
+ shortened = self[0, count]
120
+ splitted = shortened.split(/\s/)
121
+ words = splitted.length
122
+ splitted[0, words-1].join(" ") + ' ...'
123
+ else
124
+ self
125
+ end
126
+ end
127
+
128
+ end
@@ -0,0 +1,24 @@
1
+ Feature: My Zipcode Gem
2
+ In order to manage zipcode resources
3
+ As a rails developer
4
+ I want to generate models for zipcode, county and state, and populate their tables
5
+
6
+ Scenario: Generate models and migration for zipcode, county and state
7
+ Given a new Rails app
8
+ Then I should see "my_zipcode_gem:models" when running "rails g"
9
+ When I run "rails g my_zipcode_gem:models"
10
+ Then I should see the following files
11
+ | app/models/zipcode.rb |
12
+ | app/models/state.rb |
13
+ | app/models/county.rb |
14
+ | lib/tasks/zipcodes.rake |
15
+ | db/migrate |
16
+ And I should see "gem "mocha", :group => :test" in file "Gemfile"
17
+ And I should successfully run "rake db:migrate"
18
+
19
+ Scenario: Update data for zipcodes, counties and states tables
20
+ Given a new migrated Rails app
21
+ Then I should successfully run "rake zipcodes:update"
22
+ And I should see 51 records in the "states" table
23
+ And I should see 3142 records in the "counties" table
24
+ And I should see 42366 records in the "zipcodes" table
@@ -0,0 +1,46 @@
1
+ module MyZipcodeGem
2
+ class ModelsGenerator < Base
3
+ include Rails::Generators::Migration
4
+
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ def initialize(*args, &block)
8
+ super
9
+ end
10
+
11
+ def generate_models
12
+ # puts ">>> generate_zipcodes:"
13
+ end
14
+
15
+ def add_gems
16
+ add_gem "mocha", :group => :test
17
+ end
18
+
19
+ def create_models
20
+ template 'zipcode_model.rb', "app/models/zipcode.rb"
21
+ template 'county_model.rb', "app/models/county.rb"
22
+ template 'state_model.rb', "app/models/state.rb"
23
+ end
24
+
25
+ # Implement the required interface for Rails::Generators::Migration.
26
+ # taken from http://github.com/rails/rails/blob/master/activerecord/lib/generators/active_record.rb
27
+ def self.next_migration_number(dirname)
28
+ if ActiveRecord::Base.timestamped_migrations
29
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
30
+ else
31
+ "%.3d" % (current_migration_number(dirname) + 1)
32
+ end
33
+ end
34
+
35
+ def create_migration
36
+ migration_template 'migration.rb', "db/migrate/create_my_zipcode_gem_models.rb"
37
+ end
38
+
39
+ def create_rakefile
40
+ template 'zipcodes.rake', "lib/tasks/zipcodes.rake"
41
+ end
42
+
43
+ end
44
+ end
45
+
46
+ # /Users/cblackburn/.rvm/gems/ruby-1.9.2-p136/gems/activerecord-3.0.3/lib/rails/generators/active_record/
@@ -0,0 +1,17 @@
1
+ class County < ActiveRecord::Base
2
+ extend ActiveSupport::Memoizable
3
+ attr_accessible :state_id, :region_id, :abbr, :name, :count_seat
4
+
5
+ belongs_to :state
6
+ has_many :zipcodes
7
+
8
+ validates :name, :uniqueness => {:scope => :state_id, :case_sensitive => false}, :presence => true
9
+
10
+ scope :without_zipcodes, joins("LEFT JOIN zipcodes ON zipcodes.county_id = counties.id").where("zipcodes.county_id IS NULL")
11
+ scope :without_state, where("state_id IS NULL")
12
+
13
+ def cities
14
+ zipcodes.map(&:city).sort.uniq
15
+ end
16
+ memoize :cities
17
+ end
@@ -0,0 +1,44 @@
1
+ class CreateMyZipcodeGemModels < ActiveRecord::Migration
2
+ def self.up
3
+ # Zipcodes Table
4
+ create_table :zipcodes do |t|
5
+ t.string :code
6
+ t.string :city
7
+ t.integer :state_id
8
+ t.integer :county_id
9
+ t.string :area_code
10
+ t.decimal :lat, :precision => 15, :scale => 10
11
+ t.decimal :lon, :precision => 15, :scale => 10
12
+ t.timestamps
13
+ end
14
+ add_index :zipcodes, :code
15
+ add_index :zipcodes, :county_id
16
+ add_index :zipcodes, :state_id
17
+ add_index :zipcodes, [:lat, :lon]
18
+
19
+ # States Table
20
+ create_table :states do |t|
21
+ t.string :abbr, :limit => 2
22
+ t.string :name
23
+ t.timestamps
24
+ end
25
+ add_index :states, :abbr
26
+
27
+ # Counties Table
28
+ create_table :counties do |t|
29
+ t.integer :state_id
30
+ t.string :abbr
31
+ t.string :name
32
+ t.string :county_seat
33
+ t.timestamps
34
+ end
35
+ add_index :counties, :name
36
+ add_index :counties, :state_id
37
+ end
38
+
39
+ def self.down
40
+ drop_table :counties
41
+ drop_table :states
42
+ drop_table :zipcodes
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ class State < ActiveRecord::Base
2
+ extend ActiveSupport::Memoizable
3
+ attr_accessible :abbr, :name
4
+
5
+ has_many :zipcodes
6
+ has_many :counties
7
+
8
+ validates :abbr, :uniqueness => { :case_sensitive => false }, :presence => true
9
+ validates :name, :uniqueness => { :case_sensitive => false }, :presence => true
10
+
11
+ def cities
12
+ zipcodes.map(&:city).sort.uniq
13
+ end
14
+ memoize :cities
15
+ end
@@ -0,0 +1,27 @@
1
+ class Zipcode < ActiveRecord::Base
2
+ attr_accessible :code, :city, :state_id, :county_id, :lat, :lon
3
+
4
+ belongs_to :county
5
+ belongs_to :state
6
+
7
+ validates :code, :uniqueness => true, :presence => true
8
+ validates :state_id, :county_id, :city, :presence => true
9
+
10
+ scope :without_county, where("county_id IS NULL")
11
+ scope :without_state, where("state_id IS NULL")
12
+ scope :ungeocoded, where("lat IS NULL OR lon IS NULL")
13
+
14
+ class << self
15
+ def find_by_city_state(city, state)
16
+ find(:first, :conditions => "city like '#{city}%' AND states.abbr like '%#{state}%'", :include => [:county => :state])
17
+ end
18
+ end
19
+
20
+ def latlon
21
+ [lat, lon]
22
+ end
23
+
24
+ def is_geocoded?
25
+ (!lat.nil? && !lon.nil?)
26
+ end
27
+ end
@@ -0,0 +1,96 @@
1
+ require 'open-uri'
2
+ require 'fastercsv'
3
+ namespace :zipcodes do
4
+
5
+ desc "Update states table"
6
+ task :update_states => :environment do
7
+ puts ">>> Begin update of states table..."
8
+ url = "https://github.com/midwire/free_zipcode_data/raw/master/all_us_states.csv"
9
+ data = open(url)
10
+ file = nil
11
+ if data.is_a? StringIO
12
+ file = Tempfile.new('all_us_states.csv')
13
+ file.write(data.read)
14
+ file.flush
15
+ file.close
16
+ else
17
+ file = data
18
+ end
19
+ FasterCSV.foreach(file.path, :headers => true) do |row|
20
+ puts "Updating state: [#{row['name']}]"
21
+ state = State.find_or_initialize_by_abbr(row['abbr'])
22
+ state.update_attribute(:name, row['name'])
23
+ end
24
+ data.close
25
+ puts ">>> End update of states table..."
26
+ end
27
+
28
+ desc "Update counties table"
29
+ task :update_counties => :update_states do
30
+ puts ">>> Begin update of counties table..."
31
+ url = "https://github.com/midwire/free_zipcode_data/raw/master/all_us_counties.csv"
32
+ data = open(url)
33
+ file = nil
34
+ if data.is_a? StringIO
35
+ file = Tempfile.new('all_us_counties.csv')
36
+ file.write(data.read)
37
+ file.flush
38
+ file.close
39
+ else
40
+ file = data
41
+ end
42
+ FasterCSV.foreach(file.path, :headers => true) do |row|
43
+ puts "Updating county: [#{row['name']}]"
44
+ # lookup state
45
+ state = State.find_by_abbr!(row['state'])
46
+ county = County.find_or_initialize_by_name_and_state_id(row['name'], state.to_param)
47
+ county.update_attribute(:county_seat, row['county_seat'])
48
+ end
49
+ data.close
50
+ puts ">>> End update of counties table..."
51
+ end
52
+
53
+ desc "Update zipcodes table"
54
+ task :update_zipcodes => :update_counties do
55
+ puts ">>> Begin update of zipcodes table..."
56
+ url = "https://github.com/midwire/free_zipcode_data/raw/master/all_us_zipcodes.csv"
57
+ data = open(url)
58
+ file = nil
59
+ if data.is_a? StringIO
60
+ file = Tempfile.new('all_us_zipcodes.csv')
61
+ file.write(data.read)
62
+ file.flush
63
+ file.close
64
+ else
65
+ file = data
66
+ end
67
+ FasterCSV.foreach(file.path, :headers => true) do |row|
68
+ puts "Updating zipcode: [#{row['code']}], '#{row['city']}, #{row['state']}, #{row['county']}"
69
+ # lookup state
70
+ state = State.find_by_abbr!(row['state'])
71
+ begin
72
+ county = County.find_by_name_and_state_id!(row['county'], state.to_param)
73
+ rescue Exception => e
74
+ puts ">>> e: [#{e}]"
75
+ puts ">>>> No county found for zipcode: [#{row['code']}], '#{row['city']}, #{row['state']}, #{row['county']}... SKIPPING..."
76
+ next
77
+ end
78
+ zipcode = Zipcode.find_or_initialize_by_code(row['code'])
79
+ zipcode.update_attributes!(
80
+ :city => row['city'].titleize,
81
+ :state_id => state.to_param,
82
+ :county_id => county.to_param,
83
+ :lat => row['lat'],
84
+ :lon => row['lon']
85
+ )
86
+ end
87
+ data.close
88
+ puts ">>> End update of zipcodes table..."
89
+ end
90
+
91
+ desc "Populate or update the zipcodes related tables"
92
+ task :update => :environment do
93
+ Rake::Task['zipcodes:update_zipcodes'].invoke
94
+ end
95
+
96
+ end
@@ -0,0 +1,3 @@
1
+ module MyZipcodeGem
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,33 @@
1
+ require 'rails/generators/base'
2
+
3
+ module MyZipcodeGem
4
+ class Base < Rails::Generators::Base #:nodoc:
5
+ # def self.source_root
6
+ # @_source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'nifty', generator_name, 'templates'))
7
+ # puts ">>> @_source_root: [#{@_source_root}]"
8
+ # @_source_root
9
+ # end
10
+
11
+ def self.banner
12
+ "rails generate my_zipcode_gem:#{generator_name} #{self.arguments.map{ |a| a.usage }.join(' ')} [options]"
13
+ end
14
+
15
+ private
16
+
17
+ def add_gem(name, options = {})
18
+ gemfile_content = File.read(destination_path("Gemfile"))
19
+ File.open(destination_path("Gemfile"), 'a') { |f| f.write("\n") } unless gemfile_content =~ /\n\Z/
20
+ gem name, options unless gemfile_content.include? name
21
+ end
22
+
23
+ def print_usage
24
+ self.class.help(Thor::Base.shell.new)
25
+ exit
26
+ end
27
+
28
+ def destination_path(path)
29
+ File.join(destination_root, path)
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "generators/my_zipcode_gem/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "my_zipcode_gem"
7
+ s.version = MyZipcodeGem::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Chris Blackburn"]
10
+ s.email = ["chris [at] midwiretech [dot] com"]
11
+ s.homepage = "https://github.com/midwire/my_zipcode_gem"
12
+ s.summary = %q{A Ruby gem to handle all things zipcode.}
13
+ s.description = %q{A Ruby gem for looking up and manipulating US postal codes and geocodes.}
14
+
15
+ s.rubyforge_project = "my_zipcode_gem"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency('rails', '3.0.3')
23
+ s.add_dependency('rubigen', '1.5.6')
24
+ s.add_dependency('fastercsv')
25
+
26
+ s.add_development_dependency('sqlite3-ruby')
27
+ s.add_development_dependency('shoulda', '2.11.3')
28
+ s.add_development_dependency('rspec')
29
+ s.add_development_dependency('rspec-rails')
30
+ s.add_development_dependency('cucumber')
31
+ s.add_development_dependency('cucumber-rails')
32
+ end
metadata ADDED
@@ -0,0 +1,220 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: my_zipcode_gem
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 1
10
+ version: 0.1.1
11
+ platform: ruby
12
+ authors:
13
+ - Chris Blackburn
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-05-03 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rails
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - "="
28
+ - !ruby/object:Gem::Version
29
+ hash: 1
30
+ segments:
31
+ - 3
32
+ - 0
33
+ - 3
34
+ version: 3.0.3
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rubigen
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - "="
44
+ - !ruby/object:Gem::Version
45
+ hash: 15
46
+ segments:
47
+ - 1
48
+ - 5
49
+ - 6
50
+ version: 1.5.6
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: fastercsv
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 3
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ type: :runtime
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: sqlite3-ruby
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ type: :development
80
+ version_requirements: *id004
81
+ - !ruby/object:Gem::Dependency
82
+ name: shoulda
83
+ prerelease: false
84
+ requirement: &id005 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - "="
88
+ - !ruby/object:Gem::Version
89
+ hash: 37
90
+ segments:
91
+ - 2
92
+ - 11
93
+ - 3
94
+ version: 2.11.3
95
+ type: :development
96
+ version_requirements: *id005
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ prerelease: false
100
+ requirement: &id006 !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ hash: 3
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ type: :development
110
+ version_requirements: *id006
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec-rails
113
+ prerelease: false
114
+ requirement: &id007 !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ hash: 3
120
+ segments:
121
+ - 0
122
+ version: "0"
123
+ type: :development
124
+ version_requirements: *id007
125
+ - !ruby/object:Gem::Dependency
126
+ name: cucumber
127
+ prerelease: false
128
+ requirement: &id008 !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ hash: 3
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ type: :development
138
+ version_requirements: *id008
139
+ - !ruby/object:Gem::Dependency
140
+ name: cucumber-rails
141
+ prerelease: false
142
+ requirement: &id009 !ruby/object:Gem::Requirement
143
+ none: false
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ hash: 3
148
+ segments:
149
+ - 0
150
+ version: "0"
151
+ type: :development
152
+ version_requirements: *id009
153
+ description: A Ruby gem for looking up and manipulating US postal codes and geocodes.
154
+ email:
155
+ - chris [at] midwiretech [dot] com
156
+ executables: []
157
+
158
+ extensions: []
159
+
160
+ extra_rdoc_files: []
161
+
162
+ files:
163
+ - .gitignore
164
+ - Gemfile
165
+ - README.textile
166
+ - Rakefile
167
+ - features/step_definitions/common_steps.rb
168
+ - features/step_definitions/rails_setup_steps.rb
169
+ - features/support/env.rb
170
+ - features/support/string.rb
171
+ - features/zipcodes.feature
172
+ - lib/generators/my_zipcode_gem/models_generator.rb
173
+ - lib/generators/my_zipcode_gem/templates/county_model.rb
174
+ - lib/generators/my_zipcode_gem/templates/migration.rb
175
+ - lib/generators/my_zipcode_gem/templates/state_model.rb
176
+ - lib/generators/my_zipcode_gem/templates/zipcode_model.rb
177
+ - lib/generators/my_zipcode_gem/templates/zipcodes.rake
178
+ - lib/generators/my_zipcode_gem/version.rb
179
+ - lib/my_zipcode_gem.rb
180
+ - my_zipcode_gem.gemspec
181
+ has_rdoc: true
182
+ homepage: https://github.com/midwire/my_zipcode_gem
183
+ licenses: []
184
+
185
+ post_install_message:
186
+ rdoc_options: []
187
+
188
+ require_paths:
189
+ - lib
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ none: false
192
+ requirements:
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ hash: 3
196
+ segments:
197
+ - 0
198
+ version: "0"
199
+ required_rubygems_version: !ruby/object:Gem::Requirement
200
+ none: false
201
+ requirements:
202
+ - - ">="
203
+ - !ruby/object:Gem::Version
204
+ hash: 3
205
+ segments:
206
+ - 0
207
+ version: "0"
208
+ requirements: []
209
+
210
+ rubyforge_project: my_zipcode_gem
211
+ rubygems_version: 1.6.2
212
+ signing_key:
213
+ specification_version: 3
214
+ summary: A Ruby gem to handle all things zipcode.
215
+ test_files:
216
+ - features/step_definitions/common_steps.rb
217
+ - features/step_definitions/rails_setup_steps.rb
218
+ - features/support/env.rb
219
+ - features/support/string.rb
220
+ - features/zipcodes.feature