nkallen-cache-money 0.2.1
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.
- data/README +1 -0
- data/TODO +20 -0
- data/UNSUPPORTED_FEATURES +14 -0
- data/config/environment.rb +6 -0
- data/config/memcache.yml +6 -0
- data/db/schema.rb +11 -0
- data/lib/cash.rb +53 -0
- data/lib/cash/accessor.rb +78 -0
- data/lib/cash/buffered.rb +126 -0
- data/lib/cash/config.rb +64 -0
- data/lib/cash/finders.rb +40 -0
- data/lib/cash/index.rb +207 -0
- data/lib/cash/local.rb +59 -0
- data/lib/cash/lock.rb +52 -0
- data/lib/cash/mock.rb +86 -0
- data/lib/cash/query/abstract.rb +162 -0
- data/lib/cash/query/calculation.rb +45 -0
- data/lib/cash/query/primary_key.rb +51 -0
- data/lib/cash/query/select.rb +16 -0
- data/lib/cash/transactional.rb +42 -0
- data/lib/cash/util/array.rb +9 -0
- data/lib/cash/write_through.rb +72 -0
- data/spec/cash/accessor_spec.rb +133 -0
- data/spec/cash/active_record_spec.rb +190 -0
- data/spec/cash/calculations_spec.rb +67 -0
- data/spec/cash/finders_spec.rb +343 -0
- data/spec/cash/lock_spec.rb +87 -0
- data/spec/cash/order_spec.rb +166 -0
- data/spec/cash/transactional_spec.rb +574 -0
- data/spec/cash/window_spec.rb +195 -0
- data/spec/cash/write_through_spec.rb +223 -0
- data/spec/spec_helper.rb +55 -0
- metadata +100 -0
data/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
README.markdown
|
data/TODO
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
TOP PRIORITY
|
2
|
+
* cache_fu adapter
|
3
|
+
|
4
|
+
REFACTOR
|
5
|
+
* Reorganize transactional spec
|
6
|
+
* Clarify terminology around cache/key/index, etc.
|
7
|
+
|
8
|
+
INFRASTRUCTURE
|
9
|
+
|
10
|
+
NEW FEATURES
|
11
|
+
* transactional get multi isn't really multi
|
12
|
+
* add on "not stored" should yield
|
13
|
+
|
14
|
+
BUGS
|
15
|
+
* Handle append strategy (using add rather than set?) to avoid race condition
|
16
|
+
|
17
|
+
MISSING TESTS:
|
18
|
+
* missing tests for Klass.transaction do ... end
|
19
|
+
* non "id" pks work but lack test coverage
|
20
|
+
* expire_cache
|
@@ -0,0 +1,14 @@
|
|
1
|
+
* does not work with :dependent => nullify because
|
2
|
+
def nullify_has_many_dependencies(record, reflection_name, association_class, primary_key_name, dependent_conditions)
|
3
|
+
association_class.update_all("#{primary_key_name} = NULL", dependent_conditions)
|
4
|
+
end
|
5
|
+
This does not trigger callbacks
|
6
|
+
* update_all, delete, update_counter, increment_counter, decrement_counter, counter_caches in general - counter caches are replaced by this gem, bear that in mind.
|
7
|
+
* attr_readonly - no technical obstacle, just not yet supported
|
8
|
+
* attributes before typecast behave unpredictably - hard to support
|
9
|
+
* ActiveRecord::Rollback is unsupported - the exception gets swallowed so there isn't an opportunity to rollback the cache transaction - not hard to support
|
10
|
+
* Named bind variables :conditions => ["name = :name", { :name => "37signals!" }] - not hard to support
|
11
|
+
* printf style binds: :conditions => ["name = '%s'", "37signals!"] - not too hard to support
|
12
|
+
* objects as attributes that are serialized. story.title = {:foo => :bar}; customer.balance = Money.new(...) - these could be coerced using Column#type_cast?
|
13
|
+
|
14
|
+
With a lot of these features the issue is not technical but performance. Every special case costs some overhead.
|
data/config/memcache.yml
ADDED
data/db/schema.rb
ADDED
data/lib/cash.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'activesupport'
|
5
|
+
require 'activerecord'
|
6
|
+
|
7
|
+
require 'cash/lock'
|
8
|
+
require 'cash/transactional'
|
9
|
+
require 'cash/write_through'
|
10
|
+
require 'cash/finders'
|
11
|
+
require 'cash/buffered'
|
12
|
+
require 'cash/index'
|
13
|
+
require 'cash/config'
|
14
|
+
require 'cash/accessor'
|
15
|
+
|
16
|
+
require 'cash/request'
|
17
|
+
require 'cash/mock'
|
18
|
+
require 'cash/local'
|
19
|
+
|
20
|
+
require 'cash/query/abstract'
|
21
|
+
require 'cash/query/select'
|
22
|
+
require 'cash/query/primary_key'
|
23
|
+
require 'cash/query/calculation'
|
24
|
+
|
25
|
+
require 'cash/util/array'
|
26
|
+
|
27
|
+
class ActiveRecord::Base
|
28
|
+
def self.is_cached(options = {})
|
29
|
+
include Cash
|
30
|
+
Config.create(self, options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Cash
|
35
|
+
def self.included(active_record_class)
|
36
|
+
active_record_class.class_eval do
|
37
|
+
include Config, Accessor, WriteThrough, Finders
|
38
|
+
extend ClassMethods
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
def self.extended(active_record_class)
|
44
|
+
class << active_record_class
|
45
|
+
alias_method_chain :transaction, :cache_transaction
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def transaction_with_cache_transaction(&block)
|
50
|
+
repository.transaction { transaction_without_cache_transaction(&block) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Cash
|
2
|
+
module Accessor
|
3
|
+
def self.included(a_module)
|
4
|
+
a_module.module_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
include InstanceMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def fetch(keys, options = {}, &block)
|
12
|
+
case keys
|
13
|
+
when Array
|
14
|
+
keys = keys.collect { |key| cache_key(key) }
|
15
|
+
hits = repository.get_multi(keys)
|
16
|
+
if (missed_keys = keys - hits.keys).any?
|
17
|
+
missed_values = block.call(missed_keys)
|
18
|
+
hits.merge!(missed_keys.zip(Array(missed_values)).to_hash)
|
19
|
+
end
|
20
|
+
hits
|
21
|
+
else
|
22
|
+
repository.get(cache_key(keys), options[:raw]) || (block ? block.call : nil)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(keys, options = {}, &block)
|
27
|
+
case keys
|
28
|
+
when Array
|
29
|
+
fetch(keys, options, &block)
|
30
|
+
else
|
31
|
+
fetch(keys, options) do
|
32
|
+
if block_given?
|
33
|
+
add(keys, result = yield(keys), options)
|
34
|
+
result
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def add(key, value, options = {})
|
41
|
+
repository.add(cache_key(key), value, options[:ttl] || 0, options[:raw])
|
42
|
+
end
|
43
|
+
|
44
|
+
def set(key, value, options = {})
|
45
|
+
repository.set(cache_key(key), value, options[:ttl] || 0, options[:raw])
|
46
|
+
end
|
47
|
+
|
48
|
+
def incr(key, delta = 1, ttl = 0)
|
49
|
+
repository.incr(cache_key(key), delta) || begin
|
50
|
+
repository.add(cache_key(key), (result = yield).to_s, ttl, true)
|
51
|
+
result
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def decr(key, delta = 1, ttl = 0)
|
56
|
+
repository.decr(cache_key(key), delta) || begin
|
57
|
+
repository.add(cache_key(key), (result = yield).to_s, ttl, true)
|
58
|
+
result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def expire(key)
|
63
|
+
repository.delete(cache_key(key))
|
64
|
+
end
|
65
|
+
|
66
|
+
def cache_key(key)
|
67
|
+
"#{name}/#{key.to_s.gsub(' ', '+')}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module InstanceMethods
|
72
|
+
def expire
|
73
|
+
self.class.expire(id)
|
74
|
+
end
|
75
|
+
alias_method :expire_cache, :expire
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Cash
|
2
|
+
class Buffered
|
3
|
+
def self.push(cache, lock)
|
4
|
+
if cache.is_a?(Buffered)
|
5
|
+
cache.push
|
6
|
+
else
|
7
|
+
Buffered.new(cache, lock)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(memcache, lock)
|
12
|
+
@buffer = {}
|
13
|
+
@commands = []
|
14
|
+
@cache = memcache
|
15
|
+
@lock = lock
|
16
|
+
end
|
17
|
+
|
18
|
+
def pop
|
19
|
+
@cache
|
20
|
+
end
|
21
|
+
|
22
|
+
def push
|
23
|
+
NestedBuffered.new(self, @lock)
|
24
|
+
end
|
25
|
+
|
26
|
+
def get(key, *options)
|
27
|
+
if @buffer.has_key?(key)
|
28
|
+
@buffer[key]
|
29
|
+
else
|
30
|
+
@buffer[key] = @cache.get(key, *options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def set(key, value, *options)
|
35
|
+
@buffer[key] = value
|
36
|
+
buffer_command Command.new(:set, key, value, *options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def incr(key, amount = 1)
|
40
|
+
return unless value = get(key, true)
|
41
|
+
|
42
|
+
@buffer[key] = value.to_i + amount
|
43
|
+
buffer_command Command.new(:incr, key, amount)
|
44
|
+
@buffer[key]
|
45
|
+
end
|
46
|
+
|
47
|
+
def decr(key, amount = 1)
|
48
|
+
return unless value = get(key, true)
|
49
|
+
|
50
|
+
@buffer[key] = [value.to_i - amount, 0].max
|
51
|
+
buffer_command Command.new(:decr, key, amount)
|
52
|
+
@buffer[key]
|
53
|
+
end
|
54
|
+
|
55
|
+
def add(key, value, *options)
|
56
|
+
@buffer[key] = value
|
57
|
+
buffer_command Command.new(:add, key, value, *options)
|
58
|
+
end
|
59
|
+
|
60
|
+
def delete(key, *options)
|
61
|
+
@buffer[key] = nil
|
62
|
+
buffer_command Command.new(:delete, key, *options)
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_multi(keys)
|
66
|
+
values = keys.collect { |key| get(key) }
|
67
|
+
keys.zip(values).to_hash
|
68
|
+
end
|
69
|
+
|
70
|
+
def flush
|
71
|
+
sorted_keys = @commands.select(&:requires_lock?).collect(&:key).uniq.sort
|
72
|
+
sorted_keys.each do |key|
|
73
|
+
@lock.acquire_lock(key)
|
74
|
+
end
|
75
|
+
perform_commands
|
76
|
+
ensure
|
77
|
+
@buffer = {}
|
78
|
+
sorted_keys.each do |key|
|
79
|
+
@lock.release_lock(key)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def method_missing(method, *args, &block)
|
84
|
+
@cache.send(method, *args, &block)
|
85
|
+
end
|
86
|
+
|
87
|
+
def respond_to?(method)
|
88
|
+
@cache.respond_to?(method)
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
def perform_commands
|
93
|
+
@commands.each do |command|
|
94
|
+
command.call(@cache)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def buffer_command(command)
|
99
|
+
@commands << command
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class NestedBuffered < Buffered
|
104
|
+
def flush
|
105
|
+
perform_commands
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class Command
|
110
|
+
attr_accessor :key
|
111
|
+
|
112
|
+
def initialize(name, key, *args)
|
113
|
+
@name = name
|
114
|
+
@key = key
|
115
|
+
@args = args
|
116
|
+
end
|
117
|
+
|
118
|
+
def requires_lock?
|
119
|
+
@name == :set
|
120
|
+
end
|
121
|
+
|
122
|
+
def call(cache)
|
123
|
+
cache.send @name, @key, *@args
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/cash/config.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module Cash
|
2
|
+
module Config
|
3
|
+
def self.included(a_module)
|
4
|
+
a_module.module_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
delegate :repository, :to => "self.class"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def self.extended(a_class)
|
12
|
+
a_class.class_eval do
|
13
|
+
class << self
|
14
|
+
delegate :repository, :indices, :to => :@cache_config
|
15
|
+
alias_method_chain :inherited, :cache_config
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def inherited_with_cache_config(subclass)
|
21
|
+
inherited_without_cache_config(subclass)
|
22
|
+
@cache_config.inherit(subclass)
|
23
|
+
end
|
24
|
+
|
25
|
+
def index(attributes, options = {})
|
26
|
+
options.assert_valid_keys(:ttl, :order, :limit, :buffer)
|
27
|
+
(@cache_config.indices.unshift(Index.new(@cache_config, self, attributes, options))).uniq!
|
28
|
+
end
|
29
|
+
|
30
|
+
def cache_config=(config)
|
31
|
+
@cache_config = config
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Config
|
36
|
+
attr_reader :active_record, :options
|
37
|
+
|
38
|
+
def self.create(active_record, options, indices = [])
|
39
|
+
active_record.cache_config = new(active_record, options)
|
40
|
+
indices.each { |i| active_record.index i.attributes, i.options }
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(active_record, options = {})
|
44
|
+
@active_record, @options = active_record, options
|
45
|
+
end
|
46
|
+
|
47
|
+
def repository
|
48
|
+
@options[:repository]
|
49
|
+
end
|
50
|
+
|
51
|
+
def ttl
|
52
|
+
@options[:ttl]
|
53
|
+
end
|
54
|
+
|
55
|
+
def indices
|
56
|
+
@indices ||= active_record == ActiveRecord::Base ? [] : [Index.new(self, active_record, active_record.primary_key)]
|
57
|
+
end
|
58
|
+
|
59
|
+
def inherit(active_record)
|
60
|
+
self.class.create(active_record, @options, indices)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/cash/finders.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Cash
|
2
|
+
module Finders
|
3
|
+
def self.included(active_record_class)
|
4
|
+
active_record_class.class_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def self.extended(active_record_class)
|
11
|
+
active_record_class.class_eval do
|
12
|
+
class << self
|
13
|
+
alias_method_chain :find_every, :cache
|
14
|
+
alias_method_chain :find_from_ids, :cache
|
15
|
+
alias_method_chain :calculate, :cache
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def without_cache(&block)
|
21
|
+
User.with_scope(:find => {:readonly => true}, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
# User.find(:first, ...), User.find_by_foo(...), User.find(:all, ...), User.find_all_by_foo(...)
|
25
|
+
def find_every_with_cache(options)
|
26
|
+
Query::Select.perform(self, options, scope(:find))
|
27
|
+
end
|
28
|
+
|
29
|
+
# User.find(1), User.find(1, 2, 3), User.find([1, 2, 3]), User.find([])
|
30
|
+
def find_from_ids_with_cache(ids, options)
|
31
|
+
Query::PrimaryKey.perform(self, ids, options, scope(:find))
|
32
|
+
end
|
33
|
+
|
34
|
+
# User.count(:all), User.count, User.sum(...)
|
35
|
+
def calculate_with_cache(operation, column_name, options = {})
|
36
|
+
Query::Calculation.perform(self, operation, column_name, options, scope(:find))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/cash/index.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
module Cash
|
2
|
+
class Index
|
3
|
+
attr_reader :attributes, :options
|
4
|
+
delegate :each, :hash, :to => :@attributes
|
5
|
+
delegate :get, :set, :expire, :find_every_without_cache, :calculate_without_cache, :calculate_with_cache, :incr, :decr, :primary_key, :to => :@active_record
|
6
|
+
|
7
|
+
DEFAULT_OPTIONS = { :ttl => 1.day }
|
8
|
+
|
9
|
+
def initialize(config, active_record, attributes, options = {})
|
10
|
+
@config, @active_record, @attributes, @options = config, active_record, Array(attributes).collect(&:to_s).sort, DEFAULT_OPTIONS.merge(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
case other
|
15
|
+
when Index
|
16
|
+
attributes == other.attributes
|
17
|
+
else
|
18
|
+
attributes == Array(other)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
alias_method :eql?, :==
|
22
|
+
|
23
|
+
module Commands
|
24
|
+
def add(object)
|
25
|
+
clone = object.shallow_clone
|
26
|
+
_, new_attribute_value_pairs = old_and_new_attribute_value_pairs(object)
|
27
|
+
add_to_index_with_minimal_network_operations(new_attribute_value_pairs, clone)
|
28
|
+
end
|
29
|
+
|
30
|
+
def update(object)
|
31
|
+
clone = object.shallow_clone
|
32
|
+
old_attribute_value_pairs, new_attribute_value_pairs = old_and_new_attribute_value_pairs(object)
|
33
|
+
update_index_with_minimal_network_operations(old_attribute_value_pairs, new_attribute_value_pairs, clone)
|
34
|
+
end
|
35
|
+
|
36
|
+
def remove(object)
|
37
|
+
old_attribute_value_pairs, _ = old_and_new_attribute_value_pairs(object)
|
38
|
+
remove_from_index_with_minimal_network_operations(old_attribute_value_pairs, object)
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(object)
|
42
|
+
old_attribute_value_pairs, _ = old_and_new_attribute_value_pairs(object)
|
43
|
+
key = cache_key(old_attribute_value_pairs)
|
44
|
+
expire(key)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
include Commands
|
48
|
+
|
49
|
+
module Attributes
|
50
|
+
def ttl
|
51
|
+
@ttl ||= options[:ttl] || config.ttl
|
52
|
+
end
|
53
|
+
|
54
|
+
def order
|
55
|
+
@order ||= options[:order] || :asc
|
56
|
+
end
|
57
|
+
|
58
|
+
def limit
|
59
|
+
options[:limit]
|
60
|
+
end
|
61
|
+
|
62
|
+
def buffer
|
63
|
+
options[:buffer]
|
64
|
+
end
|
65
|
+
|
66
|
+
def window
|
67
|
+
options[:limit] && options[:limit] + options[:buffer]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
include Attributes
|
71
|
+
|
72
|
+
def serialize_object(object)
|
73
|
+
primary_key? ? object : object.id
|
74
|
+
end
|
75
|
+
|
76
|
+
def matches?(query)
|
77
|
+
query.calculation? ||
|
78
|
+
(query.order == ['id', order] &&
|
79
|
+
(!limit || (query.limit && query.limit + query.offset <= limit)))
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
def old_and_new_attribute_value_pairs(object)
|
84
|
+
old_attribute_value_pairs = []
|
85
|
+
new_attribute_value_pairs = []
|
86
|
+
@attributes.each do |name|
|
87
|
+
new_value = object.attributes[name]
|
88
|
+
original_value = object.send("#{name}_was")
|
89
|
+
old_attribute_value_pairs << [name, original_value]
|
90
|
+
new_attribute_value_pairs << [name, new_value]
|
91
|
+
end
|
92
|
+
[old_attribute_value_pairs, new_attribute_value_pairs]
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_to_index_with_minimal_network_operations(attribute_value_pairs, object)
|
96
|
+
if primary_key?
|
97
|
+
add_object_to_primary_key_cache(attribute_value_pairs, object)
|
98
|
+
else
|
99
|
+
add_object_to_cache(attribute_value_pairs, object)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def primary_key?
|
104
|
+
@attributes.size == 1 && @attributes.first == primary_key
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_object_to_primary_key_cache(attribute_value_pairs, object)
|
108
|
+
set(cache_key(attribute_value_pairs), [object], :ttl => ttl)
|
109
|
+
end
|
110
|
+
|
111
|
+
def cache_key(attribute_value_pairs)
|
112
|
+
attribute_value_pairs.flatten.join('/')
|
113
|
+
end
|
114
|
+
|
115
|
+
def add_object_to_cache(attribute_value_pairs, object, overwrite = true)
|
116
|
+
return if invalid_cache_key?(attribute_value_pairs)
|
117
|
+
|
118
|
+
key, cache_value, cache_hit = get_key_and_value_at_index(attribute_value_pairs)
|
119
|
+
if !cache_hit || overwrite
|
120
|
+
object_to_add = serialize_object(object)
|
121
|
+
objects = (cache_value + [object_to_add]).sort do |a, b|
|
122
|
+
(a <=> b) * (order == :asc ? 1 : -1)
|
123
|
+
end.uniq
|
124
|
+
objects = truncate_if_necessary(objects)
|
125
|
+
set(key, objects, :ttl => ttl)
|
126
|
+
incr("#{key}/count") { calculate_at_index(:count, attribute_value_pairs) }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def invalid_cache_key?(attribute_value_pairs)
|
131
|
+
attribute_value_pairs.collect { |_,value| value }.any? { |x| x.nil? }
|
132
|
+
end
|
133
|
+
|
134
|
+
def get_key_and_value_at_index(attribute_value_pairs)
|
135
|
+
key = cache_key(attribute_value_pairs)
|
136
|
+
cache_hit = true
|
137
|
+
cache_value = get(key) do
|
138
|
+
cache_hit = false
|
139
|
+
conditions = attribute_value_pairs.to_hash
|
140
|
+
find_every_without_cache(:select => primary_key, :conditions => conditions, :limit => window).collect do |object|
|
141
|
+
serialize_object(object)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
[key, cache_value, cache_hit]
|
145
|
+
end
|
146
|
+
|
147
|
+
def truncate_if_necessary(objects)
|
148
|
+
objects.slice(0, window || objects.size)
|
149
|
+
end
|
150
|
+
|
151
|
+
def calculate_at_index(operation, attribute_value_pairs)
|
152
|
+
conditions = attribute_value_pairs.to_hash
|
153
|
+
calculate_without_cache(operation, :all, :conditions => conditions)
|
154
|
+
end
|
155
|
+
|
156
|
+
def update_index_with_minimal_network_operations(old_attribute_value_pairs, new_attribute_value_pairs, object)
|
157
|
+
if index_is_stale?(old_attribute_value_pairs, new_attribute_value_pairs)
|
158
|
+
remove_object_from_cache(old_attribute_value_pairs, object)
|
159
|
+
add_object_to_cache(new_attribute_value_pairs, object)
|
160
|
+
elsif primary_key?
|
161
|
+
add_object_to_primary_key_cache(new_attribute_value_pairs, object)
|
162
|
+
else
|
163
|
+
add_object_to_cache(new_attribute_value_pairs, object, false)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def index_is_stale?(old_attribute_value_pairs, new_attribute_value_pairs)
|
168
|
+
old_attribute_value_pairs != new_attribute_value_pairs
|
169
|
+
end
|
170
|
+
|
171
|
+
def remove_from_index_with_minimal_network_operations(attribute_value_pairs, object)
|
172
|
+
if primary_key?
|
173
|
+
remove_object_from_primary_key_cache(attribute_value_pairs, object)
|
174
|
+
else
|
175
|
+
remove_object_from_cache(attribute_value_pairs, object)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def remove_object_from_primary_key_cache(attribute_value_pairs, object)
|
180
|
+
set(cache_key(attribute_value_pairs), [], :ttl => ttl)
|
181
|
+
end
|
182
|
+
|
183
|
+
def remove_object_from_cache(attribute_value_pairs, object)
|
184
|
+
return if invalid_cache_key?(attribute_value_pairs)
|
185
|
+
|
186
|
+
key, cache_value, _ = get_key_and_value_at_index(attribute_value_pairs)
|
187
|
+
object_to_remove = serialize_object(object)
|
188
|
+
objects = cache_value - [object_to_remove]
|
189
|
+
objects = resize_if_necessary(attribute_value_pairs, objects)
|
190
|
+
set(key, objects, :ttl => ttl)
|
191
|
+
end
|
192
|
+
|
193
|
+
def resize_if_necessary(attribute_value_pairs, objects)
|
194
|
+
conditions = attribute_value_pairs.to_hash
|
195
|
+
key = cache_key(attribute_value_pairs)
|
196
|
+
count = decr("#{key}/count") { calculate_at_index(:count, attribute_value_pairs) }
|
197
|
+
|
198
|
+
if limit && objects.size < limit && objects.size < count
|
199
|
+
find_every_without_cache(:select => :id, :conditions => conditions).collect do |object|
|
200
|
+
serialize_object(object)
|
201
|
+
end
|
202
|
+
else
|
203
|
+
objects
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|