second_level_cache 2.5.3 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|