look_up_table 0.1.0.rc8 → 0.1.0.rc9
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/lib/look_up_table/base.rb +155 -0
- data/lib/look_up_table/cache.rb +170 -0
- data/lib/look_up_table/method_missing.rb +22 -0
- data/lib/look_up_table/no_cache.rb +45 -0
- data/lib/look_up_table/version.rb +2 -1
- data/lib/look_up_table.rb +7 -268
- data/test/dummy/app/models/bar.rb +4 -0
- data/test/dummy/app/models/foo.rb +4 -0
- data/test/dummy/app/models/foobar.rb +9 -0
- data/test/dummy/config/database.yml +9 -14
- data/test/dummy/config/environments/test.rb +3 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/20111024130333_create_foobars.rb +2 -1
- data/test/dummy/db/migrate/20111024130334_create_foos.rb +11 -0
- data/test/dummy/db/migrate/20111024130335_create_bars.rb +11 -0
- data/test/dummy/db/schema.rb +13 -1
- data/test/dummy/log/development.log +14 -0
- data/test/dummy/log/test.log +59 -0
- data/test/dummy/tmp/cache/3F4/570/Foobarid%2F0 +0 -0
- data/test/dummy/tmp/cache/3F5/580/Foobarid%2F1 +0 -0
- data/test/dummy/tmp/cache/45C/750/Foobarbar%2F0 +0 -0
- data/test/dummy/tmp/cache/45D/760/Foobarbar%2F1 +0 -0
- data/test/dummy/tmp/cache/45E/770/Foobarbar%2F2 +0 -0
- data/test/dummy/tmp/cache/45F/780/Foobarbar%2F3 +0 -0
- data/test/dummy/tmp/cache/460/790/Foobarbar%2F4 +0 -0
- data/test/dummy/tmp/cache/461/7A0/Foobarbar%2F5 +0 -0
- data/test/dummy/tmp/cache/462/7B0/Foobarbar%2F6 +0 -0
- data/test/dummy/tmp/cache/463/7C0/Foobarbar%2F7 +0 -0
- data/test/dummy/tmp/cache/464/7D0/Foobarbar%2F8 +0 -0
- data/test/dummy/tmp/cache/465/7E0/Foobarbar%2F9 +0 -0
- data/test/dummy/tmp/cache/46B/D60/Foobarfoo%2F0 +0 -0
- data/test/dummy/tmp/cache/46C/D70/Foobarfoo%2F1 +0 -0
- data/test/dummy/tmp/cache/46D/D80/Foobarfoo%2F2 +0 -0
- data/test/dummy/tmp/cache/46E/D90/Foobarfoo%2F3 +0 -0
- data/test/dummy/tmp/cache/46F/DA0/Foobarfoo%2F4 +0 -0
- data/test/dummy/tmp/cache/470/DB0/Foobarfoo%2F5 +0 -0
- data/test/dummy/tmp/cache/471/DC0/Foobarfoo%2F6 +0 -0
- data/test/dummy/tmp/cache/472/DD0/Foobarfoo%2F7 +0 -0
- data/test/dummy/tmp/cache/473/DE0/Foobarfoo%2F8 +0 -0
- data/test/dummy/tmp/cache/474/DF0/Foobarfoo%2F9 +0 -0
- data/test/dummy/tmp/cache/48D/030/Foobarbar%2F10 +0 -0
- data/test/dummy/tmp/cache/48E/040/Foobarbar%2F11 +0 -0
- data/test/dummy/tmp/cache/48E/050/Foobarbar%2F20 +0 -0
- data/test/dummy/tmp/cache/48F/050/Foobarbar%2F12 +0 -0
- data/test/dummy/tmp/cache/48F/060/Foobarbar%2F21 +0 -0
- data/test/dummy/tmp/cache/48F/070/Foobarbar%2F30 +0 -0
- data/test/dummy/tmp/cache/490/060/Foobarbar%2F13 +0 -0
- data/test/dummy/tmp/cache/490/070/Foobarbar%2F22 +0 -0
- data/test/dummy/tmp/cache/490/080/Foobarbar%2F31 +0 -0
- data/test/dummy/tmp/cache/490/090/Foobarbar%2F40 +0 -0
- data/test/dummy/tmp/cache/491/070/Foobarbar%2F14 +0 -0
- data/test/dummy/tmp/cache/491/080/Foobarbar%2F23 +0 -0
- data/test/dummy/tmp/cache/491/090/Foobarbar%2F32 +0 -0
- data/test/dummy/tmp/cache/491/0A0/Foobarbar%2F41 +0 -0
- data/test/dummy/tmp/cache/492/080/Foobarbar%2F15 +0 -0
- data/test/dummy/tmp/cache/492/090/Foobarbar%2F24 +0 -0
- data/test/dummy/tmp/cache/492/0A0/Foobarbar%2F33 +0 -0
- data/test/dummy/tmp/cache/493/090/Foobarbar%2F16 +0 -0
- data/test/dummy/tmp/cache/493/0A0/Foobarbar%2F25 +0 -0
- data/test/dummy/tmp/cache/493/0B0/Foobarbar%2F34 +0 -0
- data/test/dummy/tmp/cache/494/0A0/Foobarbar%2F17 +0 -0
- data/test/dummy/tmp/cache/494/0B0/Foobarbar%2F26 +0 -0
- data/test/dummy/tmp/cache/494/0C0/Foobarbar%2F35 +0 -0
- data/test/dummy/tmp/cache/495/0B0/Foobarbar%2F18 +0 -0
- data/test/dummy/tmp/cache/495/0C0/Foobarbar%2F27 +0 -0
- data/test/dummy/tmp/cache/495/0D0/Foobarbar%2F36 +0 -0
- data/test/dummy/tmp/cache/496/0C0/Foobarbar%2F19 +0 -0
- data/test/dummy/tmp/cache/496/0D0/Foobarbar%2F28 +0 -0
- data/test/dummy/tmp/cache/496/0E0/Foobarbar%2F37 +0 -0
- data/test/dummy/tmp/cache/497/0E0/Foobarbar%2F29 +0 -0
- data/test/dummy/tmp/cache/497/0F0/Foobarbar%2F38 +0 -0
- data/test/dummy/tmp/cache/498/100/Foobarbar%2F39 +0 -0
- data/test/dummy/tmp/cache/49C/730/Foobarfoo%2F10 +0 -0
- data/test/dummy/tmp/cache/49D/740/Foobarfoo%2F11 +0 -0
- data/test/dummy/tmp/cache/49D/750/Foobarfoo%2F20 +0 -0
- data/test/dummy/tmp/cache/49E/750/Foobarfoo%2F12 +0 -0
- data/test/dummy/tmp/cache/49E/760/Foobarfoo%2F21 +0 -0
- data/test/dummy/tmp/cache/49E/770/Foobarfoo%2F30 +0 -0
- data/test/dummy/tmp/cache/49F/760/Foobarfoo%2F13 +0 -0
- data/test/dummy/tmp/cache/49F/770/Foobarfoo%2F22 +0 -0
- data/test/dummy/tmp/cache/49F/780/Foobarfoo%2F31 +0 -0
- data/test/dummy/tmp/cache/49F/790/Foobarfoo%2F40 +0 -0
- data/test/dummy/tmp/cache/4A0/770/Foobarfoo%2F14 +0 -0
- data/test/dummy/tmp/cache/4A0/780/Foobarfoo%2F23 +0 -0
- data/test/dummy/tmp/cache/4A0/790/Foobarfoo%2F32 +0 -0
- data/test/dummy/tmp/cache/4A0/7A0/Foobarfoo%2F41 +0 -0
- data/test/dummy/tmp/cache/4A1/780/Foobarfoo%2F15 +0 -0
- data/test/dummy/tmp/cache/4A1/790/Foobarfoo%2F24 +0 -0
- data/test/dummy/tmp/cache/4A1/7A0/Foobarfoo%2F33 +0 -0
- data/test/dummy/tmp/cache/4A2/790/Foobarfoo%2F16 +0 -0
- data/test/dummy/tmp/cache/4A2/7A0/Foobarfoo%2F25 +0 -0
- data/test/dummy/tmp/cache/4A2/7B0/Foobarfoo%2F34 +0 -0
- data/test/dummy/tmp/cache/4A3/7A0/Foobarfoo%2F17 +0 -0
- data/test/dummy/tmp/cache/4A3/7B0/Foobarfoo%2F26 +0 -0
- data/test/dummy/tmp/cache/4A3/7C0/Foobarfoo%2F35 +0 -0
- data/test/dummy/tmp/cache/4A4/7B0/Foobarfoo%2F18 +0 -0
- data/test/dummy/tmp/cache/4A4/7C0/Foobarfoo%2F27 +0 -0
- data/test/dummy/tmp/cache/4A4/7D0/Foobarfoo%2F36 +0 -0
- data/test/dummy/tmp/cache/4A5/7C0/Foobarfoo%2F19 +0 -0
- data/test/dummy/tmp/cache/4A5/7D0/Foobarfoo%2F28 +0 -0
- data/test/dummy/tmp/cache/4A5/7E0/Foobarfoo%2F37 +0 -0
- data/test/dummy/tmp/cache/4A6/7E0/Foobarfoo%2F29 +0 -0
- data/test/dummy/tmp/cache/4A6/7F0/Foobarfoo%2F38 +0 -0
- data/test/dummy/tmp/cache/4A7/800/Foobarfoo%2F39 +0 -0
- metadata +191 -7
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
require 'look_up_table'
|
|
2
|
+
require 'look_up_table/base'
|
|
3
|
+
require 'look_up_table/cache'
|
|
4
|
+
require 'look_up_table/method_missing'
|
|
5
|
+
require 'look_up_table/no_cache'
|
|
6
|
+
|
|
7
|
+
module LookUpTable
|
|
8
|
+
module ClassMethods
|
|
9
|
+
# Defining a LookUpTable
|
|
10
|
+
# TODO: Usage
|
|
11
|
+
def look_up_table(lut_key, options = {}, &block)
|
|
12
|
+
options = {
|
|
13
|
+
:batch_size => 10000,
|
|
14
|
+
:prefix => self.name,
|
|
15
|
+
:read_on_init => false,
|
|
16
|
+
:use_cache => true,
|
|
17
|
+
:sql_mode => true,
|
|
18
|
+
:where => nil
|
|
19
|
+
}.merge(options)
|
|
20
|
+
|
|
21
|
+
self.lut_set_proc(lut_key, block)
|
|
22
|
+
self.lut_set_options(lut_key, options)
|
|
23
|
+
|
|
24
|
+
self.lut(lut_key) if options[:read_on_init]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Call to a LookUpTable.
|
|
28
|
+
# * Example:
|
|
29
|
+
# * Tag.lut (Returns all LookUpTables)
|
|
30
|
+
# * Tag.lut :name (Returns LookUpTable given by :name)
|
|
31
|
+
# * Tag.lut(:name, "Berlin") (Returns Value of LookUpTable named :name with :key "Berlin")
|
|
32
|
+
# * Tag.lut({:name => "Berlin", :title => "Berlin"}) TODO: returns hash
|
|
33
|
+
# TODO: TestCases
|
|
34
|
+
def lut(lut_key = nil, lut_item_key = nil)
|
|
35
|
+
@lut ||= {}
|
|
36
|
+
|
|
37
|
+
if lut_key.nil?
|
|
38
|
+
hash = {}
|
|
39
|
+
self.lut_keys.each { |key| hash[key] = self.lut(key) } # CHECK: use .inject?
|
|
40
|
+
return hash
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if (lut_key.respond_to?(:keys))
|
|
44
|
+
hash = {}
|
|
45
|
+
lut_key.each { |k,v| hash[k.intern] = self.lut(k,v) }
|
|
46
|
+
return hash
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
lut = @lut[lut_key.to_sym] ||= lut_read(lut_key) || {}
|
|
50
|
+
lut_item_key ? lut[lut_item_key] : lut
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Reset complete lut if name is omitted, resets given lut otherwise.
|
|
54
|
+
# HACK: not cool do access and define @lut here
|
|
55
|
+
def lut_reset(name = nil)
|
|
56
|
+
@lut ||= {}
|
|
57
|
+
|
|
58
|
+
if name
|
|
59
|
+
@lut[name.to_sym] = nil
|
|
60
|
+
lut_write_cache_item(name, 0, nil) unless lut_options[:skip_memcached] # CHECK: options call w/o name?
|
|
61
|
+
else
|
|
62
|
+
lut_keys.each { |k| lut_reset(k) }
|
|
63
|
+
@lut = {}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Reading LUT and writing cache again
|
|
68
|
+
def lut_reload(name = nil)
|
|
69
|
+
if name
|
|
70
|
+
lut_reset(name)
|
|
71
|
+
lut(name)
|
|
72
|
+
else
|
|
73
|
+
lut_keys.each { |k| lut_reload(k) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
lut_keys
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Init complete LUT with all keys define.
|
|
80
|
+
# But won't rewrite cache if allready written!
|
|
81
|
+
def lut_init(name = nil)
|
|
82
|
+
if name
|
|
83
|
+
lut(name)
|
|
84
|
+
else
|
|
85
|
+
lut_keys.each { |k| lut_init(k) }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
lut_keys
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns keys of LookUpTables defined
|
|
92
|
+
def lut_keys
|
|
93
|
+
lut_options.keys
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Usage
|
|
97
|
+
# * Klass.lut_options
|
|
98
|
+
# * Klass.lut_options(:lut_key)
|
|
99
|
+
# * Klass.lut_options(:lut_key, :option_key)
|
|
100
|
+
# * Klass.lut_options(:lut_key => :option_key)
|
|
101
|
+
# * Klass.lut_options(:lut_key => [:option_key, :second_option_key])
|
|
102
|
+
# TODO: test_cases
|
|
103
|
+
# CHECK: some sweeter implementation?
|
|
104
|
+
def lut_options(name = nil)#, option_key = nil)
|
|
105
|
+
@lut_options ||= {}
|
|
106
|
+
|
|
107
|
+
# if name && name.respond_to?(:keys)
|
|
108
|
+
# if name[:lut_key] && name[:lut_key].respond_to?(:values)
|
|
109
|
+
# return name[:lut_key].each.inject([]) do |arr, el|
|
|
110
|
+
# arr << @lut_options[name.intern][el.intern]
|
|
111
|
+
# end
|
|
112
|
+
# elsif name[:lut_key]
|
|
113
|
+
# return @lut_options[name.intern][option_key.intern]
|
|
114
|
+
# else
|
|
115
|
+
# return @lut_options[name.intern]
|
|
116
|
+
# end
|
|
117
|
+
# elsif name
|
|
118
|
+
# (option_key) ? @lut_options[name.intern][option_key.intern] : @lut_options[name.intern]
|
|
119
|
+
# else
|
|
120
|
+
# return @lut_options
|
|
121
|
+
# end
|
|
122
|
+
|
|
123
|
+
(name) ? @lut_options[name.to_sym] : @lut_options
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
protected
|
|
127
|
+
def lut_proc(lut_key = nil)
|
|
128
|
+
@lut_proc ||= {}
|
|
129
|
+
|
|
130
|
+
(lut_key) ? @lut_proc[lut_key.to_sym] : @lut_proc
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# lut_set_proc(lut_name, block) / lut_proc(lut_name)
|
|
134
|
+
def lut_set_proc(lut_key, block)
|
|
135
|
+
lut_proc[lut_key.to_sym] = block
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# lut_set_options(lut_name, options) / lut_options(lut_name)
|
|
139
|
+
def lut_set_options(lut_key, options)
|
|
140
|
+
lut_options[lut_key.to_sym] = options
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Reads a single lut
|
|
144
|
+
def lut_read(name)
|
|
145
|
+
return nil unless options = lut_options(name)# HACK
|
|
146
|
+
|
|
147
|
+
if options[:use_cache]
|
|
148
|
+
lut_read_from_cache(name)
|
|
149
|
+
else
|
|
150
|
+
lut_read_without_cache(name)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
module LookUpTable
|
|
2
|
+
module ClassMethods
|
|
3
|
+
protected
|
|
4
|
+
|
|
5
|
+
# Cache entry for given LUT exists?
|
|
6
|
+
def lut_cache_exists?(name)
|
|
7
|
+
!lut_read_cache_item(name, 0).nil?
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Reads a complete lut from cache
|
|
11
|
+
# HACK: this still looks ugly somehow
|
|
12
|
+
def lut_read_from_cache(name)
|
|
13
|
+
lut_write_to_cache(name) unless lut_cache_exists?(name)
|
|
14
|
+
|
|
15
|
+
i = 0
|
|
16
|
+
lut = {}
|
|
17
|
+
|
|
18
|
+
while item = lut_read_cache_item(name, i)
|
|
19
|
+
# FIXME: merge will override existing values
|
|
20
|
+
# lut.merge!(item) if item
|
|
21
|
+
if item
|
|
22
|
+
item.each do |k,v|
|
|
23
|
+
if lut[k]
|
|
24
|
+
v = [v] unless v.respond_to?(:concat)
|
|
25
|
+
lut[k] = [lut[k]] unless lut[k].respond_to?(:concat)
|
|
26
|
+
lut[k].concat(v)
|
|
27
|
+
else
|
|
28
|
+
lut[k] = v
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
i += 1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
return lut
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Write a LookUpTable into Cache
|
|
40
|
+
def lut_write_to_cache(lut_key)
|
|
41
|
+
if lut_options(lut_key)[:sql_mode]
|
|
42
|
+
count = lut_write_to_cache_sql_mode(lut_key)
|
|
43
|
+
else
|
|
44
|
+
count = lut_write_to_cache_no_sql_mode(lut_key)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# HACK: Writing a \0 to terminate batch_items
|
|
48
|
+
lut_write_cache_item(lut_key, count, nil)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# HACK: somehow long method
|
|
52
|
+
def lut_write_to_cache_sql_mode(lut_key)
|
|
53
|
+
batch_count = 0
|
|
54
|
+
|
|
55
|
+
self.where(lut_options(lut_key)[:where]).find_in_batches(:batch_size => lut_options(lut_key)[:batch_size]) do |items| #FIXME not DRY here
|
|
56
|
+
lut = {}
|
|
57
|
+
block = lut_proc(lut_key)
|
|
58
|
+
|
|
59
|
+
items.each do |item|
|
|
60
|
+
if block
|
|
61
|
+
block.call(lut, item)
|
|
62
|
+
else
|
|
63
|
+
# HACK: doing a merge w/o replacing, just add elements for childs and may transform elements to array
|
|
64
|
+
k = item.send(lut_key)
|
|
65
|
+
v = item.id
|
|
66
|
+
if lut[k]
|
|
67
|
+
v = [v] unless v.respond_to?(:concat)
|
|
68
|
+
lut[k] = [lut[k]] unless lut[k].respond_to?(:concat)
|
|
69
|
+
lut[k].concat(v)
|
|
70
|
+
else
|
|
71
|
+
lut[k] = v
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
batch_count = self.lut_write_cache_item(lut_key, batch_count, lut)
|
|
77
|
+
batch_count += 1
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
batch_count
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# HACK: somehow long method
|
|
84
|
+
def lut_write_to_cache_no_sql_mode(lut_key)
|
|
85
|
+
lut = {}
|
|
86
|
+
batch_count = 0
|
|
87
|
+
|
|
88
|
+
block = lut_proc(lut_key)
|
|
89
|
+
block.call(lut)
|
|
90
|
+
|
|
91
|
+
keys = lut.keys
|
|
92
|
+
|
|
93
|
+
while
|
|
94
|
+
key_block = keys.slice!(0, lut_options(lut_key)[:batch_size])
|
|
95
|
+
break if key_block.empty?
|
|
96
|
+
|
|
97
|
+
lut_block = {}
|
|
98
|
+
key_block.each{|key| lut_block[key] = lut[key]}
|
|
99
|
+
|
|
100
|
+
batch_count = self.lut_write_cache_item(lut_key, batch_count, lut_block)
|
|
101
|
+
batch_count += 1
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
batch_count
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Reads a single item of a LookUpTable from Cache
|
|
108
|
+
def lut_read_cache_item(lut_key, lut_item_key)
|
|
109
|
+
prefix = lut_options(lut_key)[:prefix]
|
|
110
|
+
|
|
111
|
+
Rails.cache.read("#{prefix}#{lut_key}/#{lut_item_key}")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Write a single Item into LookUpTable-Cache
|
|
115
|
+
# TODO: refactor
|
|
116
|
+
def lut_write_cache_item(lut_key, lut_item_count, lut_data)
|
|
117
|
+
prefix = lut_options(lut_key)[:prefix]
|
|
118
|
+
success = Rails.cache.write("#{prefix}#{lut_key}/#{lut_item_count}", lut_data)
|
|
119
|
+
|
|
120
|
+
# Do some magic here and just throw a warning
|
|
121
|
+
# * Divide and conquer on error
|
|
122
|
+
if !success
|
|
123
|
+
warn "WARNING - LookUpTable: Cache.write failed, trying to 'divide and conquer'"
|
|
124
|
+
|
|
125
|
+
if lut_data.respond_to?(:keys) && lut_data.respond_to?(:values)
|
|
126
|
+
# Got a hash
|
|
127
|
+
keys = lut_data.keys
|
|
128
|
+
keys_length = keys.length
|
|
129
|
+
|
|
130
|
+
if keys_length < 2
|
|
131
|
+
# Try to slice down values
|
|
132
|
+
entries = lut_data[keys.first].entries
|
|
133
|
+
|
|
134
|
+
first_entries = { keys.first => entries.slice!(0, entries.length/2) }
|
|
135
|
+
second_entries = { keys.first => entries }
|
|
136
|
+
|
|
137
|
+
lut_item_count = self.lut_write_cache_item(lut_key, lut_item_count, first_entries)
|
|
138
|
+
lut_item_count = self.lut_write_cache_item(lut_key, lut_item_count + 1, second_entries)
|
|
139
|
+
|
|
140
|
+
else
|
|
141
|
+
first_keys = keys.slice!(0, keys.length/2)
|
|
142
|
+
second_keys = keys
|
|
143
|
+
first_hash = {}
|
|
144
|
+
second_hash = {}
|
|
145
|
+
|
|
146
|
+
first_keys.each { |k| first_hash[k] = lut_data[k] }
|
|
147
|
+
second_keys.each { |k| second_hash[k] = lut_data[k] }
|
|
148
|
+
|
|
149
|
+
lut_item_count = self.lut_write_cache_item(lut_key, lut_item_count, first_hash)
|
|
150
|
+
lut_item_count = self.lut_write_cache_item(lut_key, lut_item_count + 1, second_hash)
|
|
151
|
+
end
|
|
152
|
+
elsif lut_data.respond_to?(:entries)
|
|
153
|
+
# Got an Array or a Set
|
|
154
|
+
entries = lut_data.entries
|
|
155
|
+
first_entries = entries.slice!(0, entries.length/2)
|
|
156
|
+
second_entries = entries
|
|
157
|
+
|
|
158
|
+
lut_item_count = self.lut_write_cache_item(lut_key, lut_item_count, first_entries)
|
|
159
|
+
lut_item_count = self.lut_write_cache_item(lut_key, lut_item_count + 1, second_entries)
|
|
160
|
+
else
|
|
161
|
+
# Finally we can't help here
|
|
162
|
+
raise "Cache::write failed - Try lower :batch_size"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
return lut_item_count
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module LookUpTable
|
|
2
|
+
module ClassMethods
|
|
3
|
+
# Delegating <attribute>_lut(args) method calls
|
|
4
|
+
# e.g.: Klass.foo_lut => Klass.lut :foo
|
|
5
|
+
def method_missing(sym, *args, &block)
|
|
6
|
+
method_name = sym.to_s
|
|
7
|
+
|
|
8
|
+
if method_name.end_with?("_lut")
|
|
9
|
+
lut_name = method_name[0..-5]
|
|
10
|
+
self.lut(lut_name, args.first)
|
|
11
|
+
else
|
|
12
|
+
super(sym, *args, &block)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# CHECK: what's bool?
|
|
17
|
+
def respond_to?(sym, bool)
|
|
18
|
+
sym.to_s.end_with?("_lut") || super(sym, bool)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module LookUpTable
|
|
2
|
+
module ClassMethods
|
|
3
|
+
protected
|
|
4
|
+
|
|
5
|
+
# Reads a complete from given block or generic version
|
|
6
|
+
# HACK: some duplicated methods from Class.lut_write_to_cache
|
|
7
|
+
def lut_read_without_cache(lut_key)
|
|
8
|
+
if lut_options(lut_key)[:sql_mode]
|
|
9
|
+
return lut_read_without_cache_sql_mode(lut_key)
|
|
10
|
+
else
|
|
11
|
+
return lut_read_without_cache_no_sql_mode(lut_key)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# CHECK: somehow long method
|
|
16
|
+
def lut_read_without_cache_sql_mode(lut_key)
|
|
17
|
+
lut = {}
|
|
18
|
+
block = lut_proc(lut_key)
|
|
19
|
+
|
|
20
|
+
self.where(lut_options(lut_key)[:where]).find_in_batches(:batch_size => lut_options(lut_key)[:batch_size]) do |items| #FIXME not DRY here
|
|
21
|
+
items.each do |item|
|
|
22
|
+
if block
|
|
23
|
+
block.call(lut, item)
|
|
24
|
+
else
|
|
25
|
+
lut[item.send(lut_key)] = item.id
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
return lut
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# CHECK: ugly method_name
|
|
34
|
+
def lut_read_without_cache_no_sql_mode(lut_key)
|
|
35
|
+
lut = {}
|
|
36
|
+
|
|
37
|
+
block = lut_proc(lut_key)
|
|
38
|
+
block.call(lut)
|
|
39
|
+
|
|
40
|
+
return lut
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
data/lib/look_up_table.rb
CHANGED
|
@@ -7,278 +7,17 @@
|
|
|
7
7
|
# * use symbols freely
|
|
8
8
|
|
|
9
9
|
# FIXME:
|
|
10
|
-
# * Avoid
|
|
11
|
-
module LookUpTable
|
|
12
|
-
extend ActiveSupport::Concern
|
|
13
|
-
|
|
14
|
-
# Class methods
|
|
15
|
-
module ClassMethods
|
|
16
|
-
# Defining a LookUpTable
|
|
17
|
-
# TODO: Usage
|
|
18
|
-
def look_up_table(lut_name, options={}, &block)
|
|
19
|
-
options = {
|
|
20
|
-
:batch_size => 10000,
|
|
21
|
-
:prefix => "",
|
|
22
|
-
:read_on_init => false,
|
|
23
|
-
:skip_memcached => false,
|
|
24
|
-
:sql_mode => true,
|
|
25
|
-
:where => nil
|
|
26
|
-
}.merge(options)
|
|
27
|
-
|
|
28
|
-
self.lut_proc[lut_name.to_sym] = block
|
|
29
|
-
self.lut_options[lut_name.to_sym] = options
|
|
30
|
-
|
|
31
|
-
self.lut(lut_name) if options[:read_on_init]
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Call to a LookUpTable.
|
|
35
|
-
# * Example: Tag.lut(:name, "Berlin")
|
|
36
|
-
# * Returns: nil or stored objects
|
|
37
|
-
def lut(name, search_by=nil)
|
|
38
|
-
@@lut ||= {}
|
|
39
|
-
@@lut[name.to_sym] ||= lut_read(name) || {}
|
|
40
|
-
|
|
41
|
-
search_by ? @@lut[name.to_sym][search_by] : @@lut[name.to_sym]
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Reset complete lut if name is omitted, resets given lut otherwise.
|
|
45
|
-
# HACK
|
|
46
|
-
def lut_reset(name=nil)
|
|
47
|
-
@@lut ||= {}
|
|
48
|
-
|
|
49
|
-
if name
|
|
50
|
-
@@lut[name.to_sym] = nil
|
|
51
|
-
lut_write_cache_item(name, 0, nil) unless lut_options[:skip_memcached]
|
|
52
|
-
else
|
|
53
|
-
lut_keys.each { |k| lut_reset(k) }
|
|
54
|
-
@@lut = {}
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Reloads allready loaded LUTs.
|
|
59
|
-
# Will also rewrites cache.
|
|
60
|
-
def lut_reload(name = nil)
|
|
61
|
-
if name
|
|
62
|
-
lut_reset(name)
|
|
63
|
-
lut(name)
|
|
64
|
-
else
|
|
65
|
-
lut_keys.each { |k| lut_reload(k) }
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
return lut_keys
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Init complete LUT with all keys define.
|
|
72
|
-
# But won't rewrite cache if allready written!
|
|
73
|
-
def lut_init(name = nil)
|
|
74
|
-
if name
|
|
75
|
-
lut(name)
|
|
76
|
-
else
|
|
77
|
-
lut_keys.each { |k| lut_init(k) }
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
return lut_keys
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
#private
|
|
86
|
-
def lut_keys
|
|
87
|
-
lut_options.keys
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# lut_set_proc(lut_name, block) / lut_proc(lut_name)
|
|
91
|
-
def lut_set_proc(name, block)
|
|
92
|
-
lut_proc[name.to_sym] = block
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def lut_proc(name = nil)
|
|
96
|
-
@@lut_proc ||= {}
|
|
97
|
-
(name) ? @@lut_proc[name.to_sym] : @@lut_proc
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# lut_set_options(lut_name, options) / lut_options(lut_name)
|
|
101
|
-
def lut_set_options(name, options)
|
|
102
|
-
lut_options[name.to_sym] = options
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def lut_options(name = nil)#, option=nil)
|
|
106
|
-
@@lut_options ||= {}
|
|
107
|
-
(name) ? @@lut_options[name.to_sym] : @@lut_options
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Reads a single lut
|
|
111
|
-
# HACK
|
|
112
|
-
def lut_read(name)
|
|
113
|
-
return nil unless options = lut_options(name)
|
|
114
|
-
|
|
115
|
-
if options[:skip_memcached]
|
|
116
|
-
return lut_read_without_cache(name)
|
|
117
|
-
else
|
|
118
|
-
return lut_read_from_cache(name)
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
################
|
|
125
|
-
### NO-CACHE ###
|
|
126
|
-
################
|
|
127
|
-
|
|
128
|
-
# Reads a complete from given block or generic version
|
|
129
|
-
# HACK: some duplicated methods from Class.lut_write_to_cache
|
|
130
|
-
def lut_read_without_cache(name)
|
|
131
|
-
if lut_options(name)[:sql_mode]
|
|
132
|
-
return lut_read_without_cache_sql_mode(name)
|
|
133
|
-
else
|
|
134
|
-
return lut_read_without_cache_no_sql_mode(name)
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# HACK: somehow long method
|
|
139
|
-
def lut_read_without_cache_sql_mode(name)
|
|
140
|
-
lut = {}
|
|
141
|
-
block = lut_proc(name)
|
|
142
|
-
|
|
143
|
-
self.where(lut_options(name)[:where]).find_in_batches(:batch_size => lut_options(name)[:batch_size]) do |items| #FIXME not DRY here
|
|
144
|
-
items.each do |item|
|
|
145
|
-
if block
|
|
146
|
-
block.call(lut, item)
|
|
147
|
-
else
|
|
148
|
-
lut[item.send(name)] = item.id
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
return lut
|
|
154
|
-
end
|
|
10
|
+
# * Avoid @class_variables, issues if "subclassed"
|
|
155
11
|
|
|
156
|
-
# HACK: ugly method_name
|
|
157
|
-
def lut_read_without_cache_no_sql_mode(name)
|
|
158
|
-
lut = {}
|
|
159
12
|
|
|
160
|
-
|
|
161
|
-
block.call(lut)
|
|
13
|
+
require 'look_up_table'
|
|
162
14
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
#############
|
|
169
|
-
### CACHE ###
|
|
170
|
-
#############
|
|
171
|
-
|
|
172
|
-
# Cache entry for given LUT exists?
|
|
173
|
-
# TODO: just check if given key exists
|
|
174
|
-
def lut_cache_exists?(name)
|
|
175
|
-
!lut_read_cache_item(name, 0).nil?
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
# Reads a complete lut from cache
|
|
179
|
-
# HACK: this still looks ugly somehow
|
|
180
|
-
def lut_read_from_cache(name)
|
|
181
|
-
lut_write_to_cache(name) unless lut_cache_exists?(name)
|
|
182
|
-
|
|
183
|
-
i = 0
|
|
184
|
-
lut = {}
|
|
185
|
-
|
|
186
|
-
while item = lut_read_cache_item(name, i)
|
|
187
|
-
# HACK: merge will override existing values
|
|
188
|
-
lut.merge!(item) if item
|
|
189
|
-
i += 1
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
return lut
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
# Reads a single item of a LookUpTable from Cache
|
|
196
|
-
def lut_read_cache_item(name, item)
|
|
197
|
-
prefix = lut_options(name)[:prefix]
|
|
198
|
-
Rails.cache.read("#{prefix}#{name}/#{item}")
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
# Write a LookUpTable into Cache
|
|
202
|
-
def lut_write_to_cache(name)
|
|
203
|
-
if lut_options(name)[:sql_mode]
|
|
204
|
-
count = lut_write_to_cache_sql_mode(name)
|
|
205
|
-
else
|
|
206
|
-
count = lut_write_to_cache_no_sql_mode(name)
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
# HACK: Writing a \0 to terminate batch_items
|
|
210
|
-
lut_write_cache_item(name, count, nil)
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
# HACK: somehow long method
|
|
214
|
-
def lut_write_to_cache_sql_mode(name)
|
|
215
|
-
batch_count = 0
|
|
216
|
-
|
|
217
|
-
self.where(lut_options(name)[:where]).find_in_batches(:batch_size => lut_options(name)[:batch_size]) do |items| #FIXME not DRY here
|
|
218
|
-
lut = {}
|
|
219
|
-
block = lut_proc(name)
|
|
220
|
-
|
|
221
|
-
items.each do |item|
|
|
222
|
-
if block
|
|
223
|
-
block.call(lut, item)
|
|
224
|
-
else
|
|
225
|
-
lut[item.send(name)] = item.id
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
self.lut_write_cache_item(name, batch_count, lut)
|
|
230
|
-
batch_count += 1
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
batch_count
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
# HACK: somehow long method
|
|
237
|
-
def lut_write_to_cache_no_sql_mode(name)
|
|
238
|
-
lut = {}
|
|
239
|
-
batch_count = 0
|
|
240
|
-
|
|
241
|
-
block = lut_proc(name)
|
|
242
|
-
block.call(lut)
|
|
243
|
-
|
|
244
|
-
keys = lut.keys
|
|
245
|
-
|
|
246
|
-
while
|
|
247
|
-
key_block = keys.slice!(0, lut_options(name)[:batch_size])
|
|
248
|
-
break if key_block.empty?
|
|
249
|
-
|
|
250
|
-
lut_block = {}
|
|
251
|
-
key_block.each{|key| lut_block[key] = lut[key]}
|
|
252
|
-
|
|
253
|
-
self.lut_write_cache_item(name, batch_count, lut_block)
|
|
254
|
-
batch_count += 1
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
batch_count
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
# Write a single Item into LookUpTable-Cache
|
|
261
|
-
def lut_write_cache_item(name, lut_item, lut_data)
|
|
262
|
-
prefix = lut_options(name)[:prefix]
|
|
263
|
-
status = Rails.cache.write("#{prefix}#{name}/#{lut_item}", lut_data)
|
|
264
|
-
raise "Cache::write failed - Try lower :batch_size" unless status
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
# Delegating <attribute>_lut(args) method calls
|
|
270
|
-
# HACK
|
|
271
|
-
def method_missing(m, *args, &block)
|
|
272
|
-
method_name = m.to_s
|
|
273
|
-
if method_name.end_with?("_lut")
|
|
274
|
-
lut_name = method_name[0..-5]
|
|
275
|
-
self.lut(lut_name, args.first)
|
|
276
|
-
else
|
|
277
|
-
super(m, *args, &block)
|
|
278
|
-
end
|
|
279
|
-
end
|
|
15
|
+
module LookUpTable
|
|
16
|
+
extend ActiveSupport::Autoload
|
|
17
|
+
extend ActiveSupport::Concern
|
|
280
18
|
|
|
281
|
-
|
|
19
|
+
require 'look_up_table/base'
|
|
20
|
+
#autoload :Base # CHECK
|
|
282
21
|
end
|
|
283
22
|
|
|
284
23
|
ActiveRecord::Base.send :include, LookUpTable
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
class Foobar < ActiveRecord::Base
|
|
2
|
+
|
|
3
|
+
look_up_table :id, :batch_size => 500000
|
|
2
4
|
look_up_table :foo
|
|
5
|
+
look_up_table :bar, :sql_mode => false do |lut|
|
|
6
|
+
lut[1] = []
|
|
7
|
+
500000.times { |key| lut[1] << Random.rand }
|
|
8
|
+
lut[1].sort!
|
|
9
|
+
end
|
|
10
|
+
#look_up_table :foobar
|
|
11
|
+
|
|
3
12
|
end
|
|
4
13
|
|