Class: Sfn::Cache

Inherits:
Object
  • Object
show all
Defined in:
lib/sfn/cache.rb

Overview

Data caching helper

Defined Under Namespace

Classes: LocalLock, LocalValue, Stamped

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key) ⇒ Cache

Create new instance

Parameters:

  • key (String, Array)


79
80
81
82
83
84
85
86
# File 'lib/sfn/cache.rb', line 79

def initialize(key)
  if key.respond_to?(:sort)
    key = key.flatten if key.respond_to?(:flatten)
    key = key.map(&:to_s).sort
  end
  @key = Digest::SHA256.hexdigest(key.to_s)
  @apply_limit = self.class.default_limits
end

Instance Attribute Details

#keyString (readonly)

Returns custom key for this cache

Returns:

  • (String)

    custom key for this cache



74
75
76
# File 'lib/sfn/cache.rb', line 74

def key
  @key
end

Class Method Details

.apply_limit(kind, seconds = nil) ⇒ Object

Set/get time limit on data type

return [Integer] seconds

Parameters:

  • kind (String, Symbol)

    data type

  • seconds (Integer) (defaults to: nil)


51
52
53
54
55
56
57
# File 'lib/sfn/cache.rb', line 51

def apply_limit(kind, seconds = nil)
  @apply_limit ||= {}
  if seconds
    @apply_limit[kind.to_sym] = seconds.to_i
  end
  @apply_limit[kind.to_sym].to_i
end

.configure(type, args = {}) ⇒ Object

Configure the caching approach to use

Parameters:

  • type (Symbol)

    :redis or :local

  • args (Hash) (defaults to: {})

    redis connection arguments if used



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/sfn/cache.rb', line 14

def configure(type, args = {})
  type = type.to_sym
  case type
  when :redis
    begin
      require "redis-objects"
    rescue LoadError
      $stderr.puts "The `redis-objects` gem is required for Cache support!"
      raise
    end
    @_pid = Process.pid
    Redis::Objects.redis = Redis.new(args)
  when :local
  else
    raise TypeError.new("Unsupported caching type: #{type}")
  end
  enable(type)
end

.default_limitsHash

Returns default limits

Returns:

  • (Hash)

    default limits



60
61
62
# File 'lib/sfn/cache.rb', line 60

def default_limits
  (@apply_limit || {}).dup
end

.enable(type) ⇒ Symbol

Set enabled caching type

Parameters:

  • type (Symbol)

Returns:

  • (Symbol)


37
38
39
# File 'lib/sfn/cache.rb', line 37

def enable(type)
  @type = type.to_sym
end

.redis_ping!Object

Ping the redis connection and reconnect if dead



65
66
67
68
69
70
# File 'lib/sfn/cache.rb', line 65

def redis_ping!
  if (@_pid && @_pid != Process.pid) || !Redis::Objects.redis.connected?
    Redis::Objects.redis.client.reconnect
    @_pid = Process.pid
  end
end

.typeSymbol

Returns type of caching enabled

Returns:

  • (Symbol)

    type of caching enabled



42
43
44
# File 'lib/sfn/cache.rb', line 42

def type
  @type || :local
end

Instance Method Details

#[](name) ⇒ Object, NilClass

Fetch data

Parameters:

  • name (String, Symbol)

Returns:

  • (Object, NilClass)


216
217
218
219
220
221
222
# File 'lib/sfn/cache.rb', line 216

def [](name)
  if kind = registry[name.to_s]
    get_storage(self.class.type, kind, name)
  else
    nil
  end
end

#[]=(key, val) ⇒ Object

Note:

this will never work, thus you should never use it

Set data

Parameters:

  • key (Object)
  • val (Object)


229
230
231
# File 'lib/sfn/cache.rb', line 229

def []=(key, val)
  raise "Setting backend data is not allowed"
end

#apply_limit(kind, seconds = nil) ⇒ Object

Apply time limit for data type

return [Integer]

Parameters:

  • kind (String, Symbol)

    data type

  • seconds (Integer) (defaults to: nil)


247
248
249
250
251
252
253
# File 'lib/sfn/cache.rb', line 247

def apply_limit(kind, seconds = nil)
  @apply_limit ||= {}
  if seconds
    @apply_limit[kind.to_sym] = seconds.to_i
  end
  @apply_limit[kind.to_sym].to_i
end

#clear!(*args) ⇒ TrueClass

Note:

clears all data if no names provided

Clear data

Parameters:

  • args (Symbol)

    list of names to delete

Returns:

  • (TrueClass)


108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/sfn/cache.rb', line 108

def clear!(*args)
  internal_lock do
    args = registry.keys if args.empty?
    args.each do |key|
      value = self[key]
      if value.respond_to?(:clear)
        value.clear
      elsif value.respond_to?(:value)
        value.value = nil
      end
      registry.delete(key)
    end
    yield if block_given?
  end
  true
end

