Module: RedisMemo::MemoizeQuery

Defined in:
lib/redis_memo/memoize_query.rb,
lib/redis_memo/memoize_query/memoize_table_column.rb

Overview

Hook into ActiveRecord to cache SQL queries and perform auto cache invalidation

Defined Under Namespace

Classes: CachedSelect, Invalidation, ModelCallback

Constant Summary collapse

@@memoized_columns =

Class variable containing all memoized columns on all ActiveRecord models

Hash.new { |h, k| h[k] = [Set.new, Set.new] }

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.create_memo(model_class, **extra_props) ⇒ Object

Creates a RedisMemo::Memoizable from the given ActiveRecord model class and column values.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/redis_memo/memoize_query.rb', line 112

def self.create_memo(model_class, **extra_props)
  using_active_record!(model_class)

  keys = extra_props.keys.sort
  if !keys.empty? && !memoized_columns(model_class).include?(keys)
    raise RedisMemo::ArgumentError.new("'#{model_class.name}' has not memoized columns: #{keys}")
  end

  extra_props.each do |key, value|
    # The data type is ensured by the database, thus we don't need to cast
    # types here for better performance
    column_name = key.to_s
    extra_props[key] =
      if model_class.defined_enums.include?(column_name)
        enum_mapping = model_class.defined_enums[column_name]
        # Assume a value is a converted enum if it does not exist in the
        # enum mapping
        (enum_mapping[value.to_s] || value).to_s
      else
        value.to_s
      end
  end

  RedisMemo::Memoizable.new(
    __redis_memo_memoize_query_table_name__: model_class.table_name,
    **extra_props,
  )
end

.invalidate(*records) ⇒ Object

Invalidates all memoized SQL queries which would contain the given records.



89
90
91
92
93
# File 'lib/redis_memo/memoize_query.rb', line 89

def self.invalidate(*records)
  RedisMemo::Memoizable.invalidate(
    records.map { |record| to_memos(record) }.flatten,
  )
end

.invalidate_all(model_class) ⇒ Object

Invalidates all memoized SQL queries on the given model.



77
78
79
80
81
82
83
84
# File 'lib/redis_memo/memoize_query.rb', line 77

def self.invalidate_all(model_class)
  RedisMemo::Tracer.trace(
    'redis_memo.memoizable.invalidate_all',
    model_class.name,
  ) do
    RedisMemo::Memoizable.invalidate([model_class.redis_memo_class_memoizable])
  end
end

.memoized_columns(model_or_table, editable_only: false) ⇒ Object

Returns the list of columns currently memoized on the model or table



102
103
104
105
# File 'lib/redis_memo/memoize_query.rb', line 102

def self.memoized_columns(model_or_table, editable_only: false)
  table = model_or_table.is_a?(Class) ? model_or_table.table_name : model_or_table
  @@memoized_columns[table.to_sym][editable_only ? 1 : 0]
end

Instance Method Details

#memoize_table_column(*raw_columns, editable: true) ⇒ Object

Core entry method for using RedisMemo to cache SQL select queries on the given column names. We intercept any ActiveRecord select queries and extract the column dependencies from SQL query parameters. From the extracted dependencies and the memoized columns on the table, we determine whether or not the query should be cached on RedisMemo. Learn more in RedisMemo::MemoizeQuery::CachedSelect.

class User < ApplicationRecord
  extend RedisMemo::MemoizeQuery
  memoize_table_column :id
  memoize_table_column :first_name
  memoize_table_column :first_name, last_name
end

On the User model, queries such as

- record.user
- User.find(user_id)
- User.where(id: user_id).first
- User.where(first_name: first_name).first
- User.where(first_name: first_name, last_name: last_name).first
- User.find_by_first_name(first_name)

will first check the Redis cache for the data before hitting the SQL database; the cache results are invalidated automatically when user records are changed.

Note that memoize_table_column :first_name, last_name specifies that only AND queries that contain both columns will be memoized. The query User.where(last_name: last_name) will NOT be memoized with the given configuration.

will be used to create memos that are invalidatable after each record save.



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
# File 'lib/redis_memo/memoize_query.rb', line 42

def memoize_table_column(*raw_columns, editable: true)
  RedisMemo::MemoizeQuery.__send__(:using_active_record!, self)
  return if RedisMemo::DefaultOptions.disable_all
  return if RedisMemo::DefaultOptions.model_disabled_for_caching?(self)

  columns = raw_columns.map(&:to_sym).sort

  RedisMemo::MemoizeQuery.memoized_columns(self, editable_only: true) << columns if editable
  RedisMemo::MemoizeQuery.memoized_columns(self, editable_only: false) << columns

  RedisMemo::MemoizeQuery::ModelCallback.install(self)
  RedisMemo::MemoizeQuery::Invalidation.install(self)

  unless RedisMemo::DefaultOptions.disable_cached_select
    RedisMemo::MemoizeQuery::CachedSelect.install(ActiveRecord::Base.connection)
  end

  # The code below might fail due to missing DB/table errors
  columns.each do |column|
    next if columns_hash.include?(column.to_s)

    raise RedisMemo::ArgumentError.new("'#{name}' does not contain column '#{column}'")
  end

  unless RedisMemo::DefaultOptions.model_disabled_for_caching?(self)
    RedisMemo::MemoizeQuery::CachedSelect.enabled_models[table_name] = self
  end
rescue ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid
  # no-opts: models with memoize_table_column decleared might be loaded in
  # rake tasks that are used to create databases
end