looksist 0.0.8 → 0.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 548c83e23d27f194d2d8f18a525def021867efe1
4
- data.tar.gz: 6f26ee73643662a541bb3f2b7ac0359d964092e7
3
+ metadata.gz: 4692f5e6e9889da65c8102e96df4c6568bc055d6
4
+ data.tar.gz: c29a30d638075b5b1c8ea4640eb0150d6835eb83
5
5
  SHA512:
6
- metadata.gz: 906eaab20e3069cb554ca7b5fbd82d082a8e9cb2ebc893aef1ef737bf21f57bd597e2ddd6e65acf5f015832c1acaa50bb4b495235420147a3e272755baaf8d96
7
- data.tar.gz: e9ad61712df22cba869456e71a30612d6f0d03ea5375e6ba601884e9260cc396e27475c17458277701ab7e44c2b0c58e8a6e7301a17df2be331eba928f12a2ca
6
+ metadata.gz: 0a9842bcb7941d3647be3ae3ab274665ddf8442a831499fc73b49b35e4023277c2b4f72a04af91e8c7db3a754afa8c6d5c84a0221db7db180133d9d429987575
7
+ data.tar.gz: 7a51b99b6bdd7c1e526408618cd780f3f336cd6e47ae3eeeed37433116ef124513d77b49be4289cbf8cdb21b6009c5d3aef8259cb399dfa836e2ca6b3a6e178d
data/README.md CHANGED
@@ -28,8 +28,10 @@ Or install it yourself as:
28
28
  * Add an initializer to configure looksist
29
29
 
30
30
  ``` ruby
31
- Looksist.lookup_store_client ||= Redis.new(:url => (ENV['REDIS_URL'], :driver => :hiredis)
32
- Looksist.driver = Looksist::Serializers::Her
31
+ Looksist.configure do |looksist|
32
+ looksist.lookup_store = Redis.new(:url => (ENV['REDIS_URL'], :driver => :hiredis)
33
+ looksist.driver = Looksist::Serializers::Her
34
+ end
33
35
  ```
