From c468f6548186a5d29371e14726dda9aae84cb28e Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Fri, 21 Aug 2009 17:30:51 -0700 Subject: [PATCH] Added better API Compatibility with the memcache-client gem for exception handling. --- lib/memcached.rb | 2 +- lib/memcached/rails.rb | 66 +++++++++++++++++++++++++++++-- test/unit/rails_test.rb | 88 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 150 insertions(+), 6 deletions(-) diff --git a/lib/memcached.rb b/lib/memcached.rb index 36b61545..5ab9c4db 100644 --- a/lib/memcached.rb +++ b/lib/memcached.rb @@ -28,4 +28,4 @@ class Memcached require 'memcached/exceptions' require 'memcached/behaviors' require 'memcached/memcached' -require 'memcached/rails' +require 'memcached/rails' if defined?(RAILS_ENV) diff --git a/lib/memcached/rails.rb b/lib/memcached/rails.rb index 7442eb85..62242518 100644 --- a/lib/memcached/rails.rb +++ b/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 (instance_methods - NilClass.instance_methods).each do |method_name| @@ -6,6 +18,30 @@ class Memcached # A legacy compatibility wrapper for the Memcached class. It has basic compatibility with the memcache-client API. 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 @@ -17,15 +53,17 @@ def initialize(*args) ).flatten.compact opts[:prefix_key] ||= opts[:namespace] - super(servers, opts) + super(servers, DEFAULTS.merge(opts)) end # Wraps Memcached#get so that it doesn't raise. This has the side-effect of preventing you from # storing nil values. def get(key, raw=false) super(key, !raw) - rescue NotFound 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. def cas(key, ttl=@default_ttl, raw=false, &block) @@ -34,16 +72,24 @@ def cas(key, ttl=@default_ttl, raw=false, &block) end 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. def get_multi(keys, raw=false) get_orig(keys, !raw) end + translate_exception :get_multi, ATimeoutOccurred => Timeout::Error + translate_exception :get_multi, Memcached::Error => MemCache::MemCacheError + # Wraps Memcached#set. def set(key, value, ttl=@default_ttl, raw=false) super(key, value, ttl, !raw) end + translate_exception :set, ATimeoutOccurred => Timeout::Error + translate_exception :set, Memcached::Error => MemCache::MemCacheError # Wraps Memcached#add so that it doesn't raise. def add(key, value, ttl=@default_ttl, raw=false) @@ -53,6 +99,12 @@ def add(key, value, ttl=@default_ttl, raw=false) false 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. def delete(key) super @@ -62,15 +114,21 @@ def delete(key) # Wraps Memcached#incr so that it doesn't raise. def incr(*args) super - rescue NotFound 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. def decr(*args) super - rescue NotFound 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. def append(*args) super diff --git a/test/unit/rails_test.rb b/test/unit/rails_test.rb index 6bebf86f..b14d25c3 100644 --- a/test/unit/rails_test.rb +++ b/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" +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 def setup @@ -69,13 +102,66 @@ def test_cas # Conflicting set cache.set key, @value - assert_raises(Memcached::ConnectionDataExists) do + begin cache.cas(key) do |current| cache.set key, value2 current 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 + + 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 @cache.delete key rescue nil