tylerkovacs-extended_fragment_cache 0.2.0

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.
Files changed (3) hide show
  1. data/VERSION.yml +4 -0
  2. data/lib/extended_fragment_cache.rb +256 -0
  3. metadata +55 -0
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 2
4
+ :patch: 0
@@ -0,0 +1,256 @@
1
+ # In-Process Memory Cache for Fragment Caching
2
+ #
3
+ # Fragment caching has a slight inefficiency that requires two lookups
4
+ # within the fragment cache store to render a single cached fragment.
5
+ # The two cache lookups are:
6
+ #
7
+ # 1. The read_fragment method invoked in a controller to determine if a
8
+ # fragment has already been cached. e.g.,
9
+ # unless read_fragment("/x/y/z")
10
+ # ...
11
+ # end
12
+ # 2. The cache helper method invoked in a view that renders the fragment. e.g.,
13
+ # <% cache("/x/y/z") do %>
14
+ # ...
15
+ # <% end %>
16
+ #
17
+ # This plugin adds an in-process cache that saves the value retrieved from
18
+ # the fragment cache store. The in-process cache has two benefits:
19
+ #
20
+ # 1. It cuts in half the number of read requests sent to the fragment cache
21
+ # store. This can result in a considerable saving for sites that make
22
+ # heavy use of memcached.
23
+ # 2. Retrieving the fragment from the in-process cache is faster than going
24
+ # to fragment cache store. On a typical dev box, the savings are
25
+ # relatively small but would be noticeable in standard production
26
+ # environment using memcached (where the fragment cache could be remote)
27
+ #
28
+ # Peter Zaitsev has a great post comparing the latencies of different
29
+ # cache types on the MySQL Performance blog:
30
+ # http://www.mysqlperformanceblog.com/2006/08/09/cache-performance-comparison/
31
+ #
32
+ # The plugin automatically installs a after_filter on the
33
+ # ApplicationController that flushes the in-process memory cache at the
34
+ # start of every request.
35
+
36
+ module ActionController
37
+ module Caching
38
+ module ExtendedFragments
39
+ # Add a local_fragment_cache object and accessor.
40
+ def self.append_features(base) #:nodoc:
41
+ super
42
+ base.class_eval do
43
+ @@local_fragment_cache = {}
44
+ cattr_accessor :local_fragment_cache
45
+ end
46
+
47
+ # add an after filter to flush the local cache after every request
48
+ base.after_filter({}) do |c|
49
+ @@local_fragment_cache.clear
50
+ end
51
+ end
52
+ end
53
+
54
+ module Fragments
55
+ # Override read_fragment so that it checks the local_fragment_cache
56
+ # object before going to the fragment_cache_store backend.
57
+ # - also allow fragments to be read using a class method (from a model)
58
+ def read_fragment(name, options = nil)
59
+ name = url_for(name.merge({:only_path => true})) if name.class == Hash
60
+ ActionController::Caching::Fragments.read_fragment(name, options)
61
+ end
62
+
63
+ def self.read_fragment(name, options=nil)
64
+ return unless ApplicationController.perform_caching
65
+
66
+ key = self.fragment_cache_key(name)
67
+ content = ApplicationController.local_fragment_cache[key]
68
+ ApplicationController.benchmark "Fragment read: #{key}" do
69
+ if content.nil?
70
+ content = ApplicationController.fragment_cache_store.read(key, options)
71
+ ApplicationController.local_fragment_cache[key] = content
72
+ end
73
+ end
74
+ content
75
+ rescue NameError => err
76
+ # ignore bogus uninitialized constant ApplicationController errors
77
+ end
78
+
79
+ def write_fragment(name, content, options=nil)
80
+ name = url_for(name.merge({:only_path => true})) if name.class == Hash
81
+ ActionController::Caching::Fragments.write_fragment(name, content, options)
82
+ rescue NameError => err
83
+ # ignore bogus uninitialized constant ApplicationController errors
84
+ # when running Rails outside of web container
85
+ end
86
+
87
+ def self.write_fragment(name, content, options = nil)
88
+ return unless ApplicationController.perform_caching
89
+
90
+ key = self.fragment_cache_key(name)
91
+ ApplicationController.benchmark "Cached fragment: #{key}" do
92
+ ApplicationController.local_fragment_cache[key] = content
93
+ ApplicationController.fragment_cache_store.write(key, content, options)
94
+ end
95
+ content
96
+ rescue NameError => err
97
+ # ignore bogus uninitialized constant ApplicationController errors
98
+ end
99
+
100
+ # Utility method needed by class methods
101
+ def self.fragment_cache_key(name)
102
+ name.is_a?(Hash) ? name.to_s : name
103
+ end
104
+
105
+ # Add expire_fragments as class method so that we can expire cached
106
+ # content from models, etc.
107
+ def self.expire_fragment(name, options = nil)
108
+ return unless ApplicationController.perform_caching
109
+
110
+ key = self.fragment_cache_key(name)
111
+
112
+ if key.is_a?(Regexp)
113
+ ApplicationController.benchmark "Expired fragments matching: #{key.source}" do
114
+ ApplicationController.fragment_cache_store.delete_matched(key, options)
115
+ end
116
+ else
117
+ ApplicationController.benchmark "Expired fragment: #{key}" do
118
+ ApplicationController.fragment_cache_store.delete(key, options)
119
+ end
120
+ end
121
+ rescue NameError => err
122
+ # ignore bogus uninitialized constant ApplicationController errors
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ # Content Interpolation for Fragment Caching
129
+ #
130
+ # Many modern websites mix a lot of static and dynamic content. The more
131
+ # dynamic content you have in your site, the harder it becomes to implement
132
+ # caching. In an effort to scale, you've implemented fragment caching
133
+ # all over the place. Fragment caching can be difficult if your static content
134
+ # is interleaved with your dynamic content. Your views become littered
135
+ # with cache calls which not only hurts performance (multiple calls to the
136
+ # cache backend), it also makes them harder to read. Content
137
+ # interpolation allows you substitude dynamic content into cached fragment.
138
+ #
139
+ # Take this example view:
140
+ # <% cache("/first_part") do %>
141
+ # This content is very expensive to generate, so let's fragment cache it.<br/>
142
+ # <% end %>
143
+ # <%= Time.now %><br/>
144
+ # <% cache("/second_part") do %>
145
+ # This content is also very expensive to generate.<br/>
146
+ # <% end %>
147
+ #
148
+ # We can replace it with:
149
+ # <% cache("/only_part", {}, {"__TIME_GOES_HERE__" => Time.now}) do %>
150
+ # This content is very expensive to generate, so let's fragment cache it.<br/>
151
+ # __TIME_GOES_HERE__<br/>
152
+ # This content is also very expensive to generate.<br/>
153
+ # <% end %>
154
+ #
155
+ # The latter is easier to read and induces less load on the cache backend.
156
+ #
157
+ # We use content interpolation at Zvents to speed up our JSON methods.
158
+ # Converting objects to JSON representation is notoriously slow.
159
+ # Unfortunately, in our application, each JSON request must return some unique
160
+ # data. This makes caching tedious because 99% of the content returned is
161
+ # static for a given object, but there's a little bit of dynamic data that
162
+ # must be sent back in the response. Using content interpolation, we cache
163
+ # the object in JSON format and substitue the dynamic values in the view.
164
+ #
165
+ # This plugin integrates Yan Pritzker's extension that allows content to be
166
+ # cached with an expiry time (from the memcache_fragments plugin) since they
167
+ # both operate on the same method. This allows you to do things like:
168
+ #
169
+ # <% cache("/only_part", {:expire => 15.minutes}) do %>
170
+ # This content is very expensive to generate, so let's fragment cache it.
171
+ # <% end %>
172
+
173
+ module ActionView
174
+ module Helpers
175
+ # See ActionController::Caching::Fragments for usage instructions.
176
+ module CacheHelper
177
+ def cache(name = {}, options=nil, interpolation = {}, &block)
178
+ if name.nil? or (options.has_key?(:if) and !options[:if])
179
+ yield
180
+ else
181
+ begin
182
+ content = @controller.cache_erb_fragment(block, name, options, interpolation) || ""
183
+ rescue MemCache::MemCacheError => err
184
+ content = ""
185
+ end
186
+
187
+ interpolation.keys.each{|k| content.sub!(k.to_s, interpolation[k].to_s)}
188
+ content
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ module ActionController
196
+ module Caching
197
+ module Fragments
198
+ # Called by CacheHelper#cache
199
+ def cache_erb_fragment(block, name={}, options=nil, interpolation={})
200
+ unless perform_caching then
201
+ content = block.call
202
+ interpolation.keys.each{|k|content.sub!(k.to_s,interpolation[k].to_s)}
203
+ content
204
+ return
205
+ end
206
+
207
+ buffer = eval("_erbout", block.binding)
208
+
209
+ if cache = read_fragment(name, options)
210
+ buffer.concat(cache)
211
+ else
212
+ pos = buffer.length
213
+ block.call
214
+ write_fragment(name, buffer[pos..-1], options)
215
+ interpolation.keys.each{|k|
216
+ buffer[pos..-1] = buffer[pos..-1].sub!(k.to_s,interpolation[k].to_s)
217
+ }
218
+ buffer[pos..-1]
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ class MemCache
226
+ # The read and write methods are required to get fragment caching to
227
+ # work with the Robot Co-op memcache_client code.
228
+ # http://rubyforge.org/projects/rctools/
229
+ #
230
+ # Lifted shamelessly from Yan Pritzker's memcache_fragments plugin.
231
+ # This should really go back into the memcache_client core.
232
+ # http://skwpspace.com/2006/08/19/rails-fragment-cache-with-memcached-client-and-time-based-expire-option/
233
+ def read(key, options=nil)
234
+ options ||= {}
235
+ common_key = options[:common_key]
236
+ cache = options[:cache] || self
237
+ if common_key
238
+ cached_data = self.get_multi(key, common_key) || {}
239
+ Zvents::CommonKeyCache._get_value(cache, key, common_key, cached_data)
240
+ else
241
+ cache.get(key, options[:raw] || false)
242
+ end
243
+ end
244
+
245
+ def write(key,content,options=nil)
246
+ options ||= {}
247
+ cache = options[:cache] || self
248
+ expiry = options && options[:expire] || 0
249
+ common_key = options[:common_key]
250
+ if common_key
251
+ Zvents::CommonKeyCache._set(cache, key, common_key, content, expiry)
252
+ else
253
+ cache.set(key, content, expiry, options[:raw] || false)
254
+ end
255
+ end
256
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tylerkovacs-extended_fragment_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - tylerkovacs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-28 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: See README
17
+ email: tyler.kovacs@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - VERSION.yml
26
+ - lib/extended_fragment_cache.rb
27
+ has_rdoc: true
28
+ homepage: http://github.com/tylerkovacs/extended_fragment_cache
29
+ post_install_message:
30
+ rdoc_options:
31
+ - --inline-source
32
+ - --charset=UTF-8
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: "0"
40
+ version:
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ requirements: []
48
+
49
+ rubyforge_project:
50
+ rubygems_version: 1.2.0
51
+ signing_key:
52
+ specification_version: 2
53
+ summary: The extended_fragment_cache plugin provides content interpolation and an in-process memory cache for fragment caching.
54
+ test_files: []
55
+