extremist_cache 0.0.3

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.
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