Skip to content

Commit 70ba22f

Browse files
authored
Merge pull request #27 from midwire/develop
Modernize gem: Ruby 3.4, rubyzip 3.x fix, tests, and rubocop cleanup
2 parents e5186bb + 438a867 commit 70ba22f

41 files changed

Lines changed: 1362 additions & 126 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
/stubs
66
/vendor/bundle/
77
/pkg
8+
.claude/

.rubocop.yml

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
AllCops:
2-
TargetRubyVersion: 2.7
2+
TargetRubyVersion: 3.4
33

44
# Include gemspec and Rakefile
55
Include:
6-
- '**/*.gemspec'
7-
- '**/*.podspec'
8-
- '**/*.jbuilder'
9-
- '**/*.rake'
10-
- '**/Gemfile'
11-
- '**/Rakefile'
12-
- '**/Capfile'
13-
- '**/Guardfile'
14-
- '**/Podfile'
15-
- '**/Thorfile'
16-
- '**/Vagrantfile'
6+
- "**/*.rb"
7+
- "**/*.gemspec"
8+
- "**/*.podspec"
9+
- "**/*.jbuilder"
10+
- "**/*.rake"
11+
- "**/Gemfile"
12+
- "**/Rakefile"
13+
- "**/Capfile"
14+
- "**/Guardfile"
15+
- "**/Podfile"
16+
- "**/Thorfile"
17+
- "**/Vagrantfile"
1718
Exclude:
18-
- 'vendor/**/*'
19-
- 'stubs/**/*'
20-
- 'spec/support/shared_contexts/*'
19+
- "vendor/**/*"
20+
- "stubs/**/*"
21+
- "spec/support/shared_contexts/*"
2122

2223
NewCops: enable
2324

@@ -51,6 +52,10 @@ Style/DoubleNegation:
5152
Style/PerlBackrefs:
5253
Enabled: false
5354

55+
Style/OpenStructUse:
56+
Exclude:
57+
- "spec/**/*"
58+
5459
########################################
5560
# Lint Cops
5661

@@ -66,6 +71,10 @@ Security/Eval:
6671
########################################
6772
# Metrics Cops
6873

74+
Metrics/BlockLength:
75+
Exclude:
76+
- "spec/**/*"
77+
6978
Metrics/MethodLength:
7079
CountComments: false # count full line comments?
7180
Max: 30
@@ -77,7 +86,7 @@ Metrics/AbcSize:
7786
Enabled: false
7887

7988
########################################
80-
# Metrics Cops
89+
# Naming Cops
8190

8291
Naming/FileName:
8392
Enabled: false

.ruby-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.0.2
1+
3.4.8

