public
Rubygem
Description: a humane, eval-safe templating system using Hpricot
Clone URL: git://github.com/mattly/hpreserve.git
Search Repo:
Click here to lend your support to: hpreserve and make a donation at www.pledgie.com !
rendering too slow? now with built-in caching for expensive variables and 
the like
mattly (author)
Tue May 13 16:55:38 -0700 2008
commit  2fe01b8cfeb32b1954550d6c247741b9b716ba57
tree    9699705029c08d590db71c73f752e5c921c9bad2
parent  e74c58b0875f11b60a1c50233895147bc499fb97
...
3
4
5
 
 
6
7
8
...
15
16
17
 
 
...
3
4
5
6
7
8
9
10
...
17
18
19
20
21
0
@@ -3,6 +3,8 @@
0
 README.txt
0
 Rakefile
0
 lib/hpreserve.rb
0
+lib/hpreserve/abstract_cacher.rb
0
+lib/hpreserve/file_cacher.rb
0
 lib/hpreserve/filters.rb
0
 lib/hpreserve/extensions.rb
0
 lib/hpreserve/parser.rb
0
@@ -15,4 +17,6 @@
0
 spec/spec_helper.rb
0
 spec/standard_filters_spec.rb
0
 spec/variables_spec.rb
0
+spec/abstract_cacher_spec.rb
0
+spec/file_cacher_spec.rb
...
6
7
8
 
 
9
10
11
...
6
7
8
9
10
11
12
13
0
@@ -6,6 +6,8 @@
0
 
0
 require 'hpreserve/parser'
0
 require 'hpreserve/extensions'
0
+require 'hpreserve/abstract_cacher'
0
+require 'hpreserve/file_cacher'
0
 require 'hpreserve/variables'
0
 require 'hpreserve/filters'
0
 require 'hpreserve/standard_filters'
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
0
@@ -1 +1,36 @@
0
+module Hpreserve
0
+ class AbstractCacher
0
+
0
+ attr_accessor :patterns, :storage
0
+
0
+ def initialize(patterns)
0
+ self.patterns = patterns
0
+ end
0
+
0
+ def match?(variable)
0
+ pattern = patterns.detect {|p| variable.match(p[:match]) }
0
+ return nil unless pattern
0
+ variable.gsub(pattern[:match], pattern[:key])
0
+ end
0
+
0
+
0
+ # overwrite these in your ConcreteCacher to do use whatever you use
0
+
0
+ def retrieve(key)
0
+ @storage ||= {}
0
+ storage[key]
0
+ end
0
+
0
+ def store(key, value)
0
+ @storage ||= {}
0
+ storage[key] = value
0
+ end
0
+
0
+ def expire(pattern)
0
+ @storage ||= {}
0
+ storage.delete_if {|key, value| key.match(pattern) }
0
+ end
0
+
0
+ end
0
+end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
0
@@ -1 +1,26 @@
0
+class Hpreserve::FileCacher < Hpreserve::AbstractCacher
0
+
0
+ attr_accessor :base_dir
0
+
0
+ def initialize(patterns, base)
0
+ super(patterns)
0
+ self.base_dir = base
0
+ end
0
+
0
+ def store(key, value)
0
+ file = File.join(base_dir, key)
0
+ FileUtils.mkdir_p(File.dirname(file)) unless File.directory?(File.dirname(file))
0
+ File.open(file, 'w') {|f| f << value }
0
+ end
0
+
0
+ def retrieve(key)
0
+ file = File.join(base_dir, key)
0
+ File.read(file) if File.exists?(file)
0
+ end
0
+
0
+ def expire(keys)
0
+ Dir[File.join(base_dir, keys)].each {|f| File.delete(f) }
0
+ end
0
+
0
+end
...
6
7
8
9
 
10
11
12
...
41
42
43
44
45
46
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
49
50
...
57
58
59
 
 
60
61
62
...
6
7
8
 
9
10
11
12
...
41
42
43
 
 
 
 
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
...
68
69
70
71
72
73
74
75
0
@@ -6,7 +6,7 @@
0
       doc.render(variables)
