Skip to content

Commit 9d46447

Browse files
Create hybrid hash/redis storage
1 parent 79adbf3 commit 9d46447

File tree

8 files changed

+184
-12
lines changed

8 files changed

+184
-12
lines changed

.travis.yml

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
language: ruby
22
sudo: false
33
rvm:
4-
- 2.0.0
5-
- 2.1.2
6-
- 2.2.5
7-
- 2.3.1
4+
# - 2.0.0
5+
# - 2.1.2
6+
# - 2.2.5
7+
- 2.3.0
88
gemfile:
9-
- gemfiles/rails_4.gemfile
10-
- gemfiles/rails_4.1.gemfile
9+
# - gemfiles/rails_4.gemfile
10+
# - gemfiles/rails_4.1.gemfile
1111
- gemfiles/rails_4.2.gemfile
1212
services:
1313
- redis-server
1414
env:
15-
- LIT_STORAGE=hash
16-
- LIT_STORAGE=redis
15+
# - LIT_STORAGE=hash
16+
# - LIT_STORAGE=redis
17+
- LIT_STORAGE=hybrid
1718
before_script:
1819
- cp test/dummy/config/database.yml.travis test/dummy/config/database.yml
1920
- psql -c 'create database lit_test;' -U postgres
2021
- RAILS_ENV=test bundle exec rake db:migrate
21-
script: "bundle exec rake test"
22+
script: "bundle exec rake test" # TEST=test/unit/lit_behaviour_test.rb"

lib/lit.rb

+3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ def self.get_key_value_engine
4646
when 'redis'
4747
require 'lit/adapters/redis_storage'
4848
return RedisStorage.new
49+
when 'hybrid'
50+
require 'lit/adapters/hybrid_storage'
51+
return HybridStorage.new
4952
else
5053
require 'lit/adapters/hash_storage'
5154
return HashStorage.new

lib/lit/adapters/hybrid_storage.rb

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
require 'redis'
2+
require 'concurrent'
3+
4+
module Lit
5+
extend self
6+
def redis
7+
$redis = Redis.new(url: determine_redis_provider) unless $redis
8+
$redis
9+
end
10+
11+
def determine_redis_provider
12+
ENV[ENV['REDIS_PROVIDER'] || 'REDIS_URL']
13+
end
14+
15+
def _hash
16+
$_hash ||= ::Concurrent::Hash.new
17+
end
18+
19+
def reset_hash
20+
$_hash = nil
21+
end
22+
23+
def hash_dirty?
24+
# Hash is considered dirty if hash snapshot is older
25+
# than Redis snapshot.
26+
Lit.hash_snapshot < Lit.redis_snapshot
27+
end
28+
29+
def hash_snapshot
30+
$_hash_snapshot ||= DateTime.new
31+
end
32+
33+
def hash_snapshot= (timestamp)
34+
$_hash_snapshot = timestamp
35+
end
36+
37+
def redis_snapshot
38+
timestamp = Lit.redis.get('lit:_snapshot')
39+
if timestamp.nil?
40+
timestamp = Time.current.to_s
41+
Lit.redis_snapshot = timestamp
42+
end
43+
DateTime.parse(timestamp)
44+
end
45+
46+
def redis_snapshot= (timestamp)
47+
Lit.redis.set('lit:_snapshot', timestamp)
48+
end
49+
50+
def determine_redis_provider
51+
ENV[ENV['REDIS_PROVIDER'] || 'REDIS_URL']
52+
end
53+
54+
class HybridStorage
55+
def initialize
56+
Lit.redis
57+
Lit._hash
58+
end
59+
60+
def [](key)
61+
if Lit.hash_dirty?
62+
Lit.hash_snapshot = DateTime.current
63+
Lit._hash.clear
64+
end
65+
if Lit._hash.key? key
66+
return Lit._hash[key]
67+
else
68+
redis_val = get_from_redis(key)
69+
Lit._hash[key] = redis_val
70+
end
71+
end
72+
73+
def get_from_redis(key)
74+
if Lit.redis.exists(_prefixed_key_for_array(key))
75+
Lit.redis.lrange(_prefixed_key(key), 0, -1)
76+
elsif Lit.redis.exists(_prefixed_key_for_nil(key))
77+
nil
78+
else
79+
Lit.redis.get(_prefixed_key(key))
80+
end
81+
end
82+
83+
def []=(k, v)
84+
delete(k)
85+
Lit._hash[k] = v
86+
if v.is_a?(Array)
87+
Lit.redis.set(_prefixed_key_for_array(k), '1')
88+
v.each do |ve|
89+
Lit.redis.rpush(_prefixed_key(k), ve.to_s)
90+
end
91+
elsif v.nil?
92+
Lit.redis.set(_prefixed_key_for_nil(k), '1')
93+
Lit.redis.set(_prefixed_key(k), '')
94+
else
95+
Lit.redis.set(_prefixed_key(k), v)
96+
end
97+
end
98+
99+
def delete(k)
100+
Lit.redis_snapshot = Time.current
101+
Lit._hash.delete(k)
102+
Lit.redis.del(_prefixed_key_for_array(k))
103+
Lit.redis.del(_prefixed_key_for_nil(k))
104+
Lit.redis.del(_prefixed_key(k))
105+
end
106+
107+
def clear
108+
Lit.redis_snapshot = Time.current
109+
Lit._hash.clear
110+
Lit.redis.del(keys) if keys.length > 0
111+
end
112+
113+
def keys
114+
Lit.redis.keys(_prefixed_key + '*')
115+
end
116+
117+
def has_key?(key)
118+
Lit._hash.has_key?(key) || Lit.redis.exists(_prefixed_key(key)) # This is a derp
119+
end
120+
121+
def incr(key)
122+
Lit.redis.incr(_prefixed_key(key))
123+
end
124+
125+
def sort
126+
Lit.redis.keys.sort.map do |k|
127+
[k, self.[](k)]
128+
end
129+
end
130+
131+
private
132+
133+
def _prefix
134+
prefix = 'lit:'
135+
if Lit.storage_options.is_a?(Hash)
136+
prefix += "#{Lit.storage_options[:prefix]}:" if Lit.storage_options.key?(:prefix)
137+
end
138+
prefix
139+
end
140+
141+
def _prefixed_key(key = '')
142+
_prefix + key.to_s
143+
end
144+
145+
def _prefixed_key_for_array(key = '')
146+
_prefix + 'array_flags:' + key.to_s
147+
end
148+
149+
def _prefixed_key_for_nil(key = '')
150+
_prefix + 'nil_flags:' + key.to_s
151+
end
152+
end
153+
end

