redis-memo 0.0.0.beta → 0.0.0.beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ require_relative 'middleware'
6
6
  require_relative 'options'
7
7
 
8
8
  module RedisMemo::MemoizeMethod
9
- def memoize_method(method_name, **options, &depends_on)
9
+ def memoize_method(method_name, method_id: nil, **options, &depends_on)
10
10
  method_name_without_memo = :"_redis_memo_#{method_name}_without_memo"
11
11
  method_name_with_memo = :"_redis_memo_#{method_name}_with_memo"
12
12
 
@@ -15,11 +15,24 @@ module RedisMemo::MemoizeMethod
15
15
  define_method method_name_with_memo do |*args|
16
16
  return send(method_name_without_memo, *args) if RedisMemo.without_memo?
17
17
 
18
+ dependent_memos = nil
19
+ if depends_on
20
+ dependency = RedisMemo::MemoizeMethod.get_or_extract_dependencies(self, *args, &depends_on)
21
+ dependent_memos = dependency.memos
22
+ end
23
+
18
24
  future = RedisMemo::Future.new(
19
25
  self,
20
- RedisMemo::MemoizeMethod.method_id(self, method_name),
26
+ case method_id
27
+ when NilClass
28
+ RedisMemo::MemoizeMethod.method_id(self, method_name)
29
+ when String, Symbol
30
+ method_id
31
+ else
32
+ method_id.call(self, *args)
33
+ end,
21
34
  args,
22
- depends_on,
35
+ dependent_memos,
23
36
  options,
24
37
  method_name_without_memo,
25
38
  )
@@ -30,9 +43,25 @@ module RedisMemo::MemoizeMethod
30
43
  end
31
44
 
32
45
  future.execute
46
+ rescue RedisMemo::WithoutMemoization
47
+ send(method_name_without_memo, *args)
33
48
  end
34
49
 
35
50
  alias_method method_name, method_name_with_memo
51
+
52
+ @__redis_memo_method_dependencies ||= Hash.new
53
+ @__redis_memo_method_dependencies[method_name] = depends_on
54
+
55
+ define_method :dependency_of do |method_name, *method_args|
56
+ method_depends_on = self.class.instance_variable_get(:@__redis_memo_method_dependencies)[method_name]
57
+ unless method_depends_on
58
+ raise(
59
+ RedisMemo::ArgumentError,
60
+ "#{method_name} is not a memoized method"
61
+ )
62
+ end
63
+ RedisMemo::MemoizeMethod.get_or_extract_dependencies(self, *method_args, &method_depends_on)
64
+ end
36
65
  end
37
66
 
38
67
  def self.method_id(ref, method_name)
@@ -42,23 +71,34 @@ module RedisMemo::MemoizeMethod
42
71
  "#{class_name}#{is_class_method ? '::' : '#'}#{method_name}"
43
72
  end
44
73
 
45
- def self.method_cache_keys(future_contexts)
46
- memos = Array.new(future_contexts.size)
47
- future_contexts.each_with_index do |(ref, _, method_args, depends_on), i|
48
- if depends_on
49
- dependency = RedisMemo::Memoizable::Dependency.new
74
+ def self.extract_dependencies(ref, *method_args, &depends_on)
75
+ dependency = RedisMemo::Memoizable::Dependency.new
50
76
 
51
- # Resolve the dependency recursively
52
- dependency.instance_exec(ref, *method_args, &depends_on)
77
+ # Resolve the dependency recursively
78
+ dependency.instance_exec(ref, *method_args, &depends_on)
79
+ dependency
80
+ end
53
81
 
54
- memos[i] = dependency.memos
55
- end
82
+ def self.get_or_extract_dependencies(ref, *method_args, &depends_on)
83
+ if RedisMemo::Cache.local_dependency_cache
84
+ RedisMemo::Cache.local_dependency_cache[ref] ||= {}
85
+ RedisMemo::Cache.local_dependency_cache[ref][depends_on] ||= {}
86
+ RedisMemo::Cache.local_dependency_cache[ref][depends_on][method_args] ||= extract_dependencies(ref, *method_args, &depends_on)
87
+ else
88
+ extract_dependencies(ref, *method_args, &depends_on)
89
+ end
90
+ end
91
+
92
+ def self.method_cache_keys(future_contexts)
93
+ memos = Array.new(future_contexts.size)
94
+ future_contexts.each_with_index do |(_, _, dependent_memos), i|
95
+ memos[i] = dependent_memos
56
96
  end