0
     end
0
     
0
- attr_accessor :doc, :variables, :filter_sandbox, :include_base
0
+ attr_accessor :doc, :variables, :filter_sandbox, :include_base, :cacher
0
   
0
     def initialize(doc='')
0
       self.doc = Hpricot(doc)
0
@@ -41,10 +41,21 @@
0
     end
0
     
0
     def render_node_content(node)
0
- value = variables[node.remove_attribute('content').strip.split('.')]
0
- value = value['default'] if value.respond_to?(:has_key?)
0
- render_collection(node, value) and return if value.is_a?(Array)
0
- node.inner_html = value
0
+ variable = node.remove_attribute('content').strip
0
+ cache = cacher.nil? ? nil : cacher.match?(variable)
0
+ if cache and stored = cacher.retrieve(cache)
0
+ node.swap(stored)
0
+ else
0
+ value = variables[variable.split('.')]
0
+ value = value['default'] if value.respond_to?(:has_key?)
0
+ if value.is_a?(Array)
0
+ render_collection(node, value)
0
+ else
0
+ node.inner_html = value
0
+ end
0
+ render_node_filters(node) if node['filter']
0
+ cacher.store(cache, node.to_s) if cache
0
+ end
0
     end
0
     
0
     def render_node_filters(node)
0
@@ -57,6 +68,8 @@
0
       end
0
     end
0
     
0
+ # todo: Marshal.load seems to be a huge performance bottleneck. Maybe some other way?
0
+ # for now let's use the render cache
0
     def render_collection(node, values=[])
0
       variable_name = node.remove_attribute('local') || 'item'
0
       base = node.children.detect {|n| !n.is_a?(Hpricot::Text) }
...
1
2
3
4
5
 
 
6
7
8
...
1
2
3
 
 
4
5
6
7
8
0
@@ -1,8 +1,8 @@
0
 module Hpreserve #:nodoc:
0
   module VERSION #:nodoc:
0
     MAJOR = 0
0
- MINOR = 1
0
- TINY = 3
0
+ MINOR = 2
0
+ TINY = 0
0
 
0
     STRING = [MAJOR, MINOR, TINY].join('.')
0
   end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
