Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit 5e76559724a8f51b7423717b225185e6811cee3e @schneems committed Aug 6, 2011
Showing with 287 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +17 −0 .rvmrc
  3. +15 −0 Gemfile
  4. +1 −0 autotest/discover.rb
  5. +68 −0 lib/johnny_cache.rb
  6. +49 −0 readme.md
  7. +37 −0 spec/johnny_cache/cache_spec.rb
  8. +55 −0 spec/johnny_cache_spec.rb
  9. +44 −0 spec/spec_helper.rb
@@ -0,0 +1 @@
+Gemfile.lock
17 .rvmrc
@@ -0,0 +1,17 @@
+ruby_string="ree"
+gemset_name="johnny_cache"
+
+if rvm list strings | grep -q "${ruby_string}" ; then
+
+ rvm use "${ruby_string}@${gemset_name}" --create
+
+ # Complain if bundler isn't installed
+ if [[ -z "`gem which bundler 2>&1 | grep -v ERROR`" ]]; then
+ echo "You need bundler:"
+ echo ""
+ echo " gem install bundler"
+ echo ""
+ fi
+else
+ echo "${ruby_string} was not found, please run 'rvm install ${ruby_string}' and then cd back into the project directory."
+fi
15 Gemfile
@@ -0,0 +1,15 @@
+source 'http://rubygems.org'
+ gem 'activesupport'
+ gem 'keytar'
+
+group :development, :test do
+ gem 'rake', '~>0.8.7'
+ gem 'jeweler', '~>1.5.2'
+ gem "autotest-standalone"
+ gem "autotest-growl"
+end
+
+group :test do
+ gem 'sqlite3', '~> 1.3.3'
+ gem 'rspec', '~> 2.5'
+end
@@ -0,0 +1 @@
+Autotest.add_discovery { "rspec2" }
@@ -0,0 +1,68 @@
+require 'rubygems' #todo remove
+
+require 'keytar'
+require 'active_support/concern'
+
+
+
+module JohnnyCache
+ extend ActiveSupport::Concern
+ STORE = nil || Rails.cache
+
+ def cache(*args)
+ Cache.new(self, *args)
+ end
+
+
+ module ClassMethods
+ def cache(*args)
+ Cache.new(self, *args)
+ end
+ end
+
+ included do
+ include Keytar
+ end
+
+ class Cache
+ attr_accessor :caller, :method, :args, :options, :cache_method
+
+ def initialize(caller, *args)
+ cache_method = args.map {|x| x if x.is_a? Symbol }.compact.first
+ options = args.map {|x| x if x.is_a? Hash }.compact.first
+ self.cache_method = cache_method||:fetch
+ self.options = options
+ self.caller = caller
+ end
+
+ def key
+ key_method = "#{method}_key".to_sym
+ key = caller.send key_method, *args if caller.respond_to? key_method
+ key ||= caller.build_key(:name => method, :args => args)
+ end
+
+ def call_cach_method(options = {})
+ if cache_method == :fetch
+ JohnnyCache::STORE.fetch(key, options) do
+ caller.send method.to_sym, *args
+ end
+ elsif cache_method == :read
+ JohnnyCache::STORE.read(key, options)
+ elsif cache_method == :write
+ val = caller.send method.to_sym, *args
+ JohnnyCache::STORE.write(key, val, options)
+ end
+ end
+
+
+ def method_missing(method, *args, &blk)
+ if caller.respond_to? method
+ self.method = method
+ self.args = args
+ call_cach_method(options)
+ else
+ super
+ end
+ end
+ end
+end
@@ -0,0 +1,49 @@
+Johnny Cache
+------------
+
+I fell into a burning ring of slow methods, but they were cached cached cached, so the they came back super fast...
+
+JohnnyCache is used to wholesale cache a ruby method
+
+ class User < ActiveRecord::Base
+ include JohnnyCache
+
+ has_many :friends, pictures
+
+ def foo
+ # ... slow query
+ end
+ end
+ user = User.find_by_username("schneems")
+ user.friends # => [<# User ... >, <# User ... >]# normal method
+ user.cache.friends # => [<# User ... >, <# User ... >]# result from cache
+
+you can explicitly write, read methods.
+
+ user.cache(:read).pictures # => nil
+ user.cache(:write).pictures # => [<# Picture ...>, <# Picture ...>] # refreshes the cache
+ user.cache(:read).pictures # => [<# Picture ...>, <# Picture ...>]
+
+by default the `cache` method will will `:fetch` from the cache store. This means that if the key exists it will be pulled, if not the key will be set.
+
+ user.cache(:read).foo # => nil
+ user.cache.foo # => `# ... slow query` # sets the cache via :fetch
+ user.cache.foo # => `# ... slow query` # pulls from the cache
+
+You can also call `:fetch` explicitly if you prefer
+
+ user.cache(:fetch).foo # => `# ... slow query` # pulls from the cache
+
+
+Configuration
+=============
+
+Any configuration hashes passed to the cache method will be passed to the cache store
+
+ user.cache(:write, :expires_in => 5.seconds).pictures # => [<# Picture ...>, #... ]
+ user.cache(:read).pictures # => [<# Picture ...>, #... ]
+ sleep 10
+ user.cache(:read).pictures # => nil
+
+# TODO, finish docs
+
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+
+describe JohnnyCache::Cache do
+ before(:all) do
+ @user = User.new
+ end
+
+ before(:each) do
+ @uniq ||= 0
+ @uniq += 1
+ end
+
+ describe 'initialize' do
+ it 'saves caller' do
+ @user.cache.caller.should eq(@user)
+ end
+
+ it 'saves cache_method' do
+ cache_method = :fetch
+ @user.cache(cache_method).cache_method.should eq(cache_method)
+ end
+
+ it 'saves options' do
+ options = {:foo => "bar"}
+ @user.cache(options).options.should eq(options)
+ end
+
+ it 'saves options and cache_method' do
+ cache_method = :write
+ options = {:foo => "bar"}
+ cache = @user.cache(cache_method, options)
+ cache.options.should eq(options)
+ cache.cache_method.should eq(cache_method)
+ end
+ end
+end
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+
+describe JohnnyCache do
+ before(:each) do
+ @user = User.new
+ @uniq ||= 0
+ @uniq += 1
+ end
+
+ describe '' do
+ describe 'calling a cached method' do
+ describe 'fetch' do
+ it 'should return the result of the normal method' do
+ @user.cache.foo(@uniq).should == @user.foo(@uniq)
+ end
+
+ it 'should bypass the normal method if the cache is written' do
+ @user.cache(:write).foo(@uniq)
+ @user.should_not_receive(:foo)
+ @user.cache(:fetch).foo(@uniq)
+ end
+
+ it 'should bypass the normal method if the cache has been fetched before' do
+ @user.cache(:fetch).foo(@uniq)
+ @user.should_not_receive(:foo)
+ @user.cache(:fetch).foo(@uniq)
+ end
+
+ it 'should call the normal method if the cache has been not fetched before' do
+ @user.should_receive(:foo)
+ @user.cache(:fetch).foo(@uniq)
+ end
+
+ it 'should call the normal method if the cache has been not fetched before' do
+ @user.should_receive(:foo)
+ @user.cache.foo(@uniq)
+ end
+
+ end
+
+ describe 'read' do
+ it 'read should return nil if cache has not been set yet' do
+ @user.cache(:read).foo(@uniq).should eq(nil)
+ end
+
+ it 'read should return value if cache has been set' do
+ result = @user.cache.foo(@uniq)
+ @user.cache(:read).foo(@uniq).should eq(result)
+ end
+ end
+ end
+ end
+
+end
@@ -0,0 +1,44 @@
+require 'rubygems'
+
+
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '../..', 'lib'))
+
+## Fake rails for testing Rails.cache
+class Rails
+ def self.cache
+ self
+ end
+
+ def self.fetch(key, options, &block)
+ eval("@#{key.gsub(':', '_')} ||= block.call")
+ end
+
+ def self.write(key, val, options, &block)
+ eval("@#{key.gsub(':', '_')} = val")
+ end
+
+ def self.read(key, options)
+ eval("@#{key.gsub(':', '_')}")
+ end
+end
+
+
+require 'johnny_cache'
+class User
+ include JohnnyCache
+ define_keys :foo
+
+ def foo(var=nil)
+ "bar#{var}"
+ end
+
+ def id
+ @id ||= rand(100)
+ end
+end
+
+
+require 'rspec'
+require 'rspec/autorun'
+

0 comments on commit 5e76559

Please sign in to comment.