viximo-cache-money 0.3.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.
Files changed (51) hide show
  1. data/LICENSE +201 -0
  2. data/README +204 -0
  3. data/README.markdown +204 -0
  4. data/TODO +17 -0
  5. data/UNSUPPORTED_FEATURES +13 -0
  6. data/config/environment.rb +8 -0
  7. data/config/memcached.yml +4 -0
  8. data/db/schema.rb +18 -0
  9. data/init.rb +1 -0
  10. data/lib/cache_money.rb +105 -0
  11. data/lib/cash/accessor.rb +83 -0
  12. data/lib/cash/adapter/memcache_client.rb +36 -0
  13. data/lib/cash/adapter/memcached.rb +127 -0
  14. data/lib/cash/adapter/redis.rb +144 -0
  15. data/lib/cash/buffered.rb +137 -0
  16. data/lib/cash/config.rb +78 -0
  17. data/lib/cash/fake.rb +83 -0
  18. data/lib/cash/finders.rb +50 -0
  19. data/lib/cash/index.rb +211 -0
  20. data/lib/cash/local.rb +105 -0
  21. data/lib/cash/lock.rb +63 -0
  22. data/lib/cash/mock.rb +158 -0
  23. data/lib/cash/query/abstract.rb +219 -0
  24. data/lib/cash/query/calculation.rb +45 -0
  25. data/lib/cash/query/primary_key.rb +50 -0
  26. data/lib/cash/query/select.rb +16 -0
  27. data/lib/cash/request.rb +3 -0
  28. data/lib/cash/transactional.rb +43 -0
  29. data/lib/cash/util/array.rb +9 -0
  30. data/lib/cash/util/marshal.rb +19 -0
  31. data/lib/cash/version.rb +3 -0
  32. data/lib/cash/write_through.rb +71 -0
  33. data/lib/mem_cached_session_store.rb +49 -0
  34. data/lib/mem_cached_support_store.rb +143 -0
  35. data/rails/init.rb +1 -0
  36. data/spec/cash/accessor_spec.rb +186 -0
  37. data/spec/cash/active_record_spec.rb +224 -0
  38. data/spec/cash/buffered_spec.rb +9 -0
  39. data/spec/cash/calculations_spec.rb +78 -0
  40. data/spec/cash/finders_spec.rb +455 -0
  41. data/spec/cash/local_buffer_spec.rb +9 -0
  42. data/spec/cash/local_spec.rb +9 -0
  43. data/spec/cash/lock_spec.rb +110 -0
  44. data/spec/cash/marshal_spec.rb +60 -0
  45. data/spec/cash/order_spec.rb +172 -0
  46. data/spec/cash/transactional_spec.rb +602 -0
  47. data/spec/cash/window_spec.rb +195 -0
  48. data/spec/cash/without_caching_spec.rb +32 -0
  49. data/spec/cash/write_through_spec.rb +252 -0
  50. data/spec/spec_helper.rb +87 -0
  51. metadata +300 -0
