second_level_cache 2.6.2 → 2.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84fecc39005beffa957b1602ff86ace6ca5419a1215d35011034ac6c78b5e0bf
4
- data.tar.gz: f0a09a62d06ef9f10a1ee4559a4aac1b8e39eaa8a86ee81ce90d67212c9c1811
3
+ metadata.gz: 95b4a082c060e99d4508f72111b51922c1776d6cb6529bf51670410c1156489a
4
+ data.tar.gz: 890f2e7c6a1c25172118cef92fba3ab1b1a43f05bc4e13352cf718040eb62cea
5
5
  SHA512:
6
- metadata.gz: 40c406b9b4181c5e1085127bbab26b71c5a47842d685521b9fa1ec43e58e529d8591c24ae675760ed2a3dcc9644dca7fcb6db12a5e651b67670af413c16addcc
7
- data.tar.gz: fc5f6ee2184884f4580123550e3d3edbb113114de4f7dacad340ef8c37711e9388335fdf029a06971fefe6a666ae0ca615f2852101384f986750333c3f85b0de
6
+ metadata.gz: 91e112bda2704f8538a722d9adcb6b4d98221b22a447c8e02203e8faa9b5cacbf883764eae105d90bfaa315880f3e1882808b3b322d62b81276de7c3599e03dd
7
+ data.tar.gz: 6bd5db40ab2feade041191582176e95ff854d8ab007bbc8ad58261e6839c412b34e8f3bc5a5ebcbbbce92b7854d47052e9f2479e4c934fb68456833622d9a243
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
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
+
1
14
  2.6.2
2
15
  -------
3
16
 
data/Gemfile CHANGED
@@ -3,6 +3,5 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
- gem "paranoia", "~> 2.4.2"
7
6
  gem "pry"
