extremist_cache 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,11 @@
1
+ === 0.0.3 / 2009-01-19
2
+
3
+ * Fix missing fragment cache helper error on fresh Rails
4
+
5
+ === 0.0.2 / 2009-01-19
6
+
7
+ * Fix botched builds
8
+
9
+ === 0.0.1 / 2009-01-19
10
+
11
+ * Gemification
@@ -0,0 +1,11 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ TODO.txt
6
+ init.rb
7
+ lib/extremist_cache.rb
8
+ lib/marking_store.rb
9
+ lib/touching_store.rb
10
+ test/digest_benchmark.rb
11
+ test/test_extremist_cache.rb
@@ -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.
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'hoe'
3
+ require './lib/extremist_cache'
4
+
5
+ Hoe::RUBY_FLAGS.gsub!(/^-w/, '') # zap!
6
+ Hoe.new('extremist_cache', ExtremistCache::VERSION) do |p|
7
+ p.developer('Julik', 'me@julik.nl')
8
+ p.description = "Object-keyed caches for anything"
9
+ p.extra_deps << 'rails'
10
+ end
11
+
12
+ begin
13
+ require 'load_multi_rails_rake_tasks'
14
+ rescue LoadError
15
+ end
@@ -0,0 +1 @@
1
+ * There is no cache_erb_fragment in modern Rails
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + '/lib/extremist_cache'
2
+ ExtremistCache.bootstrap!
@@ -0,0 +1,45 @@
1
+ require 'openssl'
2
+ module ExtremistCache
3
+ DIGEST = OpenSSL::Digest::MD4
4
+ VERSION = '0.0.3'
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
+ begin
29
+ name = {:__extremist => lazy_cache_key_for(whatever)}
30
+ @controller.fragment_for(output_buffer, name, nil, &block)
31
+ rescue NoMethodError
32
+ @controller.cache_erb_fragment(block, lazy_cache_key_for(*whatever))
33
+ end
34
+ end
35
+
36
+ private
37
+ def lazy_cache_key_for(*anything)
38
+ calling_method = caller(2)[0..1]
39
+ # OpenSSL's MD5 is much faster than the Ruby one - like ten times
40
+ checksum = DIGEST.hexdigest(Marshal.dump(calling_method + anything))
41
+ # Splitting an MD5 on 2 symbols will give us good rainbow spread across
42
+ # directories with 256 subdirectories max, in each given directory
43
+ segmented_path = "extremist-cache/" + checksum.scan(/(.{2})/).join('/')
44
+ end
45
+ 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,128 @@
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
+
88
+ def action_that_renders_with_cache
89
+ t = '<% erb_cache_based_on(params[:id]) do %> Foo<%= params[:id] %> <%= Time.now.usec %> <% end %>'
90
+ render :inline => t
91
+ end
92
+ end
93
+
94
+ class ValueReturnTest < Test::Unit::TestCase
95
+ def setup
96
+ @store = ActiveSupport::Cache.lookup_store :memory_store
97
+ ::ActionController::Base.cache_store = @store
98
+
99
+ @controller = BogusController.new
100
+ @controller.logger = Logger.new(StringIO.new)
101
+ @request = ActionController::TestRequest.new
102
+ @response = ActionController::TestResponse.new
103
+
104
+ super
105
+ end
106
+
107
+ def test_cache_through
108
+ wrapped_key = '123'
109
+
110
+ flexmock(@controller).should_receive(:lazy_cache_key_for).with([wrapped_key]).at_least.times(6).and_return("some_key")
111
+
112
+ computation_result = "abcdef #{rand}"
113
+ flexmock(@controller).should_receive(:perform_expensive_computation).at_most.once.and_return(computation_result)
114
+ flexmock(@controller).should_receive(:signal_return_value).with(computation_result).at_least.times(6)
115
+
116
+ 6.times do
117
+ assert_nothing_raised { get :action_that_calls_cache, :id => wrapped_key }
118
+ end
119
+ end
120
+
121
+ def test_cache_through_helper
122
+ get :action_that_renders_with_cache, :id => "Poeing"
123
+ first_body = @response.body.dup
124
+
125
+ 10.times { get :action_that_renders_with_cache, :id => "Poeing" }
126
+ assert_equal first_body, @response.body
127
+ end
128
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: extremist_cache
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 3
10
+ version: 0.0.3
11
+ platform: ruby
12
+ authors:
13
+ - Julik
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-22 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rails
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: hoe
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 35
44
+ segments:
45
+ - 2
46
+ - 9
47
+ - 4
48
+ version: 2.9.4
49
+ type: :development
50
+ version_requirements: *id002
51
+ description: Object-keyed caches for anything
52
+ email:
53
+ - me@julik.nl
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files:
59
+ - History.txt
60
+ - Manifest.txt
61
+ - README.txt
62
+ - TODO.txt
63
+ files:
64
+ - History.txt
65
+ - Manifest.txt
66
+ - README.txt
67
+ - Rakefile
68
+ - TODO.txt
69
+ - init.rb
70
+ - lib/extremist_cache.rb
71
+ - lib/marking_store.rb
72
+ - lib/touching_store.rb
73
+ - test/digest_benchmark.rb
74
+ - test/test_extremist_cache.rb
75
+ - .gemtest
76
+ has_rdoc: true
77
+ homepage: http://live.julik.nl/2008/07/cache-with-automatic-transmission
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options:
82
+ - --main
83
+ - README.txt
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project: extremist_cache
107
+ rubygems_version: 1.6.2
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Oh please spare me.
111
+ test_files:
112
+ - test/test_extremist_cache.rb