bank_routing 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/lib/bank_routing/routing_numbers.rb +33 -177
- data/lib/bank_routing/storage/base.rb +58 -0
- data/lib/bank_routing/storage/memory.rb +29 -0
- data/lib/bank_routing/storage/redis.rb +73 -0
- data/lib/bank_routing/version.rb +2 -2
- data/spec/bank_routing_spec.rb +16 -3
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6095689d8c9a679401259f9aa5fda46f02bdd3d1
|
4
|
+
data.tar.gz: 49eeb38e38a0b73334103bc1ae47fb4673451895
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb6c5ab3054ab5fad8a5e9b1770a04660b09b4eaef9b8904bfeb707fcb71d40f181c337c9b7382e3ffd068cf0d52db39832805839c15d1160ebd0aa746151ca0
|
7
|
+
data.tar.gz: 69612ef6bfa9b1be5fe9fa58cef15c3c3632f312386cddddc4c6cfec1744f04a126c45d117e26a8b3db67e600f174f6175f436cf2a511f8fc210e856eb9e3886
|
data/README.md
CHANGED
@@ -26,6 +26,7 @@ RoutingNumber.get(121000358)["name"] # => "Bank of America"
|
|
26
26
|
By default, the routing number database is loaded from a local copy of the Federal Reserve dump file and stored in memory (it's not really that big). To change that behavior:
|
27
27
|
|
28
28
|
```ruby
|
29
|
+
require 'bank_routing/storage/redis'
|
29
30
|
RoutingNumber.init!( store_in: :redis, store_opts: { db: 15 }, fetch_fed_data: true )
|
30
31
|
```
|
31
32
|
|
@@ -34,6 +35,7 @@ This will store the routing number database, after being loaded from the Federal
|
|
34
35
|
You can also configure the service independently of loading the routing database. This works especially well in systems where there are child processes connecting to the same store (Redis, for example). In this case, the routing database will be loaded on first access (by the first child process of a Unicorn web server, for example) - it's essentially lazy-loaded, so a priming call is suggested after system startup. It is worth noting that if a shared data store is used (Redis), it will load the database only once, no matter how many processes attempt access.
|
35
36
|
|
36
37
|
```ruby
|
38
|
+
require 'bank_routing/storage/redis'
|
37
39
|
RoutingNumber.options = {
|
38
40
|
store_in: :redis,
|
39
41
|
store_opts: {
|
@@ -1,163 +1,14 @@
|
|
1
1
|
require 'logger'
|
2
2
|
require 'yajl'
|
3
3
|
|
4
|
+
require "bank_routing/storage/memory"
|
5
|
+
|
4
6
|
class RoutingNumber
|
5
|
-
|
7
|
+
|
6
8
|
class InvalidStore < StandardError; end
|
7
|
-
|
8
|
-
class StoreBase
|
9
|
-
|
10
|
-
def initialize(opts = {})
|
11
|
-
@options = opts
|
12
|
-
@logger = opts.delete(:logger)
|
13
|
-
end
|
14
|
-
|
15
|
-
def log
|
16
|
-
@logger ||= Logger.new(STDOUT)
|
17
|
-
end
|
18
|
-
|
19
|
-
def options
|
20
|
-
@options
|
21
|
-
end
|
22
|
-
|
23
|
-
def loaded?
|
24
|
-
@loaded
|
25
|
-
end
|
26
|
-
|
27
|
-
def loaded!
|
28
|
-
@loaded = true
|
29
|
-
end
|
30
|
-
|
31
|
-
def loading!
|
32
|
-
return false if loading?
|
33
|
-
@loading = true
|
34
|
-
end
|
35
|
-
|
36
|
-
def loading?
|
37
|
-
@loading
|
38
|
-
end
|
39
|
-
|
40
|
-
def done_loading!
|
41
|
-
@loading = false
|
42
|
-
loaded!
|
43
|
-
end
|
44
|
-
|
45
|
-
def save(num,obj)
|
46
|
-
puts "Don't know how to save!"
|
47
|
-
end
|
48
|
-
|
49
|
-
def get(num)
|
50
|
-
puts "Don't know how to get!"
|
51
|
-
end
|
52
|
-
|
53
|
-
def shutdown!
|
54
|
-
end
|
55
|
-
|
56
|
-
def reconnect!
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
class MemStore < StoreBase
|
62
|
-
|
63
|
-
def initialize(opts = {})
|
64
|
-
@options = opts
|
65
|
-
@vals = {}
|
66
|
-
end
|
67
|
-
|
68
|
-
def save(num,obj)
|
69
|
-
@vals[num.to_s] = obj
|
70
|
-
end
|
71
|
-
|
72
|
-
def get(num)
|
73
|
-
@vals[num.to_s]
|
74
|
-
end
|
75
|
-
|
76
|
-
def shutdown!
|
77
|
-
@vals = nil
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|
81
|
-
|
82
|
-
class RedisStore < StoreBase
|
83
|
-
|
84
|
-
LOADED_KEY = "LoadedRoutingNumbers".freeze
|
85
|
-
LOADING_KEY = "#{LOADED_KEY}::loading".freeze
|
86
|
-
STORAGE_KEY = "RoutingNumberLookup"
|
87
|
-
|
88
|
-
def initialize(*args)
|
89
|
-
super(*args)
|
90
|
-
require 'redis'
|
91
|
-
end
|
92
|
-
|
93
|
-
def loaded?
|
94
|
-
store.exists(LOADED_KEY)
|
95
|
-
end
|
96
|
-
|
97
|
-
def loaded!
|
98
|
-
store.set(LOADED_KEY,"yes")
|
99
|
-
store.expire(LOADED_KEY,60*60*24*7)
|
100
|
-
end
|
101
|
-
|
102
|
-
def loading!
|
103
|
-
store.setnx(LOADING_KEY,"yup")
|
104
|
-
end
|
105
|
-
|
106
|
-
def loading?
|
107
|
-
loading!
|
108
|
-
end
|
109
|
-
|
110
|
-
def done_loading!
|
111
|
-
store.del(LOADING_KEY)
|
112
|
-
loaded!
|
113
|
-
end
|
114
|
-
|
115
|
-
def save(num,obj)
|
116
|
-
store.hset(STORAGE_KEY,num.to_s,Yajl::Encoder.encode(obj))
|
117
|
-
end
|
118
|
-
|
119
|
-
def get(num)
|
120
|
-
if cnt = store.hget(STORAGE_KEY,num.to_s)
|
121
|
-
Yajl::Parser.new(symbolize_keys: true).parse(cnt)
|
122
|
-
else
|
123
|
-
nil
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def store
|
128
|
-
@store ||= (
|
129
|
-
log.info "Connecting to Redis."
|
130
|
-
log.debug "Connection settings: #{options.inspect}"
|
131
|
-
Redis.new(options)
|
132
|
-
)
|
133
|
-
end
|
134
|
-
|
135
|
-
def reconnect!
|
136
|
-
@store = nil
|
137
|
-
store
|
138
|
-
end
|
139
|
-
|
140
|
-
def shutdown!
|
141
|
-
@store = nil
|
142
|
-
end
|
143
|
-
|
144
|
-
end
|
145
|
-
|
146
|
-
# class MemcacheStore < StoreBase
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
# end
|
151
|
-
|
152
|
-
Stores = {
|
153
|
-
default: MemStore,
|
154
|
-
memory: MemStore,
|
155
|
-
redis: RedisStore,
|
156
|
-
# memcached: MemcachedStore
|
157
|
-
}
|
158
|
-
|
9
|
+
|
159
10
|
class << self
|
160
|
-
|
11
|
+
|
161
12
|
FORMAT = [
|
162
13
|
[:route,9],
|
163
14
|
[:office_code,1],
|
@@ -178,7 +29,7 @@ class RoutingNumber
|
|
178
29
|
[:data_view_code,1],
|
179
30
|
[:filler,5]
|
180
31
|
].freeze
|
181
|
-
|
32
|
+
|
182
33
|
DefaultOptions = {
|
183
34
|
store_in: :memory,
|
184
35
|
routing_data_url: "https://www.fededirectory.frb.org/FedACHdir.txt",
|
@@ -188,7 +39,7 @@ class RoutingNumber
|
|
188
39
|
mapping_file: File.expand_path(File.dirname(__FILE__) + "/../../data/mappings.json"),
|
189
40
|
metadata_file: File.expand_path(File.dirname(__FILE__) + "/../../data/metadata.json")
|
190
41
|
}.freeze
|
191
|
-
|
42
|
+
|
192
43
|
def init!(opts = {})
|
193
44
|
return if @initted
|
194
45
|
@options = options.merge(opts)
|
@@ -202,11 +53,11 @@ class RoutingNumber
|
|
202
53
|
end
|
203
54
|
@initted = true
|
204
55
|
end
|
205
|
-
|
56
|
+
|
206
57
|
def options=(opts)
|
207
58
|
@options = options.merge(opts)
|
208
59
|
end
|
209
|
-
|
60
|
+
|
210
61
|
def get(num)
|
211
62
|
init!(options)
|
212
63
|
if cnt = store.get(num.to_s)
|
@@ -217,7 +68,7 @@ class RoutingNumber
|
|
217
68
|
end
|
218
69
|
alias_method :[], :get
|
219
70
|
alias_method :find, :get
|
220
|
-
|
71
|
+
|
221
72
|
def store_in(name, opts={})
|
222
73
|
raise InvalidStore unless s_cls = Stores[name.to_sym]
|
223
74
|
return if @store.is_a?(s_cls)
|
@@ -228,26 +79,31 @@ class RoutingNumber
|
|
228
79
|
options[:store_opts] = opts
|
229
80
|
init!
|
230
81
|
end
|
231
|
-
|
82
|
+
|
232
83
|
def reconnect!
|
233
84
|
log.info "Reconnecting!"
|
234
85
|
store.reconnect!
|
235
86
|
end
|
236
|
-
|
87
|
+
|
88
|
+
def fetch_fresh_data!( fetch=true )
|
89
|
+
options[ :fetch_fed_data ] = fetch
|
90
|
+
load_routing_numbers( get_raw_data )
|
91
|
+
end
|
92
|
+
|
237
93
|
private
|
238
|
-
|
94
|
+
|
239
95
|
def store
|
240
96
|
@store ||= default_store
|
241
97
|
end
|
242
|
-
|
98
|
+
|
243
99
|
def default_store
|
244
100
|
Stores[:default].new(logger: log)
|
245
101
|
end
|
246
|
-
|
102
|
+
|
247
103
|
def options
|
248
104
|
@options ||= DefaultOptions.dup
|
249
105
|
end
|
250
|
-
|
106
|
+
|
251
107
|
def get_raw_data
|
252
108
|
unless options[:fetch_fed_data]
|
253
109
|
log.info "Using routing data from local file at: #{options[:routing_data_file]}"
|
@@ -258,7 +114,7 @@ class RoutingNumber
|
|
258
114
|
Typhoeus::Request.get(options[:routing_data_url], :ssl_verifypeer => false).body
|
259
115
|
end
|
260
116
|
end
|
261
|
-
|
117
|
+
|
262
118
|
def load_routing_numbers(data=get_raw_data)
|
263
119
|
if store.loading!
|
264
120
|
loading!
|
@@ -269,7 +125,7 @@ class RoutingNumber
|
|
269
125
|
done_loading!
|
270
126
|
end
|
271
127
|
end
|
272
|
-
|
128
|
+
|
273
129
|
def process_line(line)
|
274
130
|
obj = unpack_line(line)
|
275
131
|
rt = obj.delete(:route)
|
@@ -280,24 +136,24 @@ class RoutingNumber
|
|
280
136
|
print "\r #{@cur} loaded (#{tm.round(2)} seconds ~ #{tm > 0 ? (@cur / tm).round(0) : "?"}/sec) "
|
281
137
|
end
|
282
138
|
end
|
283
|
-
|
139
|
+
|
284
140
|
def pretty_maps
|
285
141
|
@pretty_maps
|
286
142
|
end
|
287
|
-
|
143
|
+
|
288
144
|
def loading!
|
289
145
|
@cur = 0
|
290
146
|
@st_time = Time.now
|
291
147
|
@pretty_maps = Yajl::Parser.parse(IO.read(options[:mapping_file]))
|
292
148
|
@metadata = Yajl::Parser.parse(IO.read(options[:metadata_file]))
|
293
149
|
end
|
294
|
-
|
150
|
+
|
295
151
|
def done_loading!
|
296
152
|
tm = Time.now - @st_time
|
297
153
|
print "\r #{@cur} loaded (#{tm.round(2)} seconds ~ #{tm > 0 ? (@cur / tm).round(0) : "?"}/sec) \n"
|
298
154
|
@cur = @st_time = @pretty_maps = @metadata = nil
|
299
155
|
end
|
300
|
-
|
156
|
+
|
301
157
|
def unpack_line(line)
|
302
158
|
vals = line.unpack(unpack_template)
|
303
159
|
obj = {}
|
@@ -310,7 +166,7 @@ class RoutingNumber
|
|
310
166
|
obj.delete(:garbage)
|
311
167
|
obj
|
312
168
|
end
|
313
|
-
|
169
|
+
|
314
170
|
def converted(name,val)
|
315
171
|
if pretty_maps[name.to_s] and (v = pretty_maps[name.to_s][val])
|
316
172
|
v
|
@@ -318,17 +174,17 @@ class RoutingNumber
|
|
318
174
|
val
|
319
175
|
end
|
320
176
|
end
|
321
|
-
|
177
|
+
|
322
178
|
def unpack_template
|
323
179
|
@unpack_format ||= FORMAT.map do |(name,length)|
|
324
180
|
"A#{length}"
|
325
181
|
end.join
|
326
182
|
end
|
327
|
-
|
183
|
+
|
328
184
|
def log
|
329
185
|
@logger ||= Logger.new(STDOUT)
|
330
186
|
end
|
331
|
-
|
187
|
+
|
332
188
|
end
|
333
|
-
|
334
|
-
end
|
189
|
+
|
190
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class RoutingNumber
|
2
|
+
|
3
|
+
Stores = {}
|
4
|
+
|
5
|
+
class StoreBase
|
6
|
+
|
7
|
+
def initialize(opts = {})
|
8
|
+
@options = opts
|
9
|
+
@logger = opts.delete(:logger)
|
10
|
+
end
|
11
|
+
|
12
|
+
def log
|
13
|
+
@logger ||= Logger.new(STDOUT)
|
14
|
+
end
|
15
|
+
|
16
|
+
def options
|
17
|
+
@options
|
18
|
+
end
|
19
|
+
|
20
|
+
def loaded?
|
21
|
+
@loaded
|
22
|
+
end
|
23
|
+
|
24
|
+
def loaded!
|
25
|
+
@loaded = true
|
26
|
+
end
|
27
|
+
|
28
|
+
def loading!
|
29
|
+
return false if loading?
|
30
|
+
@loading = true
|
31
|
+
end
|
32
|
+
|
33
|
+
def loading?
|
34
|
+
@loading
|
35
|
+
end
|
36
|
+
|
37
|
+
def done_loading!
|
38
|
+
@loading = false
|
39
|
+
loaded!
|
40
|
+
end
|
41
|
+
|
42
|
+
def save(num,obj)
|
43
|
+
puts "Don't know how to save!"
|
44
|
+
end
|
45
|
+
|
46
|
+
def get(num)
|
47
|
+
puts "Don't know how to get!"
|
48
|
+
end
|
49
|
+
|
50
|
+
def shutdown!
|
51
|
+
end
|
52
|
+
|
53
|
+
def reconnect!
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "bank_routing/storage/base"
|
2
|
+
|
3
|
+
class RoutingNumber
|
4
|
+
|
5
|
+
class MemStore < StoreBase
|
6
|
+
|
7
|
+
def initialize(opts = {})
|
8
|
+
@options = opts
|
9
|
+
@vals = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def save(num,obj)
|
13
|
+
@vals[num.to_s] = obj
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(num)
|
17
|
+
@vals[num.to_s]
|
18
|
+
end
|
19
|
+
|
20
|
+
def shutdown!
|
21
|
+
@vals = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
Stores[:default] = MemStore
|
27
|
+
Stores[:memory] = MemStore
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "bank_routing/storage/base"
|
2
|
+
|
3
|
+
class RoutingNumber
|
4
|
+
|
5
|
+
class RedisStore < StoreBase
|
6
|
+
|
7
|
+
LOADED_KEY = "LoadedRoutingNumbers".freeze
|
8
|
+
LOADING_KEY = "#{LOADED_KEY}::loading".freeze
|
9
|
+
STORAGE_KEY = "RoutingNumberLookup"
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
super(*args)
|
13
|
+
require 'redis'
|
14
|
+
end
|
15
|
+
|
16
|
+
def loaded?
|
17
|
+
store.exists(LOADED_KEY)
|
18
|
+
end
|
19
|
+
|
20
|
+
def loaded!
|
21
|
+
store.set(LOADED_KEY,"yes")
|
22
|
+
store.expire(LOADED_KEY,60*60*24*7)
|
23
|
+
end
|
24
|
+
|
25
|
+
def loading!
|
26
|
+
store.setnx(LOADING_KEY,"yup")
|
27
|
+
end
|
28
|
+
|
29
|
+
def loading?
|
30
|
+
loading!
|
31
|
+
end
|
32
|
+
|
33
|
+
def done_loading!
|
34
|
+
store.del(LOADING_KEY)
|
35
|
+
loaded!
|
36
|
+
end
|
37
|
+
|
38
|
+
def save(num,obj)
|
39
|
+
store.hset(STORAGE_KEY,num.to_s,Yajl::Encoder.encode(obj))
|
40
|
+
end
|
41
|
+
|
42
|
+
def get(num)
|
43
|
+
if cnt = store.hget(STORAGE_KEY,num.to_s)
|
44
|
+
Yajl::Parser.new(symbolize_keys: true).parse(cnt)
|
45
|
+
else
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def store
|
51
|
+
@store ||= connect
|
52
|
+
end
|
53
|
+
|
54
|
+
def connect
|
55
|
+
log.info "Connecting to Redis."
|
56
|
+
log.debug "Connection settings: #{options.inspect}"
|
57
|
+
Redis.new(options)
|
58
|
+
end
|
59
|
+
|
60
|
+
def reconnect!
|
61
|
+
@store = nil
|
62
|
+
store
|
63
|
+
end
|
64
|
+
|
65
|
+
def shutdown!
|
66
|
+
@store = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
Stores[:redis] = RedisStore
|
72
|
+
|
73
|
+
end
|
data/lib/bank_routing/version.rb
CHANGED
data/spec/bank_routing_spec.rb
CHANGED
@@ -8,14 +8,27 @@ describe BankRouting do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
describe RoutingNumber do
|
11
|
-
|
11
|
+
|
12
12
|
it "should get a number with no config" do
|
13
13
|
RoutingNumber.get(121000358)[:name].should eq("Bank of America")
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
it "should take a config and reload" do
|
17
|
+
require 'bank_routing/storage/redis'
|
18
|
+
RoutingNumber.store_in :redis
|
19
|
+
RoutingNumber.get(121000358)[:name].should eq("Bank of America")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should load fresh data" do
|
23
|
+
require 'bank_routing/storage/redis'
|
17
24
|
RoutingNumber.store_in :redis
|
18
25
|
RoutingNumber.get(121000358)[:name].should eq("Bank of America")
|
26
|
+
RoutingNumber.fetch_fresh_data!(false)
|
27
|
+
RoutingNumber.get(121000358)[:name].should eq("Bank of America")
|
19
28
|
end
|
20
|
-
|
29
|
+
|
30
|
+
it "should include metadata" do
|
31
|
+
RoutingNumber.get(114994196)[:prepaid_card].should eq(true)
|
32
|
+
end
|
33
|
+
|
21
34
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bank_routing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cozy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-04-
|
11
|
+
date: 2014-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -131,6 +131,9 @@ files:
|
|
131
131
|
- data/metadata.json
|
132
132
|
- lib/bank_routing.rb
|
133
133
|
- lib/bank_routing/routing_numbers.rb
|
134
|
+
- lib/bank_routing/storage/base.rb
|
135
|
+
- lib/bank_routing/storage/memory.rb
|
136
|
+
- lib/bank_routing/storage/redis.rb
|
134
137
|
- lib/bank_routing/version.rb
|
135
138
|
- spec/bank_routing_spec.rb
|
136
139
|
- spec/spec_helper.rb
|