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.
Files changed (105) hide show
  1. data/lib/look_up_table/base.rb +155 -0
  2. data/lib/look_up_table/cache.rb +170 -0
  3. data/lib/look_up_table/method_missing.rb +22 -0
  4. data/lib/look_up_table/no_cache.rb +45 -0
  5. data/lib/look_up_table/version.rb +2 -1
  6. data/lib/look_up_table.rb +7 -268
  7. data/test/dummy/app/models/bar.rb +4 -0
  8. data/test/dummy/app/models/foo.rb +4 -0
  9. data/test/dummy/app/models/foobar.rb +9 -0
  10. data/test/dummy/config/database.yml +9 -14
  11. data/test/dummy/config/environments/test.rb +3 -0
  12. data/test/dummy/db/development.sqlite3 +0 -0
  13. data/test/dummy/db/migrate/20111024130333_create_foobars.rb +2 -1
  14. data/test/dummy/db/migrate/20111024130334_create_foos.rb +11 -0
  15. data/test/dummy/db/migrate/20111024130335_create_bars.rb +11 -0
  16. data/test/dummy/db/schema.rb +13 -1
  17. data/test/dummy/log/development.log +14 -0
  18. data/test/dummy/log/test.log +59 -0
  19. data/test/dummy/tmp/cache/3F4/570/Foobarid%2F0 +0 -0
  20. data/test/dummy/tmp/cache/3F5/580/Foobarid%2F1 +0 -0
  21. data/test/dummy/tmp/cache/45C/750/Foobarbar%2F0 +0 -0
  22. data/test/dummy/tmp/cache/45D/760/Foobarbar%2F1 +0 -0
  23. data/test/dummy/tmp/cache/45E/770/Foobarbar%2F2 +0 -0
  24. data/test/dummy/tmp/cache/45F/780/Foobarbar%2F3 +0 -0
  25. data/test/dummy/tmp/cache/460/790/Foobarbar%2F4 +0 -0
  26. data/test/dummy/tmp/cache/461/7A0/Foobarbar%2F5 +0 -0
  27. data/test/dummy/tmp/cache/462/7B0/Foobarbar%2F6 +0 -0
  28. data/test/dummy/tmp/cache/463/7C0/Foobarbar%2F7 +0 -0
  29. data/test/dummy/tmp/cache/464/7D0/Foobarbar%2F8 +0 -0
  30. data/test/dummy/tmp/cache/465/7E0/Foobarbar%2F9 +0 -0
  31. data/test/dummy/tmp/cache/46B/D60/Foobarfoo%2F0 +0 -0
  32. data/test/dummy/tmp/cache/46C/D70/Foobarfoo%2F1 +0 -0
  33. data/test/dummy/tmp/cache/46D/D80/Foobarfoo%2F2 +0 -0
  34. data/test/dummy/tmp/cache/46E/D90/Foobarfoo%2F3 +0 -0
  35. data/test/dummy/tmp/cache/46F/DA0/Foobarfoo%2F4 +0 -0
  36. data/test/dummy/tmp/cache/470/DB0/Foobarfoo%2F5 +0 -0
  37. data/test/dummy/tmp/cache/471/DC0/Foobarfoo%2F6 +0 -0
  38. data/test/dummy/tmp/cache/472/DD0/Foobarfoo%2F7 +0 -0
  39. data/test/dummy/tmp/cache/473/DE0/Foobarfoo%2F8 +0 -0
  40. data/test/dummy/tmp/cache/474/DF0/Foobarfoo%2F9 +0 -0
  41. data/test/dummy/tmp/cache/48D/030/Foobarbar%2F10 +0 -0
  42. data/test/dummy/tmp/cache/48E/040/Foobarbar%2F11 +0 -0
  43. data/test/dummy/tmp/cache/48E/050/Foobarbar%2F20 +0 -0
  44. data/test/dummy/tmp/cache/48F/050/Foobarbar%2F12 +0 -0
  45. data/test/dummy/tmp/cache/48F/060/Foobarbar%2F21 +0 -0
  46. data/test/dummy/tmp/cache/48F/070/Foobarbar%2F30 +0 -0
  47. data/test/dummy/tmp/cache/490/060/Foobarbar%2F13 +0 -0
  48. data/test/dummy/tmp/cache/490/070/Foobarbar%2F22 +0 -0
  49. data/test/dummy/tmp/cache/490/080/Foobarbar%2F31 +0 -0
  50. data/test/dummy/tmp/cache/490/090/Foobarbar%2F40 +0 -0
  51. data/test/dummy/tmp/cache/491/070/Foobarbar%2F14 +0 -0
  52. data/test/dummy/tmp/cache/491/080/Foobarbar%2F23 +0 -0
  53. data/test/dummy/tmp/cache/491/090/Foobarbar%2F32 +0 -0
  54. data/test/dummy/tmp/cache/491/0A0/Foobarbar%2F41 +0 -0
  55. data/test/dummy/tmp/cache/492/080/Foobarbar%2F15 +0 -0
  56. data/test/dummy/tmp/cache/492/090/Foobarbar%2F24 +0 -0
  57. data/test/dummy/tmp/cache/492/0A0/Foobarbar%2F33 +0 -0
  58. data/test/dummy/tmp/cache/493/090/Foobarbar%2F16 +0 -0
  59. data/test/dummy/tmp/cache/493/0A0/Foobarbar%2F25 +0 -0
  60. data/test/dummy/tmp/cache/493/0B0/Foobarbar%2F34 +0 -0
  61. data/test/dummy/tmp/cache/494/0A0/Foobarbar%2F17 +0 -0
  62. data/test/dummy/tmp/cache/494/0B0/Foobarbar%2F26 +0 -0
  63. data/test/dummy/tmp/cache/494/0C0/Foobarbar%2F35 +0 -0
  64. data/test/dummy/tmp/cache/495/0B0/Foobarbar%2F18 +0 -0
  65. data/test/dummy/tmp/cache/495/0C0/Foobarbar%2F27 +0 -0
  66. data/test/dummy/tmp/cache/495/0D0/Foobarbar%2F36 +0 -0
  67. data/test/dummy/tmp/cache/496/0C0/Foobarbar%2F19 +0 -0
  68. data/test/dummy/tmp/cache/496/0D0/Foobarbar%2F28 +0 -0
  69. data/test/dummy/tmp/cache/496/0E0/Foobarbar%2F37 +0 -0
  70. data/test/dummy/tmp/cache/497/0E0/Foobarbar%2F29 +0 -0
  71. data/test/dummy/tmp/cache/497/0F0/Foobarbar%2F38 +0 -0
  72. data/test/dummy/tmp/cache/498/100/Foobarbar%2F39 +0 -0
  73. data/test/dummy/tmp/cache/49C/730/Foobarfoo%2F10 +0 -0
  74. data/test/dummy/tmp/cache/49D/740/Foobarfoo%2F11 +0 -0
  75. data/test/dummy/tmp/cache/49D/750/Foobarfoo%2F20 +0 -0
  76. data/test/dummy/tmp/cache/49E/750/Foobarfoo%2F12 +0 -0
  77. data/test/dummy/tmp/cache/49E/760/Foobarfoo%2F21 +0 -0
  78. data/test/dummy/tmp/cache/49E/770/Foobarfoo%2F30 +0 -0
  79. data/test/dummy/tmp/cache/49F/760/Foobarfoo%2F13 +0 -0
  80. data/test/dummy/tmp/cache/49F/770/Foobarfoo%2F22 +0 -0
  81. data/test/dummy/tmp/cache/49F/780/Foobarfoo%2F31 +0 -0
  82. data/test/dummy/tmp/cache/49F/790/Foobarfoo%2F40 +0 -0
  83. data/test/dummy/tmp/cache/4A0/770/Foobarfoo%2F14 +0 -0
  84. data/test/dummy/tmp/cache/4A0/780/Foobarfoo%2F23 +0 -0
  85. data/test/dummy/tmp/cache/4A0/790/Foobarfoo%2F32 +0 -0
  86. data/test/dummy/tmp/cache/4A0/7A0/Foobarfoo%2F41 +0 -0
  87. data/test/dummy/tmp/cache/4A1/780/Foobarfoo%2F15 +0 -0
  88. data/test/dummy/tmp/cache/4A1/790/Foobarfoo%2F24 +0 -0
  89. data/test/dummy/tmp/cache/4A1/7A0/Foobarfoo%2F33 +0 -0
  90. data/test/dummy/tmp/cache/4A2/790/Foobarfoo%2F16 +0 -0
  91. data/test/dummy/tmp/cache/4A2/7A0/Foobarfoo%2F25 +0 -0
  92. data/test/dummy/tmp/cache/4A2/7B0/Foobarfoo%2F34 +0 -0
  93. data/test/dummy/tmp/cache/4A3/7A0/Foobarfoo%2F17 +0 -0
  94. data/test/dummy/tmp/cache/4A3/7B0/Foobarfoo%2F26 +0 -0
  95. data/test/dummy/tmp/cache/4A3/7C0/Foobarfoo%2F35 +0 -0
  96. data/test/dummy/tmp/cache/4A4/7B0/Foobarfoo%2F18 +0 -0
  97. data/test/dummy/tmp/cache/4A4/7C0/Foobarfoo%2F27 +0 -0
  98. data/test/dummy/tmp/cache/4A4/7D0/Foobarfoo%2F36 +0 -0
  99. data/test/dummy/tmp/cache/4A5/7C0/Foobarfoo%2F19 +0 -0
  100. data/test/dummy/tmp/cache/4A5/7D0/Foobarfoo%2F28 +0 -0
  101. data/test/dummy/tmp/cache/4A5/7E0/Foobarfoo%2F37 +0 -0
  102. data/test/dummy/tmp/cache/4A6/7E0/Foobarfoo%2F29 +0 -0
  103. data/test/dummy/tmp/cache/4A6/7F0/Foobarfoo%2F38 +0 -0
  104. data/test/dummy/tmp/cache/4A7/800/Foobarfoo%2F39 +0 -0
  105. 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
+
@@ -1,3 +1,4 @@
1
1
  module LookUpTable
2
- VERSION = "0.1.0.rc8"
2
+ VERSION = "0.1.0.rc9"
3
3
  end
4
+
data/lib/look_up_table.rb CHANGED
@@ -7,278 +7,17 @@
7
7
  # * use symbols freely
8
8
 
9
9
  # FIXME:
10
- # * Avoid @@class_variables, issues if "subclassed"
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
- block = lut_proc(name)
161
- block.call(lut)
13
+ require 'look_up_table'
162
14
 
163
- return lut
164
- end
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
- end
19
+ require 'look_up_table/base'
20
+ #autoload :Base # CHECK
282
21
  end
283
22
 
284
23
  ActiveRecord::Base.send :include, LookUpTable
@@ -0,0 +1,4 @@
1
+ class Bar < Foobar
2
+ #look_up_table :bar
3
+ end
4
+
@@ -0,0 +1,4 @@
1
+ class Foo < Foobar
2
+ #look_up_table :foo
3
+ end
4
+
@@ -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