lawnchair 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +59 -0
- data/Rakefile +44 -0
- data/VERSION +1 -0
- data/lawnchair.gemspec +55 -0
- data/lib/lawnchair.rb +49 -0
- data/spec/lawnchair_spec.rb +77 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/speed.rb +39 -0
- data/spec/speed_results.png +0 -0
- metadata +70 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Shane Wolf
|
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,59 @@
|
|
1
|
+
= Lawnchair
|
2
|
+
|
3
|
+
Very simple caching mechanism for arbitrary pieces of ruby code using Redis as the distributed (or local) cache
|
4
|
+
|
5
|
+
== Usage Examples
|
6
|
+
|
7
|
+
|
8
|
+
All you really need to do is wrap some expensive piece of Ruby code in the Lawnchair::Cache.me method as a block and it will be evaluated and the return value will cached in the given cache key.
|
9
|
+
|
10
|
+
First, connect to the Redis database
|
11
|
+
|
12
|
+
Lawnchair.connectdb
|
13
|
+
|
14
|
+
This will connect to a default database on localhost, if you want to connect to a particular database you can do:
|
15
|
+
|
16
|
+
Lawnchair.connectdb(Redis.new(:database => 11, :host => "127.0.0.1", :port => 6379))
|
17
|
+
|
18
|
+
Obligatory example:
|
19
|
+
|
20
|
+
Lawnchair::Cache.me(:key => "contrived_example") do
|
21
|
+
# ideally this would be something a little more computationally expensive, but sleep will have to do
|
22
|
+
(1..3).inject([]) do
|
23
|
+
|set, i| set << Time.now.strftime("%H:%M:%S")
|
24
|
+
sleep 1
|
25
|
+
set
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
The return value is exactly what you think it should be
|
30
|
+
|
31
|
+
["12:26:08", "12:26:09", "12:26:10"]
|
32
|
+
|
33
|
+
Now, since it is cached, any time this block method is called (for the next 60 minute) it will return those values. also, you will note it comes back instantly, instead of waiting on those sleeps.
|
34
|
+
|
35
|
+
If an hour is too long, or short for the cache key expiration you can set that to anything you want using the :expires_in hash key and entering a time in milliseconds
|
36
|
+
|
37
|
+
Lawnchair::Cache.me(:key => "contrived_example", :expires_in => 1.day) do
|
38
|
+
# expensive code to be cached for 24 hours
|
39
|
+
end
|
40
|
+
|
41
|
+
If you need to manually expire a key just call:
|
42
|
+
|
43
|
+
Lawnchair::Cache.expire("contrived_example")
|
44
|
+
|
45
|
+
If you need to flush all the values in the database
|
46
|
+
|
47
|
+
Lawnchair.flushdb
|
48
|
+
|
49
|
+
|
50
|
+
== Note on Patches/Pull Requests
|
51
|
+
|
52
|
+
* Fork the project.
|
53
|
+
* Make your feature addition or bug fix.
|
54
|
+
* I will promply ignore anything that is not a refactor that does not have associated specs :)
|
55
|
+
* Have fun
|
56
|
+
|
57
|
+
== Copyright
|
58
|
+
|
59
|
+
Copyright (c) 2010 Shane Wolf. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "lawnchair"
|
8
|
+
gem.summary = "Enclose resource expensive Ruby code in a block and cache it in redis"
|
9
|
+
gem.description = "Very simple caching mechanism for arbitrary pieces of resoucre ruby code using Redis as the distributed (or local) cache"
|
10
|
+
gem.email = "shanewolf@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/gizm0duck/lawnchair"
|
12
|
+
gem.authors = ["Shane Wolf"]
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'spec/rake/spectask'
|
21
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
22
|
+
spec.libs << 'lib' << 'spec'
|
23
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
24
|
+
end
|
25
|
+
|
26
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
27
|
+
spec.libs << 'lib' << 'spec'
|
28
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
29
|
+
spec.rcov = true
|
30
|
+
end
|
31
|
+
|
32
|
+
task :spec => :check_dependencies
|
33
|
+
|
34
|
+
task :default => :spec
|
35
|
+
|
36
|
+
require 'rake/rdoctask'
|
37
|
+
Rake::RDocTask.new do |rdoc|
|
38
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
39
|
+
|
40
|
+
rdoc.rdoc_dir = 'rdoc'
|
41
|
+
rdoc.title = "lawnchair #{version}"
|
42
|
+
rdoc.rdoc_files.include('README*')
|
43
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
44
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.0
|
data/lawnchair.gemspec
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{lawnchair}
|
8
|
+
s.version = "0.3.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Shane Wolf"]
|
12
|
+
s.date = %q{2010-02-07}
|
13
|
+
s.description = %q{Very simple caching mechanism for arbitrary pieces of resoucre ruby code using Redis as the distributed (or local) cache}
|
14
|
+
s.email = %q{shanewolf@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lawnchair.gemspec",
|
27
|
+
"lib/lawnchair.rb",
|
28
|
+
"spec/lawnchair_spec.rb",
|
29
|
+
"spec/spec.opts",
|
30
|
+
"spec/spec_helper.rb",
|
31
|
+
"spec/speed.rb",
|
32
|
+
"spec/speed_results.png"
|
33
|
+
]
|
34
|
+
s.homepage = %q{http://github.com/gizm0duck/lawnchair}
|
35
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
36
|
+
s.require_paths = ["lib"]
|
37
|
+
s.rubygems_version = %q{1.3.5}
|
38
|
+
s.summary = %q{Enclose resource expensive Ruby code in a block and cache it in redis}
|
39
|
+
s.test_files = [
|
40
|
+
"spec/lawnchair_spec.rb",
|
41
|
+
"spec/spec_helper.rb",
|
42
|
+
"spec/speed.rb"
|
43
|
+
]
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
47
|
+
s.specification_version = 3
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
50
|
+
else
|
51
|
+
end
|
52
|
+
else
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
data/lib/lawnchair.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'redis'
|
3
|
+
|
4
|
+
module Lawnchair
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_reader :redis
|
8
|
+
|
9
|
+
def connectdb(redis=nil)
|
10
|
+
@redis ||= Redis.new(:db => 11)
|
11
|
+
end
|
12
|
+
|
13
|
+
def flushdb
|
14
|
+
redis.flushdb
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Cache
|
19
|
+
def self.me(options = {}, &block)
|
20
|
+
raise "Cache key please!" unless options.has_key?(:key)
|
21
|
+
|
22
|
+
if exists?(options[:key])
|
23
|
+
Marshal.load(Lawnchair.redis[compute_key(options[:key])])
|
24
|
+
else
|
25
|
+
val = block.call
|
26
|
+
expires_in = compute_expiry(options[:expires_in])
|
27
|
+
Lawnchair.redis.set(compute_key(options[:key]), Marshal.dump(val), expires_in)
|
28
|
+
return val
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.compute_key(key)
|
33
|
+
"Lawnchair:#{key}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.expire(key)
|
37
|
+
Lawnchair.redis.del compute_key(key)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.exists?(key)
|
41
|
+
return !!Lawnchair.redis[compute_key(key)]
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.compute_expiry(ms)
|
45
|
+
ms ||= 3600000
|
46
|
+
ms/1000
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Lawnchair::Cache" do
|
4
|
+
describe ".me" do
|
5
|
+
it "raises an exception if no key is given" do
|
6
|
+
lambda do
|
7
|
+
Lawnchair::Cache.me { 1 }
|
8
|
+
end.should raise_error("Cache key please!")
|
9
|
+
end
|
10
|
+
|
11
|
+
context "when the object the block returns is a string" do
|
12
|
+
it "returns the item from the cache if it exists" do
|
13
|
+
Lawnchair::Cache.me(:key => "yogurt") { "strawberry/banana" }
|
14
|
+
|
15
|
+
Lawnchair.redis["Lawnchair:yogurt"] = Marshal.dump("FROM THE CACHE")
|
16
|
+
x = Lawnchair::Cache.me(:key => "yogurt") { "strawberry/banana" }
|
17
|
+
x.should == "FROM THE CACHE"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "sets the return value in the cache key given" do
|
21
|
+
Lawnchair::Cache.me(:key => "pizza") { "muschroom/onion" }
|
22
|
+
Lawnchair.redis["Lawnchair:pizza"].should == Marshal.dump("muschroom/onion")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when the object the block returns is an object" do
|
27
|
+
it "returns the value if it exists" do
|
28
|
+
expected_object = [1,2,3,4]
|
29
|
+
Lawnchair::Cache.me(:key => "marshalled_array") { expected_object }
|
30
|
+
|
31
|
+
x = Lawnchair::Cache.me(:key => "marshalled_array") { "JUNK DATA" }
|
32
|
+
x.should == expected_object
|
33
|
+
end
|
34
|
+
|
35
|
+
it "marshalls the object into redis" do
|
36
|
+
expected_object = [1,2,3,4]
|
37
|
+
Lawnchair::Cache.me(:key => "marshalled_array") { expected_object }
|
38
|
+
|
39
|
+
Marshal.load(Lawnchair.redis["Lawnchair:marshalled_array"]).should == [1,2,3,4]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "sets a default ttl of 60 minutes" do
|
44
|
+
Lawnchair::Cache.me(:key => "pizza") { "muschroom/onion" }
|
45
|
+
Lawnchair.redis.ttl("Lawnchair:pizza").should == 3600 # seconds
|
46
|
+
end
|
47
|
+
|
48
|
+
it "allows you to override the default ttl" do
|
49
|
+
Lawnchair::Cache.me(:key => "pizza", :expires_in => 1000) { "muschroom/onion" }
|
50
|
+
Lawnchair.redis.ttl("Lawnchair:pizza").should == 1 # seconds
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe ".exists?" do
|
55
|
+
it "returns false when the key does not exist" do
|
56
|
+
Lawnchair.redis.keys('*').should_not include("Lawnchair:mu")
|
57
|
+
Lawnchair::Cache.exists?("mu").should be_false
|
58
|
+
end
|
59
|
+
|
60
|
+
it "returns true when the key exists" do
|
61
|
+
Lawnchair.redis["Lawnchair:mu"] = "fasa"
|
62
|
+
Lawnchair.redis.keys('*').should include("Lawnchair:mu")
|
63
|
+
Lawnchair::Cache.exists?("mu").should be_true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe ".expire" do
|
68
|
+
it "should only expire the key specified" do
|
69
|
+
Lawnchair.redis["Lawnchair:mu"] = "fasa"
|
70
|
+
Lawnchair.redis["Lawnchair:sim"] = "ba"
|
71
|
+
|
72
|
+
Lawnchair::Cache.expire("mu")
|
73
|
+
Lawnchair.redis["Lawnchair:mu"].should be_nil
|
74
|
+
Lawnchair.redis["Lawnchair:sim"].should == "ba"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
require 'rubygems'
|
4
|
+
require 'lawnchair'
|
5
|
+
require 'spec'
|
6
|
+
require 'spec/autorun'
|
7
|
+
require 'redis'
|
8
|
+
|
9
|
+
Spec::Runner.configure do |config|
|
10
|
+
config.before(:all) { Lawnchair.connectdb(Redis.new(:db => 11)) }
|
11
|
+
config.before(:each) { Lawnchair.flushdb }
|
12
|
+
end
|
data/spec/speed.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require "#{File.dirname(__FILE__)}/../lib/lawnchair"
|
3
|
+
|
4
|
+
Lawnchair.redis.flushdb
|
5
|
+
|
6
|
+
# Totally contrived and fairly useless example... just wanted to make sure the overhead of
|
7
|
+
# reading and marshalling the data isn't obscene
|
8
|
+
|
9
|
+
# *** Performing 1000 iterations ***
|
10
|
+
# user system total real
|
11
|
+
# cached: 0.140000 0.040000 0.180000 ( 0.292324)
|
12
|
+
# not cached: 26.070000 0.620000 26.690000 ( 27.156388)
|
13
|
+
|
14
|
+
n = (ARGV.shift || 1000).to_i
|
15
|
+
|
16
|
+
puts "*** Performing #{n} iterations ***"
|
17
|
+
|
18
|
+
def expensive_stuff
|
19
|
+
a = []
|
20
|
+
100.times do
|
21
|
+
a << Date.parse("Dec 3. 1981")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Benchmark.bm(7) do |x|
|
26
|
+
x.report("cached:") do
|
27
|
+
(1..n).each do |i|
|
28
|
+
Lawnchair::Cache.me(:key => "foo") do
|
29
|
+
expensive_stuff
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
x.report("not cached:") do
|
35
|
+
(1..n).each do |i|
|
36
|
+
expensive_stuff
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
Binary file
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lawnchair
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shane Wolf
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-07 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Very simple caching mechanism for arbitrary pieces of resoucre ruby code using Redis as the distributed (or local) cache
|
17
|
+
email: shanewolf@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- .document
|
27
|
+
- .gitignore
|
28
|
+
- LICENSE
|
29
|
+
- README.rdoc
|
30
|
+
- Rakefile
|
31
|
+
- VERSION
|
32
|
+
- lawnchair.gemspec
|
33
|
+
- lib/lawnchair.rb
|
34
|
+
- spec/lawnchair_spec.rb
|
35
|
+
- spec/spec.opts
|
36
|
+
- spec/spec_helper.rb
|
37
|
+
- spec/speed.rb
|
38
|
+
- spec/speed_results.png
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://github.com/gizm0duck/lawnchair
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --charset=UTF-8
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.3.5
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: Enclose resource expensive Ruby code in a block and cache it in redis
|
67
|
+
test_files:
|
68
|
+
- spec/lawnchair_spec.rb
|
69
|
+
- spec/spec_helper.rb
|
70
|
+
- spec/speed.rb
|