second_level_cache 2.5.0 → 2.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -0
- data/README.md +1 -1
- data/lib/second_level_cache.rb +1 -0
- data/lib/second_level_cache/active_record.rb +8 -2
- data/lib/second_level_cache/active_record/base.rb +1 -5
- data/lib/second_level_cache/active_record/belongs_to_association.rb +6 -4
- data/lib/second_level_cache/active_record/fetch_by_uniq_key.rb +24 -17
- data/lib/second_level_cache/active_record/finder_methods.rb +44 -42
- data/lib/second_level_cache/active_record/has_one_association.rb +14 -20
- data/lib/second_level_cache/active_record/persistence.rb +6 -2
- data/lib/second_level_cache/active_record/preloader.rb +34 -28
- data/lib/second_level_cache/adapter/paranoia.rb +24 -0
- data/lib/second_level_cache/config.rb +1 -0
- data/lib/second_level_cache/log_subscriber.rb +15 -0
- data/lib/second_level_cache/mixin.rb +0 -12
- data/lib/second_level_cache/record_marshal.rb +7 -40
- data/lib/second_level_cache/version.rb +1 -1
- data/second_level_cache.gemspec +23 -23
- data/test/active_record_test_case_helper.rb +11 -1
- data/test/fetch_by_uniq_key_test.rb +47 -4
- data/test/finder_methods_test.rb +39 -0
- data/test/has_one_association_test.rb +16 -4
- data/test/model/account.rb +2 -2
- data/test/model/animal.rb +1 -1
- data/test/model/application_record.rb +3 -0
- data/test/model/book.rb +6 -1
- data/test/model/contribution.rb +14 -0
- data/test/model/image.rb +1 -1
- data/test/model/order.rb +1 -1
- data/test/model/order_item.rb +1 -1
- data/test/model/paranoid.rb +10 -0
- data/test/model/post.rb +1 -1
- data/test/model/topic.rb +1 -1
- data/test/model/user.rb +7 -7
- data/test/paranoid_test.rb +16 -0
- data/test/{preloader_test.rb → preloader_belongs_to_test.rb} +17 -15
- data/test/preloader_has_many_test.rb +13 -0
- data/test/preloader_has_one_test.rb +69 -0
- data/test/record_marshal_test.rb +1 -1
- data/test/test_helper.rb +3 -4
- metadata +28 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 84fecc39005beffa957b1602ff86ace6ca5419a1215d35011034ac6c78b5e0bf
|
4
|
+
data.tar.gz: f0a09a62d06ef9f10a1ee4559a4aac1b8e39eaa8a86ee81ce90d67212c9c1811
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 40c406b9b4181c5e1085127bbab26b71c5a47842d685521b9fa1ec43e58e529d8591c24ae675760ed2a3dcc9644dca7fcb6db12a5e651b67670af413c16addcc
|
7
|
+
data.tar.gz: fc5f6ee2184884f4580123550e3d3edbb113114de4f7dacad340ef8c37711e9388335fdf029a06971fefe6a666ae0ca615f2852101384f986750333c3f85b0de
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
2.6.2
|
2
|
+
-------
|
3
|
+
|
4
|
+
- Fix activerecord association cache. (#109)
|
5
|
+
- Fix fetch_by_uniq_key cache key with prefix. (#120)
|
6
|
+
|
7
|
+
2.6.1
|
8
|
+
-------
|
9
|
+
|
10
|
+
- Improve proload debug log output, and deprecated logger method. (#106)
|
11
|
+
|
12
|
+
2.6.0
|
13
|
+
-------
|
14
|
+
|
15
|
+
- Add has_one through cache support. (#98)
|
16
|
+
- Fix string query, eager_load, includes/preload for fetch from db. ( #103, #102, #101)
|
17
|
+
- Fix preloader if exists default scope. (#104)
|
18
|
+
- Change cache hit log as `DEBUG` level. (#105)
|
19
|
+
|
20
|
+
2.5.3
|
21
|
+
-------
|
22
|
+
|
23
|
+
- Fix `fetch_by_uniq_keys` method that cache incorrect when A record modified uniq key and B reocrd used old uniq key of A record (#96)
|
24
|
+
|
25
|
+
2.5.2
|
26
|
+
-------
|
27
|
+
|
28
|
+
- Fix methods argument as keyword warning in Ruby 2.7. (#94)
|
29
|
+
|
30
|
+
2.5.1
|
31
|
+
-------
|
32
|
+
|
33
|
+
- Fix cache expire issue for SoftDelete cases.
|
34
|
+
|
1
35
|
2.5.0
|
2
36
|
-------
|
3
37
|
|
data/README.md
CHANGED
@@ -134,7 +134,7 @@ config.cache_store = [:dalli_store, APP_CONFIG["memcached_host"], { namespace: "
|
|
134
134
|
## Tips:
|
135
135
|
|
136
136
|
* When you want to clear only second level cache apart from other cache for example fragment cache in cache store,
|
137
|
-
you can only change the `cache_key_prefix
|
137
|
+
you can only change the `cache_key_prefix` (default: `slc`):
|
138
138
|
|
139
139
|
```ruby
|
140
140
|
SecondLevelCache.configure.cache_key_prefix = "slc1"
|
data/lib/second_level_cache.rb
CHANGED
@@ -5,6 +5,7 @@ require "second_level_cache/config"
|
|
5
5
|
require "second_level_cache/record_marshal"
|
6
6
|
require "second_level_cache/record_relation"
|
7
7
|
require "second_level_cache/active_record"
|
8
|
+
require "second_level_cache/log_subscriber"
|
8
9
|
|
9
10
|
module SecondLevelCache
|
10
11
|
def self.configure
|
@@ -12,7 +12,13 @@ require "second_level_cache/active_record/preloader"
|
|
12
12
|
|
13
13
|
# http://api.rubyonrails.org/classes/ActiveSupport/LazyLoadHooks.html
|
14
14
|
# ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
|
15
|
-
ActiveSupport.on_load(:active_record) do
|
15
|
+
ActiveSupport.on_load(:active_record, run_once: true) do
|
16
|
+
if (Bundler.definition.gem("paranoia") rescue false)
|
17
|
+
require "second_level_cache/adapter/paranoia"
|
18
|
+
include SecondLevelCache::Adapter::Paranoia::ActiveRecord
|
19
|
+
SecondLevelCache::Mixin.send(:prepend, SecondLevelCache::Adapter::Paranoia::Mixin)
|
20
|
+
end
|
21
|
+
|
16
22
|
include SecondLevelCache::Mixin
|
17
23
|
prepend SecondLevelCache::ActiveRecord::Base
|
18
24
|
extend SecondLevelCache::ActiveRecord::FetchByUniqKey
|
@@ -23,5 +29,5 @@ ActiveSupport.on_load(:active_record) do
|
|
23
29
|
ActiveRecord::Relation.send(:prepend, SecondLevelCache::ActiveRecord::FinderMethods)
|
24
30
|
# Rails 5.2 has removed ActiveRecord::Associations::Preloader::BelongsTo
|
25
31
|
# https://github.com/rails/rails/pull/31079
|
26
|
-
ActiveRecord::Associations::Preloader::Association.send(:prepend, SecondLevelCache::ActiveRecord::Associations::Preloader
|
32
|
+
ActiveRecord::Associations::Preloader::Association.send(:prepend, SecondLevelCache::ActiveRecord::Associations::Preloader)
|
27
33
|
end
|
@@ -6,11 +6,7 @@ module SecondLevelCache
|
|
6
6
|
def self.prepended(base)
|
7
7
|
base.after_commit :update_second_level_cache, on: :update
|
8
8
|
base.after_commit :write_second_level_cache, on: :create
|
9
|
-
|
10
|
-
base.after_destroy :expire_second_level_cache
|
11
|
-
else
|
12
|
-
base.after_commit :expire_second_level_cache, on: :destroy
|
13
|
-
end
|
9
|
+
base.after_commit :expire_second_level_cache, on: :destroy
|
14
10
|
|
15
11
|
class << base
|
16
12
|
prepend ClassMethods
|
@@ -6,6 +6,9 @@ module SecondLevelCache
|
|
6
6
|
module BelongsToAssociation
|
7
7
|
def find_target
|
8
8
|
return super unless klass.second_level_cache_enabled?
|
9
|
+
return super if klass.default_scopes.present? || reflection.scope
|
10
|
+
return super if reflection.active_record_primary_key.to_s != klass.primary_key
|
11
|
+
|
9
12
|
cache_record = klass.read_second_level_cache(second_level_cache_key)
|
10
13
|
if cache_record
|
11
14
|
return cache_record.tap { |record| set_inverse_instance(record) }
|
@@ -21,10 +24,9 @@ module SecondLevelCache
|
|
21
24
|
end
|
22
25
|
|
23
26
|
private
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
27
|
+
def second_level_cache_key
|
28
|
+
owner[reflection.foreign_key]
|
29
|
+
end
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
@@ -6,19 +6,22 @@ module SecondLevelCache
|
|
6
6
|
def fetch_by_uniq_keys(where_values)
|
7
7
|
cache_key = cache_uniq_key(where_values)
|
8
8
|
obj_id = SecondLevelCache.cache_store.read(cache_key)
|
9
|
+
|
9
10
|
if obj_id
|
10
|
-
begin
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
record = begin
|
12
|
+
find(obj_id)
|
13
|
+
rescue StandardError
|
14
|
+
nil
|
15
|
+
end
|
15
16
|
end
|
16
|
-
|
17
|
+
return record if record_attributes_equal_where_values?(record, where_values)
|
17
18
|
record = where(where_values).first
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
if record
|
20
|
+
SecondLevelCache.cache_store.write(cache_key, record.id)
|
21
|
+
record
|
22
|
+
else
|
23
|
+
SecondLevelCache.cache_store.delete(cache_key)
|
24
|
+
nil
|
22
25
|
end
|
23
26
|
end
|
24
27
|
|
@@ -39,16 +42,20 @@ module SecondLevelCache
|
|
39
42
|
end
|
40
43
|
|
41
44
|
private
|
45
|
+
def cache_uniq_key(where_values)
|
46
|
+
keys = where_values.collect do |k, v|
|
47
|
+
v = Digest::MD5.hexdigest(v) if v.respond_to?(:size) && v.size >= 32
|
48
|
+
[k, v].join("_")
|
49
|
+
end
|
42
50
|
|
43
|
-
|
44
|
-
|
45
|
-
v = Digest::MD5.hexdigest(v) if v && v.size >= 32
|
46
|
-
[k, v].join("_")
|
51
|
+
ext_key = keys.join(",")
|
52
|
+
"#{SecondLevelCache.configure.cache_key_prefix}/uniq_key_#{name}_#{ext_key}"
|
47
53
|
end
|
48
54
|
|
49
|
-
|
50
|
-
|
51
|
-
|
55
|
+
def record_attributes_equal_where_values?(record, where_values)
|
56
|
+
# https://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html#method-i-type_for_attribute
|
57
|
+
where_values.all? { |k, v| record&.read_attribute(k) == type_for_attribute(k).cast(v) }
|
58
|
+
end
|
52
59
|
end
|
53
60
|
end
|
54
61
|
end
|
@@ -17,20 +17,13 @@ module SecondLevelCache
|
|
17
17
|
# Article.where("articles.user_id = 1").find(prams[:id])
|
18
18
|
# Article.where("user_id = 1 AND ...").find(params[:id])
|
19
19
|
def find_one(id)
|
20
|
-
return super
|
21
|
-
return super(id) unless select_all_column?
|
20
|
+
return super unless cachable?
|
22
21
|
|
23
22
|
id = id.id if ActiveRecord::Base == id
|
24
|
-
|
25
|
-
|
26
|
-
if record
|
27
|
-
if where_values_hash.blank? || where_values_match_cache?(record)
|
28
|
-
return record
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
23
|
+
record = @klass.read_second_level_cache(id)
|
24
|
+
return record if record && where_values_match_cache?(record)
|
32
25
|
|
33
|
-
record = super
|
26
|
+
record = super
|
34
27
|
record.write_second_level_cache
|
35
28
|
record
|
36
29
|
end
|
@@ -48,53 +41,62 @@ module SecondLevelCache
|
|
48
41
|
# User.where(age: 18).first
|
49
42
|
#
|
50
43
|
def first(limit = nil)
|
51
|
-
return super
|
44
|
+
return super if limit.to_i > 1
|
45
|
+
return super unless cachable?
|
52
46
|
# only have primary_key condition in where
|
53
47
|
if where_values_hash.length == 1 && where_values_hash.key?(primary_key)
|
54
48
|
record = @klass.read_second_level_cache(where_values_hash[primary_key])
|
55
49
|
return record if record
|
56
50
|
end
|
57
51
|
|
58
|
-
record = super
|
59
|
-
record&.write_second_level_cache
|
52
|
+
record = super
|
53
|
+
record&.write_second_level_cache
|
60
54
|
record
|
61
55
|
end
|
62
56
|
|
63
57
|
private
|
58
|
+
# readonly_value - active_record/relation/query_methods.rb Rails 5.1 true/false
|
59
|
+
def cachable?
|
60
|
+
second_level_cache_enabled? &&
|
61
|
+
limit_one? &&
|
62
|
+
# !eager_loading? &&
|
63
|
+
includes_values.blank? &&
|
64
|
+
preload_values.blank? &&
|
65
|
+
eager_load_values.blank? &&
|
66
|
+
select_values.blank? &&
|
67
|
+
order_values_can_cache? &&
|
68
|
+
readonly_value.blank? &&
|
69
|
+
joins_values.blank? &&
|
70
|
+
!@klass.locking_enabled? &&
|
71
|
+
where_clause_predicates_all_equality?
|
72
|
+
end
|
64
73
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
order_values.
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
!@klass.locking_enabled? &&
|
74
|
-
where_clause_match_equality?
|
75
|
-
end
|
74
|
+
def order_values_can_cache?
|
75
|
+
return true if order_values.empty?
|
76
|
+
return false unless order_values.one?
|
77
|
+
return true if order_values.first == klass.primary_key
|
78
|
+
return false unless order_values.first.is_a?(::Arel::Nodes::Ordering)
|
79
|
+
return true if order_values.first.expr == klass.primary_key
|
80
|
+
order_values.first.expr.try(:name) == klass.primary_key
|
81
|
+
end
|
76
82
|
|
77
|
-
|
78
|
-
|
79
|
-
|
83
|
+
def where_clause_predicates_all_equality?
|
84
|
+
where_clause.send(:predicates).size == where_values_hash.size
|
85
|
+
end
|
80
86
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
+
def where_values_match_cache?(record)
|
88
|
+
where_values_hash.all? do |key, value|
|
89
|
+
if value.is_a?(Array)
|
90
|
+
value.include?(record.read_attribute(key))
|
91
|
+
else
|
92
|
+
record.read_attribute(key) == value
|
93
|
+
end
|
87
94
|
end
|
88
95
|
end
|
89
|
-
end
|
90
96
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
def select_all_column?
|
96
|
-
select_values.blank?
|
97
|
-
end
|
97
|
+
def limit_one?
|
98
|
+
limit_value.blank? || limit_value == 1
|
99
|
+
end
|
98
100
|
end
|
99
101
|
end
|
100
102
|
end
|
@@ -6,31 +6,25 @@ module SecondLevelCache
|
|
6
6
|
module HasOneAssociation
|
7
7
|
def find_target
|
8
8
|
return super unless klass.second_level_cache_enabled?
|
9
|
-
return super if
|
10
|
-
# TODO: implement cache with has_one
|
9
|
+
return super if klass.default_scopes.present? || reflection.scope
|
10
|
+
# TODO: implement cache with has_one scope
|
11
11
|
|
12
|
-
|
13
|
-
if
|
14
|
-
|
15
|
-
|
16
|
-
reflection.
|
17
|
-
|
18
|
-
|
12
|
+
through = reflection.options[:through]
|
13
|
+
record = if through
|
14
|
+
return super unless klass.reflections[through.to_s].klass.second_level_cache_enabled?
|
15
|
+
begin
|
16
|
+
reflection.klass.find(owner.send(through).read_attribute(reflection.foreign_key))
|
17
|
+
rescue StandardError
|
18
|
+
nil
|
19
|
+
end
|
19
20
|
else
|
20
|
-
|
21
|
+
uniq_keys = { reflection.foreign_key => owner[reflection.active_record_primary_key] }
|
22
|
+
uniq_keys[reflection.type] = owner.class.base_class.name if reflection.options[:as]
|
23
|
+
klass.fetch_by_uniq_keys(uniq_keys)
|
21
24
|
end
|
22
25
|
|
23
|
-
if cache_record
|
24
|
-
return cache_record.tap { |record| set_inverse_instance(record) }
|
25
|
-
end
|
26
|
-
|
27
|
-
record = super
|
28
26
|
return nil unless record
|
29
|
-
|
30
|
-
record.tap do |r|
|
31
|
-
set_inverse_instance(r)
|
32
|
-
r.write_second_level_cache
|
33
|
-
end
|
27
|
+
record.tap { |r| set_inverse_instance(r) }
|
34
28
|
end
|
35
29
|
end
|
36
30
|
end
|
@@ -4,17 +4,21 @@ module SecondLevelCache
|
|
4
4
|
module ActiveRecord
|
5
5
|
module Persistence
|
6
6
|
# update_column will call update_columns
|
7
|
+
# https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/persistence.rb#L315
|
7
8
|
def update_columns(attributes)
|
8
9
|
super(attributes).tap { update_second_level_cache }
|
9
10
|
end
|
10
11
|
|
12
|
+
# https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/persistence.rb#L441
|
11
13
|
def reload(options = nil)
|
12
14
|
expire_second_level_cache
|
13
15
|
super(options)
|
14
16
|
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
+
# https://github.com/rails/rails/blob/5-0-stable/activerecord/lib/active_record/persistence.rb#L490
|
19
|
+
def touch(*names, **opts)
|
20
|
+
# super: touch(*names, time: nil)
|
21
|
+
super(*names, **opts).tap { update_second_level_cache }
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
@@ -3,42 +3,48 @@
|
|
3
3
|
module SecondLevelCache
|
4
4
|
module ActiveRecord
|
5
5
|
module Associations
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
return SecondLevelCache::RecordRelation.new(record_marshals)
|
6
|
+
module Preloader
|
7
|
+
RAILS6 = ::ActiveRecord.version >= ::Gem::Version.new("6")
|
8
|
+
|
9
|
+
def records_for(ids, &block)
|
10
|
+
return super unless klass.second_level_cache_enabled?
|
11
|
+
return super unless reflection.is_a?(::ActiveRecord::Reflection::BelongsToReflection)
|
12
|
+
return super if klass.default_scopes.present? || reflection.scope
|
13
|
+
return super if association_key_name.to_s != klass.primary_key
|
14
|
+
|
15
|
+
map_cache_keys = ids.map { |id| klass.second_level_cache_key(id) }
|
16
|
+
records_from_cache = ::SecondLevelCache.cache_store.read_multi(*map_cache_keys)
|
17
|
+
|
18
|
+
record_marshals = if RAILS6
|
19
|
+
RecordMarshal.load_multi(records_from_cache.values) do |record|
|
20
|
+
# This block is copy from:
|
21
|
+
# https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/associations/preloader/association.rb#L101
|
22
|
+
owner = owners_by_key[convert_key(record[association_key_name])].first
|
23
|
+
association = owner.association(reflection.name)
|
24
|
+
association.set_inverse_instance(record)
|
26
25
|
end
|
26
|
+
else
|
27
|
+
RecordMarshal.load_multi(records_from_cache.values, &block)
|
28
|
+
end
|
27
29
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
# NOTICE
|
31
|
+
# Rails.cache.read_multi return hash that has keys only hitted.
|
32
|
+
# eg. Rails.cache.read_multi(1,2,3) => {2 => hit_value, 3 => hit_value}
|
33
|
+
hitted_ids = record_marshals.map { |record| record.read_attribute(association_key_name).to_s }
|
34
|
+
missed_ids = ids.map(&:to_s) - hitted_ids
|
35
|
+
ActiveSupport::Notifications.instrument("preload.second_level_cache", key: association_key_name, hit: hitted_ids, miss: missed_ids)
|
36
|
+
return SecondLevelCache::RecordRelation.new(record_marshals) if missed_ids.empty?
|
32
37
|
|
33
|
-
|
34
|
-
|
38
|
+
records_from_db = super(missed_ids, &block)
|
39
|
+
records_from_db.map { |r| write_cache(r) }
|
35
40
|
|
36
|
-
|
41
|
+
SecondLevelCache::RecordRelation.new(records_from_db + record_marshals)
|
42
|
+
end
|
37
43
|
|
44
|
+
private
|
38
45
|
def write_cache(record)
|
39
46
|
record.write_second_level_cache
|
40
47
|
end
|
41
|
-
end
|
42
48
|
end
|
43
49
|
end
|
44
50
|
end
|