second_level_cache 2.6.1 → 2.7.0
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 +4 -4
- data/CHANGELOG.md +19 -0
- data/Gemfile +1 -2
- data/LICENSE +21 -0
- data/README.md +22 -17
- data/lib/second_level_cache/active_record/base.rb +1 -5
- data/lib/second_level_cache/active_record/belongs_to_association.rb +3 -0
- data/lib/second_level_cache/active_record/fetch_by_uniq_key.rb +16 -15
- data/lib/second_level_cache/active_record/has_one_association.rb +2 -2
- data/lib/second_level_cache/active_record/preloader/association.rb +61 -0
- data/lib/second_level_cache/active_record/preloader/legacy.rb +57 -0
- data/lib/second_level_cache/active_record.rb +20 -5
- data/lib/second_level_cache/adapter/paranoia.rb +25 -0
- data/lib/second_level_cache/mixin.rb +2 -3
- data/lib/second_level_cache/version.rb +1 -1
- data/second_level_cache.gemspec +13 -12
- data/test/belongs_to_association_test.rb +1 -1
- data/test/fetch_by_uniq_key_test.rb +6 -5
- data/test/has_one_association_test.rb +4 -0
- data/test/model/paranoid.rb +10 -0
- data/test/model/post.rb +10 -0
- data/test/model/user.rb +0 -2
- data/test/paranoid_test.rb +18 -0
- data/test/test_helper.rb +2 -2
- metadata +39 -44
- data/lib/second_level_cache/active_record/preloader.rb +0 -50
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9161291526eed87047708e5128c64e2907ddf61f017dbf073831c1281b6b504d
|
|
4
|
+
data.tar.gz: 192fffc74fe056dd8bc2d8e6486f4f5de380698bfc728d65bf02853c110a9d66
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 84e57628cd9eb58d52d7a4d5c23844d8e470ee599d865845287ac9fee9b0659fe7a0b563ab5f2e6d7fd3c59ffce6de543ec0ae91a36e77fd8d2c628f05cd53a1
|
|
7
|
+
data.tar.gz: 60aa90c2dfa0be0920ee121772a426c3ce209423a4c5a2c28bf61efad3ad5da14738317f40df8704e6628db9fc7a2e439619ca2bdb1d7cb2d22073d1ccded656
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
New version releases please visit https://github.com/hooopo/second_level_cache/releases
|
|
3
|
+
|
|
4
|
+
2.6.4
|
|
5
|
+
-------
|
|
6
|
+
|
|
7
|
+
- Fix `undefined method klass` error for has_one through. (#123)
|
|
8
|
+
|
|
9
|
+
2.6.3
|
|
10
|
+
-------
|
|
11
|
+
|
|
12
|
+
- Fix paranoia load error.
|
|
13
|
+
|
|
14
|
+
2.6.2
|
|
15
|
+
-------
|
|
16
|
+
|
|
17
|
+
- Fix activerecord association cache. (#109)
|
|
18
|
+
- Fix fetch_by_uniq_key cache key with prefix. (#120)
|
|
19
|
+
|
|
1
20
|
2.6.1
|
|
2
21
|
-------
|
|
3
22
|
|
data/Gemfile
CHANGED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Hackershare
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
# SecondLevelCache
|
|
2
2
|
|
|
3
3
|
[](http://badge.fury.io/rb/second_level_cache)
|
|
4
|
-
[](https://github.com/hooopo/second_level_cache/actions/workflows/build.yml)
|
|
5
5
|
[](https://codeclimate.com/github/hooopo/second_level_cache)
|
|
6
6
|
|
|
7
|
-
SecondLevelCache is a write-through and read-through caching library inspired by Cache Money and cache_fu, support ActiveRecord 4.
|
|
7
|
+
SecondLevelCache is a write-through and read-through caching library inspired by Cache Money and cache_fu, support ActiveRecord 4, ActiveRecord 5 and ActiveRecord 6.
|
|
8
8
|
|
|
9
9
|
Read-Through: Queries by ID, like `current_user.articles.find(params[:id])`, will first look in cache store and then look in the database for the results of that query. If there is a cache miss, it will populate the cache.
|
|
10
10
|
|
|
11
11
|
Write-Through: As objects are created, updated, and deleted, all of the caches are automatically kept up-to-date and coherent.
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
## Install
|
|
15
14
|
|
|
16
15
|
In your gem file:
|
|
17
16
|
|
|
18
|
-
ActiveRecord
|
|
17
|
+
ActiveRecord 7
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
gem 'second_level_cache', '~> 2.7'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
ActiveRecord 5.2 and 6.0:
|
|
19
24
|
|
|
20
25
|
```ruby
|
|
21
|
-
gem 'second_level_cache', '~> 2.
|
|
26
|
+
gem 'second_level_cache', '~> 2.6.3'
|
|
22
27
|
```
|
|
23
28
|
|
|
24
29
|
ActiveRecord 5.0.x, 5.1.x:
|
|
@@ -97,9 +102,9 @@ User.select("id, name").find(1)
|
|
|
97
102
|
|
|
98
103
|
## Notice
|
|
99
104
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
- SecondLevelCache cache by model name and id, so only find_one query will work.
|
|
106
|
+
- Only equal conditions query WILL get cache; and SQL string query like `User.where("name = 'Hooopo'").find(1)` WILL NOT work.
|
|
107
|
+
- SecondLevelCache sync cache after transaction commit:
|
|
103
108
|
|
|
104
109
|
```ruby
|
|
105
110
|
# user and account's write_second_level_cache operation will invoke after the logger.
|
|
@@ -117,7 +122,7 @@ end # <- Cache write
|
|
|
117
122
|
Rails.logger.info "info"
|
|
118
123
|
```
|
|
119
124
|
|
|
120
|
-
|
|
125
|
+
- If you are using SecondLevelCache with database_cleaner, you should set cleaning strategy to `:truncation`:
|
|
121
126
|
|
|
122
127
|
```ruby
|
|
123
128
|
DatabaseCleaner.strategy = :truncation
|
|
@@ -133,15 +138,15 @@ config.cache_store = [:dalli_store, APP_CONFIG["memcached_host"], { namespace: "
|
|
|
133
138
|
|
|
134
139
|
## Tips:
|
|
135
140
|
|
|
136
|
-
|
|
137
|
-
you can only change the `cache_key_prefix
|
|
141
|
+
- When you want to clear only second level cache apart from other cache for example fragment cache in cache store,
|
|
142
|
+
you can only change the `cache_key_prefix` (default: `slc`):
|
|
138
143
|
|
|
139
144
|
```ruby
|
|
140
145
|
SecondLevelCache.configure.cache_key_prefix = "slc1"
|
|
141
146
|
```
|
|
142
147
|
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
- SecondLevelCache was added model schema digest as cache version, this means when you add/remove/change columns, the caches of this Model will expires.
|
|
149
|
+
- When your want change the model cache version by manualy, just add the `version` option like this:
|
|
145
150
|
|
|
146
151
|
```ruby
|
|
147
152
|
class User < ActiveRecord::Base
|
|
@@ -149,7 +154,7 @@ class User < ActiveRecord::Base
|
|
|
149
154
|
end
|
|
150
155
|
```
|
|
151
156
|
|
|
152
|
-
|
|
157
|
+
- It provides a great feature, not hits db when fetching record via unique key (not primary key).
|
|
153
158
|
|
|
154
159
|
```ruby
|
|
155
160
|
# this will fetch from cache
|
|
@@ -160,7 +165,7 @@ post = Post.fetch_by_uniq_keys(user_id: 2, slug: "foo")
|
|
|
160
165
|
user = User.fetch_by_uniq_keys!(nick_name: "hooopo") # this will raise `ActiveRecord::RecordNotFound` Exception when nick name not exists.
|
|
161
166
|
```
|
|
162
167
|
|
|
163
|
-
|
|
168
|
+
- You can use Rails's [Eager Loading](http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations) feature as normal. Even better, second_level_cache will transform the `IN` query into a Rails.cache.multi_read operation. For example:
|
|
164
169
|
|
|
165
170
|
```ruby
|
|
166
171
|
Answer.includes(:question).limit(10).order("id DESC").each{|answer| answer.question.title}
|
|
@@ -171,8 +176,8 @@ Answer Load (0.2ms) SELECT `answers`.* FROM `answers` ORDER BY id DESC LIMIT 10
|
|
|
171
176
|
|
|
172
177
|
## Original design by:
|
|
173
178
|
|
|
174
|
-
|
|
175
|
-
|
|
179
|
+
- [chloerei](https://github.com/chloerei)
|
|
180
|
+
- [hooopo](https://github.com/hooopo)
|
|
176
181
|
|
|
177
182
|
## Contributors
|
|
178
183
|
|
|
@@ -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) }
|
|
@@ -9,10 +9,10 @@ module SecondLevelCache
|
|
|
9
9
|
|
|
10
10
|
if obj_id
|
|
11
11
|
record = begin
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
find(obj_id)
|
|
13
|
+
rescue
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
16
|
end
|
|
17
17
|
return record if record_attributes_equal_where_values?(record, where_values)
|
|
18
18
|
record = where(where_values).first
|
|
@@ -42,20 +42,21 @@ module SecondLevelCache
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
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
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
def cache_uniq_key(where_values)
|
|
47
|
+
keys = where_values.collect do |k, v|
|
|
48
|
+
v = Digest::MD5.hexdigest(v) if v.respond_to?(:size) && v.size >= 32
|
|
49
|
+
[k, v].join("_")
|
|
53
50
|
end
|
|
54
51
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
ext_key = keys.join(",")
|
|
53
|
+
"#{SecondLevelCache.configure.cache_key_prefix}/uniq_key_#{name}_#{ext_key}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def record_attributes_equal_where_values?(record, where_values)
|
|
57
|
+
# https://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html#method-i-type_for_attribute
|
|
58
|
+
where_values.all? { |k, v| record&.read_attribute(k) == type_for_attribute(k).cast(v) }
|
|
59
|
+
end
|
|
59
60
|
end
|
|
60
61
|
end
|
|
61
62
|
end
|
|
@@ -6,12 +6,12 @@ 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 reflection.scope
|
|
9
|
+
return super if klass.default_scopes.present? || reflection.scope
|
|
10
10
|
# TODO: implement cache with has_one scope
|
|
11
11
|
|
|
12
12
|
through = reflection.options[:through]
|
|
13
13
|
record = if through
|
|
14
|
-
return super unless
|
|
14
|
+
return super unless owner.class.reflections[through.to_s].klass.second_level_cache_enabled?
|
|
15
15
|
begin
|
|
16
16
|
reflection.klass.find(owner.send(through).read_attribute(reflection.foreign_key))
|
|
17
17
|
rescue StandardError
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SecondLevelCache
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Associations
|
|
6
|
+
module Preloader
|
|
7
|
+
module Association
|
|
8
|
+
# Override load_query method for add Association instance in arguments to LoaderQuery
|
|
9
|
+
# https://github.com/rails/rails/blob/7-0-stable/activerecord/lib/active_record/associations/preloader/association.rb#L148
|
|
10
|
+
def loader_query
|
|
11
|
+
::ActiveRecord::Associations::Preloader::Association::LoaderQuery.new(self, scope, association_key_name)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Override load_records_for_keys for use SecondLevelCache before preload association
|
|
15
|
+
# https://github.com/rails/rails/blob/8f5b35b6107c28125b571b9842e248b13f804e5c/activerecord/lib/active_record/associations/preloader/association.rb#L7
|
|
16
|
+
module LoaderQuery
|
|
17
|
+
attr_reader :association
|
|
18
|
+
|
|
19
|
+
delegate :klass, to: :association
|
|
20
|
+
|
|
21
|
+
def initialize(association, scope, association_key_name)
|
|
22
|
+
@association = association
|
|
23
|
+
@scope = scope
|
|
24
|
+
@association_key_name = association_key_name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reflection
|
|
28
|
+
association.send(:reflection)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def load_records_for_keys(keys, &block)
|
|
32
|
+
ids = keys.to_a
|
|
33
|
+
|
|
34
|
+
return super unless klass.second_level_cache_enabled?
|
|
35
|
+
return super unless reflection.is_a?(::ActiveRecord::Reflection::BelongsToReflection)
|
|
36
|
+
return super if klass.default_scopes.present? || reflection.scope
|
|
37
|
+
return super if association_key_name.to_s != klass.primary_key
|
|
38
|
+
|
|
39
|
+
map_cache_keys = ids.map { |id| klass.second_level_cache_key(id) }
|
|
40
|
+
records_from_cache = ::SecondLevelCache.cache_store.read_multi(*map_cache_keys)
|
|
41
|
+
record_marshals = RecordMarshal.load_multi(records_from_cache.values, &block)
|
|
42
|
+
|
|
43
|
+
# NOTICE
|
|
44
|
+
# Rails.cache.read_multi return hash that has keys only hitted.
|
|
45
|
+
# eg. Rails.cache.read_multi(1,2,3) => {2 => hit_value, 3 => hit_value}
|
|
46
|
+
hitted_ids = record_marshals.map { |record| record.read_attribute(association_key_name).to_s }
|
|
47
|
+
missed_ids = ids.map(&:to_s) - hitted_ids
|
|
48
|
+
ActiveSupport::Notifications.instrument("preload.second_level_cache", key: association_key_name, hit: hitted_ids, miss: missed_ids)
|
|
49
|
+
return SecondLevelCache::RecordRelation.new(record_marshals) if missed_ids.empty?
|
|
50
|
+
|
|
51
|
+
records_from_db = super(missed_ids.to_set, &block)
|
|
52
|
+
records_from_db.map { |r| r.write_second_level_cache }
|
|
53
|
+
|
|
54
|
+
SecondLevelCache::RecordRelation.new(records_from_db + record_marshals)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SecondLevelCache
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Associations
|
|
6
|
+
module Preloader
|
|
7
|
+
module Association
|
|
8
|
+
# For < Rails 7
|
|
9
|
+
module Legacy
|
|
10
|
+
RAILS6 = ::ActiveRecord.version >= ::Gem::Version.new("6")
|
|
11
|
+
|
|
12
|
+
def records_for(ids, &block)
|
|
13
|
+
return super unless klass.second_level_cache_enabled?
|
|
14
|
+
return super unless reflection.is_a?(::ActiveRecord::Reflection::BelongsToReflection)
|
|
15
|
+
return super if klass.default_scopes.present? || reflection.scope
|
|
16
|
+
return super if association_key_name.to_s != klass.primary_key
|
|
17
|
+
|
|
18
|
+
map_cache_keys = ids.map { |id| klass.second_level_cache_key(id) }
|
|
19
|
+
records_from_cache = ::SecondLevelCache.cache_store.read_multi(*map_cache_keys)
|
|
20
|
+
|
|
21
|
+
record_marshals = if RAILS6
|
|
22
|
+
RecordMarshal.load_multi(records_from_cache.values) do |record|
|
|
23
|
+
# This block is copy from:
|
|
24
|
+
# https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/associations/preloader/association.rb#L101
|
|
25
|
+
owner = owners_by_key[convert_key(record[association_key_name])].first
|
|
26
|
+
association = owner.association(reflection.name)
|
|
27
|
+
association.set_inverse_instance(record)
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
RecordMarshal.load_multi(records_from_cache.values, &block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# NOTICE
|
|
34
|
+
# Rails.cache.read_multi return hash that has keys only hitted.
|
|
35
|
+
# eg. Rails.cache.read_multi(1,2,3) => {2 => hit_value, 3 => hit_value}
|
|
36
|
+
hitted_ids = record_marshals.map { |record| record.read_attribute(association_key_name).to_s }
|
|
37
|
+
missed_ids = ids.map(&:to_s) - hitted_ids
|
|
38
|
+
ActiveSupport::Notifications.instrument("preload.second_level_cache", key: association_key_name, hit: hitted_ids, miss: missed_ids)
|
|
39
|
+
return SecondLevelCache::RecordRelation.new(record_marshals) if missed_ids.empty?
|
|
40
|
+
|
|
41
|
+
records_from_db = super(missed_ids, &block)
|
|
42
|
+
records_from_db.map { |r| write_cache(r) }
|
|
43
|
+
|
|
44
|
+
SecondLevelCache::RecordRelation.new(records_from_db + record_marshals)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def write_cache(record)
|
|
50
|
+
record.write_second_level_cache
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -8,11 +8,18 @@ require "second_level_cache/active_record/finder_methods"
|
|
|
8
8
|
require "second_level_cache/active_record/persistence"
|
|
9
9
|
require "second_level_cache/active_record/belongs_to_association"
|
|
10
10
|
require "second_level_cache/active_record/has_one_association"
|
|
11
|
-
require "second_level_cache/active_record/preloader"
|
|
11
|
+
require "second_level_cache/active_record/preloader/association"
|
|
12
|
+
require "second_level_cache/active_record/preloader/legacy"
|
|
12
13
|
|
|
13
14
|
# http://api.rubyonrails.org/classes/ActiveSupport/LazyLoadHooks.html
|
|
14
15
|
# ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
|
|
15
|
-
ActiveSupport.on_load(:active_record) do
|
|
16
|
+
ActiveSupport.on_load(:active_record, run_once: true) do
|
|
17
|
+
if Bundler.definition.dependencies.find { |x| x.name == "paranoia" }
|
|
18
|
+
require "second_level_cache/adapter/paranoia"
|
|
19
|
+
include SecondLevelCache::Adapter::Paranoia::ActiveRecord
|
|
20
|
+
SecondLevelCache::Mixin.send(:prepend, SecondLevelCache::Adapter::Paranoia::Mixin)
|
|
21
|
+
end
|
|
22
|
+
|
|
16
23
|
include SecondLevelCache::Mixin
|
|
17
24
|
prepend SecondLevelCache::ActiveRecord::Base
|
|
18
25
|
extend SecondLevelCache::ActiveRecord::FetchByUniqKey
|
|
@@ -21,7 +28,15 @@ ActiveSupport.on_load(:active_record) do
|
|
|
21
28
|
ActiveRecord::Associations::BelongsToAssociation.send(:prepend, SecondLevelCache::ActiveRecord::Associations::BelongsToAssociation)
|
|
22
29
|
ActiveRecord::Associations::HasOneAssociation.send(:prepend, SecondLevelCache::ActiveRecord::Associations::HasOneAssociation)
|
|
23
30
|
ActiveRecord::Relation.send(:prepend, SecondLevelCache::ActiveRecord::FinderMethods)
|
|
24
|
-
|
|
25
|
-
# https://github.com/rails/rails/
|
|
26
|
-
ActiveRecord::
|
|
31
|
+
|
|
32
|
+
# https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/associations/preloader/association.rb#L117
|
|
33
|
+
if ::ActiveRecord.version < ::Gem::Version.new("7")
|
|
34
|
+
ActiveRecord::Associations::Preloader::Association.send(:prepend, SecondLevelCache::ActiveRecord::Associations::Preloader::Association::Legacy)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if ::ActiveRecord.version >= ::Gem::Version.new("7")
|
|
38
|
+
# https://github.com/rails/rails/blob/7-0-stable/activerecord/lib/active_record/associations/preloader/association.rb#L25
|
|
39
|
+
ActiveRecord::Associations::Preloader::Association.send(:prepend, SecondLevelCache::ActiveRecord::Associations::Preloader::Association)
|
|
40
|
+
ActiveRecord::Associations::Preloader::Association::LoaderQuery.send(:prepend, SecondLevelCache::ActiveRecord::Associations::Preloader::Association::LoaderQuery)
|
|
41
|
+
end
|
|
27
42
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module SecondLevelCache
|
|
2
|
+
module Adapter
|
|
3
|
+
module Paranoia
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
after_destroy :expire_second_level_cache
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module Mixin
|
|
13
|
+
extend ActiveSupport::Concern
|
|
14
|
+
|
|
15
|
+
def write_second_level_cache
|
|
16
|
+
# Avoid rewrite cache again, when record has been soft deleted
|
|
17
|
+
return if respond_to?(:deleted?) && send(:deleted?)
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
alias_method :update_second_level_cache, :write_second_level_cache
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -71,13 +71,12 @@ module SecondLevelCache
|
|
|
71
71
|
|
|
72
72
|
def write_second_level_cache
|
|
73
73
|
return unless klass.second_level_cache_enabled?
|
|
74
|
-
# Avoid rewrite cache again, when record has been soft deleted
|
|
75
|
-
return if respond_to?(:deleted?) && send(:deleted?)
|
|
76
74
|
|
|
77
75
|
marshal = RecordMarshal.dump(self)
|
|
78
76
|
expires_in = klass.second_level_cache_options[:expires_in]
|
|
79
77
|
SecondLevelCache.cache_store.write(second_level_cache_key, marshal, expires_in: expires_in)
|
|
80
78
|
end
|
|
81
|
-
|
|
79
|
+
|
|
80
|
+
alias_method :update_second_level_cache, :write_second_level_cache
|
|
82
81
|
end
|
|
83
82
|
end
|
data/second_level_cache.gemspec
CHANGED
|
@@ -5,10 +5,10 @@ lib = File.expand_path("../lib", __FILE__)
|
|
|
5
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
6
6
|
|
|
7
7
|
Gem::Specification.new do |gem|
|
|
8
|
-
gem.authors
|
|
9
|
-
gem.email
|
|
10
|
-
gem.description
|
|
11
|
-
gem.summary
|
|
8
|
+
gem.authors = ["Hooopo"]
|
|
9
|
+
gem.email = ["hoooopo@gmail.com"]
|
|
10
|
+
gem.description = "Write Through and Read Through caching library inspired by CacheMoney and cache_fu, support ActiveRecord 4."
|
|
11
|
+
gem.summary = <<-SUMMARY
|
|
12
12
|
SecondLevelCache is a write-through and read-through caching library inspired by Cache Money and cache_fu, support only Rails3 and ActiveRecord.
|
|
13
13
|
|
|
14
14
|
Read-Through: Queries by ID, like current_user.articles.find(params[:id]), will first look in cache store and then look in the database for the results of that query. If there is a cache miss, it will populate the cache.
|
|
@@ -16,23 +16,24 @@ Gem::Specification.new do |gem|
|
|
|
16
16
|
Write-Through: As objects are created, updated, and deleted, all of the caches are automatically kept up-to-date and coherent.
|
|
17
17
|
SUMMARY
|
|
18
18
|
|
|
19
|
-
gem.homepage
|
|
19
|
+
gem.homepage = "https://github.com/hooopo/second_level_cache"
|
|
20
20
|
|
|
21
|
-
gem.files
|
|
21
|
+
gem.files = Dir.glob("lib/**/*.rb") + [
|
|
22
22
|
"README.md",
|
|
23
|
+
"LICENSE",
|
|
23
24
|
"Rakefile",
|
|
24
25
|
"Gemfile",
|
|
25
26
|
"CHANGELOG.md",
|
|
26
27
|
"second_level_cache.gemspec"
|
|
27
28
|
]
|
|
28
|
-
gem.test_files
|
|
29
|
-
gem.executables
|
|
30
|
-
gem.name
|
|
29
|
+
gem.test_files = Dir.glob("test/**/*.rb")
|
|
30
|
+
gem.executables = gem.files.grep(%r{^bin/})
|
|
31
|
+
gem.name = "second_level_cache"
|
|
31
32
|
gem.require_paths = ["lib"]
|
|
32
|
-
gem.version
|
|
33
|
+
gem.version = SecondLevelCache::VERSION
|
|
33
34
|
|
|
34
|
-
gem.add_runtime_dependency "activerecord",
|
|
35
|
-
gem.add_runtime_dependency "activesupport",
|
|
35
|
+
gem.add_runtime_dependency "activerecord", ">= 6.0"
|
|
36
|
+
gem.add_runtime_dependency "activesupport", ">= 6.0"
|
|
36
37
|
|
|
37
38
|
gem.add_development_dependency "database_cleaner"
|
|
38
39
|
gem.add_development_dependency "rake"
|
|
@@ -6,15 +6,16 @@ class FetchByUinqKeyTest < ActiveSupport::TestCase
|
|
|
6
6
|
def setup
|
|
7
7
|
@user = User.create name: "hooopo", email: "hoooopo@gmail.com"
|
|
8
8
|
@post = Post.create slug: "foobar", topic_id: 2
|
|
9
|
+
@cache_prefix = SecondLevelCache.configure.cache_key_prefix
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def test_cache_uniq_key
|
|
12
|
-
assert_equal User.send(:cache_uniq_key, name: "hooopo"), "uniq_key_User_name_hooopo"
|
|
13
|
-
assert_equal User.send(:cache_uniq_key, foo: 1, bar: 2), "uniq_key_User_foo_1,bar_2"
|
|
14
|
-
assert_equal User.send(:cache_uniq_key, foo: 1, bar: nil), "uniq_key_User_foo_1,bar_"
|
|
13
|
+
assert_equal User.send(:cache_uniq_key, name: "hooopo"), "#{@cache_prefix}/uniq_key_User_name_hooopo"
|
|
14
|
+
assert_equal User.send(:cache_uniq_key, foo: 1, bar: 2), "#{@cache_prefix}/uniq_key_User_foo_1,bar_2"
|
|
15
|
+
assert_equal User.send(:cache_uniq_key, foo: 1, bar: nil), "#{@cache_prefix}/uniq_key_User_foo_1,bar_"
|
|
15
16
|
long_val = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
16
|
-
assert_equal User.send(:cache_uniq_key, foo: 1, bar: long_val), "uniq_key_User_foo_1,bar_#{Digest::MD5.hexdigest(long_val)}"
|
|
17
|
-
assert Contribution.send(:cache_uniq_key, user_id: 1, date: Time.current.to_date), "uniq_key_Contribution_user_id_1,date_#{Time.current.to_date}"
|
|
17
|
+
assert_equal User.send(:cache_uniq_key, foo: 1, bar: long_val), "#{@cache_prefix}/uniq_key_User_foo_1,bar_#{Digest::MD5.hexdigest(long_val)}"
|
|
18
|
+
assert Contribution.send(:cache_uniq_key, user_id: 1, date: Time.current.to_date), "#{@cache_prefix}/uniq_key_Contribution_user_id_1,date_#{Time.current.to_date}"
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def test_record_attributes_equal_where_values
|
|
@@ -30,6 +30,10 @@ class HasOneAssociationTest < ActiveSupport::TestCase
|
|
|
30
30
|
ForkedUserLink.without_second_level_cache do
|
|
31
31
|
assert_queries(1) { user.forked_from_user }
|
|
32
32
|
end
|
|
33
|
+
# NoMethodError: undefined method `klass' for nil:NilClass active_record/has_one_association.rb:14:in `find_target'
|
|
34
|
+
hotspot = Hotspot.create(summary: "summary")
|
|
35
|
+
assert_equal hotspot.persisted?, true
|
|
36
|
+
assert_nil hotspot.topic
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
def test_has_one_with_conditions
|
data/test/model/post.rb
CHANGED
|
@@ -6,7 +6,17 @@ ActiveRecord::Base.connection.create_table(:posts, force: true) do |t|
|
|
|
6
6
|
t.integer :topic_id
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
ActiveRecord::Base.connection.create_table(:hotspots, force: true) do |t|
|
|
10
|
+
t.integer :post_id
|
|
11
|
+
t.string :summary
|
|
12
|
+
end
|
|
13
|
+
|
|
9
14
|
class Post < ApplicationRecord
|
|
10
15
|
second_level_cache
|
|
11
16
|
belongs_to :topic, touch: true
|
|
12
17
|
end
|
|
18
|
+
|
|
19
|
+
class Hotspot < ApplicationRecord
|
|
20
|
+
belongs_to :post, required: false
|
|
21
|
+
has_one :topic, through: :post
|
|
22
|
+
end
|
data/test/model/user.rb
CHANGED
|
@@ -9,7 +9,6 @@ ActiveRecord::Base.connection.create_table(:users, force: true) do |t|
|
|
|
9
9
|
t.integer :status, default: 0
|
|
10
10
|
t.integer :books_count, default: 0
|
|
11
11
|
t.integer :images_count, default: 0
|
|
12
|
-
t.datetime :deleted_at
|
|
13
12
|
t.timestamps null: false, precision: 6
|
|
14
13
|
end
|
|
15
14
|
|
|
@@ -29,7 +28,6 @@ end
|
|
|
29
28
|
class User < ApplicationRecord
|
|
30
29
|
CACHE_VERSION = 3
|
|
31
30
|
second_level_cache(version: CACHE_VERSION, expires_in: 3.days)
|
|
32
|
-
acts_as_paranoid
|
|
33
31
|
|
|
34
32
|
serialize :options, Array
|
|
35
33
|
serialize :json_options, JSON if ::ActiveRecord::VERSION::STRING >= "4.1.0"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class ParanoidTest < ActiveSupport::TestCase
|
|
6
|
+
def setup
|
|
7
|
+
skip unless defined?(Paranoi)
|
|
8
|
+
@paranoid = Paranoid.create
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_should_expire_cache_when_destroy
|
|
12
|
+
skip unless defined(Paranoi)
|
|
13
|
+
@paranoid.destroy
|
|
14
|
+
assert_nil Paranoid.find_by(id: @paranoid.id)
|
|
15
|
+
assert_nil SecondLevelCache.cache_store.read(@paranoid.second_level_cache_key)
|
|
16
|
+
assert_nil User.read_second_level_cache(@paranoid.id)
|
|
17
|
+
end
|
|
18
|
+
end
|
data/test/test_helper.rb
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require "bundler/setup"
|
|
4
4
|
require "minitest/autorun"
|
|
5
|
-
require "active_support/
|
|
5
|
+
require "active_support/all"
|
|
6
6
|
require "active_record_test_case_helper"
|
|
7
7
|
require "database_cleaner"
|
|
8
8
|
require "active_record"
|
|
9
|
-
require "paranoia"
|
|
10
9
|
require "pry"
|
|
11
10
|
ActiveSupport.test_order = :sorted if ActiveSupport.respond_to?(:test_order=)
|
|
12
11
|
# Force hook :active_record on_load event to make sure loader can work.
|
|
@@ -27,6 +26,7 @@ require "model/order_item"
|
|
|
27
26
|
require "model/account"
|
|
28
27
|
require "model/animal"
|
|
29
28
|
require "model/contribution"
|
|
29
|
+
require "model/paranoid" if defined?(Paranoid)
|
|
30
30
|
|
|
31
31
|
DatabaseCleaner[:active_record].strategy = :truncation
|
|
32
32
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: second_level_cache
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hooopo
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-12-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -16,40 +16,28 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
20
|
-
- - "<"
|
|
21
|
-
- !ruby/object:Gem::Version
|
|
22
|
-
version: '7'
|
|
19
|
+
version: '6.0'
|
|
23
20
|
type: :runtime
|
|
24
21
|
prerelease: false
|
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
26
23
|
requirements:
|
|
27
24
|
- - ">="
|
|
28
25
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '
|
|
30
|
-
- - "<"
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '7'
|
|
26
|
+
version: '6.0'
|
|
33
27
|
- !ruby/object:Gem::Dependency
|
|
34
28
|
name: activesupport
|
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
|
36
30
|
requirements:
|
|
37
31
|
- - ">="
|
|
38
32
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '
|
|
40
|
-
- - "<"
|
|
41
|
-
- !ruby/object:Gem::Version
|
|
42
|
-
version: '7'
|
|
33
|
+
version: '6.0'
|
|
43
34
|
type: :runtime
|
|
44
35
|
prerelease: false
|
|
45
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
46
37
|
requirements:
|
|
47
38
|
- - ">="
|
|
48
39
|
- !ruby/object:Gem::Version
|
|
49
|
-
version: '
|
|
50
|
-
- - "<"
|
|
51
|
-
- !ruby/object:Gem::Version
|
|
52
|
-
version: '7'
|
|
40
|
+
version: '6.0'
|
|
53
41
|
- !ruby/object:Gem::Dependency
|
|
54
42
|
name: database_cleaner
|
|
55
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -116,6 +104,7 @@ extra_rdoc_files: []
|
|
|
116
104
|
files:
|
|
117
105
|
- CHANGELOG.md
|
|
118
106
|
- Gemfile
|
|
107
|
+
- LICENSE
|
|
119
108
|
- README.md
|
|
120
109
|
- Rakefile
|
|
121
110
|
- lib/second_level_cache.rb
|
|
@@ -127,7 +116,9 @@ files:
|
|
|
127
116
|
- lib/second_level_cache/active_record/finder_methods.rb
|
|
128
117
|
- lib/second_level_cache/active_record/has_one_association.rb
|
|
129
118
|
- lib/second_level_cache/active_record/persistence.rb
|
|
130
|
-
- lib/second_level_cache/active_record/preloader.rb
|
|
119
|
+
- lib/second_level_cache/active_record/preloader/association.rb
|
|
120
|
+
- lib/second_level_cache/active_record/preloader/legacy.rb
|
|
121
|
+
- lib/second_level_cache/adapter/paranoia.rb
|
|
131
122
|
- lib/second_level_cache/config.rb
|
|
132
123
|
- lib/second_level_cache/log_subscriber.rb
|
|
133
124
|
- lib/second_level_cache/mixin.rb
|
|
@@ -150,9 +141,11 @@ files:
|
|
|
150
141
|
- test/model/image.rb
|
|
151
142
|
- test/model/order.rb
|
|
152
143
|
- test/model/order_item.rb
|
|
144
|
+
- test/model/paranoid.rb
|
|
153
145
|
- test/model/post.rb
|
|
154
146
|
- test/model/topic.rb
|
|
155
147
|
- test/model/user.rb
|
|
148
|
+
- test/paranoid_test.rb
|
|
156
149
|
- test/persistence_test.rb
|
|
157
150
|
- test/polymorphic_association_test.rb
|
|
158
151
|
- test/preloader_belongs_to_test.rb
|
|
@@ -167,7 +160,7 @@ files:
|
|
|
167
160
|
homepage: https://github.com/hooopo/second_level_cache
|
|
168
161
|
licenses: []
|
|
169
162
|
metadata: {}
|
|
170
|
-
post_install_message:
|
|
163
|
+
post_install_message:
|
|
171
164
|
rdoc_options: []
|
|
172
165
|
require_paths:
|
|
173
166
|
- lib
|
|
@@ -182,8 +175,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
182
175
|
- !ruby/object:Gem::Version
|
|
183
176
|
version: '0'
|
|
184
177
|
requirements: []
|
|
185
|
-
rubygems_version: 3.
|
|
186
|
-
signing_key:
|
|
178
|
+
rubygems_version: 3.2.3
|
|
179
|
+
signing_key:
|
|
187
180
|
specification_version: 4
|
|
188
181
|
summary: 'SecondLevelCache is a write-through and read-through caching library inspired
|
|
189
182
|
by Cache Money and cache_fu, support only Rails3 and ActiveRecord. Read-Through:
|
|
@@ -192,32 +185,34 @@ summary: 'SecondLevelCache is a write-through and read-through caching library i
|
|
|
192
185
|
is a cache miss, it will populate the cache. Write-Through: As objects are created,
|
|
193
186
|
updated, and deleted, all of the caches are automatically kept up-to-date and coherent.'
|
|
194
187
|
test_files:
|
|
195
|
-
- test/
|
|
196
|
-
- test/
|
|
197
|
-
- test/require_test.rb
|
|
198
|
-
- test/finder_methods_test.rb
|
|
199
|
-
- test/preloader_has_one_test.rb
|
|
200
|
-
- test/preloader_non_integer_test.rb
|
|
188
|
+
- test/active_record_test_case_helper.rb
|
|
189
|
+
- test/base_test.rb
|
|
201
190
|
- test/belongs_to_association_test.rb
|
|
202
|
-
- test/second_level_cache_test.rb
|
|
203
191
|
- test/enum_attr_test.rb
|
|
204
|
-
- test/
|
|
205
|
-
- test/
|
|
206
|
-
- test/
|
|
207
|
-
- test/single_table_inheritance_test.rb
|
|
208
|
-
- test/model/image.rb
|
|
192
|
+
- test/fetch_by_uniq_key_test.rb
|
|
193
|
+
- test/finder_methods_test.rb
|
|
194
|
+
- test/has_one_association_test.rb
|
|
209
195
|
- test/model/account.rb
|
|
210
|
-
- test/model/order_item.rb
|
|
211
|
-
- test/model/contribution.rb
|
|
212
|
-
- test/model/book.rb
|
|
213
|
-
- test/model/topic.rb
|
|
214
196
|
- test/model/animal.rb
|
|
215
|
-
- test/model/order.rb
|
|
216
197
|
- test/model/application_record.rb
|
|
198
|
+
- test/model/book.rb
|
|
199
|
+
- test/model/contribution.rb
|
|
200
|
+
- test/model/image.rb
|
|
201
|
+
- test/model/order.rb
|
|
202
|
+
- test/model/order_item.rb
|
|
203
|
+
- test/model/paranoid.rb
|
|
217
204
|
- test/model/post.rb
|
|
205
|
+
- test/model/topic.rb
|
|
218
206
|
- test/model/user.rb
|
|
219
|
-
- test/
|
|
207
|
+
- test/paranoid_test.rb
|
|
208
|
+
- test/persistence_test.rb
|
|
209
|
+
- test/polymorphic_association_test.rb
|
|
210
|
+
- test/preloader_belongs_to_test.rb
|
|
211
|
+
- test/preloader_has_many_test.rb
|
|
212
|
+
- test/preloader_has_one_test.rb
|
|
213
|
+
- test/preloader_non_integer_test.rb
|
|
214
|
+
- test/record_marshal_test.rb
|
|
215
|
+
- test/require_test.rb
|
|
216
|
+
- test/second_level_cache_test.rb
|
|
217
|
+
- test/single_table_inheritance_test.rb
|
|
220
218
|
- test/test_helper.rb
|
|
221
|
-
- test/has_one_association_test.rb
|
|
222
|
-
- test/base_test.rb
|
|
223
|
-
- test/active_record_test_case_helper.rb
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SecondLevelCache
|
|
4
|
-
module ActiveRecord
|
|
5
|
-
module Associations
|
|
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?
|
|
13
|
-
|
|
14
|
-
map_cache_keys = ids.map { |id| klass.second_level_cache_key(id) }
|
|
15
|
-
records_from_cache = ::SecondLevelCache.cache_store.read_multi(*map_cache_keys)
|
|
16
|
-
|
|
17
|
-
record_marshals = if RAILS6
|
|
18
|
-
RecordMarshal.load_multi(records_from_cache.values) do |record|
|
|
19
|
-
# This block is copy from:
|
|
20
|
-
# https://github.com/rails/rails/blob/6-0-stable/activerecord/lib/active_record/associations/preloader/association.rb#L101
|
|
21
|
-
owner = owners_by_key[convert_key(record[association_key_name])].first
|
|
22
|
-
association = owner.association(reflection.name)
|
|
23
|
-
association.set_inverse_instance(record)
|
|
24
|
-
end
|
|
25
|
-
else
|
|
26
|
-
RecordMarshal.load_multi(records_from_cache.values, &block)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# NOTICE
|
|
30
|
-
# Rails.cache.read_multi return hash that has keys only hitted.
|
|
31
|
-
# eg. Rails.cache.read_multi(1,2,3) => {2 => hit_value, 3 => hit_value}
|
|
32
|
-
hitted_ids = record_marshals.map { |record| record.read_attribute(association_key_name).to_s }
|
|
33
|
-
missed_ids = ids.map(&:to_s) - hitted_ids
|
|
34
|
-
ActiveSupport::Notifications.instrument("preload.second_level_cache", key: association_key_name, hit: hitted_ids, miss: missed_ids)
|
|
35
|
-
return SecondLevelCache::RecordRelation.new(record_marshals) if missed_ids.empty?
|
|
36
|
-
|
|
37
|
-
records_from_db = super(missed_ids, &block)
|
|
38
|
-
records_from_db.map { |r| write_cache(r) }
|
|
39
|
-
|
|
40
|
-
SecondLevelCache::RecordRelation.new(records_from_db + record_marshals)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
private
|
|
44
|
-
def write_cache(record)
|
|
45
|
-
record.write_second_level_cache
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|