lib/lit/i18n_backend.rb

+7-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def lookup(locale, key, scope = [], options = {})
5959
return content if parts.size <= 1
6060

6161
if should_cache?(key_with_locale)
62+
puts "SHOULD CACHE #{key_with_locale}"
6263
new_content = @cache.init_key_with_value(key_with_locale, content)
6364
content = new_content if content.nil? # Content can change when Lit.humanize is true for example
6465

@@ -141,12 +142,17 @@ def is_ignored_key(key_without_locale)
141142
end
142143

143144
def should_cache?(key_with_locale)
144-
return false if @cache.has_key?(key_with_locale)
145+
return false if @cache[key_with_locale] != nil
145146

146147
_, key_without_locale = ::Lit::Cache.split_key(key_with_locale)
147148
return false if is_ignored_key(key_without_locale)
148149

149150
true
150151
end
152+
153+
def extract_non_symbol_default!(options)
154+
defaults = [options[:default]].flatten
155+
defaults.detect{|default| !default.is_a?(Symbol)}
156+
end
151157
end
152158
end

lit.gemspec

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
2020
s.add_dependency 'rails', '> 3.1.0'
2121
s.add_dependency 'i18n', '~> 0.7.0'
2222
s.add_dependency 'jquery-rails'
23+
s.add_dependency 'concurrent-ruby'
2324

2425
s.add_development_dependency 'pg'
2526
s.add_development_dependency 'devise'

test/dummy/config/database.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ development:
1515
test:
1616
adapter: postgresql
1717
database: lit_test
18-
username: lit
19-
password: lit
18+
username: ebin
19+
password: ebin
2020
host: localhost
2121
pool: 5
2222
timeout: 5000

test/support/clear_snapshots.rb

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
require 'lit/adapters/hybrid_storage'
2+
def clear_snapshots
3+
trace_var :$_hash, proc { |h| print 'hash is now', v }
4+
Lit.reset_hash if defined?($_hash)
5+
Lit.hash_snapshot = nil if defined?($_hash_snapshot)
6+
Lit.redis.del('lit:_snapshot') if defined?($redis)
7+
end

test/test_helper.rb

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
class ActiveSupport::TestCase
2929
self.use_transactional_fixtures = true
3030
setup do
31+
clear_snapshots
3132
clear_redis
3233
Lit.init.cache.reset
3334
end

0 commit comments

Comments
 (0)