record-cache 0.1.3 → 0.1.4
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.
- checksums.yaml +8 -8
- data/lib/record_cache/base.rb +4 -4
- data/lib/record_cache/datastore/active_record_30.rb +1 -1
- data/lib/record_cache/datastore/active_record_31.rb +1 -1
- data/lib/record_cache/datastore/active_record_32.rb +2 -2
- data/lib/record_cache/datastore/active_record_40.rb +445 -0
- data/lib/record_cache/datastore/active_record_41.rb +446 -0
- data/lib/record_cache/strategy/full_table_cache.rb +1 -1
- data/lib/record_cache/strategy/util.rb +20 -3
- data/lib/record_cache/version.rb +1 -1
- data/spec/db/create-record-cache-db_and_user.sql +5 -0
- data/spec/db/database.yml +7 -0
- data/spec/db/schema.rb +9 -15
- data/spec/initializers/backward_compatibility.rb +32 -0
- data/spec/lib/active_record/visitor_spec.rb +1 -1
- data/spec/lib/base_spec.rb +2 -2
- data/spec/lib/dispatcher_spec.rb +1 -1
- data/spec/lib/multi_read_spec.rb +1 -1
- data/spec/lib/query_spec.rb +1 -1
- data/spec/lib/statistics_spec.rb +1 -1
- data/spec/lib/strategy/base_spec.rb +39 -39
- data/spec/lib/strategy/full_table_cache_spec.rb +18 -18
- data/spec/lib/strategy/index_cache_spec.rb +58 -52
- data/spec/lib/strategy/query_cache_spec.rb +1 -1
- data/spec/lib/strategy/unique_index_on_id_cache_spec.rb +57 -45
- data/spec/lib/strategy/unique_index_on_string_cache_spec.rb +47 -45
- data/spec/lib/strategy/util_spec.rb +49 -43
- data/spec/lib/version_store_spec.rb +1 -1
- data/spec/models/apple.rb +1 -2
- data/spec/spec_helper.rb +16 -7
- data/spec/support/matchers/hit_cache_matcher.rb +1 -1
- data/spec/support/matchers/miss_cache_matcher.rb +1 -1
- data/spec/support/matchers/use_cache_matcher.rb +1 -1
- metadata +63 -17
- data/spec/support/after_commit.rb +0 -73
@@ -0,0 +1,446 @@
|
|
1
|
+
module RecordCache
|
2
|
+
module ActiveRecord
|
3
|
+
|
4
|
+
module Base
|
5
|
+
class << self
|
6
|
+
def included(klass)
|
7
|
+
klass.extend ClassMethods
|
8
|
+
klass.class_eval do
|
9
|
+
class << self
|
10
|
+
alias_method_chain :find_by_sql, :record_cache
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
# the tests are always run within a transaction, so the threshold is one higher
|
18
|
+
RC_TRANSACTIONS_THRESHOLD = ENV['RAILS_ENV'] == 'test' ? 1 : 0
|
19
|
+
|
20
|
+
# add cache invalidation hooks on initialization
|
21
|
+
def record_cache_init
|
22
|
+
after_commit :record_cache_create, :on => :create, :prepend => true
|
23
|
+
after_commit :record_cache_update, :on => :update, :prepend => true
|
24
|
+
after_commit :record_cache_destroy, :on => :destroy, :prepend => true
|
25
|
+
end
|
26
|
+
|
27
|
+
# Retrieve the records, possibly from cache
|
28
|
+
def find_by_sql_with_record_cache(sql, binds = [])
|
29
|
+
# Shortcut, no caching please
|
30
|
+
return find_by_sql_without_record_cache(sql, binds) unless record_cache?
|
31
|
+
|
32
|
+
# check the piggy-back'd ActiveRelation record to see if the query can be retrieved from cache
|
33
|
+
arel = sql.is_a?(String) ? sql.instance_variable_get(:@arel) : sql
|
34
|
+
|
35
|
+
sanitized_sql = sanitize_sql(sql)
|
36
|
+
sanitized_sql = connection.to_sql(sanitized_sql, binds) if sanitized_sql.respond_to?(:ast)
|
37
|
+
|
38
|
+
records = if connection.query_cache_enabled
|
39
|
+
query_cache = connection.instance_variable_get(:@query_cache)
|
40
|
+
query_cache["rc/#{sanitized_sql}"][binds] ||= try_record_cache(arel, sql, binds)
|
41
|
+
|
42
|
+
elsif connection.open_transactions > RC_TRANSACTIONS_THRESHOLD
|
43
|
+
find_by_sql_without_record_cache(sql, binds)
|
44
|
+
|
45
|
+
else
|
46
|
+
try_record_cache(arel, sql, binds)
|
47
|
+
end
|
48
|
+
records
|
49
|
+
end
|
50
|
+
|
51
|
+
def try_record_cache(arel, sql, binds)
|
52
|
+
query = arel && arel.respond_to?(:ast) ? RecordCache::Arel::QueryVisitor.new(binds).accept(arel.ast) : nil
|
53
|
+
record_cache.fetch(query) do
|
54
|
+
find_by_sql_without_record_cache(sql, binds)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module Arel
|
64
|
+
|
65
|
+
# The method <ActiveRecord::Base>.find_by_sql is used to actually
|
66
|
+
# retrieve the data from the DB.
|
67
|
+
# Unfortunately the ActiveRelation record is not accessible from
|
68
|
+
# there, so it is piggy-back'd in the SQL string.
|
69
|
+
module TreeManager
|
70
|
+
def self.included(klass)
|
71
|
+
klass.extend ClassMethods
|
72
|
+
klass.send(:include, InstanceMethods)
|
73
|
+
klass.class_eval do
|
74
|
+
alias_method_chain :to_sql, :record_cache
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
module ClassMethods
|
79
|
+
end
|
80
|
+
|
81
|
+
module InstanceMethods
|
82
|
+
def to_sql_with_record_cache
|
83
|
+
sql = to_sql_without_record_cache
|
84
|
+
sql.instance_variable_set(:@arel, self)
|
85
|
+
sql
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Visitor for the ActiveRelation to extract a simple cache query
|
91
|
+
# Only accepts single select queries with equality where statements
|
92
|
+
# Rejects queries with grouping / having / offset / etc.
|
93
|
+
class QueryVisitor < ::Arel::Visitors::Visitor
|
94
|
+
DESC = "DESC".freeze
|
95
|
+
COMMA = ",".freeze
|
96
|
+
|
97
|
+
def initialize(bindings)
|
98
|
+
super()
|
99
|
+
@bindings = (bindings || []).inject({}){ |h, cv| column, value = cv; h[column.name] = value; h}
|
100
|
+
@cacheable = true
|
101
|
+
@query = ::RecordCache::Query.new
|
102
|
+
end
|
103
|
+
|
104
|
+
def accept ast
|
105
|
+
super
|
106
|
+
@cacheable && !ast.lock ? @query : nil
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def not_cacheable o, attribute
|
112
|
+
@cacheable = false
|
113
|
+
end
|
114
|
+
|
115
|
+
def skip o, attribute
|
116
|
+
end
|
117
|
+
|
118
|
+
alias :visit_Arel_Nodes_TableAlias :not_cacheable
|
119
|
+
|
120
|
+
alias :visit_Arel_Nodes_Lock :not_cacheable
|
121
|
+
|
122
|
+
alias :visit_Arel_Nodes_Sum :not_cacheable
|
123
|
+
alias :visit_Arel_Nodes_Max :not_cacheable
|
124
|
+
alias :visit_Arel_Nodes_Min :not_cacheable
|
125
|
+
alias :visit_Arel_Nodes_Avg :not_cacheable
|
126
|
+
alias :visit_Arel_Nodes_Count :not_cacheable
|
127
|
+
alias :visit_Arel_Nodes_Addition :not_cacheable
|
128
|
+
alias :visit_Arel_Nodes_Subtraction :not_cacheable
|
129
|
+
alias :visit_Arel_Nodes_Multiplication :not_cacheable
|
130
|
+
alias :visit_Arel_Nodes_NamedFunction :not_cacheable
|
131
|
+
|
132
|
+
alias :visit_Arel_Nodes_Bin :not_cacheable
|
133
|
+
alias :visit_Arel_Nodes_Distinct :not_cacheable
|
134
|
+
alias :visit_Arel_Nodes_DistinctOn :not_cacheable
|
135
|
+
alias :visit_Arel_Nodes_Division :not_cacheable
|
136
|
+
alias :visit_Arel_Nodes_Except :not_cacheable
|
137
|
+
alias :visit_Arel_Nodes_Exists :not_cacheable
|
138
|
+
alias :visit_Arel_Nodes_InfixOperation :not_cacheable
|
139
|
+
alias :visit_Arel_Nodes_Intersect :not_cacheable
|
140
|
+
alias :visit_Arel_Nodes_Union :not_cacheable
|
141
|
+
alias :visit_Arel_Nodes_UnionAll :not_cacheable
|
142
|
+
alias :visit_Arel_Nodes_With :not_cacheable
|
143
|
+
alias :visit_Arel_Nodes_WithRecursive :not_cacheable
|
144
|
+
|
145
|
+
def visit_Arel_Nodes_JoinSource o, attribute
|
146
|
+
# left and right are array, but using blank as it also works for nil
|
147
|
+
@cacheable = o.left.blank? || o.right.blank?
|
148
|
+
end
|
149
|
+
|
150
|
+
alias :visit_Arel_Nodes_CurrentRow :not_cacheable
|
151
|
+
alias :visit_Arel_Nodes_Extract :not_cacheable
|
152
|
+
alias :visit_Arel_Nodes_Following :not_cacheable
|
153
|
+
alias :visit_Arel_Nodes_NamedWindow :not_cacheable
|
154
|
+
alias :visit_Arel_Nodes_Over :not_cacheable
|
155
|
+
alias :visit_Arel_Nodes_Preceding :not_cacheable
|
156
|
+
alias :visit_Arel_Nodes_Range :not_cacheable
|
157
|
+
alias :visit_Arel_Nodes_Rows :not_cacheable
|
158
|
+
alias :visit_Arel_Nodes_Window :not_cacheable
|
159
|
+
|
160
|
+
alias :visit_Arel_Nodes_As :skip
|
161
|
+
alias :visit_Arel_SelectManager :not_cacheable
|
162
|
+
alias :visit_Arel_Nodes_Ascending :skip
|
163
|
+
alias :visit_Arel_Nodes_Descending :skip
|
164
|
+
alias :visit_Arel_Nodes_False :skip
|
165
|
+
alias :visit_Arel_Nodes_True :skip
|
166
|
+
|
167
|
+
alias :visit_Arel_Nodes_StringJoin :not_cacheable
|
168
|
+
alias :visit_Arel_Nodes_InnerJoin :not_cacheable
|
169
|
+
alias :visit_Arel_Nodes_OuterJoin :not_cacheable
|
170
|
+
|
171
|
+
alias :visit_Arel_Nodes_DeleteStatement :not_cacheable
|
172
|
+
alias :visit_Arel_Nodes_InsertStatement :not_cacheable
|
173
|
+
alias :visit_Arel_Nodes_UpdateStatement :not_cacheable
|
174
|
+
|
175
|
+
|
176
|
+
alias :unary :not_cacheable
|
177
|
+
alias :visit_Arel_Nodes_Group :unary
|
178
|
+
alias :visit_Arel_Nodes_Having :unary
|
179
|
+
alias :visit_Arel_Nodes_Not :unary
|
180
|
+
alias :visit_Arel_Nodes_On :unary
|
181
|
+
alias :visit_Arel_Nodes_UnqualifiedColumn :unary
|
182
|
+
|
183
|
+
def visit_Arel_Nodes_Offset o, attribute
|
184
|
+
@cacheable = false unless o.expr == 0
|
185
|
+
end
|
186
|
+
|
187
|
+
def visit_Arel_Nodes_Values o, attribute
|
188
|
+
visit o.expressions if @cacheable
|
189
|
+
end
|
190
|
+
|
191
|
+
def visit_Arel_Nodes_Limit o, attribute
|
192
|
+
@query.limit = o.expr
|
193
|
+
end
|
194
|
+
alias :visit_Arel_Nodes_Top :visit_Arel_Nodes_Limit
|
195
|
+
|
196
|
+
GROUPING_EQUALS_REGEXP = /^\W?(\w*)\W?\.\W?(\w*)\W?\s*=\s*(\d+)$/ # `calendars`.account_id = 5
|
197
|
+
GROUPING_IN_REGEXP = /^^\W?(\w*)\W?\.\W?(\w*)\W?\s*IN\s*\(([\d\s,]+)\)$/ # `service_instances`.`id` IN (118,80,120,82)
|
198
|
+
def visit_Arel_Nodes_Grouping o, attribute
|
199
|
+
return unless @cacheable
|
200
|
+
if @table_name && o.expr =~ GROUPING_EQUALS_REGEXP && $1 == @table_name
|
201
|
+
@cacheable = @query.where($2, $3.to_i)
|
202
|
+
elsif @table_name && o.expr =~ GROUPING_IN_REGEXP && $1 == @table_name
|
203
|
+
@cacheable = @query.where($2, $3.split(',').map(&:to_i))
|
204
|
+
else
|
205
|
+
@cacheable = false
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def visit_Arel_Nodes_SelectCore o, attribute
|
210
|
+
@cacheable = false unless o.groups.empty?
|
211
|
+
visit o.froms if @cacheable
|
212
|
+
visit o.wheres if @cacheable
|
213
|
+
visit o.source if @cacheable
|
214
|
+
@cacheable = o.projections.none?{ |projection| projection.to_s =~ /distinct/i } unless o.projections.empty? if @cacheable
|
215
|
+
end
|
216
|
+
|
217
|
+
def visit_Arel_Nodes_SelectStatement o, attribute
|
218
|
+
@cacheable = false if o.cores.size > 1
|
219
|
+
if @cacheable
|
220
|
+
visit o.offset
|
221
|
+
o.orders.map { |x| handle_order_by(visit x) } if @cacheable && o.orders.size > 0
|
222
|
+
visit o.limit
|
223
|
+
visit o.cores
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
ORDER_BY_REGEXP = /^\s*([\w\.]*)\s*(|ASC|asc|DESC|desc)\s*$/ # people.id DESC
|
228
|
+
def handle_order_by(order)
|
229
|
+
order.to_s.split(COMMA).each do |o|
|
230
|
+
# simple sort order (+people.id+ can be replaced by +id+, as joins are not allowed anyways)
|
231
|
+
if o.match(ORDER_BY_REGEXP)
|
232
|
+
asc = $2.upcase == DESC ? false : true
|
233
|
+
@query.order_by($1.split('.').last, asc)
|
234
|
+
else
|
235
|
+
@cacheable = false
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def visit_Arel_Table o, attribute
|
241
|
+
@table_name = o.name
|
242
|
+
end
|
243
|
+
|
244
|
+
def visit_Arel_Attributes_Attribute o, attribute
|
245
|
+
o.name.to_sym
|
246
|
+
end
|
247
|
+
alias :visit_Arel_Attributes_Integer :visit_Arel_Attributes_Attribute
|
248
|
+
alias :visit_Arel_Attributes_Float :visit_Arel_Attributes_Attribute
|
249
|
+
alias :visit_Arel_Attributes_String :visit_Arel_Attributes_Attribute
|
250
|
+
alias :visit_Arel_Attributes_Time :visit_Arel_Attributes_Attribute
|
251
|
+
alias :visit_Arel_Attributes_Boolean :visit_Arel_Attributes_Attribute
|
252
|
+
alias :visit_Arel_Attributes_Decimal :visit_Arel_Attributes_Attribute
|
253
|
+
|
254
|
+
def visit_Arel_Nodes_Equality o, attribute
|
255
|
+
key, value = visit(o.left), visit(o.right)
|
256
|
+
# several different binding markers exist depending on the db driver used (MySQL, Postgress supported)
|
257
|
+
if value.to_s =~ /^(\?|\u0000|\$\d+)$/
|
258
|
+
# puts "bindings: #{@bindings.inspect}, key = #{key.to_s}"
|
259
|
+
value = @bindings[key.to_s] || value
|
260
|
+
end
|
261
|
+
# puts " =====> equality found: #{key.inspect}@#{key.class.name} => #{value.inspect}@#{value.class.name}"
|
262
|
+
@query.where(key, value)
|
263
|
+
end
|
264
|
+
alias :visit_Arel_Nodes_In :visit_Arel_Nodes_Equality
|
265
|
+
|
266
|
+
def visit_Arel_Nodes_And o, attribute
|
267
|
+
visit(o.children)
|
268
|
+
end
|
269
|
+
|
270
|
+
alias :visit_Arel_Nodes_Or :not_cacheable
|
271
|
+
alias :visit_Arel_Nodes_NotEqual :not_cacheable
|
272
|
+
alias :visit_Arel_Nodes_GreaterThan :not_cacheable
|
273
|
+
alias :visit_Arel_Nodes_GreaterThanOrEqual :not_cacheable
|
274
|
+
alias :visit_Arel_Nodes_Assignment :not_cacheable
|
275
|
+
alias :visit_Arel_Nodes_LessThan :not_cacheable
|
276
|
+
alias :visit_Arel_Nodes_LessThanOrEqual :not_cacheable
|
277
|
+
alias :visit_Arel_Nodes_Between :not_cacheable
|
278
|
+
alias :visit_Arel_Nodes_NotIn :not_cacheable
|
279
|
+
alias :visit_Arel_Nodes_DoesNotMatch :not_cacheable
|
280
|
+
alias :visit_Arel_Nodes_Matches :not_cacheable
|
281
|
+
|
282
|
+
def visit_Fixnum o, attribute
|
283
|
+
o.to_i
|
284
|
+
end
|
285
|
+
alias :visit_Bignum :visit_Fixnum
|
286
|
+
|
287
|
+
def visit_Symbol o, attribute
|
288
|
+
o.to_sym
|
289
|
+
end
|
290
|
+
|
291
|
+
def visit_Object o, attribute
|
292
|
+
o
|
293
|
+
end
|
294
|
+
alias :visit_Arel_Nodes_SqlLiteral :visit_Object
|
295
|
+
alias :visit_Arel_Nodes_BindParam :visit_Object
|
296
|
+
alias :visit_Arel_SqlLiteral :visit_Object # This is deprecated
|
297
|
+
alias :visit_String :visit_Object
|
298
|
+
alias :visit_NilClass :visit_Object
|
299
|
+
alias :visit_TrueClass :visit_Object
|
300
|
+
alias :visit_FalseClass :visit_Object
|
301
|
+
alias :visit_Arel_SqlLiteral :visit_Object
|
302
|
+
alias :visit_BigDecimal :visit_Object
|
303
|
+
alias :visit_Float :visit_Object
|
304
|
+
alias :visit_Time :visit_Object
|
305
|
+
alias :visit_Date :visit_Object
|
306
|
+
alias :visit_DateTime :visit_Object
|
307
|
+
alias :visit_Hash :visit_Object
|
308
|
+
|
309
|
+
def visit_Array o, attribute
|
310
|
+
o.map{ |x| visit x }
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
316
|
+
|
317
|
+
module RecordCache
|
318
|
+
|
319
|
+
# Patch ActiveRecord::Relation to make sure update_all will invalidate all referenced records
|
320
|
+
module ActiveRecord
|
321
|
+
module UpdateAll
|
322
|
+
class << self
|
323
|
+
def included(klass)
|
324
|
+
klass.extend ClassMethods
|
325
|
+
klass.send(:include, InstanceMethods)
|
326
|
+
klass.class_eval do
|
327
|
+
alias_method_chain :update_all, :record_cache
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
module ClassMethods
|
333
|
+
end
|
334
|
+
|
335
|
+
module InstanceMethods
|
336
|
+
def __find_in_clause(sub_select)
|
337
|
+
return nil unless sub_select.arel.constraints.count == 1
|
338
|
+
constraint = sub_select.arel.constraints.first
|
339
|
+
return constraint if constraint.is_a?(::Arel::Nodes::In) # directly an IN clause
|
340
|
+
return nil unless constraint.respond_to?(:children) && constraint.children.count == 1
|
341
|
+
constraint = constraint.children.first
|
342
|
+
return constraint if constraint.is_a?(::Arel::Nodes::In) # AND with IN clause
|
343
|
+
nil
|
344
|
+
end
|
345
|
+
|
346
|
+
def update_all_with_record_cache(updates)
|
347
|
+
result = update_all_without_record_cache(updates)
|
348
|
+
|
349
|
+
if record_cache?
|
350
|
+
# when this condition is met, the arel.update method will be called on the current scope, see ActiveRecord::Relation#update_all
|
351
|
+
unless @limit_value.present? != @order_values.present?
|
352
|
+
# get all attributes that contain a unique index for this model
|
353
|
+
unique_index_attributes = RecordCache::Strategy::UniqueIndexCache.attributes(self)
|
354
|
+
# go straight to SQL result (without instantiating records) for optimal performance
|
355
|
+
RecordCache::Base.version_store.multi do
|
356
|
+
sub_select = select(unique_index_attributes.map(&:to_s).join(','))
|
357
|
+
in_clause = __find_in_clause(sub_select)
|
358
|
+
if unique_index_attributes.size == 1 && in_clause &&
|
359
|
+
in_clause.left.try(:name).to_s == unique_index_attributes.first.to_s
|
360
|
+
# common case where the unique index is the (only) constraint on the query: SELECT id FROM people WHERE id in (...)
|
361
|
+
attribute = unique_index_attributes.first
|
362
|
+
in_clause.right.each do |value|
|
363
|
+
record_cache.invalidate(attribute, value)
|
364
|
+
end
|
365
|
+
else
|
366
|
+
connection.execute(sub_select.to_sql).each do |row|
|
367
|
+
# invalidate the unique index for all attributes
|
368
|
+
unique_index_attributes.each_with_index do |attribute, index|
|
369
|
+
record_cache.invalidate(attribute, (row.is_a?(Hash) ? row[attribute.to_s] : row[index]))
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
result
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Patch ActiveRecord::Associations::HasManyAssociation to make sure the index_cache is updated when records are
|
384
|
+
# deleted from the collection
|
385
|
+
module ActiveRecord
|
386
|
+
module HasMany
|
387
|
+
class << self
|
388
|
+
def included(klass)
|
389
|
+
klass.extend ClassMethods
|
390
|
+
klass.send(:include, InstanceMethods)
|
391
|
+
klass.class_eval do
|
392
|
+
alias_method_chain :delete_records, :record_cache
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
module ClassMethods
|
398
|
+
end
|
399
|
+
|
400
|
+
module InstanceMethods
|
401
|
+
def delete_records_with_record_cache(records, method)
|
402
|
+
records = load_target if records == :all
|
403
|
+
# invalidate :id cache for all records
|
404
|
+
records.each{ |record| record.class.record_cache.invalidate(record.id) if record.class.record_cache? unless record.new_record? }
|
405
|
+
# invalidate the referenced class for the attribute/value pair on the index cache
|
406
|
+
@reflection.klass.record_cache.invalidate(@reflection.foreign_key.to_sym, @owner.id) if @reflection.klass.record_cache?
|
407
|
+
delete_records_without_record_cache(records, method)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
module HasOne
|
413
|
+
class << self
|
414
|
+
def included(klass)
|
415
|
+
klass.extend ClassMethods
|
416
|
+
klass.send(:include, InstanceMethods)
|
417
|
+
klass.class_eval do
|
418
|
+
alias_method_chain :delete, :record_cache
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
module ClassMethods
|
424
|
+
end
|
425
|
+
|
426
|
+
module InstanceMethods
|
427
|
+
def delete_with_record_cache(method = options[:dependent])
|
428
|
+
# invalidate :id cache for all record
|
429
|
+
if load_target
|
430
|
+
target.class.record_cache.invalidate(target.id) if target.class.record_cache? unless target.new_record?
|
431
|
+
end
|
432
|
+
# invalidate the referenced class for the attribute/value pair on the index cache
|
433
|
+
@reflection.klass.record_cache.invalidate(@reflection.foreign_key.to_sym, @owner.id) if @reflection.klass.record_cache?
|
434
|
+
delete_without_record_cache(method)
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
end
|
441
|
+
|
442
|
+
ActiveRecord::Base.send(:include, RecordCache::ActiveRecord::Base)
|
443
|
+
Arel::TreeManager.send(:include, RecordCache::Arel::TreeManager)
|
444
|
+
ActiveRecord::Relation.send(:include, RecordCache::ActiveRecord::UpdateAll)
|
445
|
+
ActiveRecord::Associations::HasManyAssociation.send(:include, RecordCache::ActiveRecord::HasMany)
|
446
|
+
ActiveRecord::Associations::HasOneAssociation.send(:include, RecordCache::ActiveRecord::HasOne)
|