julik-extremist_cache 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt ADDED
@@ -0,0 +1,7 @@
1
+ === 0.0.2 / 2009-01-19
2
+
3
+ * Fix botched builds
4
+
5
+ === 0.0.1 / 2009-01-19
6
+
7
+ * Gemification
data/Manifest.txt ADDED
@@ -0,0 +1,10 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ init.rb
6
+ lib/extremist_cache.rb
7
+ lib/marking_store.rb
8
+ lib/touching_store.rb
9
+ test/digest_benchmark.rb
10
+ test/test_extremist_cache.rb
data/README.txt ADDED
@@ -0,0 +1,7 @@
1
+ = extremist_cache
2
+
3
+ * http://live.julik.nl/2008/07/cache-with-automatic-transmission
4
+
5
+ == DESCRIPTION:
6
+
7
+ Oh please spare me.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require './lib/extremist_cache'
4
+
5
+ Hoe::RUBY_FLAGS.replace ENV['RUBY_FLAGS'] || "-I#{%w(lib ext bin test).join(File::PATH_SEPARATOR)}" +
6
+ (Hoe::RUBY_DEBUG ? " #{RUBY_DEBUG}" : '')
7
+
8
+ Hoe.new('extremist_cache', ExtremistCache::VERSION) do |p|
9
+ p.developer('Julik', 'me@julik.nl')
10
+ p.extra_deps << 'rails'
11
+ p.rubyforge_name = 'extremist_cache'
12
+ end
13
+
14
+ begin
15
+ require 'load_multi_rails_rake_tasks'
16
+ rescue LoadError
17
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + '/lib/extremist_cache'
2
+ ExtremistCache.bootstrap!
@@ -0,0 +1,40 @@
1
+ require 'openssl'
2
+ module ExtremistCache
3
+ DIGEST = OpenSSL::Digest::MD4
4
+ VERSION = '0.0.2'
5
+
6
+ # To be called from a plugin init.rb
7
+ def self.bootstrap!
8
+ if !@boostrapped
9
+ ::ActionController::Base.send(:include, ExtremistCache)
10
+ ::ActionController::Base.send(:helper, ExtremistCache)
11
+ end
12
+ end
13
+
14
+ # This one should be used from controllers and helpers
15
+ def cached_based_on(*whatever)
16
+ segmented_path = lazy_cache_key_for(*whatever)
17
+ (@controller || self).read_fragment(segmented_path) || (@controller || self).write_fragment(segmented_path, yield)
18
+ end
19
+
20
+ # This one is for blocks that return objects, results of expensive computation
21
+ def value_cached_based_on(*whatever)
22
+ segmented_path = lazy_cache_key_for(whatever)
23
+ fragment = (@controller || self).read_fragment(segmented_path)
24
+ fragment ? Marshal.load(fragment) : returning(yield) {|f| (@controller || self).write_fragment(segmented_path, Marshal.dump(f)) }
25
+ end
26
+
27
+ def erb_cache_based_on(*whatever, &block)
28
+ @controller.cache_erb_fragment(block, lazy_cache_key_for(*whatever))
29
+ end
30
+
31
+ private
32
+ def lazy_cache_key_for(*anything)
33
+ calling_method = caller(2)[0..1]
34
+ # OpenSSL's MD5 is much faster than the Ruby one - like ten times
35
+ checksum = DIGEST.hexdigest(Marshal.dump(calling_method + anything))
36
+ # Splitting an MD5 on 2 symbols will give us good rainbow spread across
37
+ # directories with 256 subdirectories max, in each given directory
38
+ segmented_path = "extremist-cache/" + checksum.scan(/(.{2})/).join('/')
39
+ end
40
+ end
@@ -0,0 +1,50 @@
1
+ class MarkingStore < ::ActionController::Caching::Fragments::MemoryStore
2
+ EACH_NTH_REQUEST = 20
3
+ TTL = 20.days
4
+ class AccessMark
5
+ include Comparable
6
+ attr_reader :key, :atime
7
+ def initialize(key)
8
+ @key = key; update
9
+ end
10
+
11
+ def update
12
+ @atime = Time.now.to_i
13
+ end
14
+
15
+ def <=>(other)
16
+ @atime <=> other.atime
17
+ end
18
+ end
19
+
20
+ def initialize
21
+ @at_request = 0
22
+ @marks = []
23
+ end
24
+
25
+ def write_fragment(key, value, options = nil)
26
+ @marks << AccessMark.new(key)
27
+ @cache[key] = value
28
+ end
29
+
30
+ def read_fragment(key, value, options = nil)
31
+ @at_request += 1
32
+ return nil unless @cache[key]
33
+
34
+ @marks.find{|m| m.key == key }.update
35
+ run_gc if (@at_request % EACH_NTH_REQUEST)
36
+ @accessed[key]
37
+ end
38
+
39
+ private
40
+ def run_gc
41
+ threshold = (Time.now.to_i - TTL)
42
+ @marks.sort!
43
+ new_marks = []
44
+ ((@marks.length *-1)..0).each do | idx |
45
+ new_marks << @marks[idx]
46
+ break if @marks[idx].atime < threshold
47
+ end
48
+ @marks = new_marks
49
+ end
50
+ end
@@ -0,0 +1,13 @@
1
+ # Will not only read the file but mark it's modification date. We can then expire
2
+ # "everything that hasn't b been used for years" to save space
3
+ class TouchingStore < ::ActionController::Caching::Fragments::FileStore
4
+ def read(name, options = nil)
5
+ begin
6
+ st = Time.now
7
+ File.utime(st, st, real_file_path(name))
8
+ File.open(real_file_path(name), 'rb') { |f| f.read }
9
+ rescue
10
+ nil
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,36 @@
1
+ require 'openssl'
2
+ require 'digest'
3
+
4
+ lets = ("a".."z").to_a
5
+
6
+ require 'benchmark'
7
+
8
+ module Noop
9
+ def self.hexdigest(of); of; end
10
+ end
11
+
12
+ [Noop, Digest::MD5, OpenSSL::Digest::MD5, OpenSSL::Digest::MD4, OpenSSL::Digest::SHA1].each do | d |
13
+
14
+ puts "\n\n #{d} with value digest"
15
+ Benchmark.bm do | x|
16
+ hundred_values = (0..100).map { (0..100).map { lets[rand(lets.length)]}.join }
17
+
18
+ x.report do
19
+ 300.times do
20
+ hundred_values.map {|let| d.hexdigest(Marshal.dump(let)) }
21
+ end
22
+ end
23
+ end
24
+
25
+ puts "\n\n #{d} with hash digest"
26
+ Benchmark.bm do | x|
27
+ hundred_values = (0..100).map { (0..100).map { lets[rand(lets.length)]}.join }
28
+
29
+ x.report do
30
+ 300.times do
31
+ hundred_values.map {|let| d.hexdigest(Marshal.dump(let).hash.to_s) }
32
+ end
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,116 @@
1
+ require 'rubygems'
2
+
3
+ require 'test/unit'
4
+ require 'flexmock'
5
+ require 'flexmock/test_unit'
6
+ require 'stringio'
7
+
8
+ require 'action_controller'
9
+ require 'action_controller/caching'
10
+ require 'action_controller/caching/fragments'
11
+ require 'action_controller/test_process'
12
+ require File.dirname(__FILE__) + '/../init'
13
+
14
+ $routes = ActionController::Routing::Routes.draw do |map|
15
+ map.connect ':controller/:action/:id'
16
+ end
17
+
18
+
19
+ class HashingTest < Test::Unit::TestCase
20
+ class Handle
21
+ include ExtremistCache
22
+ def b(*a)
23
+ lazy_cache_key_for(*a)
24
+ end
25
+ end
26
+
27
+ def setup
28
+ @h = Handle.new
29
+ end
30
+
31
+ def test_hashing_yields_segmented_md5
32
+ [1, [1,2,3], {"x" => 'y'}, Object.new].each do | value |
33
+ assert_match /^extremist\-cache\/([a-z\d]{2})\/([a-z\d]{2})\/([a-z\d]{2})/, @h.b(value),
34
+ "Should be a segmented path"
35
+ end
36
+ end
37
+
38
+ def test_hashing_yields_same_hash_for_different_hashes
39
+ assert_equal @h.b({:a => 'b', :c => 'd'}), @h.b({:c => 'd', :a => 'b'})
40
+ end
41
+
42
+ def test_hashing_depends_on_values_and_caller
43
+ values = [
44
+ "abcdef",
45
+ [1, 2, 3],
46
+ (34..190),
47
+ {"foo" => :bar, :x => [1,5,10]},
48
+ Object.new,
49
+ ]
50
+
51
+ via_method_a = values.inject([]) do | hashes, one |
52
+ hashes << from_one_method(one)
53
+ end
54
+
55
+ via_method_a_once_again = values.inject([]) do | hashes, one |
56
+ hashes << from_one_method(one)
57
+ end
58
+
59
+ via_method_b = values.inject([]) do | hashes, one |
60
+ hashes << from_other_method(one)
61
+ end
62
+
63
+ assert_not_equal via_method_a, via_method_a_once_again, "Hashing depends on the caller"
64
+ assert_not_equal via_method_a, via_method_b, "Hashing depends on the caller"
65
+ end
66
+
67
+ private
68
+ def from_one_method(*a)
69
+ @h.b(*a)
70
+ end
71
+
72
+ def from_other_method(*a)
73
+ @h.b(*a)
74
+ end
75
+ end
76
+
77
+ class BogusController < ActionController::Base
78
+ def action_that_calls_cache
79
+ retval = value_cached_based_on(params[:id]) { perform_expensive_computation(params[:id]) }
80
+
81
+ signal_return_value(retval)
82
+ render :nothing => true
83
+ end
84
+ def rescue_action(e); raise e; end
85
+ def perform_expensive_computation(flag); return "wroom #{flag}"; end
86
+ def signal_return_value(retval); end
87
+ end
88
+
89
+ class ValueReturnTest < Test::Unit::TestCase
90
+ def setup
91
+ @store = ActiveSupport::Cache.lookup_store :memory_store
92
+ ::ActionController::Base.cache_store = @store
93
+
94
+ @controller = BogusController.new
95
+ @controller.logger = Logger.new(StringIO.new)
96
+ @request = ActionController::TestRequest.new
97
+ @response = ActionController::TestResponse.new
98
+
99
+ super
100
+ end
101
+
102
+ def test_cache_through
103
+ wrapped_key = '123'
104
+
105
+ flexmock(@controller).should_receive(:lazy_cache_key_for).with([wrapped_key]).at_least.times(6).and_return("some_key")
106
+
107
+ computation_result = "abcdef #{rand}"
108
+ flexmock(@controller).should_receive(:perform_expensive_computation).at_most.once.and_return(computation_result)
109
+ flexmock(@controller).should_receive(:signal_return_value).with(computation_result).at_least.times(6)
110
+
111
+ 6.times do
112
+ assert_nothing_raised { get :action_that_calls_cache, :id => wrapped_key }
113
+ end
114
+ end
115
+
116
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: julik-extremist_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Julik
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-19 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rails
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: hoe
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 1.8.2
32
+ version:
33
+ description: Oh please spare me.
34
+ email:
35
+ - me@julik.nl
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - History.txt
42
+ - Manifest.txt
43
+ - README.txt
44
+ files:
45
+ - History.txt
46
+ - Manifest.txt
47
+ - README.txt
48
+ - Rakefile
49
+ - init.rb
50
+ - lib/extremist_cache.rb
51
+ - lib/marking_store.rb
52
+ - lib/touching_store.rb
53
+ - test/digest_benchmark.rb
54
+ - test/test_extremist_cache.rb
55
+ has_rdoc: true
56
+ homepage: http://live.julik.nl/2008/07/cache-with-automatic-transmission
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --main
60
+ - README.txt
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ requirements: []
76
+
77
+ rubyforge_project: extremist_cache
78
+ rubygems_version: 1.2.0
79
+ signing_key:
80
+ specification_version: 2
81
+ summary: Oh please spare me.
82
+ test_files:
83
+ - test/test_extremist_cache.rb