/
feature_caching.rb
96 lines (82 loc) · 2.75 KB
/
feature_caching.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
require 'arturo/no_such_feature'
module Arturo
# To be extended by Arturo::Feature if you want to enable
# in-memory caching.
# NB: Arturo's feature caching only works when using
# Arturo::Feature.to_feature or when using the helper methods
# in Arturo and Arturo::FeatureAvailability.
# NB: if you have multiple application servers, you almost certainly
# want to clear this cache after each request:
#
# class ApplicationController < ActionController::Base
# after_filter { Arturo::Feature.clear_feature_cache }
# end
#
# Alternatively, you could redefine Arturo::Feature.feature_cache
# to use a shared cache like Memcached.
module FeatureCaching
def self.extended(base)
class <<base
alias_method_chain :to_feature, :caching
attr_accessor :cache_ttl, :feature_cache
end
base.cache_ttl = 0
base.feature_cache = Arturo::FeatureCaching::Cache.new
end
def caches_features?
self.cache_ttl.to_i > 0
end
# Wraps Arturo::Feature.to_feature with in-memory caching.
def to_feature_with_caching(feature_or_symbol)
if !caches_features?
to_feature_without_caching(feature_or_symbol)
elsif (feature_or_symbol.kind_of?(Arturo::Feature))
feature_cache.write(feature_or_symbol.symbol.to_sym, feature_or_symbol, :expires_in => cache_ttl)
feature_or_symbol
elsif (cached_feature = feature_cache.read(feature_or_symbol.to_sym))
cached_feature
else
symbol = feature_or_symbol.to_sym
feature = to_feature_without_caching(symbol)
feature_cache.write(symbol, feature, :expires_in => cache_ttl)
feature
end
end
# Warms the cache by fetching all `Feature`s and caching them.
# This is perfect for use in an initializer.
def warm_cache!
raise "Cannot warm Feature Cache; caching is disabled" unless caches_features?
all.each do |feature|
feature_cache.write(feature.symbol.to_sym, feature, :expires_in => cache_ttl)
end
end
protected
# Quack like a Rails cache.
class Cache
def initialize
@data = {} # of the form {key => [value, expires_at or nil]}
end
def read(name, options = nil)
value, expires_at = *@data[name]
if value && (expires_at.blank? || expires_at > Time.now)
value
else
nil
end
end
def write(name, value, options = nil)
expires_at = if options && options.respond_to?(:[]) && options[:expires_in]
Time.now + options.delete(:expires_in)
else
nil
end
value.freeze.tap do |val|
@data[name] = [value, expires_at]
end
end
def clear
@data.clear
end
end
end
end