8
- gem "rails", "~> 6"
7
+ gem "rails", "~> 7"
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
  [![Gem Version](https://badge.fury.io/rb/second_level_cache.svg)](http://badge.fury.io/rb/second_level_cache)
4
- [![Build Status](https://travis-ci.org/hooopo/second_level_cache.svg?branch=master)](https://travis-ci.org/hooopo/second_level_cache)
4
+ [![build](https://github.com/hooopo/second_level_cache/actions/workflows/build.yml/badge.svg)](https://github.com/hooopo/second_level_cache/actions/workflows/build.yml)
5
5
  [![Code Climate](https://codeclimate.com/github/hooopo/second_level_cache.svg)](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 5.2:
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.4.0'
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
- * SecondLevelCache cache by model name and id, so only find_one query will work.
101
- * Only equal conditions query WILL get cache; and SQL string query like `User.where("name = 'Hooopo'").find(1)` WILL NOT work.
102
- * SecondLevelCache sync cache after transaction commit:
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
- * If you are using SecondLevelCache with database_cleaner, you should set cleaning strategy to `:truncation`:
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
- * 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` (default: `slc`):
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
- * SecondLevelCache was added model schema digest as cache version, this means when you add/remove/change columns, the caches of this Model will expires.
144
- * When your want change the model cache version by manualy, just add the `version` option like this:
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
- * It provides a great feature, not hits db when fetching record via unique key (not primary key).
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
- * 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:
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
- * [chloerei](https://github.com/chloerei)
175
- * [hooopo](https://github.com/hooopo)
179
+ - [chloerei](https://github.com/chloerei)
180
+ - [hooopo](https://github.com/hooopo)
176
181
 
177
182
  ## Contributors
178
183
 
@@ -9,10 +9,10 @@ module SecondLevelCache
9
9
 
10
10
  if obj_id
11
11
  record = begin
12
- find(obj_id)
13
- rescue StandardError
14
- nil
15
- end
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
- ext_key = keys.join(",")
52
- "#{SecondLevelCache.configure.cache_key_prefix}/uniq_key_#{name}_#{ext_key}"
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
- 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
+ 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
@@ -11,7 +11,7 @@ module SecondLevelCache
11
11
 
12
12
  through = reflection.options[:through]
13
13
  record = if through
14
- return super unless klass.reflections[through.to_s].klass.second_level_cache_enabled?
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,12 +8,13 @@ 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
16
  ActiveSupport.on_load(:active_record, run_once: true) do
16
- if (Bundler.definition.gem("paranoia") rescue false)
17
+ if Bundler.definition.dependencies.find { |x| x.name == "paranoia" }
17
18
  require "second_level_cache/adapter/paranoia"
18
19
  include SecondLevelCache::Adapter::Paranoia::ActiveRecord
19
20
  SecondLevelCache::Mixin.send(:prepend, SecondLevelCache::Adapter::Paranoia::Mixin)
@@ -27,7 +28,15 @@ ActiveSupport.on_load(:active_record, run_once: true) do
27
28
  ActiveRecord::Associations::BelongsToAssociation.send(:prepend, SecondLevelCache::ActiveRecord::Associations::BelongsToAssociation)
28
29
  ActiveRecord::Associations::HasOneAssociation.send(:prepend, SecondLevelCache::ActiveRecord::Associations::HasOneAssociation)
29
30
  ActiveRecord::Relation.send(:prepend, SecondLevelCache::ActiveRecord::FinderMethods)
30
- # Rails 5.2 has removed ActiveRecord::Associations::Preloader::BelongsTo
31
- # https://github.com/rails/rails/pull/31079
32
- ActiveRecord::Associations::Preloader::Association.send(:prepend, SecondLevelCache::ActiveRecord::Associations::Preloader)
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
33
42
  end
@@ -17,7 +17,8 @@ module SecondLevelCache
17
17
  return if respond_to?(:deleted?) && send(:deleted?)
18
18
  super
19
19
  end
20
- alias update_second_level_cache write_second_level_cache
20
+
21
+ alias_method :update_second_level_cache, :write_second_level_cache
21
22
  end
22
23
  end
23
24
  end
@@ -76,6 +76,7 @@ module SecondLevelCache
76
76
  expires_in = klass.second_level_cache_options[:expires_in]
77
77
  SecondLevelCache.cache_store.write(second_level_cache_key, marshal, expires_in: expires_in)
78
78
  end
79
- alias update_second_level_cache write_second_level_cache
79
+
80
+ alias_method :update_second_level_cache, :write_second_level_cache
80
81
  end
81
82
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SecondLevelCache
4
- VERSION = "2.6.2"
4
+ VERSION = "2.7.1"
5
5
  end
@@ -22,6 +22,9 @@ module SecondLevelCache
22
22
  end
23
23
 
24
24
  def self.cache_enabled?
25
+ if self.cache_store.is_a?(ActiveSupport::Cache::NullStore)
26
+ return false
27
+ end
25
28
  cache_enabled = Thread.current[:slc_cache_enabled]
26
29
  cache_enabled.nil? ? true : cache_enabled
27
30
  end
@@ -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 = ["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
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 = "https://github.com/hooopo/second_level_cache"
19
+ gem.homepage = "https://github.com/hooopo/second_level_cache"
20
20
 
21
- gem.files = Dir.glob("lib/**/*.rb") + [
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 = Dir.glob("test/**/*.rb")
29
- gem.executables = gem.files.grep(%r{^bin/})
30
- gem.name = "second_level_cache"
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 = SecondLevelCache::VERSION
33
+ gem.version = SecondLevelCache::VERSION
33
34
 
34
- gem.add_runtime_dependency "activerecord", [">= 5.2", "< 7"]
35
- gem.add_runtime_dependency "activesupport", [">= 5.2", "< 7"]
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"
@@ -11,7 +11,7 @@ class BelongsToAssociationTest < ActiveSupport::TestCase
11
11
  book = @user.books.create
12
12
 
13
13
  @user.write_second_level_cache
14
- book.send(:clear_association_cache)
14
+
15
15
  assert_no_queries do
16
16
  assert_equal @user, book.user
17
17
  end
@@ -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
@@ -4,10 +4,12 @@ require "test_helper"
4
4
 
5
5
  class ParanoidTest < ActiveSupport::TestCase
6
6
  def setup
7
+ skip unless defined?(Paranoi)
7
8
  @paranoid = Paranoid.create
8
9
  end
9
10
 
10
11
  def test_should_expire_cache_when_destroy
12
+ skip unless defined(Paranoi)
11
13
  @paranoid.destroy
12
14
  assert_nil Paranoid.find_by(id: @paranoid.id)
13
15
  assert_nil SecondLevelCache.cache_store.read(@paranoid.second_level_cache_key)
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/test_case"
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,7 +26,7 @@ require "model/order_item"
27
26
  require "model/account"
28
27
  require "model/animal"
29
28
  require "model/contribution"
30
- require "model/paranoid"
29
+ require "model/paranoid" if defined?(Paranoid)
31
30
 
32
31
  DatabaseCleaner[:active_record].strategy = :truncation
33
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.6.2
4
+ version: 2.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hooopo
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-02 00:00:00.000000000 Z
11
+ date: 2022-02-15 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: '5.2'
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: '5.2'
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: '5.2'
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: '5.2'
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,8 @@ 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
131
121
  - lib/second_level_cache/adapter/paranoia.rb
132
122
  - lib/second_level_cache/config.rb
133
123
  - lib/second_level_cache/log_subscriber.rb
@@ -170,7 +160,7 @@ files:
170
160
  homepage: https://github.com/hooopo/second_level_cache
171
161
  licenses: []
172
162
  metadata: {}
173
- post_install_message:
163
+ post_install_message:
174
164
  rdoc_options: []
175
165
  require_paths:
176
166
  - lib
@@ -185,8 +175,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
185
175
  - !ruby/object:Gem::Version
186
176
  version: '0'
187
177
  requirements: []
188
- rubygems_version: 3.0.3
189
- signing_key:
178
+ rubygems_version: 3.2.3
179
+ signing_key:
190
180
  specification_version: 4
191
181
  summary: 'SecondLevelCache is a write-through and read-through caching library inspired
192
182
  by Cache Money and cache_fu, support only Rails3 and ActiveRecord. Read-Through:
@@ -195,34 +185,34 @@ summary: 'SecondLevelCache is a write-through and read-through caching library i
195
185
  is a cache miss, it will populate the cache. Write-Through: As objects are created,
196
186
  updated, and deleted, all of the caches are automatically kept up-to-date and coherent.'
197
187
  test_files:
198
- - test/preloader_belongs_to_test.rb
199
- - test/polymorphic_association_test.rb
200
- - test/require_test.rb
201
- - test/paranoid_test.rb
202
- - test/finder_methods_test.rb
203
- - test/preloader_has_one_test.rb
204
- - test/preloader_non_integer_test.rb
188
+ - test/active_record_test_case_helper.rb
189
+ - test/base_test.rb
205
190
  - test/belongs_to_association_test.rb
206
- - test/second_level_cache_test.rb
207
191
  - test/enum_attr_test.rb
208
- - test/record_marshal_test.rb
209
- - test/preloader_has_many_test.rb
210
- - test/persistence_test.rb
211
- - test/single_table_inheritance_test.rb
212
- - 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
213
195
  - test/model/account.rb
214
- - test/model/order_item.rb
215
- - test/model/contribution.rb
216
- - test/model/book.rb
217
- - test/model/topic.rb
218
196
  - test/model/animal.rb
197
+ - test/model/application_record.rb
198
+ - test/model/book.rb
199
+ - test/model/contribution.rb
200
+ - test/model/image.rb
219
201
  - test/model/order.rb
202
+ - test/model/order_item.rb
220
203
  - test/model/paranoid.rb
221
- - test/model/application_record.rb
222
204
  - test/model/post.rb
205
+ - test/model/topic.rb
223
206
  - test/model/user.rb
224
- - test/fetch_by_uniq_key_test.rb
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
225
218
  - test/test_helper.rb
226
- - test/has_one_association_test.rb
227
- - test/base_test.rb
228
- - test/active_record_test_case_helper.rb
@@ -1,51 +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? || 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)
25
- end
26
- else
27
- RecordMarshal.load_multi(records_from_cache.values, &block)
28
- end
29
-
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?
37
-
38
- records_from_db = super(missed_ids, &block)
39
- records_from_db.map { |r| write_cache(r) }
40
-
41
- SecondLevelCache::RecordRelation.new(records_from_db + record_marshals)
42
- end
43
-
44
- private
45
- def write_cache(record)
46
- record.write_second_level_cache
47
- end
48
- end
49
- end
50
- end
51
- end