itrigga-cache 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ gem "memcached"
9
+ gem "itrigga-param_fu"
10
+
11
+ group :development do
12
+ gem "rspec", "1.3.0"
13
+ gem "bundler", "~> 1.0.0"
14
+ gem "jeweler", "~> 1.6.4"
15
+ gem "rcov", ">= 0"
16
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ itrigga-param_fu (0.0.1)
6
+ jeweler (1.6.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ memcached (1.3.5)
11
+ rake (0.8.7)
12
+ rcov (0.9.11)
13
+ rspec (1.3.0)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ bundler (~> 1.0.0)
20
+ itrigga-param_fu
21
+ jeweler (~> 1.6.4)
22
+ memcached
23
+ rcov
24
+ rspec (= 1.3.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Anson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,79 @@
1
+ = itrigga-cache
2
+
3
+ Adds methods to models and controllers to help caching output.
4
+
5
+ == Models
6
+ With models use like below to cache the output of the block
7
+
8
+ with_cache(:key => "unique_key") do
9
+ Model.find(:all)
10
+ end
11
+
12
+ To use a different timeout use the below. (Only supported for memcached backend. Filecache uses the value from setup!)
13
+
14
+ with_cache(:key => "unique_key", :timeout => 42) do
15
+ Model.find(:all)
16
+ end
17
+
18
+ == Controllers
19
+ With controllers use like:
20
+
21
+ with_controller_cache do
22
+ @items = Item.new_on_site(:site_id=>@current_site.id, :page=>params[:page], :per_page=>params[:per_page])
23
+ end
24
+
25
+ This will
26
+ - call the block
27
+ - render the content
28
+ - cache the *rendered* output
29
+ - set the :content_type according to the pasted in value (defaults to 'text/html')
30
+ To bypass the cache just have the "freshen" => true key in the params (ie as a query string param). This will force fresh the cache
31
+
32
+
33
+ == Installation
34
+ In environment.rb:
35
+ config.gem 'itrigga-cache', :lib=>'itrigga/cache'
36
+
37
+ In application_controller
38
+ require 'itrigga/cache'
39
+ include Itrigga::Cache
40
+ Itrigga::Cache.setup!
41
+
42
+ Itrigga::Cache.setup! initializes a cache instance. Memcache and filecache are currently supported
43
+
44
+ Itrigga::Cache.setup! :backend => :memcached
45
+ Options are:
46
+ :servers - ip:port of the memcache instances to use
47
+ :timeout - the default life of the cache item
48
+ Any other options get passed to the memcached initializer - see https://github.com/fauna/memcached for more info
49
+
50
+
51
+ Itrigga::Cache.setup! :backend => :filecache
52
+ Options are:
53
+ :cache_dir - the file path to where the cache files will live
54
+ :timeout - how long the cache key is valid for
55
+
56
+ Itrigga::Cache.setup! can be called as many times as you like, for as many backends as you like.
57
+ The cache gem will use the last defined (setup) backend as the default backend.
58
+
59
+ Use the :backend option to force a backend
60
+ with_cache(:key => "unique_key", :backend => :memcached) do
61
+ Model.find(:all)
62
+ end
63
+
64
+
65
+ == Contributing to itrigga-cache
66
+
67
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
68
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
69
+ * Fork the project
70
+ * Start a feature/bugfix branch
71
+ * Commit and push until you are happy with your contribution
72
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
73
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
74
+
75
+ == Copyright
76
+
77
+ Copyright (c) 2011 Anson. See LICENSE.txt for
78
+ further details.
79
+
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "itrigga-cache"
18
+ gem.homepage = "http://gemcutter.com/gems/itrigga-cache"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Adds caching methods}
21
+ gem.description = %Q{Wraps memcached with helpful methods}
22
+ gem.email = "support@itrigga.com"
23
+ gem.authors = ["Al Davidson","Anson Kelly"]
24
+ # dependencies defined in Gemfile
25
+ gem.add_dependency "memcached"
26
+ gem.add_dependency "itrigga-param_fu"
27
+ end
28
+ Jeweler::RubygemsDotOrgTasks.new
29
+
30
+ require 'spec/version'
31
+ require 'spec/rake/spectask'
32
+ require 'spec/ruby'
33
+
34
+ Spec::Rake::SpecTask.new(:spec) do |spec|
35
+ spec.spec_files = FileList['spec/**/*_spec.rb']
36
+ spec.spec_opts = ['--options', 'spec/spec.opts']
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ desc "Run all specs with rcov"
42
+ Spec::Rake::SpecTask.new(:rcov) do |t|
43
+ t.spec_files = FileList['spec/**/*_spec.rb']
44
+ t.spec_opts = ['--options', 'spec/spec.opts']
45
+ t.rcov = true
46
+ t.rcov_dir = 'coverage'
47
+ t.rcov_opts = ['--exclude', "features,kernel,load-diff-lcs\.rb,instance_exec\.rb,lib/spec.rb,lib/spec/runner.rb,^spec/*,bin/spec,examples,/gems,/Library/Ruby,\.autotest,#{ENV['GEM_HOME']}"]
48
+ t.rcov_opts << '--sort coverage --text-summary --aggregate coverage.data'
49
+ end
50
+
51
+
52
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.1
@@ -0,0 +1,195 @@
1
+ require 'memcached'
2
+ require 'trigga/param_fu'
3
+
4
+ module Itrigga
5
+ module Cache
6
+
7
+ @@ITRIGGA_CACHE_TYPE = :filecache
8
+
9
+ def self.included(base)
10
+ base.extend(ClassMethods)
11
+ base.send(:include, InstanceMethods)
12
+ base.send(:include, Trigga::ParamFu)
13
+ base.send(:include, ControllerMethods) if defined?(ActionController::Base) && base.ancestors.include?(ActionController::Base)
14
+ end
15
+
16
+ def self.setup!(opts ={})
17
+ if opts[:backend] == :memcached
18
+ @@ITRIGGA_CACHE_TYPE = :memcached
19
+ Itrigga::Cache::Memcache.setup!(opts)
20
+ else
21
+ Itrigga::Cache::Filecache.setup!(opts)
22
+ end
23
+ end
24
+
25
+ def self.instance(opts = {})
26
+ case ( opts[:backend] ||= @@ITRIGGA_CACHE_TYPE )
27
+ when :memcached
28
+ Itrigga::Cache::Memcache.instance
29
+ when :filecache
30
+ Itrigga::Cache::Filecache.instance
31
+ else
32
+ nil
33
+ end
34
+ end
35
+
36
+
37
+ module InstanceMethods
38
+
39
+ def with_cache(opts = {}, &block)
40
+ self.class.with_cache(opts, &block)
41
+ end
42
+
43
+ def caching_enabled?(opts = {})
44
+ self.class.caching_enabled?(opts)
45
+ end
46
+
47
+
48
+
49
+ def get_from_cache(key, opts = {})
50
+ self.class.get_from_cache(key, opts)
51
+ end
52
+
53
+ def set_to_cache(key, value, opts = {})
54
+ self.class.set_to_cache(key, value, opts)
55
+ end
56
+
57
+ def delete_from_cache(key, opts = {})
58
+ self.class.delete_from_cache(key, opts)
59
+ end
60
+
61
+
62
+ def cache_log(text, opts = {})
63
+ self.class.cache_log(text, opts)
64
+ end
65
+
66
+ end
67
+
68
+ module ClassMethods
69
+
70
+ def with_cache(opts = {}, &block)
71
+ require_param(opts, :key)
72
+
73
+ # if no cache then just return whatever the block gives us
74
+ unless caching_enabled?(opts)
75
+ cache_log "Cache not enabled!", opts
76
+ return block.call
77
+ end
78
+
79
+ # see if the key is already in cache
80
+ value = get_from_cache(opts[:key], opts)
81
+
82
+ # if no match then call the block and save result in cache
83
+ unless value
84
+ cache_log "Key '#{opts[:key]}' missing! Calling block and setting in cache", opts
85
+ value = block.call
86
+ set_to_cache(opts.delete(:key), value, opts) rescue value # incase memcache crashes or whateversolr1-internal-itrigga.dyndns-ip.com solr1-internal-itrigga.dyndns-ip.com
87
+ else
88
+ cache_log "Key '#{opts[:key]}' found in cache!", opts
89
+ end
90
+
91
+ value
92
+ end
93
+
94
+
95
+ def caching_enabled?(opts = {})
96
+ Itrigga::Cache.instance(opts).enabled == true
97
+ end
98
+
99
+
100
+
101
+
102
+
103
+ def get_from_cache(key, opts = {})
104
+ return nil unless caching_enabled?(opts)
105
+ cache_log "get_from_cache key: #{key}, opts: #{opts.inspect}" #if opts[:debug]
106
+ begin
107
+ Itrigga::Cache.instance(opts).get key
108
+ rescue Memcached::NotFound => not_found
109
+ # we dont care
110
+ nil
111
+ rescue Exception => e
112
+ cache_log "Exception in get_from_cache: #{e.message}"
113
+ nil
114
+ end
115
+ end
116
+
117
+
118
+
119
+ def set_to_cache(key, value, opts = {})
120
+ raise "Cache not Enabled" unless caching_enabled?(opts)
121
+
122
+ cache_log "set_to_cache key: #{key}, value: #{value}, opts: #{opts.inspect}"# if opts[:debug]
123
+ begin
124
+ Itrigga::Cache.instance(opts).set key, value, opts
125
+ rescue Exception => e
126
+ cache_log "Exception in set_to_cache: #{e.message}"
127
+ end
128
+
129
+ value
130
+ end
131
+
132
+
133
+ def delete_from_cache(key, opts = {})
134
+ return nil unless caching_enabled?(opts)
135
+ begin
136
+ cache_log "Deleting key #{key}", opts
137
+ Itrigga::Cache.instance(opts).delete key
138
+ rescue Exception => e
139
+ nil
140
+ end
141
+ end
142
+
143
+
144
+ def cache_log(text, opts = {})
145
+ message = "[CACHE] [#{opts[:backend] }] #{text}"
146
+ defined?(Rails.logger) && Rails.logger ? Rails.logger.info(message) : puts(message)
147
+ end
148
+
149
+ end
150
+
151
+
152
+ module ControllerMethods
153
+
154
+ def with_controller_cache( opts = {}, &block )
155
+
156
+ if caching_enabled?(opts)
157
+
158
+ opts[:key] = opts[:cache_key] || opts[:key] || (respond_to?(:cache_key) ? cache_key.to_s : nil) # backwards compat using :cache_key
159
+ raise ArgumentError("No cache key found") unless opts[:key]
160
+
161
+ begin
162
+
163
+ if params && params.kind_of?(Hash) && params["freshen"]
164
+ delete_from_cache opts[:key], opts
165
+ end
166
+
167
+ @content = with_cache(opts, &block)
168
+ @content ||= render_to_string unless performed?
169
+
170
+ rescue Exception => ex
171
+ cache_log "Error when rendering cache block: #{ex.message}", opts
172
+ @content ||= block.call
173
+ @content ||= render_to_string unless performed?
174
+ end
175
+
176
+ # flashes need to be rendered and replaced every request if we're in a html request
177
+ render_flashes if request.format.html? && respond_to?(:render_flashes)
178
+
179
+ else # cache is not enabled so just render the block
180
+ cache_log "Cache not enabled!", opts
181
+ @content ||= block.call
182
+ @content ||= render_to_string unless performed?
183
+ end
184
+
185
+ render(:text=>@content, :content_type=>( @content_type || "text/html")) unless performed?
186
+
187
+ @content
188
+
189
+ end
190
+
191
+
192
+ end
193
+
194
+ end
195
+ end
@@ -0,0 +1,63 @@
1
+ # http://stackoverflow.com/questions/4217968/a-problem-about-singleton-in-ruby
2
+
3
+ #
4
+ # To implement a backend it needs to respond to:
5
+ # - self.setup!
6
+ # - get
7
+ # - set
8
+ # - delete
9
+ #
10
+
11
+
12
+ require 'singleton'
13
+
14
+ module Itrigga
15
+ module Cache
16
+
17
+ class Filecache
18
+ include Singleton
19
+
20
+ attr_accessor :cache_dir, :timeout, :enabled
21
+
22
+ def self.setup!(opts = {})
23
+ instance.cache_dir = opts[:cache_dir] || "#{defined?(RAILS_ROOT) ? RAILS_ROOT : ''}/tmp/cache"
24
+ instance.timeout = opts[:timeout] || 3600
25
+ instance.enabled = true
26
+ instance
27
+ end
28
+
29
+ def get(key, opts = {})
30
+ if expired?(key, opts)
31
+ delete(key, opts)
32
+ nil
33
+ else
34
+ File.open( file_path(key, opts), 'r' ){ |f| f.read } if is_in_cache?(key, opts)
35
+ end
36
+ end
37
+
38
+ def set(key, content, opts = {})
39
+ File.open( file_path(key, opts), 'w' ){ |f| f.write(content) }
40
+ end
41
+
42
+ def delete(key, opts = {})
43
+ File.delete( file_path(key, opts)) if is_in_cache?(key, opts)
44
+ end
45
+
46
+ def file_path(key, opts = {})
47
+ File.expand_path(File.join(opts[:cache_dir] || cache_dir, key ))
48
+ end
49
+
50
+ def is_in_cache?(key, opts={} )
51
+ File.exists?( file_path(key, opts) )
52
+ end
53
+
54
+ def expired?(key, opts={} )
55
+ return true unless is_in_cache?(key, opts)
56
+ opts[:timeout] ||= timeout
57
+ Time.now - File.mtime(file_path(key, opts)) > opts[:timeout].to_i
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,55 @@
1
+ # http://stackoverflow.com/questions/4217968/a-problem-about-singleton-in-ruby
2
+ #
3
+ # To implement a backend it needs to respond to:
4
+ # - self.setup!
5
+ # - get
6
+ # - set
7
+ # - delete
8
+ #
9
+
10
+ require 'singleton'
11
+
12
+ module Itrigga
13
+ module Cache
14
+
15
+ class Memcache
16
+ include Singleton
17
+
18
+ attr_accessor :cache, :enabled
19
+
20
+ def initialize
21
+ @cache = nil
22
+ end
23
+
24
+ def self.setup!(opts = {})
25
+ servers = opts[:servers] || "localhost:11211"
26
+ opts.delete(:servers)
27
+
28
+ opts[:default_ttl] ||= opts[:timeout] if opts[:timeout] # rename default_ttl to something easier to use
29
+ opts.delete(:timeout)
30
+ instance.enabled = true
31
+
32
+ puts "Memcached init with: #{servers.inspect} - #{opts.inspect}"
33
+ instance.cache = ::Memcached.new(servers, opts)
34
+ end
35
+
36
+ def get(key, opts = {})
37
+ cache.get key
38
+ end
39
+
40
+ def set(key, value, opts = {})
41
+ if opts[:timeout]
42
+ cache.set key, value, opts[:timeout]
43
+ else
44
+ cache.set key, value
45
+ end
46
+ end
47
+
48
+ def delete(key, opts = {})
49
+ cache.delete key
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1 @@
1
+ Dir[File.expand_path(File.join(File.dirname(__FILE__),"cache","*.rb"))].each{|f| require f }
@@ -0,0 +1,155 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Itrigga::Cache::Filecache do
4
+ before do
5
+ Itrigga::Cache::Filecache.reset_instance
6
+ @file = mock("File")
7
+ @file.stub!(:read).and_return("content")
8
+ @file.stub!(:write)
9
+ File.stub!(:open).and_yield(@file)
10
+ File.stub!(:delete)
11
+ File.stub!(:exists?).and_return(false)
12
+ @instance = Itrigga::Cache::Filecache.instance
13
+ @opts = {}
14
+ end
15
+
16
+ it "should include Singleton" do
17
+ Itrigga::Cache::Filecache.should include Singleton
18
+ end
19
+
20
+ describe "setup!" do
21
+ describe "when given no options" do
22
+ it "should assign default cache_dir" do
23
+ Itrigga::Cache::Filecache.setup!
24
+ Itrigga::Cache::Filecache.instance.cache_dir.should == "#{RAILS_ROOT}/tmp/cache"
25
+ end
26
+
27
+ it "should assign default timeout" do
28
+ Itrigga::Cache::Filecache.setup!
29
+ Itrigga::Cache::Filecache.instance.timeout.should == 3600
30
+ end
31
+ end
32
+
33
+ it "should assign the given cache_dir" do
34
+ Itrigga::Cache::Filecache.setup! :cache_dir => "monkeys"
35
+ Itrigga::Cache::Filecache.instance.cache_dir.should == "monkeys"
36
+ end
37
+
38
+ it "should assign the given timeout" do
39
+ Itrigga::Cache::Filecache.setup! :timeout => 42
40
+ Itrigga::Cache::Filecache.instance.timeout.should == 42
41
+ end
42
+
43
+ it "should return the instance" do
44
+ Itrigga::Cache::Filecache.setup!().should be_a_kind_of Itrigga::Cache::Filecache
45
+ end
46
+ end
47
+
48
+
49
+ describe "instance methods" do
50
+ before do
51
+ Itrigga::Cache::Filecache.setup! # need to set the defaults on the instance
52
+ @file_path = "#{RAILS_ROOT}/tmp/cache/key"
53
+ end
54
+
55
+ describe "get" do
56
+ describe "when expired" do
57
+ before do
58
+ @instance.stub!(:expired?).and_return(true)
59
+ end
60
+
61
+ it "should call delete" do
62
+ @instance.should_receive(:delete).with("key", @opts)
63
+ @instance.get("key", @opts)
64
+ end
65
+
66
+ it "should return nil" do
67
+ @instance.get("key", @opts).should == nil
68
+ end
69
+ end
70
+
71
+ describe "when not expired" do
72
+ before do
73
+ @instance.stub!(:expired?).and_return(false)
74
+ end
75
+
76
+ it "should read the file when the file exists" do
77
+ @instance.stub!(:is_in_cache?).and_return(true)
78
+ File.should_receive(:open).with(@file_path,'r').and_yield(@file)
79
+ @file.should_receive(:read).and_return("content")
80
+ @instance.get("key", @opts).should == "content"
81
+ end
82
+
83
+ it "should not try to read the file if not exist" do
84
+ @instance.stub!(:is_in_cache?).and_return(false)
85
+ File.should_not_receive(:open).with(@file_path,'r')
86
+ @instance.get("key", @opts).should == nil
87
+ end
88
+ end
89
+ end
90
+
91
+
92
+
93
+ describe "set" do
94
+ it "should write the content to file" do
95
+ File.should_receive(:open).with(@file_path,'w').and_yield(@file)
96
+ @file.should_receive(:write).with("content")
97
+ @instance.set("key","content",@opts)
98
+ end
99
+ end
100
+
101
+
102
+
103
+ describe "delete" do
104
+ describe "when the file exists" do
105
+ before do
106
+ @instance.stub!(:is_in_cache?).and_return(true)
107
+ end
108
+
109
+ it "should call File.delete" do
110
+ File.should_receive(:delete).with(@file_path)
111
+ @instance.delete("key")
112
+ end
113
+ end
114
+
115
+ describe "when the file does not exists" do
116
+ before do
117
+ @instance.stub!(:is_in_cache?).and_return(false)
118
+ end
119
+
120
+ it "should call File.delete" do
121
+ File.should_not_receive(:delete)
122
+ @instance.delete("key")
123
+ end
124
+ end
125
+ end
126
+
127
+
128
+ describe "expired?" do
129
+ describe "when file is not in cache" do
130
+ it "should return true" do
131
+ @instance.should_receive(:is_in_cache?).with("key",@opts).and_return(false)
132
+ @instance.expired?("key", @opts).should == true
133
+ end
134
+ end
135
+
136
+ describe "when file is in cache" do
137
+ before do
138
+ @instance.stub!(:is_in_cache?).and_return(true)
139
+ Time.stub!(:now).and_return(42)
140
+ File.stub!(:mtime).and_return(20)
141
+ end
142
+
143
+ it "should return false if not expired" do
144
+ @instance.expired?("key",@opts).should == false
145
+ end
146
+
147
+ it "should return true if expired" do
148
+ @instance.expired?("key",{:timeout => 5}).should == true
149
+ end
150
+ end
151
+ end
152
+
153
+ end
154
+
155
+ end