sreeix-cache-money 0.2.24.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.
Files changed (49) hide show
  1. data/LICENSE +201 -0
  2. data/README +210 -0
  3. data/README.markdown +210 -0
  4. data/TODO +17 -0
  5. data/UNSUPPORTED_FEATURES +13 -0
  6. data/config/environment.rb +16 -0
  7. data/config/memcached.yml +6 -0
  8. data/db/schema.rb +18 -0
  9. data/init.rb +1 -0
  10. data/lib/cache_money.rb +91 -0
  11. data/lib/cash/accessor.rb +83 -0
  12. data/lib/cash/buffered.rb +129 -0
  13. data/lib/cash/config.rb +75 -0
  14. data/lib/cash/fake.rb +83 -0
  15. data/lib/cash/finders.rb +38 -0
  16. data/lib/cash/index.rb +214 -0
  17. data/lib/cash/local.rb +76 -0
  18. data/lib/cash/lock.rb +63 -0
  19. data/lib/cash/mock.rb +154 -0
  20. data/lib/cash/query/abstract.rb +210 -0
  21. data/lib/cash/query/calculation.rb +45 -0
  22. data/lib/cash/query/primary_key.rb +50 -0
  23. data/lib/cash/query/select.rb +16 -0
  24. data/lib/cash/request.rb +3 -0
  25. data/lib/cash/transactional.rb +43 -0
  26. data/lib/cash/util/array.rb +9 -0
  27. data/lib/cash/util/marshal.rb +19 -0
  28. data/lib/cash/write_through.rb +69 -0
  29. data/lib/mem_cached_session_store.rb +49 -0
  30. data/lib/mem_cached_support_store.rb +135 -0
  31. data/lib/memcached_wrapper.rb +263 -0
  32. data/rails/init.rb +38 -0
  33. data/spec/cash/accessor_spec.rb +159 -0
  34. data/spec/cash/active_record_spec.rb +224 -0
  35. data/spec/cash/buffered_spec.rb +9 -0
  36. data/spec/cash/calculations_spec.rb +78 -0
  37. data/spec/cash/finders_spec.rb +430 -0
  38. data/spec/cash/local_buffer_spec.rb +9 -0
  39. data/spec/cash/local_spec.rb +9 -0
  40. data/spec/cash/lock_spec.rb +108 -0
  41. data/spec/cash/marshal_spec.rb +60 -0
  42. data/spec/cash/order_spec.rb +138 -0
  43. data/spec/cash/shared.rb +62 -0
  44. data/spec/cash/transactional_spec.rb +578 -0
  45. data/spec/cash/window_spec.rb +195 -0
  46. data/spec/cash/write_through_spec.rb +245 -0
  47. data/spec/memcached_wrapper_test.rb +209 -0
  48. data/spec/spec_helper.rb +60 -0
  49. metadata +151 -0