57
97
 
58
98
  j = 0
59
99
  memo_checksums = RedisMemo::Memoizable.checksums(memos.compact)
60
100
  method_cache_key_versions = Array.new(future_contexts.size)
61
- future_contexts.each_with_index do |(_, method_id, method_args, _), i|
101
+ future_contexts.each_with_index do |(method_id, method_args, _), i|
62
102
  if memos[i]
63
103
  method_cache_key_versions[i] = [method_id, memo_checksums[j]]
64
104
  j += 1
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'memoize_method'
3
+
4
+ if defined?(ActiveRecord)
5
+ # Hook into ActiveRecord to cache SQL queries and perform auto cache
6
+ # invalidation
7
+ module RedisMemo::MemoizeQuery
8
+ require_relative 'memoize_query/cached_select'
9
+ require_relative 'memoize_query/invalidation'
10
+ require_relative 'memoize_query/model_callback'
11
+
12
+ # Only editable columns will be used to create memos that are invalidatable
13
+ # after each record save
14
+ def memoize_table_column(*raw_columns, editable: true)
15
+ RedisMemo::MemoizeQuery.using_active_record!(self)
16
+ return if ENV["REDIS_MEMO_DISABLE_#{self.table_name.upcase}"] == 'true'
17
+
18
+ columns = raw_columns.map(&:to_sym).sort
19
+
20
+ RedisMemo::MemoizeQuery.memoized_columns(self, editable_only: true) << columns if editable
21
+ RedisMemo::MemoizeQuery.memoized_columns(self, editable_only: false) << columns
22
+
23
+ RedisMemo::MemoizeQuery::ModelCallback.install(self)
24
+ RedisMemo::MemoizeQuery::Invalidation.install(self)
25
+
26
+ if ENV['REDIS_MEMO_DISABLE_CACHED_SELECT'] != 'true'
27
+ RedisMemo::MemoizeQuery::CachedSelect.install(ActiveRecord::Base.connection)
28
+ end
29
+
30
+ # The code below might fail due to missing DB/table errors
31
+ columns.each do |column|
32
+ unless self.columns_hash.include?(column.to_s)
33
+ raise(
34
+ RedisMemo::ArgumentError,
35
+ "'#{self.name}' does not contain column '#{column}'",
36
+ )
37
+ end
38
+ end
39
+
40
+ unless ENV["REDIS_MEMO_DISABLE_QUERY_#{self.table_name.upcase}"] == 'true'
41
+ RedisMemo::MemoizeQuery::CachedSelect.enabled_models[self.table_name] = self
42
+ end
43
+ rescue ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid
44
+ # no-opts: models with memoize_table_column decleared might be loaded in
45
+ # rake tasks that are used to create databases
46
+ end
47
+
48
+ def self.using_active_record!(model_class)
49
+ unless using_active_record?(model_class)
50
+ raise RedisMemo::ArgumentError, "'#{model_class.name}' does not use ActiveRecord"
51
+ end
52
+ end
53
+
54
+ def self.using_active_record?(model_class)
55
+ model_class.respond_to?(:<) && model_class < ActiveRecord::Base
56
+ end
57
+
58
+ @@memoized_columns = Hash.new { |h, k| h[k] = [Set.new, Set.new] }
59
+
60
+ def self.memoized_columns(model_or_table, editable_only: false)
61
+ table = model_or_table.is_a?(Class) ? model_or_table.table_name : model_or_table
62
+ @@memoized_columns[table.to_sym][editable_only ? 1 : 0]
63
+ end
64
+
65
+ # extra_props are considered as AND conditions on the model class
66
+ def self.create_memo(model_class, **extra_props)
67
+ using_active_record!(model_class)
68
+
69
+ keys = extra_props.keys.sort
70
+ if !keys.empty? && !memoized_columns(model_class).include?(keys)
71
+ raise(
72
+ RedisMemo::ArgumentError,
73
+ "'#{model_class.name}' has not memoized columns: #{keys}",
74
+ )
75
+ end
76
+
77
+ extra_props.each do |key, value|
78
+ # The data type is ensured by the database, thus we don't need to cast
79
+ # types here for better performance
80
+ column_name = key.to_s
81
+ extra_props[key] =
82
+ if model_class.defined_enums.include?(column_name)
83
+ enum_mapping = model_class.defined_enums[column_name]
84
+ # Assume a value is a converted enum if it does not exist in the
85
+ # enum mapping
86
+ (enum_mapping[value.to_s] || value).to_s
87
+ else
88
+ value.to_s
89
+ end
90
+ end
91
+
92
+ RedisMemo::Memoizable.new(
93
+ __redis_memo_memoize_query_table_name__: model_class.table_name,
94
+ **extra_props,
95
+ )
96
+ end
97
+
98
+ def self.invalidate_all(model_class)
99
+ RedisMemo::Tracer.trace(
100
+ 'redis_memo.memoizable.invalidate_all',
101
+ model_class.name,
102
+ ) do
103
+ RedisMemo::Memoizable.invalidate([model_class.redis_memo_class_memoizable])
104
+ end
105
+ end
106
+
107
+ def self.invalidate(*records)
108
+ RedisMemo::Memoizable.invalidate(
109
+ records.map { |record| to_memos(record) }.flatten,
110
+ )
111
+ end
112
+
113
+ def self.to_memos(record)
114
+ # Invalidate memos with current values
115
+ memos_to_invalidate = memoized_columns(record.class).map do |columns|
116
+ props = {}
117
+ columns.each do |column|
118
+ props[column] = record.send(column)
119
+ end
120
+
121
+ create_memo(record.class, **props)
122
+ end
123
+
124
+ # Create memos with previous values if
125
+ # - there are saved changes
126
+ # - this is not creating a new record
127
+ if !record.saved_changes.empty? && !record.saved_changes.include?(record.class.primary_key)
128
+ previous_values = {}
129
+ record.saved_changes.each do |column, (previous_value, _)|
130
+ previous_values[column.to_sym] = previous_value
131
+ end
132
+
133
+ memoized_columns(record.class, editable_only: true).each do |columns|
134
+ props = previous_values.slice(*columns)
135
+ next if props.empty?
136
+
137
+ # Fill the column values that have not changed
138
+ columns.each do |column|
139
+ next if props.include?(column)
140
+
141
+ props[column] = record.send(column)
142
+ end
143
+
144
+ memos_to_invalidate << create_memo(record.class, **props)
145
+ end
146
+ end
147
+
148
+ memos_to_invalidate
149
+ end
150
+ end
151
+ end
@@ -38,7 +38,7 @@
38
38
  # the `memoize_table_column` declaration on the model class.
