Module: SupportTableCache

Extended by:
ActiveSupport::Concern
Defined in:
lib/support_table_cache.rb,
lib/support_table_cache/associations.rb,
lib/support_table_cache/fiber_locals.rb,
lib/support_table_cache/memory_cache.rb,
lib/support_table_cache/find_by_override.rb,
lib/support_table_cache/relation_override.rb

Overview

This concern can be added to a model to add the ability to look up entries in the table using Rails.cache when calling find_by rather than hitting the database every time.

Defined Under Namespace

Modules: Associations, ClassMethods, FindByOverride, RelationOverride Classes: FiberLocals, MemoryCache

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.cacheActiveSupport::Cache::Store

Get the global cache (will default to Rails.cache if running in a Rails environment).

Returns:

  • (ActiveSupport::Cache::Store)


206
207
208
209
210
211
212
213
214
# File 'lib/support_table_cache.rb', line 206

def cache
  if testing_cache
    testing_cache
  elsif @cache != NOT_SET
    @cache
  elsif defined?(Rails.cache)
    Rails.cache
  end
end

.cache=(value) ⇒ void

This method returns an undefined value.

Set the global cache to use.

Parameters:

  • value (ActiveSupport::Cache::Store, Symbol)

    The cache instance to use. You can also specify the value :memory to use an optimized in-memory cache.



198
199
200
201
# File 'lib/support_table_cache.rb', line 198

def cache=(value)
  value = MemoryCache.new if value == :memory
  @cache = value
end

.cache_key(klass, attributes, key_attribute_names, case_sensitive) ⇒ Array(String, Hash)?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Generate a consistent cache key for a set of attributes. It will return nil if the attributes are not cacheable.

Parameters:

  • klass (Class)

    The class that is being cached.

  • attributes (Hash)

    The attributes used to find a record.

  • key_attribute_names (Array<String>)

    List of attributes that can be used as a key in the cache.

  • case_sensitive (Boolean)

    Indicator if string values are case-sensitive in the cache key.

Returns:

  • (Array(String, Hash), nil)

    A two-element array with the class name and attributes hash, or nil if not cacheable.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/support_table_cache.rb', line 250

def cache_key(klass, attributes, key_attribute_names, case_sensitive)
  return nil if attributes.blank? || key_attribute_names.blank?

  sorted_names = attributes.keys.map(&:to_s).sort
  return nil unless sorted_names == key_attribute_names

  sorted_attributes = {}
  sorted_names.each do |attribute_name|
    value = (attributes.key?(attribute_name) ? attributes[attribute_name] : attributes[attribute_name.to_sym])
    # Cast the value through the attribute type so that equivalent values (e.g. a symbol
    # and a string, or "5" and 5) always produce the same cache key. Otherwise entries
    # could be written under keys that the invalidation callbacks can never delete.
    value = klass.type_for_attribute(attribute_name).cast(value)
    if !case_sensitive && (value.is_a?(String) || value.is_a?(Symbol))
      value = value.to_s.downcase
    end
    sorted_attributes[attribute_name] = value
  end

  [klass.name, sorted_attributes]
end

.cache_key_for_query(klass, attributes) ⇒ Array(String, Hash)?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Find the cache key for a query on a set of attributes by matching the attributes against the cacheable attribute configuration for a class. Returns nil if the query cannot be cached.

Parameters:

  • klass (Class)

    The class that is being queried.

  • attributes (Hash)

    The query attributes with stringified keys.

Returns:

  • (Array(String, Hash), nil)

    The cache key or nil if the query is not cacheable.



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/support_table_cache.rb', line 280

def cache_key_for_query(klass, attributes)
  return nil if attributes.blank?

  Array(klass.support_table_cache_by_attributes).each do |attribute_names, case_sensitive, where|
    # Cast both sides through the attribute type so that equivalent values (e.g. 1 and "1")
    # match the where clause the same way they are matched when building cache keys.
    where_matched = where.nil? || where.all? do |name, value|
      type = klass.type_for_attribute(name)
      attributes.include?(name) && type.cast(attributes[name]) == type.cast(value)
    end
    next unless where_matched

    key_attributes = (where ? attributes.except(*where.keys) : attributes)
    key = cache_key(klass, key_attributes, attribute_names, case_sensitive)
    return key if key
  end

  nil
end

.cacheable_query?(klass, attributes) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return true if a query on a set of attributes can be looked up in the cache.

Parameters:

  • klass (Class)

    The class that is being queried.

  • attributes (Hash, nil)

    The query attributes with stringified keys.

Returns:

  • (Boolean)


306
307
308
309
310
# File 'lib/support_table_cache.rb', line 306

def cacheable_query?(klass, attributes)
  return false if attributes.nil?

  !cache_key_for_query(klass, attributes).nil?
end

.disable(disabled = true) { ... } ⇒ Object?

Disable the caching behavior for all classes. If a block is specified, then caching is only disabled for that block. If no block is specified, then caching is disabled globally.

Parameters:

  • disabled (Boolean) (defaults to: true)

    Caching will be disabled if this is true and enabled if false.

Yields:

  • Executes the provided block with caching disabled or enabled (if block is given).