data/db/schema.rb ADDED
@@ -0,0 +1,18 @@
1
+ ActiveRecord::Schema.define(:version => 2) do
2
+ create_table "stories", :force => true do |t|
3
+ t.string "title", "subtitle"
4
+ t.string "type"
5
+ t.boolean "published"
6
+ end
7
+
8
+ create_table "characters", :force => true do |t|
9
+ t.integer "story_id"
10
+ t.string "name"
11
+ end
12
+
13
+ create_table :sessions, :force => true do |t|
14
+ t.string :session_id
15
+ t.text :data
16
+ t.timestamps
17
+ end
18
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.basedir(__FILE__),'rails','init')
@@ -0,0 +1,91 @@
1
+ require 'active_support'
2
+ require 'active_record'
3
+
4
+ require 'cash/lock'
5
+ require 'cash/transactional'
6
+ require 'cash/write_through'
7
+ require 'cash/finders'
8
+ require 'cash/buffered'
9
+ require 'cash/index'
10
+ require 'cash/config'
11
+ require 'cash/accessor'
12
+
13
+ require 'cash/request'
14
+ require 'cash/fake'
15
+ require 'cash/local'
16
+
17
+ require 'cash/query/abstract'
18
+ require 'cash/query/select'
19
+ require 'cash/query/primary_key'
20
+ require 'cash/query/calculation'
21
+
22
+ require 'cash/util/array'
23
+ require 'cash/util/marshal'
24
+
25
+ class ActiveRecord::Base
26
+ def self.is_cached(options = {})
27
+ if options == false
28
+ include NoCash
29
+ else
30
+ options.assert_valid_keys(:ttl, :repository, :version)
31
+ include Cash unless ancestors.include?(Cash)
32
+ Cash::Config.create(self, options)
33
+ end
34
+ end
35
+
36
+ def <=>(other)
37
+ if self.id == other.id then
38
+ 0
39
+ else
40
+ self.id < other.id ? -1 : 1
41
+ end
42
+ end
43
+ end
44
+
45
+ module Cash
46
+ def self.included(active_record_class)
47
+ active_record_class.class_eval do
48
+ include Config, Accessor, WriteThrough, Finders
49
+ extend ClassMethods
50
+ end
51
+ end
52
+
53
+ module ClassMethods
54
+ def self.extended(active_record_class)
55
+ class << active_record_class
56
+ alias_method_chain :transaction, :cache_transaction
57
+ end
58
+ end
59
+
60
+ def transaction_with_cache_transaction(*args)
61
+ if cache_config
62
+ # Wrap both the db and cache transaction in another cache transaction so that the cache
63
+ # gets written only after the database commit but can still flush the inner cache
64
+ # transaction if an AR::Rollback is issued.
65
+ repository.transaction do
66
+ transaction_without_cache_transaction(*args) do
67
+ repository.transaction { yield }
68
+ end
69
+ end
70
+ else
71
+ transaction_without_cache_transaction(*args)
72
+ end
73
+ end
74
+
75
+ def cacheable?(*args)
76
+ true
77
+ end
78
+ end
79
+ end
80
+ module NoCash
81
+ def self.included(active_record_class)
82
+ active_record_class.class_eval do
83
+ extend ClassMethods
84
+ end
85
+ end
86
+ module ClassMethods
87
+ def cacheable?(*args)
88
+ false
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,83 @@
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
+ return {} if keys.empty?
15
+
16
+ keys = keys.collect { |key| cache_key(key) }
17
+ hits = repository.get_multi(*keys)
18
+ if (missed_keys = keys - hits.keys).any?
19
+ missed_values = block.call(missed_keys)
20
+ hits.merge!(missed_keys.zip(Array(missed_values)).to_hash_without_nils)
21
+ end
22
+ hits
23
+ else
24
+ repository.get(cache_key(keys), options[:raw]) || (block ? block.call : nil)
25
+ end
26
+ end
27
+
28
+ def get(keys, options = {}, &block)
29
+ case keys
30
+ when Array
31
+ fetch(keys, options, &block)
32
+ else
33
+ fetch(keys, options) do
34
+ if block_given?
35
+ add(keys, result = yield(keys), options)
36
+ result
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def add(key, value, options = {})
43
+ if repository.add(cache_key(key), value, options[:ttl] || cache_config.ttl, options[:raw]) == "NOT_STORED\r\n"
44
+ yield if block_given?
45
+ end
46
+ end
47
+
48
+ def set(key, value, options = {})
49
+ repository.set(cache_key(key), value, options[:ttl] || cache_config.ttl, options[:raw])
50
+ end
51
+
52
+ def incr(key, delta = 1, ttl = nil)
53
+ ttl ||= cache_config.ttl
54
+ repository.incr(cache_key = cache_key(key), delta) || begin
55
+ repository.add(cache_key, (result = yield).to_s, ttl, true) { repository.incr(cache_key) }
56
+ result
57
+ end
58
+ end
59
+
60
+ def decr(key, delta = 1, ttl = nil)
61
+ ttl ||= cache_config.ttl
62
+ repository.decr(cache_key = cache_key(key), delta) || begin
63
+ repository.add(cache_key, (result = yield).to_s, ttl, true) { repository.decr(cache_key) }
64
+ result
65
+ end
66
+ end
67
+
68
+ def expire(key)
69
+ repository.delete(cache_key(key))
70
+ end
71
+
72
+ def cache_key(key)
73
+ "#{name}:#{cache_config.version}/#{key.to_s.gsub(' ', '+')}"
74
+ end
75
+ end
76
+
77
+ module InstanceMethods
78
+ def expire
79
+ self.class.expire(id)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,129 @@
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_without_nils
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 respond_to?(method)
84
+ @cache.respond_to?(method)
85
+ end
86
+
87
+ protected
88
+
89
+ def perform_commands
90
+ @commands.each do |command|
91
+ command.call(@cache)
92
+ end
93
+ end
94
+
95
+ def buffer_command(command)
96
+ @commands << command
97
+ end
98
+
99
+ private
100
+
101
+ def method_missing(method, *args, &block)
102
+ @cache.send(method, *args, &block)
103
+ end
104
+ end
105
+
106
+ class NestedBuffered < Buffered
107
+ def flush
108
+ perform_commands
109
+ end
110
+ end
111
+
112
+ class Command
113
+ attr_accessor :key
114
+
115
+ def initialize(name, key, *args)
116
+ @name = name
117
+ @key = key
118
+ @args = args
119
+ end
120
+
121
+ def requires_lock?
122
+ @name == :set
123
+ end
124
+
125
+ def call(cache)
126
+ cache.send @name, @key, *@args
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,75 @@
1
+ module Cash
2
+ module Config
3
+ def self.create(active_record, options, indices = [])
4
+ active_record.cache_config = Cash::Config::Config.new(active_record, options)
5
+ indices.each { |i| active_record.index i.attributes, i.options }
6
+ end
7
+
8
+ def self.included(a_module)
9
+ a_module.module_eval do
10
+ extend ClassMethods
11
+ delegate :repository, :to => "self.class"
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def self.extended(a_class)
17
+ class << a_class
18
+ def cache_config
19
+ @cache_config ? @cache_config : superclass.cache_config
20
+ end
21
+
22
+ delegate :repository, :indices, :to => :cache_config
23
+ alias_method_chain :inherited, :cache_config
24
+ end
25
+ end
26
+
27
+ def inherited_with_cache_config(subclass)
28
+ inherited_without_cache_config(subclass)
29
+ @cache_config.inherit(subclass)
30
+ end
31
+
32
+ def index(attributes, options = {})
33
+ options.assert_valid_keys(:ttl, :order, :limit, :buffer, :order_column)
34
+ (@cache_config.indices.unshift(Index.new(@cache_config, self, attributes, options))).uniq!
35
+ end
36
+
37
+ def version(number)
38
+ @cache_config.options[:version] = number
39
+ end
40
+
41
+ def cache_config=(config)
42
+ @cache_config = config
43
+ end
44
+ end
45
+
46
+ class Config
47
+ attr_reader :active_record, :options
48
+
49
+ def initialize(active_record, options = {})
50
+ @active_record, @options = active_record, options
51
+ end
52
+
53
+ def repository
54
+ @options[:repository]
55
+ end
56
+
57
+ def ttl
58
+ repository_ttl = repository.respond_to?(:default_ttl) ? repository.default_ttl : nil
59
+ @ttl ||= @options[:ttl] || repository_ttl || 1.day
60
+ end
61
+
62
+ def version
63
+ @options[:version] || 1
64
+ end
65
+
66
+ def indices
67
+ @indices ||= active_record == ActiveRecord::Base ? [] : [Index.new(self, active_record, active_record.primary_key)]
68
+ end
69
+
70
+ def inherit(active_record)
71
+ Cash::Config.create(active_record, @options, indices)
72
+ end
73
+ end
74
+ end
75
+ end
data/lib/cash/fake.rb ADDED
@@ -0,0 +1,83 @@
1
+ module Cash
2
+ class Fake < HashWithIndifferentAccess
3
+ attr_accessor :servers
4
+
5
+ def get_multi(*keys)
6
+ slice(*keys).collect { |k,v| [k, Marshal.load(v)] }.to_hash
7
+ end
8
+
9
+ def set(key, value, ttl = 0, raw = false)
10
+ self[key] = marshal(value, raw)
11
+ end
12
+
13
+ def get(key, raw = false)
14
+ if raw
15
+ self[key]
16
+ else
17
+ if self.has_key?(key)
18
+ Marshal.load(self[key])
19
+ else
20
+ nil
21
+ end
22
+ end
23
+ end
24
+
25
+ def incr(key, amount = 1)
26
+ if self.has_key?(key)
27
+ self[key] = (self[key].to_i + amount).to_s
28
+ self[key].to_i
29
+ end
30
+ end
31
+
32
+ def decr(key, amount = 1)
33
+ if self.has_key?(key)
34
+ self[key] = (self[key].to_i - amount).to_s
35
+ self[key].to_i
36
+ end
37
+ end
38
+
39
+ def add(key, value, ttl = 0, raw = false)
40
+ return false if self.has_key?(key)
41
+
42
+ self[key] = marshal(value, raw)
43
+ true
44
+ end
45
+
46
+ def append(key, value)
47
+ set(key, get(key, true).to_s + value.to_s, nil, true)
48
+ end
49
+
50
+ def namespace
51
+ nil
52
+ end
53
+
54
+ def flush_all
55
+ clear
56
+ end
57
+
58
+ def stats
59
+ {}
60
+ end
61
+
62
+ def reset_runtime
63
+ [0, Hash.new(0)]
64
+ end
65
+
66
+ private
67
+ def marshal(value, raw)
68
+ if raw
69
+ value.to_s
70
+ else
71
+ Marshal.dump(value)
72
+ end
73
+ end
74
+
75
+ def unmarshal(marshaled_obj)
76
+ Marshal.load(marshaled_obj)
77
+ end
78
+
79
+ def deep_clone(obj)
80
+ unmarshal(marshal(obj))
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,38 @@
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
+ class << active_record_class
12
+ alias_method_chain :find_every, :cache
13
+ alias_method_chain :find_from_ids, :cache
14
+ alias_method_chain :calculate, :cache
15
+ end
16
+ end
17
+
18
+ def without_cache(&block)
19
+ with_scope(:find => {:readonly => true}, &block)
20
+ end
21
+
22
+ # User.find(:first, ...), User.find_by_foo(...), User.find(:all, ...), User.find_all_by_foo(...)
23
+ def find_every_with_cache(options)
24
+ Query::Select.perform(self, options, scope(:find))
25
+ end
26
+
27
+ # User.find(1), User.find(1, 2, 3), User.find([1, 2, 3]), User.find([])
28
+ def find_from_ids_with_cache(ids, options)
29
+ Query::PrimaryKey.perform(self, ids, options, scope(:find))
30
+ end
31
+
32
+ # User.count(:all), User.count, User.sum(...)
33
+ def calculate_with_cache(operation, column_name, options = {})
34
+ Query::Calculation.perform(self, operation, column_name, options, scope(:find))
35
+ end
36
+ end
37
+ end
38
+ end