39
39
  #
40
40
  # class MyRecord < ApplicationRecord
41
- # extend RedisMemo::MemoizeRecords
41
+ # extend RedisMemo::MemoizeQuery
42
42
  # memoize_table_column :value
43
43
  # end
44
44
  #
@@ -87,8 +87,17 @@
87
87
  #
88
88
  # See +extract_bind_params+ for the precise detection logic.
89
89
  #
90
- class RedisMemo::MemoizeRecords::CachedSelect
91
- # TODO: merge this into RedisMemo::MemoizeQuery
90
+ class RedisMemo::MemoizeQuery::CachedSelect
91
+ require_relative 'cached_select/bind_params'
92
+ require_relative 'cached_select/connection_adapter'
93
+ require_relative 'cached_select/statement_cache'
94
+
95
+ @@enabled_models = {}
96
+
97
+ def self.enabled_models
98
+ @@enabled_models
99
+ end
100
+
92
101
  def self.install(connection)
93
102
  klass = connection.class
94
103
  return if klass.singleton_class < RedisMemo::MemoizeMethod
@@ -96,8 +105,14 @@ class RedisMemo::MemoizeRecords::CachedSelect
96
105
  klass.class_eval do
97
106
  extend RedisMemo::MemoizeMethod
98
107
 
99
- memoize_method :exec_query do |_, sql, name, binds, **kwargs|
100
- RedisMemo::MemoizeRecords::CachedSelect
108
+ memoize_method(
109
+ :exec_query,
110
+ method_id: proc do |_, sql, *args|
111
+ sql.gsub(/(\$\d+)/, '?') # $1 -> ?
112
+ .gsub(/((, *)*\?)+/, '?') # (?, ?, ? ...) -> (?)
113
+ end,
114
+ ) do |_, sql, name, binds, **kwargs|
115
+ RedisMemo::MemoizeQuery::CachedSelect
101
116
  .current_query_bind_params
102
117
  .params
103
118
  .each do |model, attrs_set|
