Skip to content

Commit

Permalink
Added better API Compatibility with the memcache-client gem for excep…
Browse files Browse the repository at this point in the history
…tion handling.
  • Loading branch information
chriseppstein authored and Derek Perez committed Oct 15, 2009
1 parent d4aa695 commit c468f65
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 6 deletions.
2 changes: 1 addition & 1 deletion lib/memcached.rb
Expand Up @@ -28,4 +28,4 @@ class Memcached
require 'memcached/exceptions' require 'memcached/exceptions'
require 'memcached/behaviors' require 'memcached/behaviors'
require 'memcached/memcached' require 'memcached/memcached'
require 'memcached/rails' require 'memcached/rails' if defined?(RAILS_ENV)
66 changes: 62 additions & 4 deletions lib/memcached/rails.rb
@@ -1,3 +1,15 @@
unless defined?(Timeout::Error)
module Timeout
class Error < Interrupt; end
end
end

unless defined?(MemCache::MemCacheError)
class MemCache
class MemCacheError < RuntimeError; end
end
end

class Memcached class Memcached


(instance_methods - NilClass.instance_methods).each do |method_name| (instance_methods - NilClass.instance_methods).each do |method_name|
Expand All @@ -6,6 +18,30 @@ class Memcached


# A legacy compatibility wrapper for the Memcached class. It has basic compatibility with the <b>memcache-client</b> API. # A legacy compatibility wrapper for the Memcached class. It has basic compatibility with the <b>memcache-client</b> API.
class Rails < ::Memcached class Rails < ::Memcached

DEFAULTS = {}

def self.translate_exception(method, mapping)
from_exception = mapping.keys.first
to_exception = mapping.values.first
exception_supported = (to_exception || from_exception).name.underscore.gsub(%r{/},'_')
old_method_name = "#{method}_without_#{exception_supported}_support".to_sym
eval %Q{alias #{old_method_name} #{method}}
action_to_perform = if to_exception
%Q{
raise #{to_exception.name}, "\#{e.class.name.split('::').last.underscore.humanize}: \#{e.message}", e.backtrace
}
else
%Q{nil}
end
eval %Q{
def #{method}(*args, &block)
#{old_method_name}(*args, &block)
rescue #{from_exception.name} => e
#{action_to_perform}
end
}
end


alias :servers= :set_servers alias :servers= :set_servers


Expand All @@ -17,15 +53,17 @@ def initialize(*args)
).flatten.compact ).flatten.compact


opts[:prefix_key] ||= opts[:namespace] opts[:prefix_key] ||= opts[:namespace]
super(servers, opts) super(servers, DEFAULTS.merge(opts))
end end


# Wraps Memcached#get so that it doesn't raise. This has the side-effect of preventing you from # Wraps Memcached#get so that it doesn't raise. This has the side-effect of preventing you from
# storing <tt>nil</tt> values. # storing <tt>nil</tt> values.
def get(key, raw=false) def get(key, raw=false)
super(key, !raw) super(key, !raw)
rescue NotFound
end end
translate_exception :get, NotFound => nil
translate_exception :get, ATimeoutOccurred => Timeout::Error
translate_exception :get, Memcached::Error => MemCache::MemCacheError


# Wraps Memcached#cas so that it doesn't raise. Doesn't set anything if no value is present. # Wraps Memcached#cas so that it doesn't raise. Doesn't set anything if no value is present.
def cas(key, ttl=@default_ttl, raw=false, &block) def cas(key, ttl=@default_ttl, raw=false, &block)
Expand All @@ -34,16 +72,24 @@ def cas(key, ttl=@default_ttl, raw=false, &block)
end end


alias :compare_and_swap :cas alias :compare_and_swap :cas
translate_exception :cas, NotFound => nil
translate_exception :cas, ATimeoutOccurred => Timeout::Error
translate_exception :cas, Memcached::Error => MemCache::MemCacheError


# Wraps Memcached#get. # Wraps Memcached#get.
def get_multi(keys, raw=false) def get_multi(keys, raw=false)
get_orig(keys, !raw) get_orig(keys, !raw)
end end


translate_exception :get_multi, ATimeoutOccurred => Timeout::Error
translate_exception :get_multi, Memcached::Error => MemCache::MemCacheError

# Wraps Memcached#set. # Wraps Memcached#set.
def set(key, value, ttl=@default_ttl, raw=false) def set(key, value, ttl=@default_ttl, raw=false)
super(key, value, ttl, !raw) super(key, value, ttl, !raw)
end end
translate_exception :set, ATimeoutOccurred => Timeout::Error
translate_exception :set, Memcached::Error => MemCache::MemCacheError


# Wraps Memcached#add so that it doesn't raise. # Wraps Memcached#add so that it doesn't raise.
def add(key, value, ttl=@default_ttl, raw=false) def add(key, value, ttl=@default_ttl, raw=false)
Expand All @@ -53,6 +99,12 @@ def add(key, value, ttl=@default_ttl, raw=false)
false false
end end


translate_exception :add, ATimeoutOccurred => Timeout::Error
translate_exception :add, Memcached::Error => MemCache::MemCacheError

# Wraps Memcached#delete so that it doesn't raise.
translate_exception :delete, NotFound => nil