@@ -0,0 +1,45 @@
1
+ module Cash
2
+ module Query
3
+ class Calculation < Abstract
4
+ delegate :calculate_without_cache, :incr, :to => :@active_record
5
+
6
+ def initialize(active_record, operation, column, options1, options2)
7
+ super(active_record, options1, options2)
8
+ @operation, @column = operation, column
9
+ end
10
+
11
+ def perform
12
+ super({}, :raw => true)
13
+ end
14
+
15
+ def calculation?
16
+ true
17
+ end
18
+
19
+ protected
20
+ def miss(_, __)
21
+ calculate_without_cache(@operation, @column, @options1)
22
+ end
23
+
24
+ def uncacheable
25
+ calculate_without_cache(@operation, @column, @options1)
26
+ end
27
+
28
+ def format_results(_, objects)
29
+ objects.to_i
30
+ end
31
+
32
+ def serialize_objects(_, objects)
33
+ objects.to_s
34
+ end
35
+
36
+ def cacheable?(*optionss)
37
+ @column == :all && super(*optionss)
38
+ end
39
+
40
+ def cache_keys(attribute_value_pairs)
41
+ "#{super(attribute_value_pairs)}/#{@operation}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ module Cash
2
+ module Query
3
+ class PrimaryKey < Abstract
4
+ def initialize(active_record, ids, options1, options2)
5
+ super(active_record, options1, options2)
6
+ @expects_array = ids.first.kind_of?(Array)
7
+ @original_ids = ids
8
+ @ids = ids.flatten.compact.uniq.collect do |object|
9
+ object.respond_to?(:quoted_id) ? object.quoted_id : object.to_i
10
+ end
11
+ end
12
+
13
+ def perform
14
+ return [] if @expects_array && @ids.empty?
15
+ raise ActiveRecord::RecordNotFound if @ids.empty?
16
+
17
+ super(:conditions => { :id => @ids.first })
18
+ end
19
+
20
+ protected
21
+ def deserialize_objects(objects)
22
+ convert_to_active_record_collection(super(objects))
23
+ end
24
+
25
+ def cache_keys(attribute_value_pairs)
26
+ @ids.collect { |id| "id/#{id}" }
27
+ end
28
+
29
+ def miss(missing_keys, options)
30
+ find_from_keys(*missing_keys)
31
+ end
32
+
33
+ def uncacheable
34
+ find_from_ids_without_cache(@original_ids, @options1)
35
+ end
36
+
37
+ private
38
+ def convert_to_active_record_collection(objects)
39
+ case objects.size
40
+ when 0
41
+ raise ActiveRecord::RecordNotFound
42
+ when 1
43
+ @expects_array ? objects : objects.first
44
+ else
45
+ objects
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,16 @@
1
+ module Cash
2
+ module Query
3
+ class Select < Abstract
4
+ delegate :find_every_without_cache, :to => :@active_record
5
+
6
+ protected
7
+ def miss(_, miss_options)
8
+ find_every_without_cache(miss_options)
9
+ end
10
+
11
+ def uncacheable
12
+ find_every_without_cache(@options1)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Cash
2
+ Request = {}
3
+ end
@@ -0,0 +1,43 @@
1
+ module Cash
2
+ class Transactional
3
+ attr_reader :memcache
4
+
5
+ def initialize(memcache, lock)
6
+ @memcache, @cache = [memcache, memcache]
7
+ @lock = lock
8
+ end
9
+
10
+ def transaction
11
+ exception_was_raised = false
12
+ begin_transaction
13
+ result = yield
14
+ rescue Object => e
15
+ exception_was_raised = true
16
+ raise
17
+ ensure
18
+ begin
19
+ @cache.flush unless exception_was_raised
20
+ ensure
21
+ end_transaction
22
+ end
23
+ end
24
+
25
+ def respond_to?(method)
26
+ @cache.respond_to?(method)
27
+ end
28
+
29
+ private
30
+
31
+ def method_missing(method, *args, &block)
32
+ @cache.send(method, *args, &block)
33
+ end
34
+
35
+ def begin_transaction
36
+ @cache = Buffered.push(@cache, @lock)
37
+ end
38
+
39
+ def end_transaction
40
+ @cache = @cache.pop
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ class Array
2
+ alias_method :count, :size
3
+
4
+ def to_hash_without_nils
5
+ keys_and_values_without_nils = reject { |key, value| value.nil? }
6
+ shallow_flattened_keys_and_values_without_nils = keys_and_values_without_nils.inject([]) { |result, pair| result += pair }
7
+ Hash[*shallow_flattened_keys_and_values_without_nils]
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module Marshal
2
+ class << self
3
+ def constantize(name)
4
+ name.constantize
5
+ end
6
+
7
+ def load_with_constantize(value)
8
+ begin
9
+ Marshal.load_without_constantize value
10
+ rescue ArgumentError => e
11
+ _, class_name = *(/undefined class\/module ([\w:]*\w)/.match(e.message))
12
+ raise if !class_name
13
+ constantize(class_name)
14
+ Marshal.load value
15
+ end
16
+ end
17
+ alias_method_chain :load, :constantize
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module Cash
2
+ VERSION = '0.3.0'
3
+ end
@@ -0,0 +1,71 @@
1
+ module Cash
2
+ module WriteThrough
3
+ def self.included(active_record_class)
4
+ active_record_class.class_eval do
5
+ include InstanceMethods
6
+ extend ClassMethods
7
+ end
8
+ end
9
+
10
+ module InstanceMethods
11
+ def self.included(active_record_class)
12
+ active_record_class.class_eval do
13
+ after_create :add_to_caches
14
+ after_update :update_caches
15
+ after_destroy :remove_from_caches
16
+ end
17
+ end
18
+
19
+ def add_to_caches
20
+ InstanceMethods.unfold(self.class, :add_to_caches, self)
21
+ end
22
+
23
+ def update_caches
24
+ InstanceMethods.unfold(self.class, :update_caches, self)
25
+ end
26
+
27
+ def remove_from_caches
28
+ return if new_record?
29
+ InstanceMethods.unfold(self.class, :remove_from_caches, self)
30
+ end
31
+
32
+ def expire_caches
33
+ InstanceMethods.unfold(self.class, :expire_caches, self)
34
+ end
35
+
36
+ def shallow_clone
37
+ self.class.send(:instantiate, instance_variable_get(:@attributes))
38
+ end
39
+
40
+ private
41
+ def self.unfold(klass, operation, object)
42
+ while klass < ActiveRecord::Base && klass.ancestors.include?(WriteThrough)
43
+ klass.send(operation, object)
44
+ klass = klass.superclass
45
+ end
46
+ end
47
+ end
48
+
49
+ module ClassMethods
50
+ def add_to_caches(object)
51
+ indices.each { |index| index.add(object) } if cacheable?
52
+ true
53
+ end
54
+
55
+ def update_caches(object)
56
+ indices.each { |index| index.update(object) } if cacheable?
57
+ true
58
+ end
59
+
60
+ def remove_from_caches(object)
61
+ indices.each { |index| index.remove(object) } if cacheable?
62
+ true
63
+ end
64
+
65
+ def expire_caches(object)
66
+ indices.each { |index| index.delete(object) } if cacheable?
67
+ true
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,49 @@
1
+ # begin
2
+ require 'memcached'
3
+
4
+ class MemCachedSessionStore < ActionController::Session::AbstractStore
5
+ def initialize(app, options = {})
6
+ # Support old :expires option
7
+ options[:expire_after] ||= options[:expires]
8
+
9
+ super
10
+
11
+ @default_options = {
12
+ :namespace => 'rack:session',
13
+ :servers => 'localhost:11211'
14
+ }.merge(@default_options)
15
+
16
+ @default_options[:prefix_key] ||= @default_options[:namespace]
17
+
18
+ @pool = options[:cache] || Memcached.new(@default_options[:servers], @default_options)
19
+ # unless @pool.servers.any? { |s| s.alive? }
20
+ # raise "#{self} unable to find server during initialization."
21
+ # end
22
+ @mutex = Mutex.new
23
+
24
+ super
25
+ end
26
+
27
+ private
28
+ def get_session(env, sid)
29
+ sid ||= generate_sid
30
+ begin
31
+ session = @pool.get(sid) || {}
32
+ rescue Memcached::NotFound, MemCache::MemCacheError, Errno::ECONNREFUSED
33
+ session = {}
34
+ end
35
+ [sid, session]
36
+ end
37
+
38
+ def set_session(env, sid, session_data)
39
+ options = env['rack.session.options']
40
+ expiry = options[:expire_after] || 0
41
+ @pool.set(sid, session_data, expiry)
42
+ return true
43
+ rescue Memcached::NotStored, MemCache::MemCacheError, Errno::ECONNREFUSED
44
+ return false
45
+ end
46
+ end
47
+ # rescue LoadError
48
+ # # Memcached wasn't available so neither can the store be
49
+ # end
@@ -0,0 +1,143 @@
1
+ require 'memcached'
2
+
3
+ # A cache store implementation which stores data in Memcached:
4
+ # http://www.danga.com/memcached/
5
+ #
6
+ # This is currently the most popular cache store for production websites.
7
+ #
8
+ # Special features:
9
+ # - Clustering and load balancing. One can specify multiple memcached servers,
10
+ # and MemCacheStore will load balance between all available servers. If a
11
+ # server goes down, then MemCacheStore will ignore it until it goes back
12
+ # online.
13
+ # - Time-based expiry support. See #write and the +:expires_in+ option.
14
+ # - Per-request in memory cache for all communication with the MemCache server(s).
15
+ class MemCachedSupportStore < ActiveSupport::Cache::Store
16
+
17
+ attr_reader :addresses
18
+
19
+ # Creates a new MemCacheStore object, with the given memcached server
20
+ # addresses. Each address is either a host name, or a host-with-port string
21
+ # in the form of "host_name:port". For example:
22
+ #
23
+ # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
24
+ #
25
+ # If no addresses are specified, then MemCacheStore will connect to
26
+ # localhost port 11211 (the default memcached port).
27
+ def initialize(*addresses)
28
+ addresses = addresses.flatten
29
+ options = addresses.extract_options!
30
+ options[:prefix_key] ||= options[:namespace]
31
+ addresses = ["localhost"] if addresses.empty?
32
+ @addresses = addresses
33
+ @data = Memcached.new(addresses, options)
34
+
35
+ extend ActiveSupport::Cache::Strategy::LocalCache
36
+ end
37
+
38
+ def read(key, options = nil) # :nodoc:
39
+ super
40
+ @data.get(key, marshal?(options))
41
+ rescue Memcached::NotFound
42
+ nil
43
+ rescue Memcached::Error => e
44
+ logger.error("MemcachedError (#{e}): #{e.message}")
45
+ nil
46
+ end
47
+
48
+ def read_multi(*keys)
49
+ options = keys.extract_options!
50
+ @data.get(keys, marshal?(options))
51
+ rescue Memcached::Error => e
52
+ logger.error("MemcachedError (#{e}): #{e.message}")
53
+ {}
54
+ end
55
+
56
+ # Writes a value to the cache.
57
+ #
58
+ # Possible options:
59
+ # - +:unless_exist+ - set to true if you don't want to update the cache
60
+ # if the key is already set.
61
+ # - +:expires_in+ - the number of seconds that this value may stay in
62
+ # the cache. See ActiveSupport::Cache::Store#write for an example.
63
+ def write(key, value, options = nil)
64
+ super
65
+ method = options && options[:unless_exist] ? :add : :set
66
+ # memcache-client will break the connection if you send it an integer
67
+ # in raw mode, so we convert it to a string to be sure it continues working.
68
+ @data.send(method, key, value, expires_in(options), marshal?(options))
69
+ true
70
+ rescue Memcached::NotStored
71
+ false
72
+ rescue Memcached::NotFound
73
+ false
74
+ rescue Memcached::Error => e
75
+ logger.error("MemcachedError (#{e}): #{e.message}")
76
+ false
77
+ end
78
+
79
+ def delete(key, options = nil) # :nodoc:
80
+ super
81
+ @data.delete(key)
82
+ true
83
+ rescue Memcached::NotFound
84
+ false
85
+ rescue Memcached::Error => e
86
+ logger.error("MemcachedError (#{e}): #{e.message}")
87
+ false
88
+ end
89
+
90
+ def exist?(key, options = nil) # :nodoc:
91
+ # Doesn't call super, cause exist? in memcache is in fact a read
92
+ # But who cares? Reading is very fast anyway
93
+ # Local cache is checked first, if it doesn't know then memcache itself is read from
94
+ !read(key, options).nil?
95
+ end
96
+
97
+ def increment(key, amount = 1) # :nodoc:
98
+ log("incrementing", key, amount)
99
+
100
+ @data.incr(key, amount)
101
+ response
102
+ rescue Memcached::NotFound
103
+ nil
104
+ rescue Memcached::Error
105
+ nil
106
+ end
107
+
108
+ def decrement(key, amount = 1) # :nodoc:
109
+ log("decrement", key, amount)
110
+ @data.decr(key, amount)
111
+ response
112
+ rescue Memcached::NotFound
113
+ nil
114
+ rescue Memcached::Error
115
+ nil
116
+ end
117
+
118
+ def delete_matched(matcher, options = nil) # :nodoc:
119
+ # don't do any local caching at present, just pass
120
+ # through and let the error happen
121
+ super
122
+ raise "Not supported by Memcache"
123
+ end
124
+
125
+ def clear
126
+ @data.flush
127
+ rescue Memcached::NotFound
128
+ end
129
+
130
+ def stats
131
+ @data.stats
132
+ rescue Memcached::NotFound
133
+ end
134
+
135
+ private
136
+ def expires_in(options)
137
+ (options && options[:expires_in] && options[:expires_in].to_i) || 0
138
+ end
139
+
140
+ def marshal?(options)
141
+ !(options && options[:raw])
142
+ end
143
+ end