@@ -107,8 +122,15 @@ class RedisMemo::MemoizeRecords::CachedSelect
107
122
  end
108
123
 
109
124
  depends_on RedisMemo::Memoizable.new(
110
- __redis_memo_memoize_records_memoize_query_sql__: sql,
111
- __redis_memo_memoize_records_memoize_query_binds__: binds.map(&:value_for_database),
125
+ __redis_memo_memoize_query_memoize_query_sql__: sql,
126
+ __redis_memo_memoize_query_memoize_query_binds__: binds.map do |bind|
127
+ if bind.respond_to?(:value_for_database)
128
+ bind.value_for_database
129
+ else
130
+ # In activerecord >= 6, a bind could be an actual database value
131
+ bind
132
+ end
133
+ end
112
134
  )
113
135
  end
114
136
  end
@@ -137,64 +159,6 @@ class RedisMemo::MemoizeRecords::CachedSelect
137
159
  end
138
160
  end
139
161
 
140
- module ConnectionAdapter
141
- def cacheable_query(*args)
142
- query, binds = super(*args)
143
-
144
- # Persist the arel object to StatementCache#execute
145
- query.instance_variable_set(:@__redis_memo_memoize_records_memoize_query_arel, args.last)
146
-
147
- [query, binds]
148
- end
149
-
150
- def exec_query(*args)
151
- # An Arel AST in Thread local is set prior to supported query methods
152
- if !RedisMemo.without_memo? &&
153
- RedisMemo::MemoizeRecords::CachedSelect.extract_bind_params(args[0])
154
- # [Reids $model Load] $sql $binds
155
- RedisMemo::DefaultOptions.logger&.info(
156
- "[Redis] \u001b[36;1m#{args[1]} \u001b[34;1m#{args[0]}\u001b[0m #{
157
- args[2].map { |bind| [bind.name, bind.value_for_database]}
158
- }"
159
- )
160
-
161
- RedisMemo::Tracer.trace(
162
- 'redis_memo.memoize_query',
163
- args[0]
164
- .gsub(/(\$\d+)/, '?') # $1 -> ?
165
- .gsub(/((, *)*\?)+/, '?'), # (?, ?, ? ...) -> (?)
166
- ) do
167
- super(*args)
168
- end
169
- else
170
- RedisMemo.without_memo { super(*args) }
171
- end
172
- end
173
-
174
- def select_all(*args)
175
- if args[0].is_a?(Arel::SelectManager)
176
- RedisMemo::MemoizeRecords::CachedSelect.current_query = args[0]
177
- end
178
-
179
- super(*args)
180
- ensure
181
- RedisMemo::MemoizeRecords::CachedSelect.reset_current_query
182
- end
183
- end
184
-
185
- module StatementCache
186
- def execute(*args)
187
- arel = query_builder.instance_variable_get(:@__redis_memo_memoize_records_memoize_query_arel)
188
- RedisMemo::MemoizeRecords::CachedSelect.current_query = arel
189
- RedisMemo::MemoizeRecords::CachedSelect.current_substitutes =
190
- bind_map.map_substitutes(args[0])
191
-
192
- super(*args)
193
- ensure
194
- RedisMemo::MemoizeRecords::CachedSelect.reset_current_query
195
- end
196
- end
197
-
198
162
  def self.extract_bind_params(sql)
199
163
  ast = Thread.current[THREAD_KEY_AREL]&.ast
200
164
  return false unless ast.is_a?(Arel::Nodes::SelectStatement)
@@ -230,6 +194,19 @@ class RedisMemo::MemoizeRecords::CachedSelect
230
194
  Thread.current[THREAD_KEY_AREL_BIND_PARAMS] = nil
231
195
  end
232
196
 
197
+ def self.with_new_query_context
198
+ prev_arel = Thread.current[THREAD_KEY_AREL]
199
+ prev_substitutes = Thread.current[THREAD_KEY_SUBSTITUTES]
200
+ prev_bind_params = Thread.current[THREAD_KEY_AREL_BIND_PARAMS]
201
+ RedisMemo::MemoizeQuery::CachedSelect.reset_current_query
202
+
203
+ yield
204
+ ensure
205
+ Thread.current[THREAD_KEY_AREL] = prev_arel
206
+ Thread.current[THREAD_KEY_SUBSTITUTES] = prev_substitutes
207
+ Thread.current[THREAD_KEY_AREL_BIND_PARAMS] = prev_bind_params
208
+ end
209
+
233
210
  private