# Wraps Memcached#delete so that it doesn't raise. # Wraps Memcached#delete so that it doesn't raise.
def delete(key) def delete(key)
super super
Expand All @@ -62,15 +114,21 @@ def delete(key)
# Wraps Memcached#incr so that it doesn't raise. # Wraps Memcached#incr so that it doesn't raise.
def incr(*args) def incr(*args)
super super
rescue NotFound
end end

translate_exception :incr, NotFound => nil
translate_exception :incr, ATimeoutOccurred => Timeout::Error
translate_exception :incr, Memcached::Error => MemCache::MemCacheError


# Wraps Memcached#decr so that it doesn't raise. # Wraps Memcached#decr so that it doesn't raise.
def decr(*args) def decr(*args)
super super
rescue NotFound
end end


translate_exception :decr, NotFound => nil
translate_exception :decr, ATimeoutOccurred => Timeout::Error
translate_exception :decr, Memcached::Error => MemCache::MemCacheError

# Wraps Memcached#append so that it doesn't raise. # Wraps Memcached#append so that it doesn't raise.
def append(*args) def append(*args)
super super
Expand Down
88 changes: 87 additions & 1 deletion test/unit/rails_test.rb
@@ -1,6 +1,39 @@
class String
def underscore
self.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
def humanize
self.gsub(/_id$/, "").gsub(/_/, " ").capitalize
end
end


require "#{File.dirname(__FILE__)}/../../lib/memcached/rails"
require "#{File.dirname(__FILE__)}/../test_helper" require "#{File.dirname(__FILE__)}/../test_helper"


module TimeOuts
def get_without_timeout_error_support(*args, &block); raise Memcached::ATimeoutOccurred.new("timeout occured"); end
def cas_without_timeout_error_support(*args, &block); raise Memcached::ATimeoutOccurred.new("timeout occured"); end
def get_multi_without_timeout_error_support(*args, &block); raise Memcached::ATimeoutOccurred.new("timeout occured"); end
def set_without_timeout_error_support(*args, &block); raise Memcached::ATimeoutOccurred.new("timeout occured"); end
def add_without_timeout_error_support(*args, &block); raise Memcached::ATimeoutOccurred.new("timeout occured"); end
def incr_without_timeout_error_support(*args, &block); raise Memcached::ATimeoutOccurred.new("timeout occured"); end
def decr_without_timeout_error_support(*args, &block); raise Memcached::ATimeoutOccurred.new("timeout occured"); end
end

module OtherErrors
def get_without_mem_cache_mem_cache_error_support(*args, &block); raise Memcached::Failure.new("WTF?!"); end
def cas_without_mem_cache_mem_cache_error_support(*args, &block); raise Memcached::Failure.new("WTF?!"); end
def get_multi_without_mem_cache_mem_cache_error_support(*args, &block); raise Memcached::Failure.new("WTF?!"); end
def set_without_mem_cache_mem_cache_error_support(*args, &block); raise Memcached::Failure.new("WTF?!"); end
def add_without_mem_cache_mem_cache_error_support(*args, &block); raise Memcached::Failure.new("WTF?!"); end
def incr_without_mem_cache_mem_cache_error_support(*args, &block); raise Memcached::Failure.new("WTF?!"); end
def decr_without_mem_cache_mem_cache_error_support(*args, &block); raise Memcached::Failure.new("WTF?!"); end
end

class RailsTest < Test::Unit::TestCase class RailsTest < Test::Unit::TestCase


def setup def setup
Expand Down Expand Up @@ -69,13 +102,66 @@ def test_cas


# Conflicting set # Conflicting set
cache.set key, @value cache.set key, @value
assert_raises(Memcached::ConnectionDataExists) do begin
cache.cas(key) do |current| cache.cas(key) do |current|
cache.set key, value2 cache.set key, value2
current current
end end
assert false, "An error was not raised"
rescue MemCache::MemCacheError => e
assert_equal "Connection data exists: Key {\"test_cas\"=>\"127.0.0.1:43043:8\"}", e.message
end end
end end

def test_timeout_errors
@cache.extend TimeOuts
assert_raises Timeout::Error do
@cache.cas(key){"asdf"}
end
assert_raises Timeout::Error do
@cache.set(key, "asdf")
end
assert_raises Timeout::Error do
@cache.add(key, "asdf")
end
assert_raises Timeout::Error do
@cache.get(key)
end
assert_raises Timeout::Error do
@cache.get_multi(key, "asdf", "bacon")
end
assert_raises Timeout::Error do
@cache.incr(key)
end
assert_raises Timeout::Error do
@cache.decr(key)
end
end

def test_other_errors
@cache.extend OtherErrors
assert_raises MemCache::MemCacheError do
@cache.cas(key){"asdf"}
end
assert_raises MemCache::MemCacheError do
@cache.set(key, "asdf")
end
assert_raises MemCache::MemCacheError do
@cache.add(key, "asdf")
end
assert_raises MemCache::MemCacheError do
@cache.get(key)
end
assert_raises MemCache::MemCacheError do
@cache.get_multi(key, "asdf", "bacon")
end
assert_raises MemCache::MemCacheError do
@cache.incr(key)
end
assert_raises MemCache::MemCacheError do
@cache.decr(key)
end
end


def test_get_missing def test_get_missing
@cache.delete key rescue nil @cache.delete key rescue nil
Expand Down

0 comments on commit c468f65

Please sign in to comment.