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 +4 -4
- data/README.md +8 -16
- data/lib/looksist.rb +13 -63
- data/lib/looksist/core.rb +72 -0
- data/lib/looksist/hashed.rb +2 -6
- data/lib/looksist/redis_service.rb +33 -29
- data/lib/looksist/safe_lru_cache.rb +54 -0
- data/lib/looksist/version.rb +1 -1
- data/spec/{hashed_spec.rb → looksist/hashed_spec.rb} +31 -46
- data/spec/{looksist_spec.rb → looksist/looksist_spec.rb} +14 -12
- data/spec/looksist/redis_service_spec.rb +84 -0
- data/spec/looksist/safe_lru_cache_spec.rb +50 -0
- data/spec/spec_helper.rb +4 -0
- metadata +12 -8
- data/spec/redis_service_spec.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4692f5e6e9889da65c8102e96df4c6568bc055d6
|
4
|
+
data.tar.gz: c29a30d638075b5b1c8ea4640eb0150d6835eb83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
32
|
-
|
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.
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
15
|
+
include Core
|
16
|
+
include Hashed
|
36
17
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
18
|
+
class << self
|
19
|
+
attr_accessor :lookup_store, :driver, :cache_buffer_size, :redis_service
|
40
20
|
|
41
|
-
def
|
42
|
-
self
|
43
|
-
self.
|
44
|
-
|
45
|
-
|
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
|
data/lib/looksist/hashed.rb
CHANGED
@@ -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 =
|
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 =
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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(
|
14
|
-
if
|
15
|
-
entity =
|
16
|
-
|
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(
|
24
|
+
super(name, args)
|
19
25
|
end
|
20
26
|
end
|
21
27
|
|
22
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
42
|
-
|
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
|
46
|
-
@cache
|
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
|
data/lib/looksist/version.rb
CHANGED
@@ -2,25 +2,18 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Looksist::Hashed do
|
4
4
|
before(:each) do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
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(:
|
44
|
-
expect(@mock).to receive(:
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
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
|
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(:
|
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
|
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(:
|
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
|
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(:
|
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(:
|
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
|
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(:
|
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(:
|
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
|
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(:
|
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(:
|
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.
|
7
|
-
|
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.
|
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.
|
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.
|
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.
|
85
|
-
expect(Looksist.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
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-
|
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
|
data/spec/redis_service_spec.rb
DELETED
@@ -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
|