ar_cache 1.2.0 → 2.1.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 +4 -16
- data/Gemfile.common +2 -0
- data/Gemfile.lock +89 -80
- data/README.md +4 -4
- data/README.zh-CN.md +165 -0
- data/ar_cache.gemspec +1 -0
- data/lib/ar_cache.rb +51 -18
- data/lib/ar_cache/active_record/associations/association.rb +1 -1
- data/lib/ar_cache/active_record/associations/has_one_through_association.rb +11 -9
- data/lib/ar_cache/active_record/associations/singular_association.rb +2 -6
- data/lib/ar_cache/active_record/connection_adapters/abstract/database_statements.rb +20 -15
- data/lib/ar_cache/active_record/connection_adapters/abstract/transaction.rb +37 -32
- data/lib/ar_cache/active_record/core.rb +5 -4
- data/lib/ar_cache/active_record/insert_all.rb +1 -4
- data/lib/ar_cache/active_record/model_schema.rb +1 -7
- data/lib/ar_cache/active_record/persistence.rb +4 -5
- data/lib/ar_cache/active_record/relation.rb +7 -8
- data/lib/ar_cache/configuration.rb +46 -41
- data/lib/ar_cache/marshal.rb +36 -26
- data/lib/ar_cache/mock_table.rb +4 -4
- data/lib/ar_cache/query.rb +20 -15
- data/lib/ar_cache/table.rb +44 -51
- data/lib/ar_cache/version.rb +1 -1
- data/lib/ar_cache/where_clause.rb +35 -26
- data/lib/generators/ar_cache/install_generator.rb +0 -7
- data/lib/generators/ar_cache/templates/configuration.rb +29 -29
- metadata +23 -5
- data/lib/ar_cache/record.rb +0 -52
- data/lib/ar_cache/store.rb +0 -38
- data/lib/generators/ar_cache/templates/migrate/create_ar_cache_records.rb.tt +0 -16
data/ar_cache.gemspec
CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.require_paths = ['lib']
|
29
29
|
|
30
30
|
spec.add_runtime_dependency 'activerecord', '>= 6.1', '< 7'
|
31
|
+
spec.add_runtime_dependency 'oj', '>= 3', '< 4'
|
31
32
|
|
32
33
|
# For more information and examples about making a new gem, checkout our
|
33
34
|
# guide at: https://bundler.io/guides/creating_gem.html
|
data/lib/ar_cache.rb
CHANGED
@@ -2,11 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'active_support/cache'
|
4
4
|
require 'active_record'
|
5
|
+
require 'oj'
|
5
6
|
|
6
7
|
require 'ar_cache/version'
|
7
8
|
require 'ar_cache/configuration'
|
8
|
-
require 'ar_cache/record'
|
9
|
-
require 'ar_cache/store'
|
10
9
|
require 'ar_cache/marshal'
|
11
10
|
require 'ar_cache/table'
|
12
11
|
require 'ar_cache/mock_table'
|
@@ -18,31 +17,65 @@ require 'ar_cache/active_record'
|
|
18
17
|
require_relative './generators/ar_cache/install_generator' if defined?(Rails)
|
19
18
|
|
20
19
|
module ArCache
|
21
|
-
|
20
|
+
PLACEHOLDER = ''
|
21
|
+
|
22
|
+
@cache_reflection = {}
|
22
23
|
|
23
24
|
class << self
|
24
|
-
delegate :configure, to: Configuration
|
25
|
+
delegate :configure, :memcached?, :redis?, :cache_store, to: ArCache::Configuration
|
26
|
+
delegate :read, :read_multi, :write, :write_multi, :delete, :delete_multi, :exist?, :clear, to: :cache_store
|
27
|
+
|
28
|
+
def skip_cache?
|
29
|
+
Thread.current[:ar_cache_skip_cache]
|
30
|
+
end
|
31
|
+
|
32
|
+
def skip_cache
|
33
|
+
return yield if skip_cache?
|
34
|
+
|
35
|
+
begin
|
36
|
+
Thread.current[:ar_cache_skip_cache] = true
|
37
|
+
yield
|
38
|
+
ensure
|
39
|
+
Thread.current[:ar_cache_skip_cache] = false
|
40
|
+
end
|
41
|
+
end
|
25
42
|
|
26
|
-
def
|
27
|
-
Thread.current[:
|
43
|
+
def skip_expire?
|
44
|
+
Thread.current[:ar_cache_skip_expire]
|
28
45
|
end
|
29
46
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
47
|
+
def skip_expire
|
48
|
+
return yield if skip_expire?
|
49
|
+
|
50
|
+
begin
|
51
|
+
Thread.current[:ar_cache_skip_expire] = true
|
52
|
+
yield
|
53
|
+
ensure
|
54
|
+
Thread.current[:ar_cache_skip_expire] = false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def cache_reflection?(reflection)
|
59
|
+
@cache_reflection.fetch(reflection) do
|
60
|
+
Thread.current[:ar_cache_reflection] = true
|
61
|
+
@cache_reflection[reflection] = yield
|
62
|
+
ensure
|
63
|
+
Thread.current[:ar_cache_reflection] = false
|
64
|
+
end
|
35
65
|
end
|
36
66
|
|
37
|
-
def
|
38
|
-
|
67
|
+
def dump_attributes(attributes)
|
68
|
+
memcached? || redis? ? Oj.dump(attributes) : attributes
|
39
69
|
end
|
40
70
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
71
|
+
def load_attributes(attributes)
|
72
|
+
memcached? || redis? ? Oj.load(attributes) : attributes
|
73
|
+
end
|
74
|
+
|
75
|
+
def lock_key(key)
|
76
|
+
ArCache.write(key, PLACEHOLDER, raw: true, expires_in: 1.hour)
|
46
77
|
end
|
47
78
|
end
|
48
79
|
end
|
80
|
+
|
81
|
+
require_relative './generators/ar_cache/templates/configuration'
|
@@ -4,19 +4,21 @@ module ArCache
|
|
4
4
|
module ActiveRecord
|
5
5
|
module Associations
|
6
6
|
module HasOneThroughAssociation
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
return super if
|
7
|
+
PRELOADER = ::ActiveRecord::Associations::Preloader.new
|
8
|
+
|
9
|
+
private def find_target
|
10
|
+
return super if ArCache.skip_cache?
|
11
|
+
return super unless ArCache.cache_reflection?(reflection) do
|
12
|
+
ArCache::Query.new(owner.association(through_reflection.name).scope).exec_queries_cacheable? &&
|
13
|
+
ArCache::Query.new(source_reflection.active_record.new.association(source_reflection.name).scope).exec_queries_cacheable? # rubocop:disable Layout/LineLength
|
14
|
+
end
|
11
15
|
|
12
16
|
if (owner.strict_loading? || reflection.strict_loading?) && owner.validation_context.nil?
|
13
|
-
Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
|
17
|
+
::ActiveRecord::Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
|
14
18
|
end
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
rescue StandardError
|
19
|
-
super
|
20
|
+
PRELOADER.preload(owner, reflection.name)
|
21
|
+
target
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
@@ -5,12 +5,8 @@ module ArCache
|
|
5
5
|
module Associations
|
6
6
|
module SingularAssociation
|
7
7
|
private def skip_statement_cache?(...)
|
8
|
-
return super if ArCache.
|
9
|
-
|
10
|
-
# Polymorphic associations do not support computing the class, so can't judge ArCache status.
|
11
|
-
# But SingularAssociation query usually can hit the unique index, so here return true directly.
|
12
|
-
return true if is_a?(::ActiveRecord::Associations::BelongsToPolymorphicAssociation)
|
13
|
-
return true unless reflection.klass.ar_cache_table.disabled?
|
8
|
+
return super if ArCache.skip_cache?
|
9
|
+
return true if ArCache.cache_reflection?(reflection) { ArCache::Query.new(scope).exec_queries_cacheable? }
|
14
10
|
|
15
11
|
super
|
16
12
|
end
|
@@ -4,18 +4,23 @@ module ArCache
|
|
4
4
|
module ActiveRecord
|
5
5
|
module ConnectionAdapters
|
6
6
|
module DatabaseStatements
|
7
|
+
def select_all(arel, ...)
|
8
|
+
result = super
|
9
|
+
klass, select_values = arel.try(:klass_and_select_values)
|
10
|
+
return result if klass.nil?
|
11
|
+
|
12
|
+
klass.ar_cache_table.write(result.to_a)
|
13
|
+
|
14
|
+
if select_values
|
15
|
+
result.to_a.each { |r| r.slice!(*select_values) }
|
16
|
+
elsif klass.ignored_columns.any?
|
17
|
+
result.to_a.each { |r| r.except!(*klass.ignored_columns) }
|
18
|
+
end
|
19
|
+
|
20
|
+
result
|
21
|
+
end
|
22
|
+
|
7
23
|
# def insert(arel, ...)
|
8
|
-
# super.tap do
|
9
|
-
# if arel.is_a?(String)
|
10
|
-
# sql = arel.downcase
|
11
|
-
# ArCache::Table.all.each do |table|
|
12
|
-
# current_transaction.add_changed_table(table.name) if sql.include?(table.name)
|
13
|
-
# end
|
14
|
-
# else # is Arel::InsertManager
|
15
|
-
# klass = arel.ast.relation.instance_variable_get(:@klass)
|
16
|
-
# current_transaction.add_changed_table(klass.table_name)
|
17
|
-
# end
|
18
|
-
# end
|
19
24
|
# end
|
20
25
|
# alias create insert
|
21
26
|
|
@@ -46,24 +51,24 @@ module ArCache
|
|
46
51
|
end
|
47
52
|
|
48
53
|
private def update_ar_cache_by_arel(arel)
|
49
|
-
return if ArCache.
|
54
|
+
return if ArCache.skip_expire?
|
50
55
|
|
51
56
|
arel_table = arel.ast.relation.is_a?(Arel::Table) ? arel.ast.relation : arel.ast.relation.left
|
52
57
|
klass = arel_table.instance_variable_get(:@klass)
|
53
|
-
current_transaction.update_ar_cache_table(klass.ar_cache_table)
|
58
|
+
current_transaction.update_ar_cache_table(klass.ar_cache_table)
|
54
59
|
end
|
55
60
|
|
56
61
|
private def update_ar_cache_by_sql(sql)
|
57
62
|
sql = sql.downcase
|
58
63
|
|
59
64
|
ArCache::Table.all.each do |table|
|
60
|
-
current_transaction.update_ar_cache_table(table) if
|
65
|
+
current_transaction.update_ar_cache_table(table) if sql.include?(table.name)
|
61
66
|
end
|
62
67
|
end
|
63
68
|
|
64
69
|
private def update_ar_cache_by_table(table_name)
|
65
70
|
ArCache::Table.all.each do |table|
|
66
|
-
current_transaction.update_ar_cache_table(table) if
|
71
|
+
break current_transaction.update_ar_cache_table(table) if table_name == table.name
|
67
72
|
end
|
68
73
|
end
|
69
74
|
end
|
@@ -4,75 +4,80 @@ module ArCache
|
|
4
4
|
module ActiveRecord
|
5
5
|
module ConnectionAdapters
|
6
6
|
module NullTransaction
|
7
|
-
def
|
8
|
-
|
7
|
+
def delete_ar_cache_primary_keys(keys, table)
|
8
|
+
handle_ar_cache_primary_keys(keys) unless table.disabled?
|
9
9
|
end
|
10
10
|
|
11
|
-
def update_ar_cache_table(table
|
12
|
-
table.
|
11
|
+
def update_ar_cache_table(table)
|
12
|
+
table.update_cache
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
15
|
+
def handle_ar_cache_primary_keys(keys)
|
16
|
+
if ArCache::Configuration.cache_lock?
|
17
|
+
keys.each { |k| ArCache.lock_key(k) }
|
18
|
+
else
|
19
|
+
ArCache.delete_multi(keys)
|
20
|
+
end
|
21
|
+
end
|
16
22
|
end
|
17
23
|
|
18
24
|
module Transaction
|
19
25
|
include NullTransaction
|
20
26
|
|
27
|
+
attr_reader :ar_cache_primary_keys, :ar_cache_tables
|
28
|
+
|
21
29
|
def initialize(...)
|
22
30
|
super
|
23
|
-
@
|
31
|
+
@ar_cache_primary_keys = []
|
24
32
|
@ar_cache_tables = []
|
25
33
|
end
|
26
34
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
def update_ar_cache_table(table, delay: false)
|
33
|
-
add_changed_table(table.name) unless delay
|
35
|
+
def delete_ar_cache_primary_keys(keys, table)
|
36
|
+
connection.transaction_manager.add_transaction_table(table.name)
|
37
|
+
return if table.disabled?
|
34
38
|
|
35
|
-
super if
|
36
|
-
|
39
|
+
super if read_uncommitted?
|
40
|
+
ar_cache_primary_keys.push(*keys)
|
37
41
|
end
|
38
42
|
|
39
|
-
def
|
40
|
-
connection.transaction_manager.
|
43
|
+
def update_ar_cache_table(table)
|
44
|
+
connection.transaction_manager.add_transaction_table(table.name)
|
45
|
+
return if table.disabled?
|
46
|
+
|
47
|
+
super if read_uncommitted?
|
48
|
+
ar_cache_tables.push(table)
|
41
49
|
end
|
42
50
|
|
43
|
-
# FIXME:
|
51
|
+
# FIXME: The cache is removed after transaction commited, so dirty read may occur.
|
44
52
|
def commit
|
45
53
|
super
|
46
54
|
ensure
|
47
55
|
if @run_commit_callbacks
|
48
|
-
|
49
|
-
|
56
|
+
handle_ar_cache_primary_keys(ar_cache_primary_keys.uniq) if ar_cache_primary_keys.any?
|
57
|
+
ar_cache_tables.uniq(&:name).each(&:update_cache) if ar_cache_tables.any?
|
50
58
|
else
|
51
|
-
|
52
|
-
|
53
|
-
transaction.delete_ar_cache_keys(@ar_cache_keys, delay: true)
|
59
|
+
connection.current_transaction.ar_cache_tables.push(*ar_cache_tables)
|
60
|
+
connection.current_transaction.ar_cache_primary_keys.push(*ar_cache_primary_keys)
|
54
61
|
end
|
55
62
|
end
|
56
63
|
|
57
64
|
def read_uncommitted?
|
58
|
-
|
59
|
-
isolation_level == :read_uncommitted ||
|
60
|
-
!connection.transaction_manager.fully_joinable?
|
65
|
+
isolation_level == :read_uncommitted || !connection.transaction_manager.fully_joinable?
|
61
66
|
end
|
62
67
|
end
|
63
68
|
|
64
69
|
module TransactionManager
|
65
70
|
def initialize(...)
|
66
71
|
super
|
67
|
-
@
|
72
|
+
@transaction_tables = {}
|
68
73
|
end
|
69
74
|
|
70
|
-
def
|
71
|
-
@
|
75
|
+
def add_transaction_table(table_name)
|
76
|
+
@transaction_tables[table_name] = true if fully_joinable?
|
72
77
|
end
|
73
78
|
|
74
|
-
def
|
75
|
-
@
|
79
|
+
def transaction_table?(table_name)
|
80
|
+
@transaction_tables.key?(table_name)
|
76
81
|
end
|
77
82
|
|
78
83
|
def fully_joinable?
|
@@ -82,7 +87,7 @@ module ArCache
|
|
82
87
|
def within_new_transaction(...)
|
83
88
|
super
|
84
89
|
ensure
|
85
|
-
@
|
90
|
+
@transaction_tables.clear if @stack.count(&:joinable?).zero?
|
86
91
|
end
|
87
92
|
end
|
88
93
|
end
|
@@ -4,14 +4,15 @@ module ArCache
|
|
4
4
|
module ActiveRecord
|
5
5
|
module Core
|
6
6
|
module ClassMethods
|
7
|
-
# The #find use
|
7
|
+
# The #find and #find_by use ActiveRecord::StatementCache to execute querying first.
|
8
|
+
# For ArCache, we need force skip ActiveRecord::StatementCache.
|
9
|
+
|
8
10
|
def find(...)
|
9
|
-
ArCache.
|
11
|
+
ArCache.skip_cache? || ar_cache_table.disabled? ? super : all.find(...)
|
10
12
|
end
|
11
13
|
|
12
|
-
# The #find_by use statement cache execute querying first, so need force skip.
|
13
14
|
def find_by(...)
|
14
|
-
ArCache.
|
15
|
+
ArCache.skip_cache? || ar_cache_table.disabled? ? super : all.find_by(...)
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
@@ -5,10 +5,7 @@ module ArCache
|
|
5
5
|
module InsertAll
|
6
6
|
def execute
|
7
7
|
super.tap do
|
8
|
-
if on_duplicate == :update
|
9
|
-
connection.current_transaction.update_ar_cache_table(model.ar_cache_table)
|
10
|
-
connection.current_transaction.add_changed_table(model.table_name)
|
11
|
-
end
|
8
|
+
connection.current_transaction.update_ar_cache_table(model.ar_cache_table) if on_duplicate == :update
|
12
9
|
end
|
13
10
|
end
|
14
11
|
end
|
@@ -9,13 +9,7 @@ module ArCache
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def ar_cache_table
|
12
|
-
@ar_cache_table ||=
|
13
|
-
if abstract_class? || self == ArCache::Record
|
14
|
-
ArCache::MockTable
|
15
|
-
else
|
16
|
-
ArCache::Table.new(table_name)
|
17
|
-
end
|
18
|
-
end
|
12
|
+
@ar_cache_table ||= abstract_class? ? ArCache::MockTable : ArCache::Table.new(table_name)
|
19
13
|
end
|
20
14
|
end
|
21
15
|
|
@@ -5,14 +5,14 @@ module ArCache
|
|
5
5
|
module Persistence
|
6
6
|
module ClassMethods
|
7
7
|
def _update_record(_, constraints)
|
8
|
-
ArCache.
|
8
|
+
ArCache.skip_expire do
|
9
9
|
delete_ar_cache_key(constraints[@primary_key])
|
10
10
|
super
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
14
|
def _delete_record(constraints)
|
15
|
-
ArCache.
|
15
|
+
ArCache.skip_expire do
|
16
16
|
delete_ar_cache_key(constraints[@primary_key])
|
17
17
|
super
|
18
18
|
end
|
@@ -20,13 +20,12 @@ module ArCache
|
|
20
20
|
|
21
21
|
private def delete_ar_cache_key(id)
|
22
22
|
key = ar_cache_table.primary_cache_key(id)
|
23
|
-
connection.current_transaction.
|
24
|
-
connection.current_transaction.add_changed_table(table_name)
|
23
|
+
connection.current_transaction.delete_ar_cache_primary_keys([key], ar_cache_table)
|
25
24
|
end
|
26
25
|
end
|
27
26
|
|
28
27
|
def reload(...)
|
29
|
-
ArCache.
|
28
|
+
ArCache.skip_cache { super }
|
30
29
|
end
|
31
30
|
end
|
32
31
|
end
|
@@ -4,22 +4,22 @@ module ArCache
|
|
4
4
|
module ActiveRecord
|
5
5
|
module Relation
|
6
6
|
def reload
|
7
|
-
loaded? ? ArCache.
|
7
|
+
loaded? ? ArCache.skip_cache { super } : super
|
8
8
|
end
|
9
9
|
|
10
10
|
def explain
|
11
|
-
ArCache.
|
11
|
+
ArCache.skip_cache { super }
|
12
12
|
end
|
13
13
|
|
14
14
|
def update_all(...)
|
15
|
-
ArCache.
|
15
|
+
ArCache.skip_expire { delete_ar_cache_primary_keys ? super : 0 }
|
16
16
|
end
|
17
17
|
|
18
18
|
def delete_all
|
19
|
-
ArCache.
|
19
|
+
ArCache.skip_expire { delete_ar_cache_primary_keys ? super : 0 }
|
20
20
|
end
|
21
21
|
|
22
|
-
private def
|
22
|
+
private def delete_ar_cache_primary_keys
|
23
23
|
return true if klass.ar_cache_table.disabled?
|
24
24
|
|
25
25
|
where_clause = ArCache::WhereClause.new(klass, arel.constraints)
|
@@ -31,13 +31,12 @@ module ArCache
|
|
31
31
|
|
32
32
|
return false if keys.empty?
|
33
33
|
|
34
|
-
@klass.connection.current_transaction.
|
35
|
-
@klass.connection.current_transaction.add_changed_table(@klass.table_name)
|
34
|
+
@klass.connection.current_transaction.delete_ar_cache_primary_keys(keys, @klass.ar_cache_table)
|
36
35
|
true
|
37
36
|
end
|
38
37
|
|
39
38
|
private def exec_queries(&block)
|
40
|
-
ArCache.
|
39
|
+
ArCache.skip_cache? ? super : ArCache::Query.new(self).exec_queries(&block).freeze
|
41
40
|
end
|
42
41
|
end
|
43
42
|
end
|