234
211
 
235
212
  # A pre-order Depth First Search
@@ -240,7 +217,7 @@ class RedisMemo::MemoizeRecords::CachedSelect
240
217
  bind_params = BindParams.new
241
218
 
242
219
  case node
243
- when Arel::Nodes::Equality, Arel::Nodes::In
220
+ when NodeHasFilterCondition
244
221
  attr_node = node.left
245
222
  return unless attr_node.is_a?(Arel::Attributes::Attribute)
246
223
 
@@ -255,16 +232,8 @@ class RedisMemo::MemoizeRecords::CachedSelect
255
232
  return
256
233
  end
257
234
 
258
- type_caster = table_node.send(:type_caster)
259
- binding_relation =
260
- case type_caster
261
- when ActiveRecord::TypeCaster::Map
262
- type_caster.send(:types)
263
- when ActiveRecord::TypeCaster::Connection
264
- type_caster.instance_variable_get(:@klass)
265
- else
266
- return
267
- end
235
+ binding_relation = extract_binding_relation(table_node)
236
+ return unless binding_relation
268
237
 
269
238
  rights = node.right.is_a?(Array) ? node.right : [node.right]
270
239
  substitutes = Thread.current[THREAD_KEY_SUBSTITUTES]
@@ -285,7 +254,13 @@ class RedisMemo::MemoizeRecords::CachedSelect
285
254
  }
286
255
  when Arel::Nodes::Casted
287
256
  bind_params.params[binding_relation] << {
288
- right.attribute.name.to_sym => right.val,
257
+ right.attribute.name.to_sym =>
258
+ if right.respond_to?(:val)
259
+ right.val
260
+ else
261
+ # activerecord >= 6
262
+ right.value
263
+ end,
289
264
  }
290
265
  else
291
266
  bind_params = bind_params.union(extract_bind_params_recurse(right))
@@ -303,11 +278,17 @@ class RedisMemo::MemoizeRecords::CachedSelect
303
278
  return unless node.orders.empty?
304
279
 
305
280
  node.cores.each do |core|
281
+ # We don't support JOINs
282
+ return unless core.source.right.empty?
283
+
306
284
  # Should have a WHERE if directly selecting from a table
307
285
  source_node = core.source.left
286
+ binding_relation = nil
308
287
  case source_node
309
288
  when Arel::Table
310
- return if core.wheres.empty?
289
+ binding_relation = extract_binding_relation(source_node)
290
+
291
+ return if core.wheres.empty? || binding_relation.nil?
311
292
  when Arel::Nodes::TableAlias