CLAUDE.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
A Ruby gem that downloads postal/zipcode data from GeoNames.org, processes it via an ETL pipeline, and outputs an SQLite3 database and optional CSV files. Supports single-country or all-countries processing.
8+
9+
## Commands
10+
11+
```bash
12+
# Install dependencies (vendored to vendor/bundle, binstubs in stubs/)
13+
bundle install
14+
15+
# Run all tests
16+
bundle exec rspec
17+
18+
# Run a single test file
19+
bundle exec rspec spec/path/to/file_spec.rb
20+
21+
# Run a specific test by line number
22+
bundle exec rspec spec/path/to/file_spec.rb:42
23+
24+
# Lint
25+
bundle exec rubocop
26+
27+
# Lint with auto-correct
28+
bundle exec rubocop -a
29+
30+
# Version bumping (do on develop branch, not master)
31+
bundle exec rake version:bump_patch
32+
bundle exec rake version:bump_minor
33+
bundle exec rake version:bump_major
34+
35+
# Build and install gem
36+
bundle exec rake build
37+
bundle exec rake install
38+
39+
# Release gem
40+
bundle exec rake release
41+
```
42+
43+
## Architecture
44+
45+
The gem follows an ETL (Extract, Transform, Load) pattern using the Kiba gem:
46+
47+
1. **Extract**: `DataSource` downloads zip files from GeoNames.org, extracts them, and prepares CSV files with headers
48+
2. **Source**: `CsvSource` (Kiba source) feeds rows from the prepared CSV into the pipeline
49+
3. **Load**: Four Kiba destination table classes write rows into an in-memory SQLite database
50+
51+
### Key Flow
52+
53+
`bin/free_zipcode_data``Runner#start``DataSource#download``DataSource#datafile` (extract zip + add CSV headers) → `SqliteRam` (in-memory DB) → `ETL::FreeZipcodeDataJob` (Kiba pipeline) → `SqliteRam#save_to_disk`
54+
55+
### Core Classes
56+
57+
- **`FreeZipcodeData::Runner`** - CLI entry point; parses args via Optimist, orchestrates the full pipeline
58+
- **`FreeZipcodeData::DataSource`** - Downloads and extracts GeoNames zip files, prepares CSV with headers
59+
- **`SqliteRam`** - Wraps SQLite3; works entirely in-memory then saves to disk via `SQLite3::Backup`
60+
- **`FreeZipcodeData::DbTable`** - Base class for all table classes; provides progress bar, SQL helpers, and country lookup from `country_lookup_table.yml`
61+
- **`FreeZipcodeData::CountryTable`/`StateTable`/`CountyTable`/`ZipcodeTable`** - Kiba destinations; each has `build` (creates schema + indexes) and `write` (inserts rows, swallows duplicate constraint violations)
62+
- **`ETL::FreeZipcodeDataJob`** - Configures the Kiba pipeline with one source and four destinations
63+
- **`CsvSource`** - Kiba-compatible CSV reader
64+
65+
### Singletons
66+
67+
`Options` and `Logger` are singletons (via Ruby's `Singleton` module). `Runner` has an `.instance` convenience class method (returns `new` each time, not cached).
68+
69+
## Configuration
70+
71+
- `.ruby-version`: 3.4.8
72+
- Bundle path: `vendor/bundle` (binstubs in `stubs/`)
73+
- Environment: `APP_ENV` controls environment (`test`, `development`)
74+
- Config file: `~/.free_zipcode_data.yml` (overridable via `FZD_CONFIG_FILE` env var; uses `spec/fixtures/` version in test)
75+
76+
## Rubocop
77+
78+
Key style settings (`.rubocop.yml`):
79+
- Target Ruby 3.4
80+
- Max line length: 110
81+
- Max method length: 30 lines
82+
- `Style/ClassVars`, `Style/Documentation`, `Metrics/AbcSize`, `Lint/SuppressedException` disabled
83+
- `vendor/` and `stubs/` excluded
84+
85+
## Git Workflow
86+
87+
- `master` is the release branch
88+
- `develop` is the development branch
89+
- Version bumps should happen on `develop`, then merge to `master` before `rake release`

Gemfile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,13 @@ source 'https://rubygems.org'
44
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
55

66
gemspec
7+
8+
group :development do
9+
gem 'bundler'
10+
gem 'pry-nav', '~> 0.2'
11+
gem 'rake', '~> 13.0'
12+
gem 'rspec', '~> 3.7'
13+
gem 'rubocop'
14+
gem 'ruby-prof', '~> 0.17'
15+
gem 'simplecov', '~> 0.16'
16+
end

Gemfile.lock

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ PATH
33
specs:
44
free_zipcode_data (1.0.6)
55
colored (~> 1.2)
6+
csv
67
kiba (~> 4.0)
8+
logger
79
optimist (~> 3.0)
810
ruby-progressbar (~> 1.9)
911
rubyzip (>= 1.2.2)
@@ -12,63 +14,75 @@ PATH
1214
GEM
1315
remote: https://rubygems.org/
1416
specs:
15-
ast (2.4.2)
17+
ast (2.4.3)
1618
coderay (1.1.3)
1719
colored (1.2)
18-
diff-lcs (1.4.4)
19-
docile (1.4.0)
20+
csv (3.3.5)
21+
diff-lcs (1.6.2)
22+
docile (1.4.1)
23+
json (2.18.1)
2024
kiba (4.0.0)
25+
language_server-protocol (3.17.0.5)
26+
lint_roller (1.1.0)
27+
logger (1.7.0)
2128
method_source (0.9.2)
2229
mini_portile2 (2.8.9)
2330
optimist (3.2.1)
24-
parallel (1.21.0)
25-
parser (3.0.2.0)
31+
parallel (1.27.0)
32+
parser (3.3.10.1)
2633
ast (~> 2.4.1)
34+
racc
35+
prism (1.9.0)
2736
pry (0.12.2)
2837
coderay (~> 1.1.0)
2938
method_source (~> 0.9.0)
3039
pry-nav (0.3.0)
3140
pry (>= 0.9.10, < 0.13.0)
32-
rainbow (3.0.0)
33-
rake (13.0.6)
34-
regexp_parser (2.1.1)
35-
rexml (3.4.2)
36-
rspec (3.10.0)
37-
rspec-core (~> 3.10.0)
38-
rspec-expectations (~> 3.10.0)
39-
rspec-mocks (~> 3.10.0)
40-
rspec-core (3.10.1)
41-
rspec-support (~> 3.10.0)
42-
rspec-expectations (3.10.1)
41+
racc (1.8.1)
42+
rainbow (3.1.1)
43+
rake (13.3.1)
44+
regexp_parser (2.11.3)
45+
rspec (3.13.2)
46+
rspec-core (~> 3.13.0)
47+
rspec-expectations (~> 3.13.0)
48+
rspec-mocks (~> 3.13.0)
49+
rspec-core (3.13.6)
50+
rspec-support (~> 3.13.0)
51+
rspec-expectations (3.13.5)
4352
diff-lcs (>= 1.2.0, < 2.0)
44-
rspec-support (~> 3.10.0)
45-
rspec-mocks (3.10.2)
53+
rspec-support (~> 3.13.0)
54+
rspec-mocks (3.13.7)
4655
diff-lcs (>= 1.2.0, < 2.0)
47-
rspec-support (~> 3.10.0)
48-
rspec-support (3.10.3)
49-
rubocop (1.22.3)
56+
rspec-support (~> 3.13.0)
57+
rspec-support (3.13.7)
58+
rubocop (1.84.2)
59+
json (~> 2.3)
60+
language_server-protocol (~> 3.17.0.2)
61+
lint_roller (~> 1.1.0)
5062
parallel (~> 1.10)
51-
parser (>= 3.0.0.0)
63+
parser (>= 3.3.0.2)
5264
rainbow (>= 2.2.2, < 4.0)
53-
regexp_parser (>= 1.8, < 3.0)
54-
rexml
55-
rubocop-ast (>= 1.12.0, < 2.0)
65+
regexp_parser (>= 2.9.3, < 3.0)
66+
rubocop-ast (>= 1.49.0, < 2.0)
5667
ruby-progressbar (~> 1.7)
57-
unicode-display_width (>= 1.4.0, < 3.0)
58-
rubocop-ast (1.12.0)
59-
parser (>= 3.0.1.1)
68+
unicode-display_width (>= 2.4.0, < 4.0)
69+
rubocop-ast (1.49.0)
70+
parser (>= 3.3.7.2)
71+
prism (~> 1.7)
6072
ruby-prof (0.18.0)
61-
ruby-progressbar (1.11.0)
62-
rubyzip (3.1.1)
63-
simplecov (0.21.2)
73+
ruby-progressbar (1.13.0)
74+
rubyzip (3.2.2)
75+
simplecov (0.22.0)
6476
docile (~> 1.1)
6577
simplecov-html (~> 0.11)
6678
simplecov_json_formatter (~> 0.1)
67-
simplecov-html (0.12.3)
68-
simplecov_json_formatter (0.1.3)
79+
simplecov-html (0.13.2)
80+
simplecov_json_formatter (0.1.4)
6981
sqlite3 (1.7.3)
7082
mini_portile2 (~> 2.8.0)
71-
unicode-display_width (2.1.0)
83+
unicode-display_width (3.2.0)
84+
unicode-emoji (~> 4.1)
85+
unicode-emoji (4.2.0)
7286

7387
PLATFORMS
7488
ruby
@@ -84,4 +98,4 @@ DEPENDENCIES
8498
simplecov (~> 0.16)
8599

86100
BUNDLED WITH
87-
2.2.22
101+
2.6.9

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ require 'rubygems'
44
require 'bundler/setup'
55

66
require 'rake'
7-
Dir['lib/tasks/**/*.rake'].sort.each { |ext| load ext }
7+
Dir['lib/tasks/**/*.rake'].each { |ext| load ext }
88

99
# Install rubygem tasks
1010
Bundler::GemHelper.install_tasks

free_zipcode_data.gemspec

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,12 @@ Gem::Specification.new do |spec|
2323
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
2424
spec.require_paths = ['lib']
2525

26-
spec.add_development_dependency 'bundler'
27-
spec.add_development_dependency 'pry-nav', '~> 0.2'
28-
spec.add_development_dependency 'rake', '~> 13.0'
29-
spec.add_development_dependency 'rspec', '~> 3.7'
30-
spec.add_development_dependency 'rubocop'
31-
spec.add_development_dependency 'ruby-prof', '~> 0.17'
32-
spec.add_development_dependency 'simplecov', '~> 0.16'
33-
34-
spec.add_runtime_dependency 'colored', '~> 1.2'
35-
spec.add_runtime_dependency 'kiba', '~> 4.0'
36-
spec.add_runtime_dependency 'optimist', '~> 3.0'
37-
spec.add_runtime_dependency 'ruby-progressbar', '~> 1.9'
38-
spec.add_runtime_dependency 'rubyzip', '>= 1.2.2'
39-
spec.add_runtime_dependency 'sqlite3', '~> 1.3'
26+
spec.add_dependency 'colored', '~> 1.2'
27+
spec.add_dependency 'csv'
28+
spec.add_dependency 'kiba', '~> 4.0'
29+
spec.add_dependency 'logger'
30+
spec.add_dependency 'optimist', '~> 3.0'
31+
spec.add_dependency 'ruby-progressbar', '~> 1.9'
32+
spec.add_dependency 'rubyzip', '>= 1.2.2'
33+
spec.add_dependency 'sqlite3', '~> 1.3'
4034
end

lib/etl/common.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def show_me
1616
def limit(count)
1717
count = Integer(count || -1)
1818
return if count == -1
19+
1920
transform do |row|
2021
@counter ||= 0
2122
@counter += 1

lib/etl/csv_source.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ def initialize(filename:, headers: true, delimeter: "\t", quote_char: '"')
1414

1515
def each
1616
CSV.open(filename,
17-
col_sep: delimeter,
18-
headers: headers,
19-
header_converters: :symbol,
20-
quote_char: quote_char) do |csv|
17+
col_sep: delimeter,
18+
headers: headers,
19+
header_converters: :symbol,
20+
quote_char: quote_char) do |csv|
2121
csv.each do |row|
2222
yield(row.to_hash)
2323
end

0 commit comments

Comments
 (0)