lookup_table 0.0.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 +18 -0
- data/.rspec +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +54 -0
- data/LICENSE +20 -0
- data/README.md +36 -0
- data/lib/lookup_table.rb +106 -0
- data/lib/lookup_table/version.rb +4 -0
- data/lookup_table.gemspec +27 -0
- data/spec/lib/lookup_table_spec.rb +76 -0
- data/spec/spec_helper.rb +27 -0
- metadata +138 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-rspec_helper
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
lookup_table (0.0.1)
|
5
|
+
activerecord
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.0.2)
|
11
|
+
activesupport (= 4.0.2)
|
12
|
+
builder (~> 3.1.0)
|
13
|
+
activerecord (4.0.2)
|
14
|
+
activemodel (= 4.0.2)
|
15
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
16
|
+
activesupport (= 4.0.2)
|
17
|
+
arel (~> 4.0.0)
|
18
|
+
activerecord-deprecated_finders (1.0.3)
|
19
|
+
activesupport (4.0.2)
|
20
|
+
i18n (~> 0.6, >= 0.6.4)
|
21
|
+
minitest (~> 4.2)
|
22
|
+
multi_json (~> 1.3)
|
23
|
+
thread_safe (~> 0.1)
|
24
|
+
tzinfo (~> 0.3.37)
|
25
|
+
arel (4.0.1)
|
26
|
+
atomic (1.1.14)
|
27
|
+
builder (3.1.4)
|
28
|
+
diff-lcs (1.2.5)
|
29
|
+
i18n (0.6.9)
|
30
|
+
minitest (4.7.5)
|
31
|
+
multi_json (1.8.2)
|
32
|
+
rake (10.1.0)
|
33
|
+
rspec (2.14.1)
|
34
|
+
rspec-core (~> 2.14.0)
|
35
|
+
rspec-expectations (~> 2.14.0)
|
36
|
+
rspec-mocks (~> 2.14.0)
|
37
|
+
rspec-core (2.14.7)
|
38
|
+
rspec-expectations (2.14.4)
|
39
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
40
|
+
rspec-mocks (2.14.4)
|
41
|
+
sqlite3 (1.3.8)
|
42
|
+
thread_safe (0.1.3)
|
43
|
+
atomic
|
44
|
+
tzinfo (0.3.38)
|
45
|
+
|
46
|
+
PLATFORMS
|
47
|
+
ruby
|
48
|
+
|
49
|
+
DEPENDENCIES
|
50
|
+
bundler (~> 1.3)
|
51
|
+
lookup_table!
|
52
|
+
rake
|
53
|
+
rspec
|
54
|
+
sqlite3
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Artem Baguinski
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# lookup_table
|
2
|
+
|
3
|
+
Database fed lookup table.
|
4
|
+
|
5
|
+
This gem allows to quickly set up a look up table with content stored in the
|
6
|
+
database. The library supports two modes of operation: preload the whole table
|
7
|
+
into a lookup hash or lookup (and cache) values on demand.
|
8
|
+
|
9
|
+
The gem is useful for implementing data processing algorithms where some
|
10
|
+
functions are given as table based mappings, such as implementing statistical
|
11
|
+
models. The main purpose of the gem is making the code using such database
|
12
|
+
backed lookup more readable.
|
13
|
+
|
14
|
+
# Usage
|
15
|
+
|
16
|
+
Assuming a table "deprivation" contains a mapping from zip codes to deprivation,
|
17
|
+
a lookup table can be constructed with:
|
18
|
+
|
19
|
+
Gemfile:
|
20
|
+
|
21
|
+
```
|
22
|
+
gem "lookup_table"
|
23
|
+
```
|
24
|
+
|
25
|
+
hello.rb:
|
26
|
+
|
27
|
+
```
|
28
|
+
require "lookup_table"
|
29
|
+
|
30
|
+
ZipcodeToDeprivation = LookupTable.create "deprivation", :zip_code, :deprivation
|
31
|
+
|
32
|
+
deprivation = ZipcodeToDeprivation[ "7777AA" ]
|
33
|
+
|
34
|
+
```
|
35
|
+
|
36
|
+
More features are described in the [spec](spec/lib/lookup_table_spec.rb).
|
data/lib/lookup_table.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require "active_record"
|
2
|
+
|
3
|
+
module LookupTable
|
4
|
+
|
5
|
+
attr_reader :key_columns, :value_column
|
6
|
+
|
7
|
+
def self.create *args, &block
|
8
|
+
Class.new(ActiveRecord::Base) do
|
9
|
+
extend LookupTable
|
10
|
+
act_as_lookup_table *args
|
11
|
+
class_eval &block if block_given?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :prefetch
|
17
|
+
alias_method :prefetch?, :prefetch
|
18
|
+
attr_accessor :prefetch_limit
|
19
|
+
end
|
20
|
+
LookupTable.prefetch = true
|
21
|
+
|
22
|
+
attr_writer :prefetch
|
23
|
+
|
24
|
+
def prefetch?
|
25
|
+
(!LookupTable.prefetch_limit or
|
26
|
+
(lookup_domain.count <= LookupTable.prefetch_limit)) and
|
27
|
+
LookupTable.prefetch? and
|
28
|
+
@prefetch
|
29
|
+
end
|
30
|
+
|
31
|
+
def act_as_lookup_table table, key, value, options = {}
|
32
|
+
self.table_name = table
|
33
|
+
@key_columns = [key].flatten
|
34
|
+
@value_column = value
|
35
|
+
|
36
|
+
self.default = options[:default]
|
37
|
+
self.prefetch = options.fetch(:prefetch, true)
|
38
|
+
end
|
39
|
+
|
40
|
+
def [] *keys
|
41
|
+
lookup_table[ keys.flatten ]
|
42
|
+
end
|
43
|
+
|
44
|
+
def lookup_table
|
45
|
+
@lookup_table ||= prefetch_if_requested( create_lookup_table )
|
46
|
+
end
|
47
|
+
|
48
|
+
def db_lookup keys
|
49
|
+
record = lookup_domain.where( quoted_where keys ).first
|
50
|
+
record_value record
|
51
|
+
end
|
52
|
+
|
53
|
+
def quoted_where key_values
|
54
|
+
Hash[ key_columns.zip(key_values) ]
|
55
|
+
end
|
56
|
+
|
57
|
+
def quoted_key_columns
|
58
|
+
key_columns.map{|col| connection.quote_column_name col}
|
59
|
+
end
|
60
|
+
|
61
|
+
def default keys
|
62
|
+
@default.call *keys
|
63
|
+
end
|
64
|
+
|
65
|
+
def default= default
|
66
|
+
@default = (Proc === default) ? default : proc { default }
|
67
|
+
end
|
68
|
+
|
69
|
+
def lookup_columns
|
70
|
+
key_columns + [value_column]
|
71
|
+
end
|
72
|
+
|
73
|
+
def quoted_lookup_columns
|
74
|
+
lookup_columns.map{|col| connection.quote_column_name col}
|
75
|
+
end
|
76
|
+
|
77
|
+
def lookup_domain
|
78
|
+
select quoted_lookup_columns
|
79
|
+
end
|
80
|
+
|
81
|
+
def record_key record
|
82
|
+
key_columns.map{|column| record[column]}
|
83
|
+
end
|
84
|
+
|
85
|
+
def record_value record
|
86
|
+
record.try :[], value_column
|
87
|
+
end
|
88
|
+
|
89
|
+
def prefetch_if_requested hash
|
90
|
+
if prefetch?
|
91
|
+
lookup_domain.inject(hash) do |hash,record|
|
92
|
+
hash[ record_key(record) ] = record_value(record)
|
93
|
+
hash
|
94
|
+
end
|
95
|
+
end
|
96
|
+
hash
|
97
|
+
end
|
98
|
+
|
99
|
+
def create_lookup_table
|
100
|
+
Hash.new do |lookup_table, keys|
|
101
|
+
lookup_table[keys] = db_lookup(keys) || default(keys)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path('../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'lookup_table/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'lookup_table'
|
7
|
+
s.version = LookupTable::VERSION
|
8
|
+
s.summary = 'database fed lookup table'
|
9
|
+
s.description = 'use database table as a hash table'
|
10
|
+
s.author = 'Artem Baguinski'
|
11
|
+
s.email = 'femistofel@gmail.com'
|
12
|
+
s.homepage = 'https://github.com/artm/lookup_table'
|
13
|
+
s.license = 'MIT'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split($/)
|
16
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_runtime_dependency 'activerecord'
|
21
|
+
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'bundler', '~> 1.3'
|
24
|
+
s.add_development_dependency 'rspec'
|
25
|
+
s.add_development_dependency 'sqlite3'
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,76 @@
|
|
1
|
+
describe LookupTable do
|
2
|
+
shared_context "with mapping table" do
|
3
|
+
around(:each) do |example|
|
4
|
+
create_mapping_table(subject, table_content)
|
5
|
+
example.run
|
6
|
+
drop_mapping_table(subject)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
shared_context "simple content" do
|
10
|
+
let(:table_content) { [[1,'foo'],[2,'bar'],[42,'answer']] }
|
11
|
+
let(:unknown_key) { 666 }
|
12
|
+
end
|
13
|
+
shared_context "nil default" do
|
14
|
+
let(:default_value) { nil }
|
15
|
+
end
|
16
|
+
shared_examples "lookup table" do
|
17
|
+
it "looks up values for known keys" do
|
18
|
+
table_content.each do |record|
|
19
|
+
key = record[0...record.count-1]
|
20
|
+
value = record.last
|
21
|
+
expect( subject[key] ).to eq value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
it "returns default value for unknown keys" do
|
25
|
+
expect( subject[ unknown_key ] ).to eq default_value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "simple lookup table" do
|
30
|
+
subject { LookupTable.create 'mapping', :key, :value }
|
31
|
+
include_context "with mapping table"
|
32
|
+
include_context "simple content"
|
33
|
+
include_context "nil default"
|
34
|
+
it_behaves_like "lookup table"
|
35
|
+
end
|
36
|
+
describe "mixed case columns" do
|
37
|
+
subject { LookupTable.create 'mapping', :KeyColumn, :ValueColumn }
|
38
|
+
include_context "with mapping table"
|
39
|
+
include_context "simple content"
|
40
|
+
include_context "nil default"
|
41
|
+
it_behaves_like "lookup table"
|
42
|
+
end
|
43
|
+
describe "with default value" do
|
44
|
+
subject { LookupTable.create 'mapping', :key, :value, default: 'default' }
|
45
|
+
include_context "with mapping table"
|
46
|
+
include_context "simple content"
|
47
|
+
let(:default_value) { 'default' }
|
48
|
+
it_behaves_like "lookup table"
|
49
|
+
end
|
50
|
+
describe "with computed default" do
|
51
|
+
subject { LookupTable.create 'mapping', :key, :value, default: proc{|k| k/6} }
|
52
|
+
include_context "with mapping table"
|
53
|
+
include_context "simple content"
|
54
|
+
let(:default_value) { 111 }
|
55
|
+
it_behaves_like "lookup table"
|
56
|
+
end
|
57
|
+
describe "with compound key" do
|
58
|
+
subject { LookupTable.create 'mapping', [:key1, :key2] , :value }
|
59
|
+
include_context "with mapping table"
|
60
|
+
include_context "nil default"
|
61
|
+
let(:table_content) { [[1,2,'foo'],[2,2,'bar'],[42,2,'answer']] }
|
62
|
+
let(:unknown_key) { [666,777] }
|
63
|
+
it_behaves_like "lookup table"
|
64
|
+
end
|
65
|
+
describe "simple lookup table without prefetching" do
|
66
|
+
subject { LookupTable.create 'mapping', :key, :value, prefetch: false }
|
67
|
+
include_context "with mapping table"
|
68
|
+
include_context "simple content"
|
69
|
+
include_context "nil default"
|
70
|
+
it "doesn't prefetch" do
|
71
|
+
expect( subject ).not_to receive( :prefetch )
|
72
|
+
subject[1]
|
73
|
+
end
|
74
|
+
it_behaves_like "lookup table"
|
75
|
+
end
|
76
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "lookup_table"
|
3
|
+
|
4
|
+
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
5
|
+
|
6
|
+
def connection
|
7
|
+
ActiveRecord::Base.connection
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_mapping_table model, table_content
|
11
|
+
value = model.value_column
|
12
|
+
table_name = model.table_name
|
13
|
+
connection.create_table(table_name) do |t|
|
14
|
+
model.key_columns.each do |key|
|
15
|
+
t.integer key
|
16
|
+
end
|
17
|
+
t.string value
|
18
|
+
end
|
19
|
+
quoted_columns = model.quoted_lookup_columns
|
20
|
+
rows_sql = table_content.map{|row| "(#{row.map{|v| connection.quote(v)} * ','})"} * ','
|
21
|
+
insert_sql = "INSERT INTO #{table_name} (#{quoted_columns * ','}) VALUES #{rows_sql};"
|
22
|
+
connection.execute(insert_sql)
|
23
|
+
end
|
24
|
+
|
25
|
+
def drop_mapping_table model
|
26
|
+
connection.drop_table model.table_name
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lookup_table
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Artem Baguinski
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-12-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.3'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: sqlite3
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: use database table as a hash table
|
95
|
+
email: femistofel@gmail.com
|
96
|
+
executables: []
|
97
|
+
extensions: []
|
98
|
+
extra_rdoc_files: []
|
99
|
+
files:
|
100
|
+
- .gitignore
|
101
|
+
- .rspec
|
102
|
+
- Gemfile
|
103
|
+
- Gemfile.lock
|
104
|
+
- LICENSE
|
105
|
+
- README.md
|
106
|
+
- lib/lookup_table.rb
|
107
|
+
- lib/lookup_table/version.rb
|
108
|
+
- lookup_table.gemspec
|
109
|
+
- spec/lib/lookup_table_spec.rb
|
110
|
+
- spec/spec_helper.rb
|
111
|
+
homepage: https://github.com/artm/lookup_table
|
112
|
+
licenses:
|
113
|
+
- MIT
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ! '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
requirements: []
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 1.8.23
|
133
|
+
signing_key:
|
134
|
+
specification_version: 3
|
135
|
+
summary: database fed lookup table
|
136
|
+
test_files:
|
137
|
+
- spec/lib/lookup_table_spec.rb
|
138
|
+
- spec/spec_helper.rb
|