sreeix-cache-money 0.2.24.1

Sign up to get free protection for your applications and to get access to all the features.
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