ar_cache 1.1.0 → 2.0.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 +0 -10
- data/Gemfile.common +2 -0
- data/Gemfile.lock +89 -80
- data/README.md +20 -6
- data/README.zh-CN.md +165 -0
- data/ar_cache.gemspec +1 -0
- data/lib/ar_cache.rb +42 -15
- data/lib/ar_cache/active_record/associations/has_one_through_association.rb +10 -7
- data/lib/ar_cache/active_record/associations/singular_association.rb +2 -4
- 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 +4 -5
- 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 +3 -4
- data/lib/ar_cache/active_record/relation.rb +7 -14
- data/lib/ar_cache/configuration.rb +34 -42
- data/lib/ar_cache/marshal.rb +28 -26
- data/lib/ar_cache/mock_table.rb +4 -12
- data/lib/ar_cache/query.rb +25 -15
- data/lib/ar_cache/table.rb +45 -60
- 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 +20 -25
- metadata +23 -5
- data/lib/ar_cache/record.rb +0 -54
- 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,59 @@ 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
|
25
27
|
|
26
28
|
def skip_cache?
|
27
29
|
Thread.current[:ar_cache_skip_cache]
|
28
30
|
end
|
29
31
|
|
30
32
|
def skip_cache
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
42
|
+
|
43
|
+
def skip_expire?
|
44
|
+
Thread.current[:ar_cache_skip_expire]
|
45
|
+
end
|
46
|
+
|
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
|
-
yield
|
44
|
-
ensure
|
45
|
-
Thread.current[:ar_cache_pre_expire] = false
|
71
|
+
def load_attributes(attributes)
|
72
|
+
memcached? || redis? ? Oj.load(attributes) : attributes
|
46
73
|
end
|
47
74
|
end
|
48
75
|
end
|
@@ -4,18 +4,21 @@ module ArCache
|
|
4
4
|
module ActiveRecord
|
5
5
|
module Associations
|
6
6
|
module HasOneThroughAssociation
|
7
|
+
PRELOADER = ::ActiveRecord::Associations::Preloader.new
|
8
|
+
|
7
9
|
private def find_target
|
8
|
-
return super if
|
9
|
-
return super
|
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
|
10
15
|
|
11
16
|
if (owner.strict_loading? || reflection.strict_loading?) && owner.validation_context.nil?
|
12
|
-
Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
|
17
|
+
::ActiveRecord::Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
|
13
18
|
end
|
14
19
|
|
15
|
-
|
16
|
-
|
17
|
-
rescue StandardError
|
18
|
-
super
|
20
|
+
PRELOADER.preload(owner, reflection.name)
|
21
|
+
target
|
19
22
|
end
|
20
23
|
end
|
21
24
|
end
|
@@ -5,10 +5,8 @@ module ArCache
|
|
5
5
|
module Associations
|
6
6
|
module SingularAssociation
|
7
7
|
private def skip_statement_cache?(...)
|
8
|
-
|
9
|
-
|
10
|
-
return true if is_a?(::ActiveRecord::Associations::BelongsToPolymorphicAssociation)
|
11
|
-
return true if reflection.klass.ar_cache_table.enabled?
|
8
|
+
return super if ArCache.skip_cache?
|
9
|
+
return true if ArCache.cache_reflection?(reflection) { ArCache::Query.new(scope).exec_queries_cacheable? }
|
12
10
|
|
13
11
|
super
|
14
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.write(k, ArCache::PLACEHOLDER, raw: true, expires_in: 1.day) }
|
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,16 +4,15 @@ module ArCache
|
|
4
4
|
module ActiveRecord
|
5
5
|
module Core
|
6
6
|
module ClassMethods
|
7
|
-
|
7
|
+
# The #find and #find_by use ActiveRecord::StatementCache to execute querying first.
|
8
|
+
# For ArCache, we need force skip ActiveRecord::StatementCache.
|
8
9
|
|
9
|
-
# The #find use statement cache execute querying first, so need force skip.
|
10
10
|
def find(...)
|
11
|
-
ar_cache_table.
|
11
|
+
ArCache.skip_cache? || ar_cache_table.disabled? ? super : all.find(...)
|
12
12
|
end
|
13
13
|
|
14
|
-
# The #find_by use statement cache execute querying first, so need force skip.
|
15
14
|
def find_by(...)
|
16
|
-
ar_cache_table.
|
15
|
+
ArCache.skip_cache? || ar_cache_table.disabled? ? super : all.find_by(...)
|
17
16
|
end
|
18
17
|
end
|
19
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,8 +20,7 @@ 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
|
|
@@ -4,28 +4,22 @@ module ArCache
|
|
4
4
|
module ActiveRecord
|
5
5
|
module Relation
|
6
6
|
def reload
|
7
|
-
|
8
|
-
super
|
9
|
-
end
|
10
|
-
|
11
|
-
def skip_ar_cache
|
12
|
-
tap { @skip_ar_cache = true }
|
7
|
+
loaded? ? ArCache.skip_cache { super } : super
|
13
8
|
end
|
14
9
|
|
15
10
|
def explain
|
16
|
-
|
17
|
-
super
|
11
|
+
ArCache.skip_cache { super }
|
18
12
|
end
|
19
13
|
|
20
14
|
def update_all(...)
|
21
|
-
ArCache.
|
15
|
+
ArCache.skip_expire { delete_ar_cache_primary_keys ? super : 0 }
|
22
16
|
end
|
23
17
|
|
24
18
|
def delete_all
|
25
|
-
ArCache.
|
19
|
+
ArCache.skip_expire { delete_ar_cache_primary_keys ? super : 0 }
|
26
20
|
end
|
27
21
|
|
28
|
-
private def
|
22
|
+
private def delete_ar_cache_primary_keys
|
29
23
|
return true if klass.ar_cache_table.disabled?
|
30
24
|
|
31
25
|
where_clause = ArCache::WhereClause.new(klass, arel.constraints)
|
@@ -37,13 +31,12 @@ module ArCache
|
|
37
31
|
|
38
32
|
return false if keys.empty?
|
39
33
|
|
40
|
-
@klass.connection.current_transaction.
|
41
|
-
@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)
|
42
35
|
true
|
43
36
|
end
|
44
37
|
|
45
38
|
private def exec_queries(&block)
|
46
|
-
|
39
|
+
ArCache.skip_cache? ? super : ArCache::Query.new(self).exec_queries(&block).freeze
|
47
40
|
end
|
48
41
|
end
|
49
42
|
end
|