#get_local_storage(data_type, full_name, args = {}) ⇒ Object

TODO:

make proper singleton for local storage

Fetch item from local storage

Parameters:

  • data_type (Symbol)
  • full_name (Symbol)
  • args (Hash) (defaults to: {})

Returns:

  • (Object)


184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/sfn/cache.rb', line 184

def get_local_storage(data_type, full_name, args = {})
  @storage ||= {}
  @storage[full_name] ||= case data_type.to_sym
                          when :array
                            []
                          when :hash
                            {}
                          when :value
                            LocalValue.new
                          when :lock
                            LocalLock.new(full_name, {:expiration => 60, :timeout => 0.1}.merge(args))
                          when :stamped
                            Stamped.new(full_name.sub("#{key}_", "").to_sym, get_local_storage(:value, full_name), self)
                          else
                            raise TypeError.new("Unsupported caching data type encountered: #{data_type}")
                          end
end

#get_redis_storage(data_type, full_name, args = {}) ⇒ Object

Fetch item from redis storage

Parameters:

  • data_type (Symbol)
  • full_name (Symbol)
  • args (Hash) (defaults to: {})

Returns:

  • (Object)


159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/sfn/cache.rb', line 159

def get_redis_storage(data_type, full_name, args = {})
  self.class.redis_ping!
  case data_type.to_sym
  when :array
    Redis::List.new(full_name, {:marshal => true}.merge(args))
  when :hash
    Redis::HashKey.new(full_name)
  when :value
    Redis::Value.new(full_name, {:marshal => true}.merge(args))
  when :lock
    Redis::Lock.new(full_name, {:expiration => 60, :timeout => 0.1}.merge(args))
  when :stamped
    Stamped.new(full_name.sub("#{key}_", "").to_sym, get_redis_storage(:value, full_name), self)
  else
    raise TypeError.new("Unsupported caching data type encountered: #{data_type}")
  end
end

#get_storage(store_type, data_type, name, args = {}) ⇒ Object

Fetch item from storage

Parameters:

  • store_type (Symbol)
  • data_type (Symbol)
  • name (Symbol)

    name of data

  • args (Hash) (defaults to: {})

    options for underlying storage

Returns:

  • (Object)


132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/sfn/cache.rb', line 132

def get_storage(store_type, data_type, name, args = {})
  full_name = "#{key}_#{name}"
  result = nil
  case store_type.to_sym
  when :redis
    result = get_redis_storage(data_type, full_name.to_s, args)
  when :local
    @_local_cache ||= {}
    unless @_local_cache[full_name.to_s]
      @_local_cache[full_name.to_s] = get_local_storage(data_type, full_name.to_s, args)
    end
    result = @_local_cache[full_name.to_s]
  else
    raise TypeError.new("Unsupported caching storage type encountered: #{store_type}")
  end
  unless full_name == "#{key}_registry_#{key}"
    registry[name.to_s] = data_type
  end
  result
end

#init(name, kind, args = {}) ⇒ Object

Initialize a new data type

Parameters:

  • name (Symbol)

    name of data

  • kind (Symbol)

    data type

  • args (Hash) (defaults to: {})

    options for data type



93
94
95
96
# File 'lib/sfn/cache.rb', line 93

def init(name, kind, args = {})
  get_storage(self.class.type, kind, name, args)
  true
end

#internal_lockObject

Note:

for internal use

Execute block within internal lock

Returns:

  • (Object)

    result of yield



206
207
208
209
210
# File 'lib/sfn/cache.rb', line 206

def internal_lock
  get_storage(self.class.type, :lock, :internal_access, :timeout => 20, :expiration => 120).lock do
    yield
  end
end

#locked_action(lock_name, raise_on_locked = false) ⇒ Object

Perform action within lock

Parameters:

  • lock_name (String, Symbol)

    name of lock

  • raise_on_locked (TrueClass, FalseClass) (defaults to: false)

    raise execption if lock wait times out

Returns:

  • (Object)

    result of yield



260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/sfn/cache.rb', line 260

def locked_action(lock_name, raise_on_locked = false)
  begin
    self[lock_name].lock do
      yield
    end
  rescue => e
    if e.class.to_s.end_with?("Timeout")
      raise if raise_on_locked
    else
      raise
    end
  end
end

#registryHash

Returns data registry

Returns:

  • (Hash)

    data registry



99
100
101
# File 'lib/sfn/cache.rb', line 99

def registry
  get_storage(self.class.type, :hash, "registry_#{key}")
end

#time_check_allow?(key, stamp) ⇒ TrueClass, FalseClass

Check if cache time has expired

Parameters:

  • key (String, Symbol)

    value key

  • stamp (Time, Integer)

Returns:

  • (TrueClass, FalseClass)


238
239
240
# File 'lib/sfn/cache.rb', line 238

def time_check_allow?(key, stamp)
  Time.now.to_i - stamp.to_i > apply_limit(key)
end