second_level_cache 2.5.3 → 2.6.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 +8 -0
- data/lib/second_level_cache/active_record.rb +1 -1
- data/lib/second_level_cache/active_record/fetch_by_uniq_key.rb +4 -16
- data/lib/second_level_cache/active_record/finder_methods.rb +26 -23
- data/lib/second_level_cache/active_record/has_one_association.rb +16 -22
- data/lib/second_level_cache/active_record/preloader.rb +39 -33
- data/lib/second_level_cache/mixin.rb +0 -10
- data/lib/second_level_cache/record_marshal.rb +7 -40
- data/lib/second_level_cache/version.rb +1 -1
- data/test/fetch_by_uniq_key_test.rb +12 -9
- data/test/finder_methods_test.rb +39 -0
- data/test/has_one_association_test.rb +16 -4
- data/test/model/account.rb +1 -1
- data/test/model/book.rb +3 -0
- data/test/model/user.rb +4 -2
- 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 +1 -1
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6cfa929a72e1977223e5fc958b58ead3b3fa81daccbf38d4efe7e4ffc1d608d
|
4
|
+
data.tar.gz: 5709808afb6c5ee8d5937e1ce944005b528c4550e30d0db08b067864a5984d83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee4e3935b1cf774fe00e1432878ffee6104ad5b15c5ad4d6994521b9d5bb7fa183fcbdd97ba3dfd509317f7cd30ebabc0b5f89c18c0dd7aaad028474ef3f7d74
|
7
|
+
data.tar.gz: 173d12fbd5b5b440e37f9d219f9229f0146751debd5c8fa18e54b684ea257ec81a707a46a5b1e39309f9b7bb07784c921ea6a2737d3a681bb7480c9bb8ca6ebf
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
2.6.0
|
2
|
+
-------
|
3
|
+
|
4
|
+
- Add has_one through cache support. (#98)
|
5
|
+
- Fix string query, eager_load, includes/preload for fetch from db. ( #103, #102, #101)
|
6
|
+
- Fix preloader if exists default scope. (#104)
|
7
|
+
- Change cache hit log as `DEBUG` level. (#105)
|
8
|
+
|
1
9
|
2.5.3
|
2
10
|
-------
|
3
11
|
|
@@ -23,5 +23,5 @@ ActiveSupport.on_load(:active_record) do
|
|
23
23
|
ActiveRecord::Relation.send(:prepend, SecondLevelCache::ActiveRecord::FinderMethods)
|
24
24
|
# Rails 5.2 has removed ActiveRecord::Associations::Preloader::BelongsTo
|
25
25
|
# https://github.com/rails/rails/pull/31079
|
26
|
-
ActiveRecord::Associations::Preloader::Association.send(:prepend, SecondLevelCache::ActiveRecord::Associations::Preloader
|
26
|
+
ActiveRecord::Associations::Preloader::Association.send(:prepend, SecondLevelCache::ActiveRecord::Associations::Preloader)
|
27
27
|
end
|
@@ -14,7 +14,7 @@ module SecondLevelCache
|
|
14
14
|
nil
|
15
15
|
end
|
16
16
|
end
|
17
|
-
return record if
|
17
|
+
return record if record_attributes_equal_where_values?(record, where_values)
|
18
18
|
record = where(where_values).first
|
19
19
|
if record
|
20
20
|
SecondLevelCache.cache_store.write(cache_key, record.id)
|
@@ -53,21 +53,9 @@ module SecondLevelCache
|
|
53
53
|
"uniq_key_#{name}_#{ext_key}"
|
54
54
|
end
|
55
55
|
|
56
|
-
def
|
57
|
-
|
58
|
-
where_values.all?
|
59
|
-
attribute_value = record.read_attribute(k)
|
60
|
-
attribute_value == case attribute_value
|
61
|
-
when String
|
62
|
-
v.to_s
|
63
|
-
when Numeric
|
64
|
-
v.to_f
|
65
|
-
when Date
|
66
|
-
v.to_date
|
67
|
-
else # Maybe NilClass/?
|
68
|
-
v
|
69
|
-
end
|
70
|
-
end
|
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) }
|
71
59
|
end
|
72
60
|
end
|
73
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,15 +41,16 @@ 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
|
|
@@ -64,18 +58,31 @@ module SecondLevelCache
|
|
64
58
|
|
65
59
|
# readonly_value - active_record/relation/query_methods.rb Rails 5.1 true/false
|
66
60
|
def cachable?
|
67
|
-
|
68
|
-
|
61
|
+
second_level_cache_enabled? &&
|
62
|
+
limit_one? &&
|
63
|
+
# !eager_loading? &&
|
69
64
|
includes_values.blank? &&
|
70
65
|
preload_values.blank? &&
|
66
|
+
eager_load_values.blank? &&
|
67
|
+
select_values.blank? &&
|
68
|
+
order_values_can_cache? &&
|
71
69
|
readonly_value.blank? &&
|
72
70
|
joins_values.blank? &&
|
73
71
|
!@klass.locking_enabled? &&
|
74
|
-
|
72
|
+
where_clause_predicates_all_equality?
|
73
|
+
end
|
74
|
+
|
75
|
+
def order_values_can_cache?
|
76
|
+
return true if order_values.empty?
|
77
|
+
return false unless order_values.one?
|
78
|
+
return true if order_values.first == klass.primary_key
|
79
|
+
return false unless order_values.first.is_a?(::Arel::Nodes::Ordering)
|
80
|
+
return true if order_values.first.expr == klass.primary_key
|
81
|
+
order_values.first.expr.try(:name) == klass.primary_key
|
75
82
|
end
|
76
83
|
|
77
|
-
def
|
78
|
-
where_values_hash.
|
84
|
+
def where_clause_predicates_all_equality?
|
85
|
+
where_clause.send(:predicates).size == where_values_hash.size
|
79
86
|
end
|
80
87
|
|
81
88
|
def where_values_match_cache?(record)
|
@@ -91,10 +98,6 @@ module SecondLevelCache
|
|
91
98
|
def limit_one?
|
92
99
|
limit_value.blank? || limit_value == 1
|
93
100
|
end
|
94
|
-
|
95
|
-
def select_all_column?
|
96
|
-
select_values.blank?
|
97
|
-
end
|
98
101
|
end
|
99
102
|
end
|
100
103
|
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 reflection.
|
10
|
-
# TODO: implement cache with has_one
|
9
|
+
return super if reflection.scope
|
10
|
+
# TODO: implement cache with has_one scope
|
11
11
|
|
12
|
-
|
13
|
-
if
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
20
|
+
else
|
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)
|
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
|
@@ -3,41 +3,47 @@
|
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
+
::SecondLevelCache.logger.debug("missed #{association_key_name} -> #{missed_ids.join(',')} | hitted #{association_key_name} -> #{hitted_ids.join(',')}")
|
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
|
35
42
|
|
36
|
-
|
43
|
+
private
|
37
44
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
45
|
+
def write_cache(record)
|
46
|
+
record.write_second_level_cache
|
41
47
|
end
|
42
48
|
end
|
43
49
|
end
|
@@ -76,18 +76,8 @@ module SecondLevelCache
|
|
76
76
|
|
77
77
|
marshal = RecordMarshal.dump(self)
|
78
78
|
expires_in = klass.second_level_cache_options[:expires_in]
|
79
|
-
expire_changed_association_uniq_keys
|
80
79
|
SecondLevelCache.cache_store.write(second_level_cache_key, marshal, expires_in: expires_in)
|
81
80
|
end
|
82
|
-
|
83
81
|
alias update_second_level_cache write_second_level_cache
|
84
|
-
|
85
|
-
def expire_changed_association_uniq_keys
|
86
|
-
reflections = klass.reflections.select { |_, reflection| reflection.belongs_to? }
|
87
|
-
changed_keys = reflections.map { |_, reflection| reflection.foreign_key } & previous_changes.keys
|
88
|
-
changed_keys.each do |key|
|
89
|
-
SecondLevelCache.cache_store.delete(klass.send(:cache_uniq_key, key => previous_changes[key][0]))
|
90
|
-
end
|
91
|
-
end
|
92
82
|
end
|
93
83
|
end
|
@@ -2,54 +2,21 @@
|
|
2
2
|
|
3
3
|
module RecordMarshal
|
4
4
|
class << self
|
5
|
-
# dump ActiveRecord
|
6
|
-
# ["User",
|
7
|
-
# {"id"=>30,
|
8
|
-
# "email"=>"dddssddd@gmail.com",
|
9
|
-
# "created_at"=>2012-07-25 18:25:57 UTC
|
10
|
-
# }
|
11
|
-
# ]
|
12
|
-
|
5
|
+
# dump ActiveRecord instance with only attributes before type cast.
|
13
6
|
def dump(record)
|
14
|
-
[record.class.name, record.
|
7
|
+
[record.class.name, record.attributes_before_type_cast]
|
15
8
|
end
|
16
9
|
|
17
10
|
# load a cached record
|
18
|
-
def load(serialized)
|
11
|
+
def load(serialized, &block)
|
19
12
|
return unless serialized
|
20
|
-
# fix issues 19
|
21
|
-
# fix 2.1.2 object.changed? ActiveRecord::SerializationTypeMismatch: Attribute was supposed to be a Hash, but was a String. -- "{:a=>\"t\", :b=>\"x\"}"
|
22
|
-
# fix 2.1.4 object.changed? is true
|
23
|
-
# fix Rails 4.2 is deprecating `serialized_attributes` without replacement to Rails 5 is deprecating `serialized_attributes` without replacement
|
24
|
-
klass = serialized[0].constantize
|
25
|
-
attributes = serialized[1]
|
26
|
-
|
27
|
-
# for ActiveRecord 5.0.0
|
28
|
-
klass.columns.each do |c|
|
29
|
-
name = c.name
|
30
|
-
cast_type = klass.attribute_types[name]
|
31
|
-
next unless cast_type.is_a?(::ActiveRecord::Type::Serialized)
|
32
|
-
coder = cast_type.coder
|
33
|
-
next if attributes[name].nil? || attributes[name].is_a?(String)
|
34
|
-
if coder.is_a?(::ActiveRecord::Coders::YAMLColumn)
|
35
|
-
attributes[name] = coder.dump(attributes[name]) if attributes[name].is_a?(coder.object_class)
|
36
|
-
elsif coder.is_a?(::ActiveRecord::Store::IndifferentCoder)
|
37
|
-
# https://github.com/rails/rails/blob/5b14129/activerecord/lib/active_record/store.rb#L179
|
38
|
-
attributes[name] = coder.dump(attributes[name])
|
39
|
-
elsif coder == ::ActiveRecord::Coders::JSON
|
40
|
-
attributes[name] = attributes[name].to_json
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
klass.defined_enums.each do |key, value|
|
45
|
-
attributes[key] = value[attributes[key]] if attributes[key].is_a?(String)
|
46
|
-
end
|
47
13
|
|
48
|
-
|
14
|
+
serialized[0].constantize.instantiate(serialized[1], &block)
|
49
15
|
end
|
50
16
|
|
51
|
-
|
52
|
-
|
17
|
+
# load multi cached records
|
18
|
+
def load_multi(serializeds, &block)
|
19
|
+
serializeds.map { |serialized| load(serialized, &block) }
|
53
20
|
end
|
54
21
|
end
|
55
22
|
end
|
@@ -17,15 +17,18 @@ class FetchByUinqKeyTest < ActiveSupport::TestCase
|
|
17
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}"
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
book = Book.new
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
def test_record_attributes_equal_where_values
|
21
|
+
book = Book.new
|
22
|
+
assert_no_queries do
|
23
|
+
book.title = "foobar"
|
24
|
+
assert Book.send(:record_attributes_equal_where_values?, book, title: :foobar)
|
25
|
+
book.discount_percentage = 60.00
|
26
|
+
assert Book.send(:record_attributes_equal_where_values?, book, discount_percentage: "60")
|
27
|
+
book.publish_date = Time.current.to_date
|
28
|
+
assert Book.send(:record_attributes_equal_where_values?, book, publish_date: Time.current.to_date.to_s)
|
29
|
+
book.title = nil
|
30
|
+
assert Book.send(:record_attributes_equal_where_values?, book, title: nil)
|
31
|
+
end
|
29
32
|
end
|
30
33
|
|
31
34
|
def test_should_query_from_db_using_primary_key
|
data/test/finder_methods_test.rb
CHANGED
@@ -59,6 +59,45 @@ class FinderMethodsTest < ActiveSupport::TestCase
|
|
59
59
|
refute_equal @user.name, @from_db.name
|
60
60
|
end
|
61
61
|
|
62
|
+
def test_should_fetch_from_db_if_where_use_string
|
63
|
+
@user.write_second_level_cache
|
64
|
+
assert_queries(:any) do
|
65
|
+
assert_nil User.unscoped.where(id: @user.id).where("name = 'nonexistent'").first
|
66
|
+
end
|
67
|
+
assert_queries(:any) do
|
68
|
+
assert_raises ActiveRecord::RecordNotFound do
|
69
|
+
User.where("name = 'nonexistent'").find(@user.id)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_should_fetch_from_db_when_use_eager_load
|
75
|
+
@user.write_second_level_cache
|
76
|
+
assert_queries(:any) do
|
77
|
+
assert_sql(/LEFT\sOUTER\sJOIN\s\"books\"/m) do
|
78
|
+
User.eager_load(:books).find(@user.id)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_should_fetch_from_db_when_use_includes
|
84
|
+
@user.write_second_level_cache
|
85
|
+
assert_queries(:any) do
|
86
|
+
assert_sql(/SELECT\s\"books\"\.\*\sFROM\s\"books\"/m) do
|
87
|
+
User.includes(:books).find(@user.id)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_should_fetch_from_db_when_use_preload
|
93
|
+
@user.write_second_level_cache
|
94
|
+
assert_queries(:any) do
|
95
|
+
assert_sql(/SELECT\s\"books\"\.\*\sFROM\s\"books\"/m) do
|
96
|
+
User.preload(:books).find(@user.id)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
62
101
|
def test_where_and_first_should_with_cache
|
63
102
|
@user.write_second_level_cache
|
64
103
|
assert_no_queries do
|
@@ -20,10 +20,16 @@ class HasOneAssociationTest < ActiveSupport::TestCase
|
|
20
20
|
clean_user = user.reload
|
21
21
|
assert_equal User, clean_user.forked_from_user.class
|
22
22
|
assert_equal @user.id, user.forked_from_user.id
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
# If ForkedUserLink second_level_cache_enabled is true
|
24
|
+
user.reload
|
25
|
+
assert_no_queries do
|
26
|
+
user.forked_from_user
|
27
|
+
end
|
28
|
+
# IF ForkedUserLink second_level_cache_enabled is false
|
29
|
+
user.reload
|
30
|
+
ForkedUserLink.without_second_level_cache do
|
31
|
+
assert_queries(1) { user.forked_from_user }
|
32
|
+
end
|
27
33
|
end
|
28
34
|
|
29
35
|
def test_has_one_with_conditions
|
@@ -49,4 +55,10 @@ class HasOneAssociationTest < ActiveSupport::TestCase
|
|
49
55
|
@account.update(user_id: @user.id + 1)
|
50
56
|
assert_nil @user.reload.account
|
51
57
|
end
|
58
|
+
|
59
|
+
def test_should_one_query_when_has_one_target_is_null
|
60
|
+
Namespace.destroy_all
|
61
|
+
@user.reload
|
62
|
+
assert_queries(1) { @user.namespace }
|
63
|
+
end
|
52
64
|
end
|
data/test/model/account.rb
CHANGED
data/test/model/book.rb
CHANGED
@@ -7,11 +7,14 @@ ActiveRecord::Base.connection.create_table(:books, force: true) do |t|
|
|
7
7
|
t.decimal :discount_percentage, precision: 5, scale: 2
|
8
8
|
t.integer :images_count, default: 0
|
9
9
|
t.date :publish_date
|
10
|
+
t.boolean :normal, default: true, nil: false
|
10
11
|
end
|
11
12
|
|
12
13
|
class Book < ActiveRecord::Base
|
13
14
|
second_level_cache
|
14
15
|
|
16
|
+
default_scope -> { where(normal: true) }
|
17
|
+
|
15
18
|
belongs_to :user, counter_cache: true
|
16
19
|
has_many :images, as: :imagable
|
17
20
|
end
|
data/test/model/user.rb
CHANGED
@@ -10,7 +10,7 @@ ActiveRecord::Base.connection.create_table(:users, force: true) do |t|
|
|
10
10
|
t.integer :books_count, default: 0
|
11
11
|
t.integer :images_count, default: 0
|
12
12
|
t.datetime :deleted_at
|
13
|
-
t.timestamps null: false
|
13
|
+
t.timestamps null: false, precision: 6
|
14
14
|
end
|
15
15
|
|
16
16
|
ActiveRecord::Base.connection.create_table(:forked_user_links, force: true) do |t|
|
@@ -35,7 +35,7 @@ class User < ActiveRecord::Base
|
|
35
35
|
serialize :json_options, JSON if ::ActiveRecord::VERSION::STRING >= "4.1.0"
|
36
36
|
store :extras, accessors: %i[tagline gender]
|
37
37
|
|
38
|
-
has_one :account
|
38
|
+
has_one :account, inverse_of: :user
|
39
39
|
has_one :forked_user_link, foreign_key: "forked_to_user_id"
|
40
40
|
has_one :forked_from_user, through: :forked_user_link
|
41
41
|
has_many :namespaces
|
@@ -53,6 +53,8 @@ class Namespace < ActiveRecord::Base
|
|
53
53
|
end
|
54
54
|
|
55
55
|
class ForkedUserLink < ActiveRecord::Base
|
56
|
+
second_level_cache version: 1, expires_in: 1.day
|
57
|
+
|
56
58
|
belongs_to :forked_from_user, class_name: "User"
|
57
59
|
belongs_to :forked_to_user, class_name: "User"
|
58
60
|
end
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
require "test_helper"
|
4
4
|
|
5
|
-
class
|
6
|
-
def
|
5
|
+
class PreloaderBelongsToTest < ActiveSupport::TestCase
|
6
|
+
def test_preload_caches_includes
|
7
7
|
topics = [
|
8
8
|
Topic.create(title: "title1", body: "body1"),
|
9
9
|
Topic.create(title: "title2", body: "body2"),
|
@@ -18,7 +18,7 @@ class PreloaderTest < ActiveSupport::TestCase
|
|
18
18
|
assert_equal topics, results.map(&:topic)
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
21
|
+
def test_when_read_multi_missed_from_cache_ar_will_fetch_missed_records_from_db
|
22
22
|
topics = [
|
23
23
|
Topic.create(title: "title1", body: "body1"),
|
24
24
|
Topic.create(title: "title2", body: "body2"),
|
@@ -39,19 +39,21 @@ class PreloaderTest < ActiveSupport::TestCase
|
|
39
39
|
assert_equal topics, results.map(&:topic)
|
40
40
|
end
|
41
41
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
assert_equal
|
42
|
+
def test_preloader_caches_includes_tried_set_inverse_instance
|
43
|
+
user_id = Time.current.to_i
|
44
|
+
Account.create(site: "foobar", user_id: user_id)
|
45
|
+
User.create(id: user_id, name: "foobar", email: "foobar@test.com")
|
46
|
+
accounts = Account.includes(:user)
|
47
|
+
assert_equal accounts.first.object_id, accounts.first.user.account.object_id
|
48
48
|
end
|
49
49
|
|
50
|
-
def
|
51
|
-
user = User.create
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
50
|
+
def test_preloader_from_db_when_exists_scope
|
51
|
+
user = User.create
|
52
|
+
book = user.books.create
|
53
|
+
image = book.images.create
|
54
|
+
book.toggle!(:normal)
|
55
|
+
assert_queries(:any) do
|
56
|
+
assert_nil Image.includes(:imagable).where(id: image.id).first.imagable
|
57
|
+
end
|
56
58
|
end
|
57
59
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
class PreloaderHasManyTest < ActiveSupport::TestCase
|
6
|
+
def test_preloader_returns_correct_records
|
7
|
+
topic = Topic.create(id: 1)
|
8
|
+
Post.create(id: 1)
|
9
|
+
post = topic.posts.create
|
10
|
+
|
11
|
+
assert_equal [post], Topic.includes(:posts).find(1).posts
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
class PreloaderHasOneTest < ActiveSupport::TestCase
|
6
|
+
# def test_preload_caches_includes
|
7
|
+
# users = User.create([
|
8
|
+
# { name: "foobar1", email: "foobar1@test.com" },
|
9
|
+
# { name: "foobar2", email: "foobar2@test.com" },
|
10
|
+
# { name: "foobar3", email: "foobar3@test.com" }
|
11
|
+
# ])
|
12
|
+
# namespaces = users.map { |user| user.create_namespace(name: user.name) }
|
13
|
+
|
14
|
+
# assert_queries(2) { User.includes(:namespace).order(id: :asc).to_a } # Write cache
|
15
|
+
# assert_queries(1) do
|
16
|
+
# assert_equal namespaces, User.includes(:namespace).order(id: :asc).map(&:namespace)
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
|
20
|
+
# def test_when_read_multi_missed_from_cache_should_will_fetch_missed_records_from_db
|
21
|
+
# users = User.create([
|
22
|
+
# { name: "foobar1", email: "foobar1@test.com" },
|
23
|
+
# { name: "foobar2", email: "foobar2@test.com" },
|
24
|
+
# { name: "foobar3", email: "foobar3@test.com" }
|
25
|
+
# ])
|
26
|
+
# namespaces = users.map { |user| user.create_namespace(name: user.name) }
|
27
|
+
# assert_queries(2) { User.includes(:namespace).order(id: :asc).to_a } # Write cache
|
28
|
+
# expired_namespace = namespaces.first
|
29
|
+
# expired_namespace.expire_second_level_cache
|
30
|
+
|
31
|
+
# assert_queries(2) do
|
32
|
+
# assert_sql(/WHERE\s\"namespaces\".\"kind\"\sIS\sNULL\sAND\s\"namespaces\"\.\"user_id\"\s=\s?/m) do
|
33
|
+
# results = User.includes(:namespace).order(id: :asc).to_a
|
34
|
+
# assert_equal namespaces, results.map(&:namespace)
|
35
|
+
# assert_equal expired_namespace, results.first.namespace
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
|
40
|
+
# def test_preloader_returns_correct_records_after_modify
|
41
|
+
# user = User.create(name: "foobar", email: "foobar@test.com")
|
42
|
+
|
43
|
+
# old_namespace = user.create_namespace(name: "old")
|
44
|
+
# assert_queries(2) { User.includes(:namespace).order(id: :asc).to_a } # Write cache
|
45
|
+
# assert_queries(1) do
|
46
|
+
# assert_equal old_namespace, User.includes(:namespace).first.namespace
|
47
|
+
# end
|
48
|
+
|
49
|
+
# new_namespace = user.create_namespace(name: "new")
|
50
|
+
# assert_queries(2) { User.includes(:namespace).order(id: :asc).to_a } # Write cache
|
51
|
+
# assert_queries(1) do
|
52
|
+
# assert_equal new_namespace, User.includes(:namespace).first.namespace
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
|
56
|
+
# def test_preloader_caches_includes_tried_set_inverse_instance
|
57
|
+
# User.create(name: "foobar", email: "foobar@test.com").create_account(site: "foobar")
|
58
|
+
# users = User.includes(:account)
|
59
|
+
# assert_equal users.first.object_id, users.first.account.user.object_id
|
60
|
+
# end
|
61
|
+
|
62
|
+
def test_has_one_preloader_returns_correct_results
|
63
|
+
user = User.create(id: 1)
|
64
|
+
Account.create(id: 1)
|
65
|
+
account = user.create_account
|
66
|
+
|
67
|
+
assert_equal account, User.includes(:account).find(1).account
|
68
|
+
end
|
69
|
+
end
|
data/test/record_marshal_test.rb
CHANGED
@@ -25,7 +25,7 @@ class RecordMarshalTest < ActiveSupport::TestCase
|
|
25
25
|
dumped = RecordMarshal.dump(@user)
|
26
26
|
assert dumped.is_a?(Array)
|
27
27
|
assert_equal "User", dumped[0]
|
28
|
-
assert_equal
|
28
|
+
assert_equal dumped[1], ActiveRecord::Base.connection.select_all(User.where(id: @user.id)).first
|
29
29
|
end
|
30
30
|
|
31
31
|
def test_should_load_active_record_object
|
data/test/test_helper.rb
CHANGED
@@ -33,7 +33,7 @@ SecondLevelCache.configure do |config|
|
|
33
33
|
config.cache_store = ActiveSupport::Cache::MemoryStore.new
|
34
34
|
end
|
35
35
|
|
36
|
-
SecondLevelCache.logger.level = Logger::
|
36
|
+
SecondLevelCache.logger.level = Logger::INFO
|
37
37
|
ActiveSupport::Cache::MemoryStore.logger = SecondLevelCache.logger
|
38
38
|
ActiveRecord::Base.logger = SecondLevelCache.logger
|
39
39
|
|
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.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hooopo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -153,8 +153,10 @@ files:
|
|
153
153
|
- test/model/user.rb
|
154
154
|
- test/persistence_test.rb
|
155
155
|
- test/polymorphic_association_test.rb
|
156
|
+
- test/preloader_belongs_to_test.rb
|
157
|
+
- test/preloader_has_many_test.rb
|
158
|
+
- test/preloader_has_one_test.rb
|
156
159
|
- test/preloader_non_integer_test.rb
|
157
|
-
- test/preloader_test.rb
|
158
160
|
- test/record_marshal_test.rb
|
159
161
|
- test/require_test.rb
|
160
162
|
- test/second_level_cache_test.rb
|
@@ -188,15 +190,17 @@ summary: 'SecondLevelCache is a write-through and read-through caching library i
|
|
188
190
|
is a cache miss, it will populate the cache. Write-Through: As objects are created,
|
189
191
|
updated, and deleted, all of the caches are automatically kept up-to-date and coherent.'
|
190
192
|
test_files:
|
193
|
+
- test/preloader_belongs_to_test.rb
|
191
194
|
- test/polymorphic_association_test.rb
|
192
195
|
- test/require_test.rb
|
193
|
-
- test/preloader_test.rb
|
194
196
|
- test/finder_methods_test.rb
|
197
|
+
- test/preloader_has_one_test.rb
|
195
198
|
- test/preloader_non_integer_test.rb
|
196
199
|
- test/belongs_to_association_test.rb
|
197
200
|
- test/second_level_cache_test.rb
|
198
201
|
- test/enum_attr_test.rb
|
199
202
|
- test/record_marshal_test.rb
|
203
|
+
- test/preloader_has_many_test.rb
|
200
204
|
- test/persistence_test.rb
|
201
205
|
- test/single_table_inheritance_test.rb
|
202
206
|
- test/model/image.rb
|