0
@@ -1 +1,42 @@
0
+require File.dirname(__FILE__) + '/spec_helper.rb'
0
+
0
+describe Hpreserve::AbstractCacher do
0
+
0
+ it "returns nil if no match for the given variable" do
0
+ cacher = Hpreserve::AbstractCacher.new([])
0
+ cacher.match?('things').should be_nil
0
+ end
0
+
0
+ it "matches against the given variable and return the key" do
0
+ cacher = Hpreserve::AbstractCacher.new([{:match => %r|^things\.(.*)|, :key => 'thing::\1'}])
0
+ cacher.match?('things.foo').should == 'thing::foo'
0
+ end
0
+
0
+ it "returns nil if nothing is retrieved from the cache" do
0
+ cacher = Hpreserve::AbstractCacher.new([])
0
+ cacher.storage = {}
0
+ cacher.retrieve('thing::foo').should == nil
0
+ end
0
+
0
+ it "returns the stored value if it exists" do
0
+ cacher = Hpreserve::AbstractCacher.new([])
0
+ cacher.storage = {'thing::foo' => 'value'}
0
+ cacher.retrieve('thing::foo').should == 'value'
0
+ end
0
+
0
+ it "sets the value" do
0
+ cacher = Hpreserve::AbstractCacher.new([])
0
+ cacher.storage = {}
0
+ cacher.store('thing::foo', 'value')
0
+ cacher.storage['thing::foo'].should == 'value'
0
+ end
0
+
0
+ it "expires keys matching a given pattern" do
0
+ cacher = Hpreserve::AbstractCacher.new([])
0
+ cacher.storage = {'thing::foo' => 'foo', 'thing::bar' => 'bar', 'non::thing' => 'bee'}
0
+ cacher.expire(/^thing::.*/)
0
+ cacher.storage.should == {'non::thing' => 'bee'}
0
+ end
0
+
0
+end
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
0
@@ -1 +1,47 @@
0
+require 'tmpdir'
0
+require 'digest/md5'
0
+require File.dirname(__FILE__) + '/spec_helper.rb'
0
+
0
+describe Hpreserve::FileCacher do
0
+
0
+ before do
0
+ @cacher = Hpreserve::FileCacher.new([], Dir.tmpdir)
0
+ @key = Digest::MD5.hexdigest("#{Time.now}--#{inspect}")
0
+ end
0
+
0
+ it "knows about its base directory" do
0
+ @cacher.patterns.should == []
0
+ @cacher.base_dir.should == Dir.tmpdir
0
+ end
0
+
0
+ it "stores values in files matching the key" do
0
+ @cacher.store(@key, 'new value')
0
+ File.should be_file(File.join(Dir.tmpdir, @key))
0
+ File.read(File.join(Dir.tmpdir, @key)).should == 'new value'
0
+ end
0
+
0
+ it "creates directories as needed if they don't exist" do
0
+ @cacher.store("#{@key}/foo", 'new value')
0
+ File.should be_directory(File.join(Dir.tmpdir, @key))
0
+ File.should be_file(File.join(Dir.tmpdir, @key, 'foo'))
0
+ end
0
+
0
+ it "retrieves values from files matching the key" do
0
+ File.open(File.join(Dir.tmpdir, @key), 'w') {|f| f << 'existing value'}
0
+ @cacher.retrieve(@key).should == 'existing value'
0
+ end
0
+
0
+ it "returns nil when retrieving a key for a file that doens't exist" do
0
+ File.should_not be_file(File.join(Dir.tmpdir, @key))
0
+ @cacher.retrieve(@key).should be_nil
0
+ end
0
+
0
+ it "deletes files when expiring keys" do
0
+ files = [1,2].collect {|i| File.join(Dir.tmpdir, "#{@key}-#{i}") }
0
+ files.each {|file| File.open(file, 'w') {|f| f << 'hi'} }
0
+ @cacher.expire("#{@key}*")
0
+ files.each {|file| File.should_not be_file(file)}
0
+ end
0
+
0
+end
...
156
157
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
160
161
...
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
0
@@ -156,6 +156,50 @@
0
     
0
   end
0
   
0
+ describe "caching" do
0
+ before do
0
+ @doc = Hpreserve::Parser.new("<span content='some.thing'>non-rendered</span>")
0
+ @doc.variables = {'some' => {'thing' => 'from variables'}}
0
+ cacher_matches([{:match => /^some\.thing/, :key => 'something'}])
0
+ end
0
+
0
+ def cacher_matches(match=[])
0
+ @doc.cacher = Hpreserve::AbstractCacher.new(match)
0
+ end
0
+
0
+ def render
0
+ @doc.render_node_content(@doc.doc.at('span'))
0
+ end
0
+
0
+ it "ignores the cacher if no match is found" do
0
+ cacher_matches()
0
+ render
0
+ @doc.doc.to_s.should == '<span>from variables</span>'
0
+ end
0
+
0
+ it "checks the cache if a match is found" do
0
+ @doc.cacher.should_receive(:retrieve).with('something')
0
+ render
0
+ end
0
+
0
+ it "uses the value from the cache if a match is found and the key exists in the cache" do
0
+ @doc.cacher.store('something', 'from cacher')
0
+ render
0
+ @doc.doc.to_s.should == 'from cacher'
0
+ end
0
+
0
+ it "does not render if a match is found and the key exists in the cache" do
0
+ @doc.cacher.store('something', 'from cacher')
0
+ @doc.variables.should_not_receive(:[])
0
+ render
0
+ end
0
+
0
+ it "stores the value in the cache if a match is found and no key is pre-existing" do
0
+ @doc.cacher.should_receive(:store).with('something','<span>from variables</span>')
0
+ render
0
+ end
0
+ end
0
+
0
   describe "filter handler" do
0
 
0
     it "runs the given filterset on a node" do

Comments

    No one has commented yet.