Returns:

  • (Object, nil)

    The return value of the block if a block is given, nil otherwise.



166
167
168
169
170
171
172
# File 'lib/support_table_cache.rb', line 166

def disable(disabled = true, &block)
  if block
    SupportTableCache.with_fiber_local("support_table_cache_disabled", !!disabled, &block)
  else
    @disabled = !!disabled
  end
end

.disabled?Boolean

Return true if caching has been disabled.

Returns:

  • (Boolean)


185
186
187
188
189
190
191
192
# File 'lib/support_table_cache.rb', line 185

def disabled?
  block_value = SupportTableCache.fiber_local_value("support_table_cache_disabled")
  if block_value.nil?
    !!@disabled
  else
    block_value
  end
end

.enable { ... } ⇒ Object?

Enable the caching behavior for all classes. If a block is specified, then caching is only enabled for that block. If no block is specified, then caching is enabled globally.

Yields:

  • Executes the provided block with caching enabled (if block is given).

Returns:

  • (Object, nil)

    The return value of the block if a block is given, nil otherwise.



179
180
181
# File 'lib/support_table_cache.rb', line 179

def enable(&block)
  disable(false, &block)
end

.fiber_local_value(varname) ⇒ Object



358
359
360
# File 'lib/support_table_cache.rb', line 358

def fiber_local_value(varname)
  @fiber_locals[varname]
end

.merge_query_attributes(klass, scope_conditions, attributes) ⇒ Hash?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Merge the conditions from a relation's where clause with the attributes passed to a finder method. A cache key can only represent a single value per attribute, so if both specify a different value for the same attribute the query cannot be cached; the database has to apply both conditions.

Parameters:

  • klass (Class)

    The class that is being queried.

  • scope_conditions (Hash)

    The relation's where conditions with stringified keys.

  • attributes (Hash)

    The finder attributes with stringified keys.

Returns:

  • (Hash, nil)

    The merged attributes or nil if the conditions conflict.



322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/support_table_cache.rb', line 322

def merge_query_attributes(klass, scope_conditions, attributes)
  return attributes if scope_conditions.blank?

  conflict = scope_conditions.any? do |name, value|
    next false unless attributes.include?(name)

    type = klass.type_for_attribute(name)
    type.cast(value) != type.cast(attributes[name])
  end
  return nil if conflict

  scope_conditions.merge(attributes)
end

.open_transaction?(klass) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Return true if there is an open transaction on the class' connection. Queries should not be cached inside a transaction since they could return uncommitted data that would be invalid if the transaction is rolled back. Transactions opened with joinable: false (i.e. Rails transactional test fixtures) are ignored.

Parameters:

  • klass (Class)

    The model class being queried.

Returns:

  • (Boolean)


344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/support_table_cache.rb', line 344

def open_transaction?(klass)
  return false unless klass.connection_pool.active_connection?

  connection = klass.connection
  return false unless connection.transaction_open?

  # current_transaction and joinable? are internal Rails APIs. If a future Rails version
  # changes them, fail safe by treating the transaction as open (bypassing the cache)
  # rather than raising. There is a spec asserting the API exists so an incompatible
  # Rails upgrade fails explicitly.
  transaction = connection.current_transaction
  !transaction.respond_to?(:joinable?) || transaction.joinable?
end

.testing! { ... } ⇒ Object

Enter test mode for a block. New caches will be used within each test mode block. You can use this to wrap your test methods so that cached values from one test don't show up in subsequent tests.

Yields:

  • Executes the provided block in test mode.

Returns:

  • (Object)

    The return value of the block.



222
223
224
225
226
227
228
229
# File 'lib/support_table_cache.rb', line 222

def testing!(&block)
  save_val = SupportTableCache.fiber_local_value("support_table_cache_test_cache")
  if save_val.nil?
    SupportTableCache.with_fiber_local("support_table_cache_test_cache", MemoryCache.new, &block)
  else
    yield
  end
end

.testing_cacheSupportTableCache::MemoryCache?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Get the current test mode cache. This will only return a value inside of a testing! block.

Returns:



235
236
237
238
239
# File 'lib/support_table_cache.rb', line 235

def testing_cache
  return nil if @cache.nil?

  SupportTableCache.fiber_local_value("support_table_cache_test_cache")
end

.with_fiber_local(varname, value, &block) ⇒ Object



362
363
364
# File 'lib/support_table_cache.rb', line 362

def with_fiber_local(varname, value, &block)
  @fiber_locals.with(varname, value, &block)
end

Instance Method Details

#uncachevoid

This method returns an undefined value.

Remove the cache entry for this record.



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/support_table_cache.rb', line 370

def uncache
  cache_by_attributes = self.class.support_table_cache_by_attributes
  return if cache_by_attributes.blank?

  cache = self.class.send(:support_table_cache_for_invalidation)
  return if cache.nil?

  cache_by_attributes.each do |attribute_names, case_sensitive|
    attributes = {}
    attribute_names.each do |name|
      attributes[name] = self[name]
    end
    cache_key = SupportTableCache.cache_key(self.class, attributes, attribute_names, case_sensitive)
    cache.delete(cache_key)
  end
end