312
293
  bind_params = bind_params.union(
313
294
  extract_bind_params_recurse(source_node.left)
@@ -334,6 +315,9 @@ class RedisMemo::MemoizeRecords::CachedSelect
334
315
 
335
316
  return unless bind_params
336
317
  end
318
+
319
+ # Reject any unbound select queries
320
+ return if binding_relation && bind_params.params[binding_relation].empty?
337
321
  end
338
322
 
339
323
  bind_params
@@ -352,7 +336,7 @@ class RedisMemo::MemoizeRecords::CachedSelect
352
336
  end
353
337
 
354
338
  bind_params
355
- when Arel::Nodes::Join, Arel::Nodes::Union, Arel::Nodes::Or
339
+ when Arel::Nodes::Union, Arel::Nodes::Or
356
340
  [node.left, node.right].each do |child|
357
341
  bind_params = bind_params.union(
358
342
  extract_bind_params_recurse(child)
@@ -368,132 +352,29 @@ class RedisMemo::MemoizeRecords::CachedSelect
368
352
  end
369
353
  end
370
354
 
371
- class BindParams
372
- def params
373
- #
374
- # Bind params is hash of sets: each key is a model class, each value is a
375
- # set of hashes for memoized column conditions. Example:
376
- #
377
- # {
378
- # Site => [
379
- # {name: 'a', city: 'b'},
380
- # {name: 'a', city: 'c'},
381
- # {name: 'b', city: 'b'},
382
- # {name: 'b', city: 'c'},
383
- # ],
384
- # }
385
- #
386
- @params ||= Hash.new do |models, model|
387
- models[model] = []
388
- end
389
- end
390
-
391
- def union(other)
392
- return unless other
355
+ def self.extract_binding_relation(table_node)
356
+ enabled_models[table_node.try(:name)]
357
+ end
393
358
 
394
- # The tree is almost always right-heavy. Merge into the right node for better
395
- # performance.
396
- other.params.merge!(params) do |_, other_attrs_set, attrs_set|
397
- if other_attrs_set.empty?
398
- attrs_set
399
- elsif attrs_set.empty?
400
- other_attrs_set
359
+ class NodeHasFilterCondition
360
+ def self.===(node)
361
+ case node
362
+ when Arel::Nodes::Equality, Arel::Nodes::In
363
+ true
364
+ else
365
+ # In activerecord >= 6, a new arel node HomogeneousIn is introduced
366
+ if defined?(Arel::Nodes::HomogeneousIn) &&
367
+ node.is_a?(Arel::Nodes::HomogeneousIn)
368
+ true
401
369
  else
402
- attrs_set + other_attrs_set
403
- end
404
- end
405
-
406
- other
407
- end
408
-
409
- def product(other)
410
- # Example:
411
- #
412
- # and(
413
- # [{a: 1}, {a: 2}],
414
- # [{b: 1}, {b: 2}],
415
- # )
416
- #
417
- # =>
418
- #
419
- # [
420
- # {a: 1, b: 1},
421
- # {a: 1, b: 2},
422
- # {a: 2, b: 1},
423
- # {a: 2, b: 2},
424
- # ]
425
- return unless other
426
-
427
- # The tree is almost always right-heavy. Merge into the right node for better
428
- # performance.
429
- params.each do |model, attrs_set|
430
- next if attrs_set.empty?
431
-
432
- # The other model does not have any conditions so far: carry the
433
- # attributes over to the other node
434
- if other.params[model].empty?
435
- other.params[model] = attrs_set
436
- next
437
- end
438
-
439
- # Distribute the current attrs into the other
440
- other_attrs_set_size = other.params[model].size
441
- other_attrs_set = other.params[model]
442
- merged_attrs_set = Array.new(other_attrs_set_size * attrs_set.size)
443
-
444
- attrs_set.each_with_index do |attrs, i|
445
- other_attrs_set.each_with_index do |other_attrs, j|
446
- k = i * other_attrs_set_size + j
447
- merged_attrs = merged_attrs_set[k] = other_attrs.dup
448
- attrs.each do |name, val|
449
- # Conflict detected. For example:
450
- #
451
- # (a = 1 or b = 1) and (a = 2 or b = 2)
452
- #
453
- # Keep: a = 1 and b = 2, a = 2 and b = 1
454
- # Discard: a = 1 and a = 2, b = 1 and b = 2
455
- if merged_attrs.include?(name) && merged_attrs[name] != val
456
- merged_attrs_set[k] = nil
457
- break
458
- end
459
-
460
- merged_attrs[name] = val
461
- end
462
- end
370
+ false
463
371
  end
464
-
465
- merged_attrs_set.compact!
466
- other.params[model] = merged_attrs_set
467
- end
468
-
469
- other
470
- end
471
-
472
- def uniq!
473
- params.each do |_, attrs_set|
474
- attrs_set.uniq!
475
372
  end
476
373
  end
477
-
478
- def memoizable?
479
- return false if params.empty?
480
-
481
- params.each do |model, attrs_set|
482
- return false if attrs_set.empty?
483
-
484
- attrs_set.each do |attrs|
485
- return false unless RedisMemo::MemoizeRecords
486
- .memoized_columns(model)
487
- .include?(attrs.keys.sort)
488
- end
489
- end
490
-
491
- true
492
- end
493
374
  end
494
375
 
495
376
  # Thread locals to exchange information between RedisMemo and ActiveRecord
496
- THREAD_KEY_AREL = :__redis_memo_memoize_records_cached_select_arel__
497
- THREAD_KEY_SUBSTITUTES = :__redis_memo_memoize_records_cached_select_substitues__
498
- THREAD_KEY_AREL_BIND_PARAMS = :__redis_memo_memoize_records_cached_select_arel_bind_params__
377
+ THREAD_KEY_AREL = :__redis_memo_memoize_query_cached_select_arel__
378
+ THREAD_KEY_SUBSTITUTES = :__redis_memo_memoize_query_cached_select_substitues__
379
+ THREAD_KEY_AREL_BIND_PARAMS = :__redis_memo_memoize_query_cached_select_arel_bind_params__
499
380
  end