Permalink
Browse files

API cache extracted from mloughran.com.

Some major limitations right now:

* Only the :cache and :timeout options are implemented
* There is only one cache store and it's an in memory hash
  • Loading branch information...
0 parents commit de636b0dc227848f67ee6a9f87113a60fe05bd08 Martyn Loughran committed May 29, 2008
Showing with 167 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +20 −0 LICENSE
  3. +3 −0 README
  4. +44 −0 Rakefile
  5. 0 TODO
  6. +59 −0 lib/api_cache.rb
  7. +31 −0 lib/api_cache/memory_store.rb
  8. +7 −0 spec/api_cache_spec.rb
  9. +2 −0 spec/spec_helper.rb
1 .gitignore
@@ -0,0 +1 @@
+pkg
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008 Martyn Loughran
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3 README
@@ -0,0 +1,3 @@
+api_cache
+=========
+
44 Rakefile
@@ -0,0 +1,44 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+require 'rubygems/specification'
+require 'date'
+
+PLUGIN = "api_cache"
+NAME = "api_cache"
+GEM_VERSION = "0.0.1"
+AUTHOR = "Martyn Loughran"
+EMAIL = "me@mloughran.com"
+HOMEPAGE = "http://merb-plugins.rubyforge.org/api_cache/"
+SUMMARY = "Library to handle caching external API calls"
+
+spec = Gem::Specification.new do |s|
+ s.name = NAME
+ s.version = GEM_VERSION
+ s.platform = Gem::Platform::RUBY
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
+ s.summary = SUMMARY
+ s.description = s.summary
+ s.author = AUTHOR
+ s.email = EMAIL
+ s.homepage = HOMEPAGE
+ s.require_path = 'lib'
+ s.autorequire = PLUGIN
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
+end
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.gem_spec = spec
+end
+
+desc "install the plugin locally"
+task :install => [:package] do
+ sh %{sudo gem install pkg/#{NAME}-#{GEM_VERSION} --no-update-sources}
+end
+
+desc "create a gemspec file"
+task :make_spec do
+ File.open("#{GEM}.gemspec", "w") do |file|
+ file.puts spec.to_ruby
+ end
+end
0 TODO
No changes.
59 lib/api_cache.rb
@@ -0,0 +1,59 @@
+class APICache
+ class Error < RuntimeError; end
+ class Invalid < RuntimeError; end
+
+ class << self
+ attr_accessor :cache
+ end
+
+ # Initializes the cache
+ def self.start
+ APICache.cache = APICache::MemoryStore.new
+ end
+
+ # Raises an APICache::Error if it can't get a value. You should rescue this.
+ #
+ # Optionally call with a block. The value of the block is then used to
+ # set the cache rather than calling the url. Use it for example if you need
+ # to make another type of request, catch custom error codes etc. To signal
+ # that the call failed just throw :invalid - the value will then not be
+ # cached and the api will not be called again for options[:timeout] seconds.
+ #
+ # For example:
+ # [Add example here]
+ def self.get(url, options = {}, &block)
+ options = {
+ :cache => 600, # 10 minutes After this time fetch new value
+ :valid => 86400, # 1 day Expire even if not fetched new data
+ :period => 60, # 1 minute Don't call API more frequently than this
+ :timeout => 5 # 5 seconds Timeout to wait for response
+ }.merge(options)
+
+ if cache.exists?(url) && !cache.expired?(url, options[:cache])
+ # Cache is populated and not expired
+ cache.get(url)
+ else
+ # Cache is not populated or is expired
+ begin
+ r = Timeout::timeout(options[:timeout]) do
+ if block_given?
+ # This should raise APICache::Invalid if it is not correct
+ yield
+ else
+ r = Net::HTTP.get_response(URI.parse(url)).body
+ # TODO: Check that it's a 200 response
+ end
+ end
+ cache.set(url, r)
+ rescue Timeout::Error, APICache::Invalid
+ if cache.exists?(url)
+ cache.get(url)
+ else
+ raise APICache::Error
+ end
+ end
+ end
+ end
+end
+
+require 'api_cache/memory_store'
31 lib/api_cache/memory_store.rb
@@ -0,0 +1,31 @@
+class APICache::MemoryStore
+ def initialize
+ puts "Init cache"
+ @cache = {}
+ end
+
+ def expired?(name, timeout)
+ Time.now - created(name) > timeout
+ end
+
+ def set(name, value)
+ puts "Setting the cache"
+ @cache[name] = [Time.now, value]
+ value
+ end
+
+ def get(name)
+ puts "Serving from cache"
+ @cache[name][1]
+ end
+
+ def exists?(name)
+ !@cache[name].nil?
+ end
+
+ private
+
+ def created(name)
+ @cache[name][0]
+ end
+end
7 spec/api_cache_spec.rb
@@ -0,0 +1,7 @@
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe "api_cache" do
+ it "should do nothing" do
+ true.should == true
+ end
+end
2 spec/spec_helper.rb
@@ -0,0 +1,2 @@
+$TESTING=true
+$:.push File.join(File.dirname(__FILE__), '..', 'lib')

0 comments on commit de636b0

Please sign in to comment.