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.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -rspec_helper
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
@@ -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.
@@ -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).
@@ -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,4 @@
1
+ module LookupTable
2
+ VERSION = '0.0.1'
3
+ end
4
+
@@ -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
@@ -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