fips_lookup 0.3.0 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/Gemfile.lock +39 -30
- data/README.md +82 -138
- data/db/schema.sql +45 -0
- data/lib/data/fips.sqlite3 +0 -0
- data/lib/fips/county.rb +98 -0
- data/lib/fips/database.rb +55 -0
- data/lib/fips/state.rb +67 -0
- data/lib/fips/subdivision.rb +160 -0
- data/lib/fips/version.rb +5 -0
- data/lib/fips.rb +69 -0
- data/sig/fips.rbs +50 -0
- metadata +56 -125
- data/lib/data/county/AK.csv +0 -30
- data/lib/data/county/AL.csv +0 -67
- data/lib/data/county/AR.csv +0 -75
- data/lib/data/county/AS.csv +0 -5
- data/lib/data/county/AZ.csv +0 -15
- data/lib/data/county/CA.csv +0 -58
- data/lib/data/county/CO.csv +0 -64
- data/lib/data/county/CT.csv +0 -8
- data/lib/data/county/DC.csv +0 -1
- data/lib/data/county/DE.csv +0 -3
- data/lib/data/county/FL.csv +0 -67
- data/lib/data/county/GA.csv +0 -159
- data/lib/data/county/GU.csv +0 -1
- data/lib/data/county/HI.csv +0 -5
- data/lib/data/county/IA.csv +0 -99
- data/lib/data/county/ID.csv +0 -44
- data/lib/data/county/IL.csv +0 -102
- data/lib/data/county/IN.csv +0 -92
- data/lib/data/county/KS.csv +0 -105
- data/lib/data/county/KY.csv +0 -120
- data/lib/data/county/LA.csv +0 -64
- data/lib/data/county/MA.csv +0 -14
- data/lib/data/county/MD.csv +0 -24
- data/lib/data/county/ME.csv +0 -16
- data/lib/data/county/MI.csv +0 -83
- data/lib/data/county/MN.csv +0 -87
- data/lib/data/county/MO.csv +0 -115
- data/lib/data/county/MP.csv +0 -4
- data/lib/data/county/MS.csv +0 -82
- data/lib/data/county/MT.csv +0 -56
- data/lib/data/county/NC.csv +0 -100
- data/lib/data/county/ND.csv +0 -53
- data/lib/data/county/NE.csv +0 -93
- data/lib/data/county/NH.csv +0 -10
- data/lib/data/county/NJ.csv +0 -21
- data/lib/data/county/NM.csv +0 -33
- data/lib/data/county/NV.csv +0 -17
- data/lib/data/county/NY.csv +0 -62
- data/lib/data/county/OH.csv +0 -88
- data/lib/data/county/OK.csv +0 -77
- data/lib/data/county/OR.csv +0 -36
- data/lib/data/county/PA.csv +0 -67
- data/lib/data/county/PR.csv +0 -78
- data/lib/data/county/RI.csv +0 -5
- data/lib/data/county/SC.csv +0 -46
- data/lib/data/county/SD.csv +0 -66
- data/lib/data/county/TN.csv +0 -95
- data/lib/data/county/TX.csv +0 -254
- data/lib/data/county/UM.csv +0 -1
- data/lib/data/county/UT.csv +0 -29
- data/lib/data/county/VA.csv +0 -133
- data/lib/data/county/VI.csv +0 -3
- data/lib/data/county/VT.csv +0 -14
- data/lib/data/county/WA.csv +0 -39
- data/lib/data/county/WI.csv +0 -72
- data/lib/data/county/WV.csv +0 -55
- data/lib/data/county/WY.csv +0 -23
- data/lib/data/state.csv +0 -57
- data/lib/data/subdivision/AK.csv +0 -37
- data/lib/data/subdivision/AL.csv +0 -390
- data/lib/data/subdivision/AR.csv +0 -1210
- data/lib/data/subdivision/AS.csv +0 -16
- data/lib/data/subdivision/AZ.csv +0 -80
- data/lib/data/subdivision/CA.csv +0 -404
- data/lib/data/subdivision/CO.csv +0 -209
- data/lib/data/subdivision/CT.csv +0 -173
- data/lib/data/subdivision/DC.csv +0 -1
- data/lib/data/subdivision/DE.csv +0 -27
- data/lib/data/subdivision/FL.csv +0 -316
- data/lib/data/subdivision/GA.csv +0 -586
- data/lib/data/subdivision/GU.csv +0 -20
- data/lib/data/subdivision/HI.csv +0 -44
- data/lib/data/subdivision/IA.csv +0 -1662
- data/lib/data/subdivision/ID.csv +0 -170
- data/lib/data/subdivision/IL.csv +0 -1708
- data/lib/data/subdivision/IN.csv +0 -1012
- data/lib/data/subdivision/KS.csv +0 -1531
- data/lib/data/subdivision/KY.csv +0 -493
- data/lib/data/subdivision/LA.csv +0 -576
- data/lib/data/subdivision/MA.csv +0 -357
- data/lib/data/subdivision/MD.csv +0 -290
- data/lib/data/subdivision/ME.csv +0 -530
- data/lib/data/subdivision/MI.csv +0 -1579
- data/lib/data/subdivision/MN.csv +0 -2761
- data/lib/data/subdivision/MO.csv +0 -1394
- data/lib/data/subdivision/MP.csv +0 -12
- data/lib/data/subdivision/MS.csv +0 -410
- data/lib/data/subdivision/MT.csv +0 -194
- data/lib/data/subdivision/NC.csv +0 -1041
- data/lib/data/subdivision/ND.csv +0 -1762
- data/lib/data/subdivision/NE.csv +0 -1193
- data/lib/data/subdivision/NH.csv +0 -260
- data/lib/data/subdivision/NJ.csv +0 -570
- data/lib/data/subdivision/NM.csv +0 -130
- data/lib/data/subdivision/NV.csv +0 -71
- data/lib/data/subdivision/NY.csv +0 -1023
- data/lib/data/subdivision/OH.csv +0 -1606
- data/lib/data/subdivision/OK.csv +0 -305
- data/lib/data/subdivision/OR.csv +0 -212
- data/lib/data/subdivision/PA.csv +0 -2573
- data/lib/data/subdivision/PR.csv +0 -939
- data/lib/data/subdivision/RI.csv +0 -40
- data/lib/data/subdivision/SC.csv +0 -299
- data/lib/data/subdivision/SD.csv +0 -1331
- data/lib/data/subdivision/TN.csv +0 -844
- data/lib/data/subdivision/TX.csv +0 -862
- data/lib/data/subdivision/UM.csv +0 -1
- data/lib/data/subdivision/UT.csv +0 -93
- data/lib/data/subdivision/VA.csv +0 -552
- data/lib/data/subdivision/VI.csv +0 -23
- data/lib/data/subdivision/VT.csv +0 -255
- data/lib/data/subdivision/WA.csv +0 -242
- data/lib/data/subdivision/WI.csv +0 -1922
- data/lib/data/subdivision/WV.csv +0 -228
- data/lib/data/subdivision/WY.csv +0 -71
- data/lib/fips_lookup/county.rb +0 -65
- data/lib/fips_lookup/state.rb +0 -46
- data/lib/fips_lookup/subdivision.rb +0 -48
- data/lib/fips_lookup/version.rb +0 -5
- data/lib/fips_lookup.rb +0 -25
- data/sig/fips_lookup.rbs +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 919f1f05158a82904ea9d2dbf8713161dd1a606887e7d9cd67a008527445dc6c
|
|
4
|
+
data.tar.gz: 6729e4bfcacb070fdd1ca18d96e2fe8b28027fd1b8f9bd107ece3baf31146a63
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 87a6cc7f761894dde59bc809e31d4b5f1ad32a79af67aee9adc1253bebab32e999753df872e44df91663d4e81d243247cb000b05d4f4b478abca6ac18020b5b2
|
|
7
|
+
data.tar.gz: a7f74a403e70c4807e7d1472acb40e431f85d6e7aae2cf7faae743bf5dd797cbcc2c5d47624c29d0ffeb5fbba1ec65fac7d08c8f05ad3937dc52b9bdd0483184
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,79 +1,86 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
fips_lookup (0.
|
|
5
|
-
|
|
4
|
+
fips_lookup (1.0.0)
|
|
5
|
+
sqlite3 (~> 2.0)
|
|
6
|
+
zeitwerk (~> 2.8)
|
|
6
7
|
|
|
7
8
|
GEM
|
|
8
9
|
remote: https://rubygems.org/
|
|
9
10
|
specs:
|
|
10
|
-
ast (2.4.
|
|
11
|
+
ast (2.4.3)
|
|
12
|
+
benchmark (0.5.0)
|
|
11
13
|
csv (3.3.6)
|
|
12
|
-
diff-lcs (1.
|
|
14
|
+
diff-lcs (1.6.2)
|
|
13
15
|
erb (6.0.7)
|
|
14
|
-
io-console (0.9.
|
|
16
|
+
io-console (0.9.4)
|
|
15
17
|
irb (1.18.0)
|
|
16
18
|
pp (>= 0.6.0)
|
|
17
19
|
prism (>= 1.3.0)
|
|
18
20
|
rdoc (>= 4.0.0)
|
|
19
21
|
reline (>= 0.4.2)
|
|
20
|
-
json (
|
|
21
|
-
language_server-protocol (3.17.0.
|
|
22
|
+
json (3.0.2)
|
|
23
|
+
language_server-protocol (3.17.0.6)
|
|
24
|
+
lint_roller (1.1.0)
|
|
22
25
|
logger (1.7.0)
|
|
23
26
|
ostruct (0.6.3)
|
|
24
|
-
parallel (
|
|
25
|
-
parser (3.3.0
|
|
27
|
+
parallel (2.3.0)
|
|
28
|
+
parser (3.3.12.0)
|
|
26
29
|
ast (~> 2.4.1)
|
|
27
30
|
racc
|
|
28
31
|
pp (0.6.4)
|
|
29
32
|
prettyprint
|
|
30
33
|
prettyprint (0.2.0)
|
|
31
34
|
prism (1.9.0)
|
|
32
|
-
racc (1.
|
|
35
|
+
racc (1.8.1)
|
|
33
36
|
rainbow (3.1.1)
|
|
34
|
-
rake (13.
|
|
37
|
+
rake (13.4.2)
|
|
35
38
|
rbs (4.2.0)
|
|
36
39
|
logger
|
|
37
40
|
prism (>= 1.6.0)
|
|
38
41
|
tsort
|
|
39
|
-
rdoc (8.
|
|
42
|
+
rdoc (8.1.0)
|
|
40
43
|
erb
|
|
41
44
|
prism (>= 1.6.0)
|
|
42
45
|
rbs (>= 4.0.0)
|
|
43
46
|
tsort
|
|
44
|
-
regexp_parser (2.
|
|
47
|
+
regexp_parser (2.13.1)
|
|
45
48
|
reline (0.7.0)
|
|
46
49
|
io-console (~> 0.5)
|
|
47
|
-
|
|
48
|
-
rspec (3.13.0)
|
|
50
|
+
rspec (3.13.2)
|
|
49
51
|
rspec-core (~> 3.13.0)
|
|
50
52
|
rspec-expectations (~> 3.13.0)
|
|
51
53
|
rspec-mocks (~> 3.13.0)
|
|
52
|
-
rspec-core (3.13.
|
|
54
|
+
rspec-core (3.13.6)
|
|
53
55
|
rspec-support (~> 3.13.0)
|
|
54
|
-
rspec-expectations (3.13.
|
|
56
|
+
rspec-expectations (3.13.5)
|
|
55
57
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
56
58
|
rspec-support (~> 3.13.0)
|
|
57
|
-
rspec-mocks (3.13.
|
|
59
|
+
rspec-mocks (3.13.8)
|
|
58
60
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
59
61
|
rspec-support (~> 3.13.0)
|
|
60
|
-
rspec-support (3.13.
|
|
61
|
-
rubocop (1.
|
|
62
|
-
json (
|
|
63
|
-
language_server-protocol (
|
|
64
|
-
|
|
62
|
+
rspec-support (3.13.7)
|
|
63
|
+
rubocop (1.91.0)
|
|
64
|
+
json (>= 2.3)
|
|
65
|
+
language_server-protocol (~> 3.17.0.2)
|
|
66
|
+
lint_roller (~> 1.1.0)
|
|
67
|
+
parallel (>= 1.10)
|
|
65
68
|
parser (>= 3.3.0.2)
|
|
66
69
|
rainbow (>= 2.2.2, < 4.0)
|
|
67
|
-
regexp_parser (>=
|
|
68
|
-
|
|
69
|
-
rubocop-ast (>= 1.31.1, < 2.0)
|
|
70
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
71
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
70
72
|
ruby-progressbar (~> 1.7)
|
|
71
|
-
unicode-display_width (>= 2.4.0, <
|
|
72
|
-
rubocop-ast (1.
|
|
73
|
-
parser (>= 3.3.
|
|
73
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
74
|
+
rubocop-ast (1.50.0)
|
|
75
|
+
parser (>= 3.3.7.2)
|
|
76
|
+
prism (~> 1.7)
|
|
74
77
|
ruby-progressbar (1.13.0)
|
|
78
|
+
sqlite3 (2.9.6-arm64-darwin)
|
|
75
79
|
tsort (0.2.0)
|
|
76
|
-
unicode-display_width (
|
|
80
|
+
unicode-display_width (3.3.0)
|
|
81
|
+
unicode-emoji (~> 4.3)
|
|
82
|
+
unicode-emoji (4.3.0)
|
|
83
|
+
zeitwerk (2.8.3)
|
|
77
84
|
|
|
78
85
|
PLATFORMS
|
|
79
86
|
arm64-darwin-21
|
|
@@ -81,6 +88,8 @@ PLATFORMS
|
|
|
81
88
|
arm64-darwin-25
|
|
82
89
|
|
|
83
90
|
DEPENDENCIES
|
|
91
|
+
benchmark (~> 0.2)
|
|
92
|
+
csv (~> 3.0)
|
|
84
93
|
fips_lookup!
|
|
85
94
|
irb
|
|
86
95
|
ostruct
|
data/README.md
CHANGED
|
@@ -1,198 +1,142 @@
|
|
|
1
|
-
#
|
|
1
|
+
# FIPS Lookup
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Fips_lookup is a gem that functions as a lookup used to identify county and state FIPS codes.
|
|
6
|
-
|
|
7
|
-
What are FIPS codes? The United States Federal Communications Commission (FCC) [says:](https://transition.fcc.gov/oet/info/maps/census/fips/fips.txt)
|
|
8
|
-
|
|
9
|
-
> Federal Information Processing System (FIPS) Codes for States and Counties
|
|
10
|
-
>
|
|
11
|
-
> FIPS codes are numbers which uniquely identify geographic areas. The number of
|
|
12
|
-
digits in FIPS codes vary depending on the level of geography. State-level FIPS
|
|
13
|
-
codes have two digits, county-level FIPS codes have five digits of which the
|
|
14
|
-
first two are the FIPS code of the state to which the county belongs.
|
|
15
|
-
|
|
16
|
-
_Note:_ FIPS codes are updated by the US census department they can be seen and accessed [here](https://www.census.gov/library/reference/code-lists/ansi.html).
|
|
17
|
-
|
|
18
|
-
<br>
|
|
19
|
-
|
|
20
|
-
**Interesting challenge:** <br>
|
|
21
|
-
Multiple states can have the same county name — 16 states have a "Wayne County". This means a state & county pair is required to lookup and return the proper FIPS code.
|
|
22
|
-
This gem utilizes memoization to increase lookup efficiency to csv files without adding complexity to your app.
|
|
3
|
+
`fips_lookup` provides lookups for U.S. states, counties, and county subdivisions using Census FIPS identifiers and names. Results are hashes containing the fields for the requested geography. Lookup data is stored in a bundled SQLite database and queried read-only at runtime.
|
|
23
4
|
|
|
24
5
|
## Installation
|
|
25
6
|
|
|
26
|
-
Add
|
|
7
|
+
Add the gem to your application's Gemfile:
|
|
27
8
|
|
|
28
9
|
```ruby
|
|
29
|
-
gem
|
|
10
|
+
gem "fips_lookup"
|
|
30
11
|
```
|
|
31
12
|
|
|
32
|
-
|
|
13
|
+
Then run `bundle install`.
|
|
33
14
|
|
|
34
|
-
|
|
15
|
+
## Usage
|
|
35
16
|
|
|
36
|
-
|
|
17
|
+
Require the gem if your application does not use Bundler's automatic loading:
|
|
37
18
|
|
|
38
|
-
|
|
19
|
+
```ruby
|
|
20
|
+
require "fips"
|
|
21
|
+
```
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
<br>
|
|
23
|
+
### General lookup
|
|
42
24
|
|
|
43
|
-
|
|
25
|
+
`FIPS.lookup` dispatches to the state, county, or subdivision lookup based on the supplied identifiers:
|
|
44
26
|
|
|
45
|
-
|
|
27
|
+
```ruby
|
|
28
|
+
FIPS.lookup(fips: "02")
|
|
29
|
+
# => { fips: "02", abbr: "AK", name: "Alaska", ansi: "01785533" }
|
|
46
30
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
FipsLookup.county(state_param: "AL", county_param: "Autauga County") # => {:state_code=>"AL", :fips=>"01001", :name=>"Autauga County", :class_code=>"H1"}
|
|
50
|
-
```
|
|
31
|
+
FIPS.lookup(fips: "02060")
|
|
32
|
+
# => county record for Bristol Bay Borough
|
|
51
33
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
— "Autauga County" can be found, "Autauga" can not be found.
|
|
55
|
-
* `return_nil` - (Boolean) is an optional parameter that when used overrides any Errors from input and returns an empty hash `{}`.
|
|
56
|
-
* Ex: `FipsLookup.county(state_param: "AL", county_param: "Autauga", return_nil: true) # => {}`
|
|
34
|
+
FIPS.lookup(fips: "0206009050")
|
|
35
|
+
# => subdivision record for Bristol Bay census subarea
|
|
57
36
|
|
|
58
|
-
|
|
37
|
+
FIPS.lookup(state: "Alaska", county: "Bristol Bay Borough")
|
|
38
|
+
# => county record for Bristol Bay Borough
|
|
59
39
|
|
|
60
|
-
|
|
40
|
+
FIPS.lookup(state: "Alaska", county: "Bristol Bay Borough",
|
|
41
|
+
subdivision: "Bristol Bay census subarea")
|
|
42
|
+
# => subdivision record for Bristol Bay census subarea
|
|
43
|
+
```
|
|
61
44
|
|
|
62
|
-
|
|
45
|
+
FIPS codes must be strings so leading zeroes are preserved. The general dispatcher supports state FIPS (2 digits), county FIPS (5 digits), and full subdivision FIPS (10 digits). Contextual forms are also available through the specific lookup methods below.
|
|
63
46
|
|
|
64
|
-
|
|
47
|
+
### State lookup
|
|
65
48
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
49
|
+
```ruby
|
|
50
|
+
FIPS::State.lookup(fips: "02")
|
|
51
|
+
FIPS::State.lookup(state: "AK")
|
|
52
|
+
FIPS::State.lookup(state: "Alaska")
|
|
53
|
+
FIPS::State.lookup(state: "01785533") # ANSI code
|
|
69
54
|
```
|
|
70
|
-
<br>
|
|
71
|
-
<hr>
|
|
72
55
|
|
|
73
|
-
|
|
56
|
+
The returned state hash has `:fips`, `:abbr`, `:name`, and `:ansi` keys. `FIPS::State.all` returns all state records in the same format:
|
|
74
57
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
FipsLookup.fips_county(fips: "01001") # => ["Autauga County", "AL"]
|
|
58
|
+
```ruby
|
|
59
|
+
FIPS::State.all.map { |state| [state[:name], state[:abbr]] }
|
|
78
60
|
```
|
|
79
61
|
|
|
80
|
-
|
|
81
|
-
* `return_nil` - (Boolean) is an optional parameter that when used overrides any Errors from input and returns `nil`.
|
|
82
|
-
* Ex: `FipsLookup.fips_county(fips: "03000", return_nil: true) # => nil`
|
|
62
|
+
### County lookup
|
|
83
63
|
|
|
84
|
-
|
|
85
|
-
|
|
64
|
+
```ruby
|
|
65
|
+
FIPS::County.lookup(fips: "02060")
|
|
66
|
+
FIPS::County.lookup(fips: "060", state: "AK")
|
|
67
|
+
FIPS::County.lookup(fips: "02", county: "Bristol Bay Borough")
|
|
68
|
+
FIPS::County.lookup(state: "Alaska", county: "Bristol Bay Borough")
|
|
69
|
+
```
|
|
86
70
|
|
|
87
|
-
|
|
71
|
+
The returned county hash has `:state_abbr`, `:fips`, `:gnis`, `:name`, `:class_code`, and `:status` keys. State identifiers may be an abbreviation, name, FIPS code, or ANSI code.
|
|
88
72
|
|
|
89
|
-
|
|
73
|
+
To list counties in a state, use `FIPS::County.all`:
|
|
90
74
|
|
|
91
|
-
```
|
|
92
|
-
|
|
75
|
+
```ruby
|
|
76
|
+
counties = FIPS::County.all(state: "AK")
|
|
77
|
+
county_names = counties.map { |county| county[:name] }
|
|
93
78
|
```
|
|
94
79
|
|
|
95
|
-
|
|
96
|
-
* `return_nil` - (Boolean) is an optional parameter that when used overrides any Errors from input and returns an empty hash `{}`.
|
|
80
|
+
### Subdivision lookup
|
|
97
81
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
82
|
+
```ruby
|
|
83
|
+
FIPS::Subdivision.lookup(fips: "0206009050")
|
|
84
|
+
FIPS::Subdivision.lookup(fips: "09050", state: "AK")
|
|
85
|
+
FIPS::Subdivision.lookup(fips: "02060", subdivision: "Bristol Bay census subarea")
|
|
86
|
+
FIPS::Subdivision.lookup(fips: "060", state: "AK",
|
|
87
|
+
subdivision: "Bristol Bay census subarea")
|
|
88
|
+
FIPS::Subdivision.lookup(state: "Alaska", county: "Bristol Bay Borough",
|
|
89
|
+
subdivision: "Bristol Bay census subarea")
|
|
105
90
|
```
|
|
106
|
-
<br>
|
|
107
|
-
<hr>
|
|
108
91
|
|
|
109
|
-
|
|
92
|
+
The returned subdivision hash has `:state_abbr`, `:fips`, `:county_name`, `:gnis`, `:name`, `:class_code`, and `:status` keys. To retrieve subdivision records for a state, optionally filtered by county:
|
|
110
93
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
```
|
|
115
|
-
FipsLookup.find_state_code(state_param: "MicHiGan") # => "MI"
|
|
94
|
+
```ruby
|
|
95
|
+
FIPS::Subdivision.all(state: "AK")
|
|
96
|
+
FIPS::Subdivision.all(state: "AK", county: "Bristol Bay Borough")
|
|
116
97
|
```
|
|
117
98
|
|
|
118
|
-
|
|
119
|
-
<hr>
|
|
99
|
+
County and subdivision name matching is case-insensitive. State identifiers accept abbreviations, names, FIPS codes, and ANSI codes.
|
|
120
100
|
|
|
121
|
-
|
|
101
|
+
### Source data
|
|
122
102
|
|
|
123
|
-
|
|
103
|
+
The raw and intermediate CSV datasets are retained under `source_data/` for rebuilding and auditing. They are development inputs, are not used at runtime, and are not included in the published gem. Runtime lookups and collection methods use the bundled SQLite database.
|
|
124
104
|
|
|
125
|
-
###
|
|
105
|
+
### Database build
|
|
126
106
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
```
|
|
130
|
-
# in controller.rb
|
|
131
|
-
@state_options = []
|
|
132
|
-
CSV.foreach(FipsLookup.state_file) do |state_row|
|
|
133
|
-
@state_options << [state_row[2], state_row[1]]
|
|
134
|
-
end
|
|
135
|
-
```
|
|
107
|
+
The checked-in schema is in `db/schema.sql`. The bundled database is built from the 2020 Census county and county-subdivision source files. To rebuild it from the source text files and state data, run:
|
|
136
108
|
|
|
109
|
+
```sh
|
|
110
|
+
bundle exec ruby bin/db/build
|
|
137
111
|
```
|
|
138
|
-
# in html.erb (within `form_with do |form|` block)
|
|
139
|
-
<%= form.label :state, style: "display: block" %>
|
|
140
|
-
<%= form.select :state, @state_options %>
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### Path to state specific county csv files [.county_file(state_code: "state param")](/fips_lookup/lib/fips_lookup.rb?#L59)
|
|
144
112
|
|
|
145
|
-
|
|
113
|
+
### Errors
|
|
146
114
|
|
|
147
|
-
|
|
115
|
+
Malformed or insufficient inputs raise `ArgumentError`. Validly formatted identifiers that do not match a record raise `FIPS::NotFoundError`, a subclass of `StandardError`:
|
|
148
116
|
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
@county_options << county_row[3]
|
|
117
|
+
```ruby
|
|
118
|
+
begin
|
|
119
|
+
FIPS::County.lookup(fips: "02999")
|
|
120
|
+
rescue FIPS::NotFoundError => error
|
|
121
|
+
warn error.message
|
|
155
122
|
end
|
|
156
123
|
```
|
|
157
124
|
|
|
158
|
-
```
|
|
159
|
-
# in html.erb (within `form_with do |form|` block)
|
|
160
|
-
<%= form.label :county, style: "display: block" %>
|
|
161
|
-
<%= form.select :county, @county_options %>
|
|
162
|
-
```
|
|
163
|
-
|
|
164
125
|
## Development
|
|
165
126
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
Check installation and any changes by running `rspec` to run the tests.
|
|
169
|
-
|
|
170
|
-
Use `bin/console` to open IRB console with FIPS gem included and ready to use.
|
|
171
|
-
|
|
172
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
173
|
-
|
|
174
|
-
To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
127
|
+
Install dependencies with `bin/setup`. Run tests and lint with:
|
|
175
128
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
#### New to this gem?
|
|
182
|
-
|
|
183
|
-
* The main working file is `lib/fips_lookup.rb` with usage examples in the test file: `spec/fips_lookup_spec.rb`
|
|
184
|
-
* [The first pull request](https://github.com/3barroso/fips_lookup/pull/1) contains more details to decisions and considerations when first launching gem.
|
|
129
|
+
```sh
|
|
130
|
+
bundle exec rspec
|
|
131
|
+
bundle exec rubocop
|
|
132
|
+
```
|
|
185
133
|
|
|
134
|
+
Open an IRB console with `bin/console`. Install locally with `bundle exec rake install`.
|
|
186
135
|
|
|
187
136
|
## Contributing
|
|
188
137
|
|
|
189
|
-
Bug reports and pull requests are welcome
|
|
190
|
-
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/3barroso/fips_lookup/blob/main/CODE_OF_CONDUCT.md).
|
|
138
|
+
Bug reports and pull requests are welcome in the [FIPS repository](https://github.com/3barroso/fips_lookup). Contributors are expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
191
139
|
|
|
192
140
|
## License
|
|
193
141
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
## Code of Conduct
|
|
197
|
-
|
|
198
|
-
Everyone interacting in the FIPS project's codebase, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/3barroso/fips_lookup/blob/main/CODE_OF_CONDUCT.md).
|
|
142
|
+
This gem is available under the terms of the MIT License. See [LICENSE.txt](LICENSE.txt).
|
data/db/schema.sql
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
PRAGMA foreign_keys = ON;
|
|
2
|
+
|
|
3
|
+
CREATE TABLE states (
|
|
4
|
+
state_fips TEXT PRIMARY KEY,
|
|
5
|
+
state_abbr TEXT NOT NULL UNIQUE,
|
|
6
|
+
name TEXT NOT NULL,
|
|
7
|
+
name_key TEXT NOT NULL,
|
|
8
|
+
ansi TEXT NOT NULL UNIQUE
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
CREATE INDEX states_name_key_idx ON states(name_key);
|
|
12
|
+
|
|
13
|
+
CREATE TABLE counties (
|
|
14
|
+
state_fips TEXT NOT NULL,
|
|
15
|
+
county_fips TEXT NOT NULL,
|
|
16
|
+
full_fips TEXT NOT NULL UNIQUE,
|
|
17
|
+
name TEXT NOT NULL,
|
|
18
|
+
name_key TEXT NOT NULL,
|
|
19
|
+
gnis TEXT NOT NULL,
|
|
20
|
+
class_code TEXT NOT NULL,
|
|
21
|
+
status TEXT NOT NULL,
|
|
22
|
+
PRIMARY KEY (state_fips, county_fips),
|
|
23
|
+
FOREIGN KEY (state_fips) REFERENCES states(state_fips)
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
CREATE INDEX counties_state_name_idx ON counties(state_fips, name_key);
|
|
27
|
+
|
|
28
|
+
CREATE TABLE subdivisions (
|
|
29
|
+
full_fips TEXT PRIMARY KEY,
|
|
30
|
+
state_fips TEXT NOT NULL,
|
|
31
|
+
county_fips TEXT NOT NULL,
|
|
32
|
+
subdivision_fips TEXT NOT NULL,
|
|
33
|
+
name TEXT NOT NULL,
|
|
34
|
+
name_key TEXT NOT NULL,
|
|
35
|
+
gnis TEXT NOT NULL,
|
|
36
|
+
class_code TEXT NOT NULL,
|
|
37
|
+
status TEXT NOT NULL,
|
|
38
|
+
UNIQUE (state_fips, county_fips, subdivision_fips),
|
|
39
|
+
FOREIGN KEY (state_fips, county_fips) REFERENCES counties(state_fips, county_fips)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
CREATE INDEX subdivisions_state_subdivision_fips_idx
|
|
43
|
+
ON subdivisions(state_fips, subdivision_fips);
|
|
44
|
+
CREATE INDEX subdivisions_county_name_idx
|
|
45
|
+
ON subdivisions(state_fips, county_fips, name_key);
|
|
Binary file
|
data/lib/fips/county.rb
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# FIPS::County
|
|
4
|
+
module FIPS
|
|
5
|
+
class County
|
|
6
|
+
extend FIPS::Database::Access
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def lookup(**params)
|
|
10
|
+
fips = params.fetch(:fips, nil)
|
|
11
|
+
state = params.fetch(:state, nil)
|
|
12
|
+
county = params.fetch(:county, nil)
|
|
13
|
+
|
|
14
|
+
unless fips.nil? || (fips.is_a?(String) && fips.match?(/\A(?:\d{2}|\d{3}|\d{5})\z/))
|
|
15
|
+
raise ArgumentError, "FIPS input must be a 2, 3, or 5 digit string"
|
|
16
|
+
end
|
|
17
|
+
raise ArgumentError, "State input must be a non-empty string" unless state.nil? || (state.is_a?(String) && !state.strip.empty?)
|
|
18
|
+
raise ArgumentError, "County input must be a non-empty string" unless county.nil? || (county.is_a?(String) && !county.strip.empty?)
|
|
19
|
+
|
|
20
|
+
location = identify_with_fips(fips, state, county)
|
|
21
|
+
return location unless location.nil?
|
|
22
|
+
|
|
23
|
+
if !county.nil? && !state.nil?
|
|
24
|
+
state_fips = FIPS::State.lookup(state: state)[:fips]
|
|
25
|
+
return by_name(state_fips, county)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
raise ArgumentError, "Could not identify county with parameters provided: #{params.inspect}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def all(state:)
|
|
32
|
+
raise ArgumentError, "State input must be a non-empty string" unless state.is_a?(String) && !state.strip.empty?
|
|
33
|
+
|
|
34
|
+
state_fips = FIPS::State.lookup(state: state)[:fips]
|
|
35
|
+
rows = db_all(
|
|
36
|
+
"SELECT states.state_abbr AS state_abbr, counties.full_fips AS fips, counties.gnis, counties.name, counties.class_code, counties.status " \
|
|
37
|
+
"FROM counties JOIN states USING (state_fips) WHERE counties.state_fips = ? ORDER BY counties.county_fips",
|
|
38
|
+
[state_fips]
|
|
39
|
+
)
|
|
40
|
+
rows.map { |county_row| formatted_county(county_row) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def identify_with_fips(fips, state, county)
|
|
46
|
+
return nil if fips.nil? || !fips.is_a?(String)
|
|
47
|
+
|
|
48
|
+
case fips.length
|
|
49
|
+
when 2
|
|
50
|
+
return nil if county.nil?
|
|
51
|
+
|
|
52
|
+
return by_name(fips, county)
|
|
53
|
+
when 3
|
|
54
|
+
return nil if state.nil?
|
|
55
|
+
|
|
56
|
+
state_fips = FIPS::State.lookup(state: state)[:fips]
|
|
57
|
+
return by_fips(state_fips + fips)
|
|
58
|
+
when 5
|
|
59
|
+
return by_fips(fips)
|
|
60
|
+
end
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def by_fips(fips)
|
|
65
|
+
county_row = db_first(
|
|
66
|
+
"SELECT states.state_abbr AS state_abbr, counties.full_fips AS fips, counties.gnis, counties.name, counties.class_code, counties.status " \
|
|
67
|
+
"FROM counties JOIN states USING (state_fips) WHERE counties.full_fips = ?",
|
|
68
|
+
[fips]
|
|
69
|
+
)
|
|
70
|
+
return formatted_county(county_row) unless county_row.nil?
|
|
71
|
+
|
|
72
|
+
raise FIPS::NotFoundError, "Could not identify county with fips: #{fips}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def by_name(state_fips, county)
|
|
76
|
+
county_row = db_first(
|
|
77
|
+
"SELECT states.state_abbr AS state_abbr, counties.full_fips AS fips, counties.gnis, counties.name, counties.class_code, counties.status " \
|
|
78
|
+
"FROM counties JOIN states USING (state_fips) WHERE counties.state_fips = ? AND counties.name_key = ?",
|
|
79
|
+
[state_fips, county.upcase]
|
|
80
|
+
)
|
|
81
|
+
return formatted_county(county_row) unless county_row.nil?
|
|
82
|
+
|
|
83
|
+
raise FIPS::NotFoundError, "Could not identify county with name: #{county}, in: #{state_fips}"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def formatted_county(row)
|
|
87
|
+
{
|
|
88
|
+
state_abbr: row["state_abbr"],
|
|
89
|
+
fips: row["fips"],
|
|
90
|
+
gnis: row["gnis"],
|
|
91
|
+
name: row["name"],
|
|
92
|
+
class_code: row["class_code"],
|
|
93
|
+
status: row["status"]
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sqlite3"
|
|
4
|
+
|
|
5
|
+
module FIPS
|
|
6
|
+
module Database
|
|
7
|
+
DB_PATH = File.expand_path("../data/fips.sqlite3", __dir__).freeze
|
|
8
|
+
|
|
9
|
+
module Access
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def db_all(sql, bind_params = [])
|
|
13
|
+
Client.all(sql, bind_params)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def db_first(sql, bind_params = [])
|
|
17
|
+
Client.first(sql, bind_params)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Client
|
|
22
|
+
class << self
|
|
23
|
+
def all(sql, bind_params = [])
|
|
24
|
+
synchronize { connection.execute(sql, bind_params) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def first(sql, bind_params = [])
|
|
28
|
+
synchronize { connection.get_first_row(sql, bind_params) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def synchronize(&)
|
|
34
|
+
mutex.synchronize(&)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def mutex
|
|
38
|
+
@mutex ||= Mutex.new
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def connection
|
|
42
|
+
@connection ||= begin
|
|
43
|
+
raise LoadError, "FIPS SQLite database is missing at #{DB_PATH}; run bin/db/build" unless File.file?(DB_PATH)
|
|
44
|
+
|
|
45
|
+
database = SQLite3::Database.new(DB_PATH, readonly: true)
|
|
46
|
+
database.results_as_hash = true
|
|
47
|
+
database
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private_constant :Client
|
|
54
|
+
end
|
|
55
|
+
end
|