my_zipcode_gem 0.1.1

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