34
36
  You need to specify the driver to manage the attributes. In this case, we use [HER](https://github.com/remiprev/her). You can add support for ActiveResource or ActiveRecord as needed (also refer to specs for free form usage without a driver).
35
37
 
@@ -49,7 +51,7 @@ it 'should generate declarative attributes on the model with simple lookup value
49
51
  end
50
52
  end
51
53
 
52
- expect(Looksist.lookup_store_client).to receive(:get).with('ids/1').and_return('Employee Name')
54
+ expect(Looksist.lookup_store).to receive(:get).with('ids/1').and_return('Employee Name')
53
55
  e = SimpleLookup::Employee.new(1)
54
56
  expect(e.name).to eq('Employee Name')
55
57
  end
@@ -71,16 +73,6 @@ lookup [:name, :location], using = :employee_id
71
73
 
72
74
  ### With Plain Hashes
73
75
 
74
- * Add an initializer to configure looksist
75
-
76
- ```ruby
77
- redis_client ||= Redis.new(:url => (ENV['REDIS_URL'], :driver => :hiredis)
78
-
79
- Looksist::Hashed.redis_service = Looksist::RedisService.instance do |lookup|
80
- lookup.client = redis_client
81
- end
82
-
83
- ```
84
76
 
85
77
  #### Columnar Hashes
86
78
 
@@ -89,7 +81,7 @@ end
89
81
  ```ruby
90
82
  it 'should inject multiple attribute to an existing hash' do
91
83
  class HashService
92
- include Looksist::Hashed
84
+ include Looksist
93
85
 
94
86
  def metrics
95
87
  {
@@ -121,7 +113,7 @@ it 'should inject multiple attribute to an existing hash' do
121
113
  ```ruby
122
114
  it 'should inject multiple attribute to an existing deep hash' do
123
115
  class EmployeeHash
124
- include Looksist::Hashed
116
+ include Looksist
125
117
 
126
118
  def metrics
127
119
  {
@@ -156,7 +148,7 @@ it 'should inject multiple attribute to an existing deep hash' do
156
148
  ```ruby
157
149
  it 'should be capable to deep lookup and inject' do
158
150
  class Menu
159
- include Looksist::Hashed
151
+ include Looksist
160
152
 
161
153
  def metrics
162
154
  {
data/lib/looksist.rb CHANGED
@@ -1,81 +1,31 @@
1
1
  require 'looksist/version'
2
2
  require 'jsonpath'
3
3
  require 'json'
4
+ require 'looksist/core'
4
5
  require 'looksist/redis_service'
5
6
  require 'looksist/hashed'
6
7
  require 'looksist/her_collection'
8
+ require 'looksist/safe_lru_cache'
7
9
 
8
- module Looksist
9
- extend ActiveSupport::Concern
10
- class << self;
11
- attr_accessor :lookup_store_client, :driver
12
- end
13
-
14
- module ClassMethods
15
-
16
- def bucket_name(entity_id)
17
- entity = entity_id.to_s.gsub('_id', '')
18
- entity.pluralize
19
- end
20
10
 
21
- def memoized(key)
22
- self.storage ||= OpenStruct.new
23
- self.storage[key] = self.storage[key] || Looksist.lookup_store_client.get(key)
24
- end
11
+ module Looksist
25
12
 
26
- def mmemoized(key, values)
27
- key_and_bucket = id_and_buckets.find{|h| h[:id] == key}
28
- return if key_and_bucket.nil?
29
- redis_keys = values.collect{|v| redis_key(key_and_bucket[:bucket], v)}
30
- left_keys_to_lookup = redis_keys.select{|k| self.storage[k].nil?}
31
- Looksist.lookup_store_client.mapped_mget(left_keys_to_lookup).each do |key, value|
32
- self.storage[key] = value
33
- end
13
+ extend ActiveSupport::Concern
34
14
 
35
- end
15
+ include Core
16
+ include Hashed
36
17
 
37
- def redis_key bucket, value
38
- [bucket, '/', value].join('')
39
- end
18
+ class << self
19
+ attr_accessor :lookup_store, :driver, :cache_buffer_size, :redis_service
40
20
 
41
- def lookup(what, using, bucket = bucket_name(using))
42
- self.lookup_attributes ||= []
43
- self.id_and_buckets ||= []
44
- self.id_and_buckets << {id: using, bucket: bucket}
45
- if what.is_a? Array
46
- what.each do |method_name|
47
- define_method(method_name) do
48
- JSON.parse(self.class.memoized(self.class.redis_key(bucket, self.send(using).try(:to_s))) || '{}')[method_name.to_s]
49
- end
50
- self.lookup_attributes << method_name
51
- end
52
- else
53
- define_method(what) do
54
- self.class.memoized(self.class.redis_key(bucket, self.send(using).try(:to_s)))
55
- end
56
- self.lookup_attributes << what.to_sym
21
+ def configure
22
+ yield self
23
+ self.redis_service = Looksist::RedisService.instance do |lookup|
24
+ lookup.client = self.lookup_store
25
+ lookup.buffer_size = self.cache_buffer_size || 50000
57
26
  end
58
27
  end
59
- end
60
-
61
-
62
28
 
63
- def as_json(opts)
64
- Looksist.driver.json_opts(self, opts)
65
29
  end
66
30
 
67
- included do |base|
68
- base.class_attribute :lookup_attributes, :storage, :id_and_buckets
69
- end
70
-
71
- module Serializers
72
- class Her
73
- class << self
74
- def json_opts(obj, opts)
75
- obj.class.lookup_attributes ||= []
76
- obj.attributes.merge(obj.class.lookup_attributes.each_with_object({}) { |a, acc| acc[a] = obj.send(a) })
77
- end
78
- end
79
- end
80
- end
81
31
  end
@@ -0,0 +1,72 @@
1
+ module Looksist
2
+ module Core
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def bucket_name(entity_id)
8
+ entity = entity_id.to_s.gsub('_id', '')
9
+ entity.pluralize
10
+ end
11
+
12
+ def memoized(key)
13
+ self.storage ||= OpenStruct.new
14
+ self.storage[key] = self.storage[key] || Looksist.lookup_store.get(key)
15
+ end
16
+
17
+ def mmemoized(key, values)
18
+ key_and_bucket = id_and_buckets.find{|h| h[:id] == key}
19
+ return if key_and_bucket.nil?
20
+ redis_keys = values.collect{|v| redis_key(key_and_bucket[:bucket], v)}
21
+ left_keys_to_lookup = redis_keys.select{|k| self.storage[k].nil?}
22
+ Looksist.lookup_store.mapped_mget(left_keys_to_lookup).each do |key, value|
23
+ self.storage[key] = value
24
+ end
25
+
26
+ end
27
+
28
+ def redis_key bucket, value
29
+ [bucket, '/', value].join('')
30
+ end
31
+
32
+ def lookup(what, using, bucket = bucket_name(using))
33
+ self.lookup_attributes ||= []
34
+ self.id_and_buckets ||= []
35
+ self.id_and_buckets << {id: using, bucket: bucket}
36
+ if what.is_a? Array
37
+ what.each do |method_name|
38
+ define_method(method_name) do
39
+ JSON.parse(self.class.memoized(self.class.redis_key(bucket, self.send(using).try(:to_s))) || '{}')[method_name.to_s]
40
+ end
41
+ self.lookup_attributes << method_name
42
+ end
43
+ else
44
+ define_method(what) do
45
+ self.class.memoized(self.class.redis_key(bucket, self.send(using).try(:to_s)))
46
+ end
47
+ self.lookup_attributes << what.to_sym
48
+ end
49
+ end
50
+ end
51
+
52
+ def as_json(opts)
53
+ Looksist.driver.json_opts(self, opts)
54
+ end
55
+
56
+ included do |base|
57
+ base.class_attribute :lookup_attributes, :storage, :id_and_buckets
58
+ end
59
+
60
+ end
61
+
62
+ module Serializers
63
+ class Her
64
+ class << self
65
+ def json_opts(obj, opts)
66
+ obj.class.lookup_attributes ||= []
67
+ obj.attributes.merge(obj.class.lookup_attributes.each_with_object({}) { |a, acc| acc[a] = obj.send(a) })
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -5,10 +5,6 @@ module Looksist
5
5
  module Hashed
6
6
  extend ActiveSupport::Concern
7
7
 
8
- class << self;
9
- attr_accessor :redis_service
10
- end
11
-
12
8
  module ClassMethods
13
9
  def inject(opts)
14
10
  raise 'Incorrect usage' unless [:after, :using, :populate].all? { |e| opts.keys.include? e }
@@ -49,7 +45,7 @@ module Looksist
49
45
  def inject_attributes_at(hash_offset, opts)
50
46
  keys = hash_offset[opts[:using]]
51
47
  entity_name = entity(opts[:using])
52
- values = Hashed.redis_service.send("#{entity_name}_for", keys)
48
+ values = Looksist.redis_service.send("#{entity_name}_for", keys)
53
49
  hash_offset[opts[:populate]] = values
54
50
  hash_offset
55
51
  end
@@ -58,7 +54,7 @@ module Looksist
58
54
  arry_of_hashes.each do |elt|
59
55
  key = elt[opts[:using]]
60
56
  entity_name = entity(opts[:using])
61
- value = Hashed.redis_service.send("#{entity_name}_for", [key])
57
+ value = Looksist.redis_service.send("#{entity_name}_for", [key])
62
58
  elt[opts[:populate]] = value.first
63
59
  end
64
60
  end
@@ -1,50 +1,54 @@
1
1
  module Looksist
2
2
  class RedisService
3
+
3
4
  attr_accessor :client, :buffer_size, :cache
4
5
 
5
- def self.instance
6
- @_instance_ ||= new
7
- @_instance_.cache ||= {}
8
- yield @_instance_ if block_given?
9
- @_instance_.buffer_size ||= 50000
10
- @_instance_
6
+ class << self
7
+ private :new
8
+
9
+ def instance
10
+ @this ||= new
11
+ @this.buffer_size = 50000
12
+ yield @this if block_given?
13
+ @this.cache = SafeLruCache.new(@this.buffer_size)
14
+ @this
15
+ end
11
16
  end
12
17
 
13
- def method_missing(m, *args, &block)
14
- if m.to_s.ends_with?("_for")
15
- entity = m.to_s.gsub('_for', '')
16
- args.first.is_a?(Array) ? find_all(entity, args.first) : find(entity, args.first)
18
+ def method_missing(name, *args, &block)
19
+ if name.to_s.ends_with?("_for")
20
+ entity = name.to_s.gsub('_for','')
21
+ first_arg = args.first
22
+ first_arg.is_a?(Array) ? find_all(entity, first_arg) : find(entity, first_arg)
17
23
  else
18
- super(m, args)
24
+ super(name, args)
19
25
  end
20
26
  end
21
27
 
22
- private
23
-
24
- def find(entity, id)
25
- key = redis_key(entity, id)
26
- hit_or_miss(key) do
27
- @client.get(key)
28
- end
28
+ def flush_cache!
29
+ @cache.clear
29
30
  end
30
31
 
32
+ private
31
33
 
32
34
  def find_all(entity, ids)
33
- @client.pipelined do
34
- ids.uniq.each do |id|
35
- find(entity, id)
36
- end
37
- end
38
- ids.each_with_object([]) { |k, acc| acc << cache[redis_key(entity, k)].value }
35
+ raise 'Buffer overflow! Increase buffer size' if ids.length > @buffer_size
36
+ keys = ids.collect { |id| redis_key(entity, id) }
37
+ missed_keys = (keys - @cache.keys).uniq
38
+ values = @client.mget missed_keys
39
+ @cache.merge!(Hash[*missed_keys.zip(values).flatten])
40
+ @cache.mslice(keys)
39
41
  end
40
42
 
41
- def hit_or_miss(key, &block)
42
- @cache[key] ||= lru(&block)
43
+ def find(entity, id)
44
+ key = redis_key(entity, id)
45
+ fetch(key) do
46
+ @client.get(key)
47
+ end
43
48
  end
44
49
 
45
- def lru
46
- @cache.shift if @cache.length >= @buffer_size
47
- yield
50
+ def fetch(key, &block)
51
+ @cache[key] ||= block.call
48
52
  end
49
53
 
50
54
  def redis_key(entity, id)
@@ -0,0 +1,54 @@
1
+ require 'monitor'
2
+
3
+ module Looksist
4
+ class SafeLruCache < Hash
5
+
6
+ include MonitorMixin
7
+
8
+ def initialize(max_size)
9
+ @max_size = max_size
10
+ super(nil)
11
+ end
12
+
13
+ def []=(key, val)
14
+ synchronize do
15
+ super(key, val)
16
+ pop
17
+ val
18
+ end
19
+ end
20
+
21
+ def merge!(hash)
22
+ synchronize do
23
+ super(hash)
24
+ (count - @max_size).times { pop }
25
+ end
26
+ end
27
+
28
+ def mslice(keys)
29
+ synchronize do
30
+ keys.collect { |k| self[k] }
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def pop
37
+ # not using shift coz: http://bugs.ruby-lang.org/issues/8312
38
+ delete(first[0]) if count > @max_size
39
+ end
40
+
41
+ def self.synchronize(*methods)
42
+ methods.each do |method|
43
+ define_method method do |*args, &blk|
44
+ synchronize do
45
+ super(*args, &blk)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ synchronize :[], :each, :to_a, :delete, :count, :has_key?
52
+
53
+ end
54
+ end
@@ -1,3 +1,3 @@
1
1
  module Lookist
2
- VERSION = '0.0.8'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -2,25 +2,18 @@ require 'spec_helper'
2
2
 
3
3
  describe Looksist::Hashed do
4
4
  before(:each) do
5
- class MockRedis
6
- def pipelined
7
- yield
8
- end
9
- end
10
-
11
- @mock = MockRedis.new
12
-
13
- Looksist::Hashed.redis_service = Looksist::RedisService.instance do |lookup|
14
- lookup.client = @mock
5
+ @mock = {}
6
+ Looksist.configure do |looksist|
7
+ looksist.lookup_store = @mock
8
+ looksist.cache_buffer_size = 10
15
9
  end
16
10
  end
17
11
 
18
-
19
12
  context 'inject ' do
20
13
 
21
14
  it 'should be capable to deep lookup and inject' do
22
15
  class Menu
23
- include Looksist::Hashed
16
+ include Looksist
24
17
 
25
18
  def metrics
26
19
  {
@@ -40,26 +33,26 @@ describe Looksist::Hashed do
40
33
  inject after: :metrics, at: '$.table.menu', using: :item_id, populate: :item_name
41
34
  end
42
35
 
43
- expect(@mock).to receive(:get).with('items/1').and_return(OpenStruct.new(value: 'Idly'))
44
- expect(@mock).to receive(:get).with('items/2').and_return(OpenStruct.new(value: 'Pongal'))
36
+ expect(@mock).to receive(:mget).with(['items/1']).and_return(['Idly'])
37
+ expect(@mock).to receive(:mget).with(['items/2']).and_return(['Pongal'])
45
38
 
46
39
  expect(Menu.new.metrics).to eq({
47
- table: {
48
- menu: [{
49
- item_id: 1,
50
- item_name: 'Idly'
51
- },
52
- {
53
- item_id: 2,
54
- item_name: 'Pongal'
55
- }]
56
- }
40
+ table: {
41
+ menu: [{
42
+ item_id: 1,
43
+ item_name: 'Idly'
44
+ },
45
+ {
46
+ item_id: 2,
47
+ item_name: 'Pongal'
48
+ }]
49
+ }
57
50
  })
58
51
  end
59
52
 
60
53
  xit 'should be capable to deep lookup and inject - another example' do
61
54
  class NewMenu
62
- include Looksist::Hashed
55
+ include Looksist
63
56
 
64
57
  def metrics
65
58
  {
@@ -102,7 +95,7 @@ describe Looksist::Hashed do
102
95
 
103
96
  it 'should be capable to deep lookup and inject on columnar hashes' do
104
97
  class DeepHash
105
- include Looksist::Hashed
98
+ include Looksist
106
99
 
107
100
  def metrics
108
101
  {
@@ -117,8 +110,7 @@ describe Looksist::Hashed do
117
110
  inject after: :metrics, at: '$.table.inner_table', using: :employee_id, populate: :employee_name
118
111
  end
119
112
 
120
- expect(@mock).to receive(:get).with('employees/10').and_return(OpenStruct.new(value: 'emp 1'))
121
- expect(@mock).to receive(:get).with('employees/20').and_return(OpenStruct.new(value: 'emp 2'))
113
+ expect(@mock).to receive(:mget).with(['employees/10', 'employees/20']).and_return(['emp 1', 'emp 2'])
122
114
 
123
115
  expect(DeepHash.new.metrics).to eq({table: {
124
116
  inner_table: {
@@ -130,7 +122,7 @@ describe Looksist::Hashed do
130
122
 
131
123
  it 'should inject single attribute to an existing hash' do
132
124
  class HashService1
133
- include Looksist::Hashed
125
+ include Looksist
134
126
 
135
127
  def metrics
136
128
  {
@@ -143,8 +135,7 @@ describe Looksist::Hashed do
143
135
  inject after: :metrics, at: :table, using: :employee_id, populate: :employee_name
144
136
  end
145
137
 
146
- expect(@mock).to receive(:get).with('employees/1').and_return(OpenStruct.new(value: 'emp 1'))
147
- expect(@mock).to receive(:get).with('employees/2').and_return(OpenStruct.new(value: 'emp 2'))
138
+ expect(@mock).to receive(:mget).with(['employees/1', 'employees/2']).and_return(['emp 1', 'emp 2'])
148
139
 
149
140
  expect(HashService1.new.metrics).to eq({table: {
150
141
  employee_id: [1, 2],
@@ -154,7 +145,7 @@ describe Looksist::Hashed do
154
145
 
155
146
  it 'should inject multiple attribute to an existing hash' do
156
147
  class HashService
157
- include Looksist::Hashed
148
+ include Looksist
158
149
 
159
150
  def metrics
160
151
  {
@@ -169,11 +160,9 @@ describe Looksist::Hashed do
169
160
  inject after: :metrics, at: :table, using: :employer_id, populate: :employer_name
170
161
  end
171
162
 
172
- expect(@mock).to receive(:get).with('employees/5').and_return(OpenStruct.new(value: 'emp 5'))
173
- expect(@mock).to receive(:get).with('employees/6').and_return(OpenStruct.new(value: 'emp 6'))
163
+ expect(@mock).to receive(:mget).with(['employees/5', 'employees/6']).and_return(['emp 5', 'emp 6'])
174
164
 
175
- expect(@mock).to receive(:get).with('employers/3').and_return(OpenStruct.new(value: 'empr 3'))
176
- expect(@mock).to receive(:get).with('employers/4').and_return(OpenStruct.new(value: 'empr 4'))
165
+ expect(@mock).to receive(:mget).with(['employers/3', 'employers/4']).and_return(['empr 3', 'empr 4'])
177
166
 
178
167
  expect(HashService.new.metrics).to eq({table: {
179
168
  employee_id: [5, 6],
@@ -186,7 +175,7 @@ describe Looksist::Hashed do
186
175
 
187
176
  it 'should inject multiple attribute to an existing deep hash' do
188
177
  class EmployeeHash
189
- include Looksist::Hashed
178
+ include Looksist
190
179
 
191
180
  def metrics
192
181
  {
@@ -203,11 +192,9 @@ describe Looksist::Hashed do
203
192
  inject after: :metrics, at: '$.table.database', using: :employer_id, populate: :employer_name
204
193
  end
205
194
 
206
- expect(@mock).to receive(:get).with('employees/15').and_return(OpenStruct.new(value: 'emp 15'))
207
- expect(@mock).to receive(:get).with('employees/16').and_return(OpenStruct.new(value: 'emp 16'))
195
+ expect(@mock).to receive(:mget).with(['employees/15', 'employees/16']).and_return(['emp 15', 'emp 16'])
208
196
 
209
- expect(@mock).to receive(:get).with('employers/13').and_return(OpenStruct.new(value: 'empr 13'))
210
- expect(@mock).to receive(:get).with('employers/14').and_return(OpenStruct.new(value: 'empr 14'))
197
+ expect(@mock).to receive(:mget).with(['employers/13', 'employers/14']).and_return(['empr 13', 'empr 14'])
211
198
 
212
199
  expect(EmployeeHash.new.metrics).to eq({table: {
213
200
  database: {
@@ -223,7 +210,7 @@ describe Looksist::Hashed do
223
210
  context 'multiple methods and injections' do
224
211
  it 'should inject multiple attribute to an existing hash' do
225
212
  class HashServiceSuper
226
- include Looksist::Hashed
213
+ include Looksist
227
214
 
228
215
  def shrinkage
229
216
  {
@@ -245,11 +232,9 @@ describe Looksist::Hashed do
245
232
  inject after: :stock, at: :table, using: :dc_id, populate: :dc_name
246
233
  end
247
234
 
248
- expect(@mock).to receive(:get).with('shrinks/1').and_return(OpenStruct.new(value: 'shrink 1'))
249
- expect(@mock).to receive(:get).with('shrinks/2').and_return(OpenStruct.new(value: 'shrink 2'))
235
+ expect(@mock).to receive(:mget).with(['shrinks/1', 'shrinks/2']).and_return(['shrink 1', 'shrink 2'])
250
236
 
251
- expect(@mock).to receive(:get).with('dcs/7').and_return(OpenStruct.new(value: 'dc 7'))
252
- expect(@mock).to receive(:get).with('dcs/8').and_return(OpenStruct.new(value: 'dc 8'))
237
+ expect(@mock).to receive(:mget).with(['dcs/7', 'dcs/8']).and_return(['dc 7', 'dc 8'])
253
238
 
254
239
  hash_service_super = HashServiceSuper.new
255
240
  expect(hash_service_super.shrinkage).to eq({table: {
@@ -3,8 +3,10 @@ require 'spec_helper'
3
3
 
4
4
  describe Looksist do
5
5
  before :each do
6
- Looksist.lookup_store_client = double('store_lookup_client')
7
- Looksist.driver = Looksist::Serializers::Her
6
+ Looksist.configure do |looksist|
7
+ looksist.lookup_store = double('store_lookup_client')
8
+ looksist.driver = Looksist::Serializers::Her
9
+ end
8
10
  end
9
11
 
10
12
  context 'Serialization Support' do
@@ -22,7 +24,7 @@ describe Looksist do
22
24
  end
23
25
  end
24
26
  end
25
- expect(Looksist.lookup_store_client).to receive(:get).with('employees/1').and_return('Employee Name')
27
+ expect(Looksist.lookup_store).to receive(:get).with('employees/1').and_return('Employee Name')
26
28
  e = Her::Employee.new({employee_id: 1})
27
29
  expect(e.name).to eq('Employee Name')
28
30
  expect(e.to_json).to eq({:employee_id => 1, :name => 'Employee Name', :another_attr => 'Hello World'}.to_json)
@@ -42,7 +44,7 @@ describe Looksist do
42
44
  end
43
45
  end
44
46
  end
45
- expect(Looksist.lookup_store_client).to receive(:get).with('employees/1').and_return('Employee Name')
47
+ expect(Looksist.lookup_store).to receive(:get).with('employees/1').and_return('Employee Name')
46
48
  e = ExplicitBucket::Employee.new(1)
47
49
  expect(e.name).to eq('Employee Name')
48
50
  end
@@ -61,7 +63,7 @@ describe Looksist do
61
63
  end
62
64
  end
63
65
  it 'should not eager evaluate' do
64
- expect(Looksist.lookup_store_client).to_not receive(:get)
66
+ expect(Looksist.lookup_store).to_not receive(:get)
65
67
  LazyEval::Employee.new(1)
66
68
  end
67
69
  end
@@ -81,8 +83,8 @@ describe Looksist do
81
83
  end
82
84
  end
83
85
 
84
- expect(Looksist.lookup_store_client).to receive(:get).with('ids/1').and_return('Employee Name')
85
- expect(Looksist.lookup_store_client).to receive(:get).with('employees/1').and_return(nil)
86
+ expect(Looksist.lookup_store).to receive(:get).with('ids/1').and_return('Employee Name')
87
+ expect(Looksist.lookup_store).to receive(:get).with('employees/1').and_return(nil)
86
88
  e = SimpleLookup::Employee.new(1)
87
89
  expect(e.name).to eq('Employee Name')
88
90
  expect(e.unavailable).to be(nil)
@@ -103,9 +105,9 @@ describe Looksist do
103
105
  end
104
106
  end
105
107
 
106
- expect(Looksist.lookup_store_client).to receive(:get).with('ids/1')
108
+ expect(Looksist.lookup_store).to receive(:get).with('ids/1')
107
109
  .and_return({name: 'Employee Name', location: 'Chennai'}.to_json)
108
- expect(Looksist.lookup_store_client).to receive(:get).twice.with('employees/1')
110
+ expect(Looksist.lookup_store).to receive(:get).twice.with('employees/1')
109
111
  .and_return(nil)
110
112
  e = CompositeLookup::Employee.new(1)
111
113
 
@@ -130,12 +132,12 @@ describe Looksist do
130
132
  end
131
133
  it 'should share storage between instances to improve performance' do
132
134
  employee_first_instance = Employee.new(1)
133
- expect(Looksist.lookup_store_client).to receive(:get).with('ids/1')
135
+ expect(Looksist.lookup_store).to receive(:get).with('ids/1')
134
136
  .and_return({name: 'Employee Name', location: 'Chennai'}.to_json)
135
137
  employee_first_instance.name
136
138
 
137
139
  employee_second_instance = Employee.new(1)
138
- expect(Looksist.lookup_store_client).not_to receive(:get).with('ids/1')
140
+ expect(Looksist.lookup_store).not_to receive(:get).with('ids/1')
139
141
 
140
142
  employee_second_instance.name
141
143
  end
@@ -164,7 +166,7 @@ describe Looksist do
164
166
  AnotherDeveloperClass.storage['cities/2'] = 'Delhi'
165
167
 
166
168
  it 'make single request for multiple values' do
167
- expect(Looksist.lookup_store_client).to receive(:mapped_mget).with(%w(cities/4 cities/5))
169
+ expect(Looksist.lookup_store).to receive(:mapped_mget).with(%w(cities/4 cities/5))
168
170
  .and_return({'cities/4' => 'Bangalore', 'cities/5' => 'Kolkata'})
169
171
  AnotherDeveloperClass.mmemoized(:city_id, [1, 4, 5])
170
172
 
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe Looksist::RedisService do
4
+
5
+ context 'single lookup' do
6
+ before(:each) do
7
+ @lookup = Looksist::RedisService.instance do |lookup|
8
+ lookup.client = {}
9
+ lookup.buffer_size = 1
10
+ end
11
+ @lookup.cache.clear
12
+ end
13
+
14
+ it 'should call redis when key not present in cache' do
15
+ expect(@lookup.client).to receive(:get).once.with('sub_categories/8001').and_return('CEREALI')
16
+ expect(@lookup.sub_category_for(8001)).to eq('CEREALI')
17
+ expect(@lookup.sub_category_for(8001)).to eq('CEREALI')
18
+ end
19
+
20
+ it 'should abandon older entries incase of buffer overflow' do
21
+ expect(@lookup.client).to receive(:get).once.with('sub_families/12').and_return('DON CORLEONE')
22
+ expect(@lookup.client).to receive(:get).once.with('sub_families/34').and_return('SOPRANOS')
23
+
24
+ expect(@lookup.sub_family_for(12)).to eq('DON CORLEONE')
25
+ expect(@lookup.sub_family_for(34)).to eq('SOPRANOS')
26
+ expect(@lookup.cache.keys).to match_array(['sub_families/34'])
27
+ end
28
+
29
+ end
30
+
31
+ context 'multi lookup' do
32
+
33
+ before(:each) do
34
+ @mock = {}
35
+ @lookup = Looksist::RedisService.instance do |lookup|
36
+ lookup.client = @mock
37
+ lookup.buffer_size = 5
38
+ end
39
+ end
40
+
41
+ it 'should mget redis when there are multiple keys' do
42
+ expect(@mock).to receive(:mget).with(['snacks/1', 'snacks/2', 'snacks/3']).once.and_return(['BAJJI', 'BONDA', 'VADA'])
43
+ expect(@lookup.snacks_for([1, 2, 3])).to match_array(%w(BAJJI BONDA VADA))
44
+ end
45
+
46
+ it 'should mget redis only for unique keys' do
47
+ expect(@mock).to receive(:mget).with(['snacks/1', 'snacks/2', 'snacks/3']).once.and_return(['BAJJI', 'BONDA', 'VADA'])
48
+ expect(@lookup.snacks_for([1, 2, 3, 1, 2])).to match_array(%w(BAJJI BONDA VADA BAJJI BONDA))
49
+ end
50
+ end
51
+
52
+ context 'value not present' do
53
+ before(:each) do
54
+ @mock = {}
55
+ @lookup = Looksist::RedisService.instance do |lookup|
56
+ lookup.client = @mock
57
+ lookup.buffer_size = 5
58
+ end
59
+ end
60
+
61
+ it 'should not bomb when there are no values present' do
62
+ expect(@mock).to receive(:mget).with(['snacks/1', 'snacks/2', 'snacks/3']).once.and_return(['BAJJI', nil, 'VADA'])
63
+ expect(@lookup.snacks_for([1, 2, 3])).to match_array(["BAJJI", nil, "VADA"])
64
+ end
65
+ end
66
+
67
+ context 'flush local cache' do
68
+ before(:each) do
69
+ @mock = {}
70
+ @lookup = Looksist::RedisService.instance do |lookup|
71
+ lookup.client = @mock
72
+ lookup.buffer_size = 5
73
+ end
74
+ end
75
+
76
+ it 'should clear the local cache' do
77
+ expect(@mock).to receive(:mget).with(['snacks/1', 'snacks/2', 'snacks/3']).once.and_return(['BAJJI', nil, 'VADA'])
78
+ @lookup.snacks_for([1, 2, 3])
79
+ expect(@lookup.cache.size).to eq(3)
80
+ @lookup.flush_cache!
81
+ expect(@lookup.cache).to be_empty
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe Looksist::SafeLruCache do
4
+
5
+ before :each do
6
+ @cache = Looksist::SafeLruCache.new(3)
7
+ end
8
+
9
+ context 'one entry' do
10
+
11
+ it 'should hold only entries limited by max size' do
12
+ @cache[:a] = 1
13
+ @cache[:b] = 2
14
+ @cache[:c] = 3
15
+ @cache[:d] = 4
16
+ expect(@cache.size).to eq(3)
17
+ expect(@cache.keys).to match_array([:b, :c, :d])
18
+ end
19
+
20
+ end
21
+
22
+ context 'multiple entries' do
23
+
24
+ it 'should flush other entries when new entries are added' do
25
+ @cache = Looksist::SafeLruCache.new(3)
26
+ @cache[:a] = 1
27
+ @cache[:b] = 2
28
+ @cache.merge!(c: 3, d: 4, e: 5)
29
+ expect(@cache.size).to eq(3)
30
+ expect(@cache.keys).to match_array([:c, :d, :e])
31
+ end
32
+
33
+ it 'race conditions when actual size equals max size' do
34
+ @cache[:a] = 1
35
+ @cache[:b] = 2
36
+ @cache.merge!(c: 3)
37
+ expect(@cache.size).to eq(3)
38
+ expect(@cache.keys).to match_array([:a, :b, :c])
39
+ end
40
+
41
+ end
42
+
43
+ context '#mslice' do
44
+ it 'should slice hash for repeating keys' do
45
+ @cache.merge!(a: 1, b: 2, c: 3)
46
+ expect(@cache.mslice([:a, :b, :c, :a, :b])).to match_array([1, 2, 3, 1, 2])
47
+ end
48
+ end
49
+
50
+ end
data/spec/spec_helper.rb CHANGED
@@ -5,6 +5,7 @@ require 'ostruct'
5
5
  require 'looksist'
6
6
  require 'looksist/redis_service'
7
7
  require 'looksist/hashed'
8
+ require 'looksist/safe_lru_cache'
8
9
  require 'pry'
9
10
 
10
11
  module Her
@@ -27,3 +28,6 @@ end
27
28
 
28
29
  TEST_API.setup url: 'http://dummy.com', &config
29
30
 
31
+ RSpec.configure do |config|
32
+
33
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: looksist
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - RC
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-10-17 00:00:00.000000000 Z
12
+ date: 2014-10-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -169,14 +169,17 @@ files:
169
169
  - README.md
170
170
  - Rakefile
171
171
  - lib/looksist.rb
172
+ - lib/looksist/core.rb
172
173
  - lib/looksist/hashed.rb
173
174
  - lib/looksist/her_collection.rb
174
175
  - lib/looksist/redis_service.rb
176
+ - lib/looksist/safe_lru_cache.rb
175
177
  - lib/looksist/version.rb
176
178
  - looksist.gemspec
177
- - spec/hashed_spec.rb
178
- - spec/looksist_spec.rb
179
- - spec/redis_service_spec.rb
179
+ - spec/looksist/hashed_spec.rb
180
+ - spec/looksist/looksist_spec.rb
181
+ - spec/looksist/redis_service_spec.rb
182
+ - spec/looksist/safe_lru_cache_spec.rb
180
183
  - spec/spec_helper.rb
181
184
  homepage: https://github.com/jpsimonroy/herdis
182
185
  licenses:
@@ -203,7 +206,8 @@ signing_key:
203
206
  specification_version: 4
204
207
  summary: Redis backed lookup for your models
205
208
  test_files:
206
- - spec/hashed_spec.rb
207
- - spec/looksist_spec.rb
208
- - spec/redis_service_spec.rb
209
+ - spec/looksist/hashed_spec.rb
210
+ - spec/looksist/looksist_spec.rb
211
+ - spec/looksist/redis_service_spec.rb
212
+ - spec/looksist/safe_lru_cache_spec.rb
209
213
  - spec/spec_helper.rb
@@ -1,55 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Looksist::RedisService do
4
-
5
- class MockRedis
6
- def pipelined
7
- yield
8
- end
9
- end
10
-
11
- context 'single lookup' do
12
- before(:each) do
13
- @lookup = Looksist::RedisService.instance do |lookup|
14
- lookup.client = {}
15
- lookup.buffer_size = 1
16
- end
17
- @lookup.cache.clear
18
- end
19
-
20
- it 'should call redis when key not present in cache' do
21
- expect(@lookup.client).to receive(:get).with('sub_categories/8001').and_return('CEREALI')
22
- expect(@lookup.sub_category_for(8001)).to eq('CEREALI')
23
- expect(@lookup.sub_category_for(8001)).to eq('CEREALI')
24
- end
25
-
26
- it 'should abandon older entries incase of buffer overflow' do
27
- expect(@lookup.client).to receive(:get).with('sub_families/12').and_return('DON CORLEONE')
28
- expect(@lookup.client).to receive(:get).with('sub_families/34').and_return('SOPRANOS')
29
-
30
- expect(@lookup.sub_family_for(12)).to eq('DON CORLEONE')
31
- expect(@lookup.sub_family_for(34)).to eq('SOPRANOS')
32
- expect(@lookup.cache.keys).to match_array(['sub_families/34'])
33
- end
34
-
35
- end
36
-
37
- context 'multi lookup' do
38
-
39
- before(:each) do
40
- @mock = MockRedis.new
41
- @lookup = Looksist::RedisService.instance do |lookup|
42
- lookup.client = @mock
43
- lookup.buffer_size = 5
44
- end
45
- end
46
-
47
- it 'should pipeline calls to redis when there are multiple keys' do
48
- expect(@mock).to receive(:get).with('snacks/1').and_return(OpenStruct.new(value:'BAJJI'))
49
- expect(@mock).to receive(:get).with('snacks/2').and_return(OpenStruct.new(value:'BONDA'))
50
- expect(@mock).to receive(:get).with('snacks/3').and_return(OpenStruct.new(value:'VADA'))
51
-
52
- expect(@lookup.snacks_for([1,2,3,1])).to match_array(%w(BAJJI BONDA VADA BAJJI))
53
- end
54
- end
55
- end