ar_cache 1.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 +7 -0
- data/.github/workflows/main.yml +68 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +56 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +7 -0
- data/Gemfile.common +17 -0
- data/Gemfile.lock +192 -0
- data/LICENSE.txt +21 -0
- data/README.md +166 -0
- data/Rakefile +28 -0
- data/ar_cache.gemspec +34 -0
- data/bin/activerecord-test +20 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/gemfiles/rails-6-1 +3 -0
- data/gemfiles/rails-edge +3 -0
- data/lib/ar_cache.rb +22 -0
- data/lib/ar_cache/active_record.rb +36 -0
- data/lib/ar_cache/active_record/associations/has_one_through_association.rb +39 -0
- data/lib/ar_cache/active_record/associations/singular_association.rb +18 -0
- data/lib/ar_cache/active_record/connection_adapters/abstract/database_statements.rb +76 -0
- data/lib/ar_cache/active_record/connection_adapters/abstract/transaction.rb +90 -0
- data/lib/ar_cache/active_record/core.rb +21 -0
- data/lib/ar_cache/active_record/insert_all.rb +17 -0
- data/lib/ar_cache/active_record/model_schema.rb +27 -0
- data/lib/ar_cache/active_record/persistence.rb +23 -0
- data/lib/ar_cache/active_record/relation.rb +48 -0
- data/lib/ar_cache/configuration.rb +59 -0
- data/lib/ar_cache/log_subscriber.rb +8 -0
- data/lib/ar_cache/marshal.rb +81 -0
- data/lib/ar_cache/mock_table.rb +55 -0
- data/lib/ar_cache/query.rb +95 -0
- data/lib/ar_cache/record.rb +54 -0
- data/lib/ar_cache/store.rb +38 -0
- data/lib/ar_cache/table.rb +126 -0
- data/lib/ar_cache/version.rb +5 -0
- data/lib/ar_cache/where_clause.rb +151 -0
- data/lib/generators/ar_cache/install_generator.rb +22 -0
- data/lib/generators/ar_cache/templates/configuration.rb +34 -0
- data/lib/generators/ar_cache/templates/migrate/create_ar_cache_records.rb.tt +16 -0
- metadata +107 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArCache
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module ConnectionAdapters
|
|
6
|
+
module NullTransaction
|
|
7
|
+
def add_ar_cache_keys(keys, delay: false) # rubocop:disable Lint/UnusedMethodArgument
|
|
8
|
+
ArCache::Store.delete_multi(keys)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_ar_cache_table(table, delay: false) # rubocop:disable Lint/UnusedMethodArgument
|
|
12
|
+
table.update_version
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_changed_table(_); end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module Transaction
|
|
19
|
+
include NullTransaction
|
|
20
|
+
|
|
21
|
+
def initialize(...)
|
|
22
|
+
super
|
|
23
|
+
@ar_cache_keys = []
|
|
24
|
+
@ar_cache_tables = []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add_ar_cache_keys(keys, delay: false)
|
|
28
|
+
super if !delay && read_uncommitted?
|
|
29
|
+
@ar_cache_keys.push(*keys)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def add_ar_cache_table(table, delay: false)
|
|
33
|
+
add_changed_table(table.name) unless delay
|
|
34
|
+
|
|
35
|
+
super if !delay && read_uncommitted?
|
|
36
|
+
@ar_cache_tables.push(table)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def add_changed_table(table_name)
|
|
40
|
+
connection.transaction_manager.add_changed_table(table_name)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# FIXME: Cache update and transaction commit may cause dirty reads during this period!
|
|
44
|
+
def commit
|
|
45
|
+
super
|
|
46
|
+
ensure
|
|
47
|
+
if @run_commit_callbacks
|
|
48
|
+
@ar_cache_tables.uniq(&:name).each(&:update_version) if @ar_cache_tables.any?
|
|
49
|
+
ArCache::Store.delete_multi(@ar_cache_keys.uniq) if @ar_cache_keys.any?
|
|
50
|
+
else
|
|
51
|
+
transaction = connection.current_transaction
|
|
52
|
+
@ar_cache_tables.each { |table| transaction.add_ar_cache_table(table, delay: true) }
|
|
53
|
+
transaction.add_ar_cache_keys(@ar_cache_keys, delay: true)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def read_uncommitted?
|
|
58
|
+
ArCache::Configuration.read_uncommitted ||
|
|
59
|
+
isolation_level == :read_uncommitted ||
|
|
60
|
+
!connection.transaction_manager.fully_joinable?
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module TransactionManager
|
|
65
|
+
def initialize(...)
|
|
66
|
+
super
|
|
67
|
+
@changed_tables = {}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def add_changed_table(table_name)
|
|
71
|
+
@changed_tables[table_name] = true if fully_joinable?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def changed_table?(table_name)
|
|
75
|
+
@changed_tables.key?(table_name)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def fully_joinable?
|
|
79
|
+
@stack.all?(&:joinable?)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def within_new_transaction(...)
|
|
83
|
+
super
|
|
84
|
+
ensure
|
|
85
|
+
@changed_tables = {} if @stack.count(&:joinable?).zero?
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArCache
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Core
|
|
6
|
+
module ClassMethods
|
|
7
|
+
delegate :skip_ar_cache, to: :all
|
|
8
|
+
|
|
9
|
+
# The #find use statement cache execute querying first, so need force skip.
|
|
10
|
+
def find(...)
|
|
11
|
+
ar_cache_table.enabled? ? all.find(...) : super
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# The #find_by use statement cache execute querying first, so need force skip.
|
|
15
|
+
def find_by(...)
|
|
16
|
+
ar_cache_table.enabled? ? all.find_by(...) : super
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArCache
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module InsertAll
|
|
6
|
+
def execute
|
|
7
|
+
super.tap do
|
|
8
|
+
if on_duplicate == :update
|
|
9
|
+
connection.current_transaction.add_ar_cache_table(model.ar_cache_table)
|
|
10
|
+
else
|
|
11
|
+
connection.transaction_manager.add_changed_table(model.table_name)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArCache
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module ModelSchema
|
|
6
|
+
module ClassMethods
|
|
7
|
+
def table_name=(...)
|
|
8
|
+
super.tap { remove_instance_variable(:@ar_cache_table) if defined?(@ar_cache_table) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def ar_cache_table
|
|
12
|
+
@ar_cache_table ||= begin
|
|
13
|
+
if abstract_class? || self == ArCache::Record
|
|
14
|
+
ArCache::MockTable
|
|
15
|
+
else
|
|
16
|
+
ArCache::Table.new(table_name)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ar_cache_table
|
|
23
|
+
self.class.ar_cache_table
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArCache
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Persistence
|
|
6
|
+
def reload(options = nil)
|
|
7
|
+
self.class.connection.clear_query_cache
|
|
8
|
+
|
|
9
|
+
fresh_object =
|
|
10
|
+
if options && options[:lock]
|
|
11
|
+
self.class.unscoped { self.class.skip_ar_cache.lock(options[:lock]).find(id) }
|
|
12
|
+
else
|
|
13
|
+
self.class.unscoped { self.class.skip_ar_cache.find(id) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@attributes = fresh_object.instance_variable_get(:@attributes)
|
|
17
|
+
@new_record = false
|
|
18
|
+
@previously_new_record = false
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArCache
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Relation
|
|
6
|
+
def skip_ar_cache
|
|
7
|
+
tap { @skip_ar_cache = true }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def explain
|
|
11
|
+
@skip_ar_cache = true
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private def exec_queries(&block) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
16
|
+
skip_query_cache_if_necessary do
|
|
17
|
+
records =
|
|
18
|
+
if where_clause.contradiction?
|
|
19
|
+
[]
|
|
20
|
+
elsif eager_loading?
|
|
21
|
+
apply_join_dependency do |relation, join_dependency|
|
|
22
|
+
if relation.null_relation?
|
|
23
|
+
[]
|
|
24
|
+
else
|
|
25
|
+
relation = join_dependency.apply_column_aliases(relation)
|
|
26
|
+
rows = connection.select_all(relation.arel, 'SQL')
|
|
27
|
+
join_dependency.instantiate(rows, strict_loading_value, &block)
|
|
28
|
+
end.freeze
|
|
29
|
+
end
|
|
30
|
+
elsif @skip_ar_cache ||
|
|
31
|
+
klass.ar_cache_table.disabled? ||
|
|
32
|
+
connection.transaction_manager.changed_table?(table_name)
|
|
33
|
+
klass.find_by_sql(arel, &block).freeze
|
|
34
|
+
else
|
|
35
|
+
ArCache::Query.new(self).exec_queries(&block).freeze
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
preload_associations(records) unless skip_preloading_value
|
|
39
|
+
|
|
40
|
+
records.each(&:readonly!) if readonly_value
|
|
41
|
+
records.each(&:strict_loading!) if strict_loading_value
|
|
42
|
+
|
|
43
|
+
records
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module ArCache
|
|
6
|
+
class Configuration
|
|
7
|
+
singleton_class.attr_accessor :disabled, :select_disabled, :expires_in, :read_uncommitted, :index_column_max_size
|
|
8
|
+
singleton_class.attr_reader :cache_store, :tables_options, :coder
|
|
9
|
+
|
|
10
|
+
def self.configure
|
|
11
|
+
block_given? ? yield(self) : self
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.cache_store=(cache_store)
|
|
15
|
+
unless cache_store.is_a?(ActiveSupport::Cache::Store)
|
|
16
|
+
raise ArgumentError, 'The cache_store must be an ActiveSupport::Cache::Store object'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@cache_store = cache_store
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.tables_options=(options)
|
|
23
|
+
options.each do |name, hash|
|
|
24
|
+
raise ArgumentError, "The #{name.inspect} must be converted to Symbol type" unless name.is_a?(Symbol)
|
|
25
|
+
|
|
26
|
+
hash.each_key do |k|
|
|
27
|
+
raise ArgumentError, "The #{k.inspect} must be converted to Symbol type" unless k.is_a?(Symbol)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@tables_options = options
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.coder=(coder)
|
|
35
|
+
raise ArgumentError, 'The coder only support use YAML or JSON' unless [::YAML, ::JSON].include?(coder)
|
|
36
|
+
|
|
37
|
+
@coder = coder
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.get_table_options(name)
|
|
41
|
+
options = tables_options[name.to_sym] || {}
|
|
42
|
+
options[:disabled] = disabled unless options.key?(:disabled)
|
|
43
|
+
options[:select_disabled] = select_disabled unless options.key?(:select_disabled)
|
|
44
|
+
options[:ignored_columns] = Array(options[:ignored_columns]).map(&:to_s)
|
|
45
|
+
options[:unique_indexes] = Array(options[:unique_indexes]).map { |index| Array(index).map(&:to_s).uniq }.uniq
|
|
46
|
+
options
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The set default values
|
|
50
|
+
@cache_store = defined?(Rails) ? Rails.cache : ActiveSupport::Cache::MemoryStore.new
|
|
51
|
+
@tables_options = {}
|
|
52
|
+
@coder = ::YAML
|
|
53
|
+
@disabled = false
|
|
54
|
+
@select_disabled = true
|
|
55
|
+
@expires_in = 604_800 # 1 week
|
|
56
|
+
@read_uncommitted = false
|
|
57
|
+
@index_column_max_size = 64
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArCache
|
|
4
|
+
module Marshal
|
|
5
|
+
def delete(*ids)
|
|
6
|
+
return -1 if disabled?
|
|
7
|
+
|
|
8
|
+
ArCache::Store.delete_multi(ids.map { |id| primary_cache_key(id) })
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# WARNING:
|
|
12
|
+
# In order to ensure that the written data is consistent with the database,
|
|
13
|
+
# only the record from the query can be written.
|
|
14
|
+
def write(records)
|
|
15
|
+
return -1 if disabled?
|
|
16
|
+
|
|
17
|
+
cache_hash = {}
|
|
18
|
+
records.each do |record|
|
|
19
|
+
attributes = record.attributes_before_type_cast
|
|
20
|
+
key = nil
|
|
21
|
+
|
|
22
|
+
unique_indexes.each_with_index do |index, i|
|
|
23
|
+
if i.zero? # is primary key
|
|
24
|
+
key = primary_cache_key(attributes[primary_key])
|
|
25
|
+
cache_hash[key] = attributes
|
|
26
|
+
else
|
|
27
|
+
cache_hash[cache_key(attributes, index)] = key
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
ArCache::Store.write_multi(cache_hash)
|
|
33
|
+
rescue Encoding::UndefinedConversionError
|
|
34
|
+
0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def read(where_clause, select_values, &block)
|
|
38
|
+
entries_hash = ArCache::Store.read_multi(where_clause.cache_hash.keys)
|
|
39
|
+
where_clause.cache_hash.each_key { |k| where_clause.add_missed_values(k) unless entries_hash.key?(k) }
|
|
40
|
+
|
|
41
|
+
records = []
|
|
42
|
+
|
|
43
|
+
entries_hash.each do |k, entry|
|
|
44
|
+
entry = entry.slice(*select_values) if select_values
|
|
45
|
+
wrong_key = detect_wrong_key(entry, where_clause.to_h)
|
|
46
|
+
|
|
47
|
+
if wrong_key
|
|
48
|
+
where_clause.add_missed_values(k)
|
|
49
|
+
where_clause.add_invalid_keys(k) if column_indexes.include?(wrong_key)
|
|
50
|
+
else
|
|
51
|
+
records << instantiate(where_clause.klass, entry, &block)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
where_clause.delete_invalid_keys
|
|
56
|
+
|
|
57
|
+
records
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private def detect_wrong_key(entry, where_values_hash)
|
|
61
|
+
where_values_hash.detect do |k, v|
|
|
62
|
+
value = entry[k]
|
|
63
|
+
next if value.nil?
|
|
64
|
+
|
|
65
|
+
if v.is_a?(Array)
|
|
66
|
+
return k unless v.include?(value)
|
|
67
|
+
else
|
|
68
|
+
return k unless v == value
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private def instantiate(klass, attributes, &block)
|
|
74
|
+
attributes.except!(*klass.ignored_columns) if klass.ignored_columns.any?
|
|
75
|
+
|
|
76
|
+
return klass.instantiate(attributes, &block) if attributes.key?(klass.inheritance_column)
|
|
77
|
+
|
|
78
|
+
klass.send(:instantiate_instance_of, klass, attributes, &block)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArCache
|
|
4
|
+
class MockTable
|
|
5
|
+
class << self
|
|
6
|
+
def disabled?
|
|
7
|
+
true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def enabled?
|
|
11
|
+
false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def select_disabled?
|
|
15
|
+
true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def select_enabled?
|
|
19
|
+
false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def version
|
|
23
|
+
-1
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def update_version(...)
|
|
27
|
+
-1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def primary_key
|
|
31
|
+
''
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def primary_cache_key(...)
|
|
35
|
+
''
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def cache_key(...)
|
|
39
|
+
''
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def write(...)
|
|
43
|
+
-1
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def delete(...)
|
|
47
|
+
-1
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def read(...)
|
|
51
|
+
[]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArCache
|
|
4
|
+
class Query
|
|
5
|
+
attr_reader :relation, :table, :where_clause
|
|
6
|
+
|
|
7
|
+
def initialize(relation)
|
|
8
|
+
@relation = relation
|
|
9
|
+
@table = @relation.klass.ar_cache_table
|
|
10
|
+
@where_clause = ArCache::WhereClause.new(@relation.klass, @relation.where_clause.send(:predicates))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def exec_queries(&block)
|
|
14
|
+
return relation.skip_ar_cache.send(:exec_queries, &block) unless exec_queries_cacheable?
|
|
15
|
+
|
|
16
|
+
records = table.read(where_clause, @select_values, &block)
|
|
17
|
+
|
|
18
|
+
missed_relation = if records.empty?
|
|
19
|
+
relation
|
|
20
|
+
elsif where_clause.missed_hash.any?
|
|
21
|
+
relation.rewhere(where_clause.missed_hash)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if missed_relation
|
|
25
|
+
if table.ignored_columns.any? && relation.select_values.any?
|
|
26
|
+
missed_relation = missed_relation.reselect(table.column_names)
|
|
27
|
+
end
|
|
28
|
+
records += relation.find_by_sql(missed_relation.arel, &block).tap { |rs| table.write(rs) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
records_order(records)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private def exec_queries_cacheable? # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
|
35
|
+
return false if relation.skip_query_cache_value
|
|
36
|
+
return false if relation.lock_value
|
|
37
|
+
return false if relation.group_values.any?
|
|
38
|
+
return false if relation.joins_values.any?
|
|
39
|
+
return false if relation.left_outer_joins_values.any?
|
|
40
|
+
return false if relation.offset_value
|
|
41
|
+
return false unless relation.from_clause.empty?
|
|
42
|
+
return false unless where_clause.cacheable?
|
|
43
|
+
return false unless select_values_cacheable?
|
|
44
|
+
return false unless order_values_cacheable?
|
|
45
|
+
return false unless limit_value_cacheable?
|
|
46
|
+
|
|
47
|
+
true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private def select_values_cacheable?
|
|
51
|
+
return true if relation.select_values.empty?
|
|
52
|
+
return false if table.select_disabled?
|
|
53
|
+
|
|
54
|
+
@select_values = relation.select_values.map(&:to_s)
|
|
55
|
+
(@select_values - table.column_names).empty?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private def order_values_cacheable?
|
|
59
|
+
return true if where_clause.single?
|
|
60
|
+
|
|
61
|
+
size = relation.order_values.size
|
|
62
|
+
return true if size.zero?
|
|
63
|
+
return false if size > 1
|
|
64
|
+
|
|
65
|
+
first_order_value = relation.order_values.first
|
|
66
|
+
case first_order_value
|
|
67
|
+
when Arel::Nodes::Ordering
|
|
68
|
+
@order_name = first_order_value.expr.name
|
|
69
|
+
@order_desc = first_order_value.descending?
|
|
70
|
+
when String
|
|
71
|
+
@order_name, @order_desc = first_order_value.downcase.split
|
|
72
|
+
return false unless table.column_names.include?(@order_name)
|
|
73
|
+
|
|
74
|
+
@order_desc = @order_desc == 'desc'
|
|
75
|
+
else
|
|
76
|
+
return false
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private def limit_value_cacheable?
|
|
83
|
+
where_clause.single? || relation.limit_value.nil?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private def records_order(records)
|
|
87
|
+
return records if records.size < 2
|
|
88
|
+
|
|
89
|
+
method = "#{@order_name || table.primary_key}_for_database"
|
|
90
|
+
return records.sort! { |a, b| b.send(method) <=> a.send(method) } if @order_desc
|
|
91
|
+
|
|
92
|
+
records.sort! { |a, b| a.send(method) <=> b.send(method) }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|