diff options
Diffstat (limited to 'actionpack/lib/abstract_controller/caching/fragments.rb')
-rw-r--r-- | actionpack/lib/abstract_controller/caching/fragments.rb | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/actionpack/lib/abstract_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb new file mode 100644 index 0000000000..3257a731ed --- /dev/null +++ b/actionpack/lib/abstract_controller/caching/fragments.rb @@ -0,0 +1,143 @@ +module AbstractController + module Caching + # Fragment caching is used for caching various blocks within + # views without caching the entire action as a whole. This is + # useful when certain elements of an action change frequently or + # depend on complicated state while other parts rarely change or + # can be shared amongst multiple parties. The caching is done using + # the +cache+ helper available in the Action View. See + # ActionView::Helpers::CacheHelper for more information. + # + # While it's strongly recommended that you use key-based cache + # expiration (see links in CacheHelper for more information), + # it is also possible to manually expire caches. For example: + # + # expire_fragment('name_of_cache') + module Fragments + extend ActiveSupport::Concern + + included do + if respond_to?(:class_attribute) + class_attribute :fragment_cache_keys + else + mattr_writer :fragment_cache_keys + end + + self.fragment_cache_keys = [] + + helper_method :fragment_cache_key if respond_to?(:helper_method) + end + + module ClassMethods + # Allows you to specify controller-wide key prefixes for + # cache fragments. Pass either a constant +value+, or a block + # which computes a value each time a cache key is generated. + # + # For example, you may want to prefix all fragment cache keys + # with a global version identifier, so you can easily + # invalidate all caches. + # + # class ApplicationController + # fragment_cache_key "v1" + # end + # + # When it's time to invalidate all fragments, simply change + # the string constant. Or, progressively roll out the cache + # invalidation using a computed value: + # + # class ApplicationController + # fragment_cache_key do + # @account.id.odd? ? "v1" : "v2" + # end + # end + def fragment_cache_key(value = nil, &key) + self.fragment_cache_keys += [key || ->{ value }] + end + end + + # Given a key (as described in +expire_fragment+), returns + # a key suitable for use in reading, writing, or expiring a + # cached fragment. All keys begin with <tt>views/</tt>, + # followed by any controller-wide key prefix values, ending + # with the specified +key+ value. The key is expanded using + # ActiveSupport::Cache.expand_cache_key. + def fragment_cache_key(key) + head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) } + tail = key.is_a?(Hash) ? url_for(key).split("://").last : key + ActiveSupport::Cache.expand_cache_key([*head, *tail], :views) + end + + # Writes +content+ to the location signified by + # +key+ (see +expire_fragment+ for acceptable formats). + def write_fragment(key, content, options = nil) + return content unless cache_configured? + + key = fragment_cache_key(key) + instrument_fragment_cache :write_fragment, key do + content = content.to_str + cache_store.write(key, content, options) + end + content + end + + # Reads a cached fragment from the location signified by +key+ + # (see +expire_fragment+ for acceptable formats). + def read_fragment(key, options = nil) + return unless cache_configured? + + key = fragment_cache_key(key) + instrument_fragment_cache :read_fragment, key do + result = cache_store.read(key, options) + result.respond_to?(:html_safe) ? result.html_safe : result + end + end + + # Check if a cached fragment from the location signified by + # +key+ exists (see +expire_fragment+ for acceptable formats). + def fragment_exist?(key, options = nil) + return unless cache_configured? + key = fragment_cache_key(key) + + instrument_fragment_cache :exist_fragment?, key do + cache_store.exist?(key, options) + end + end + + # Removes fragments from the cache. + # + # +key+ can take one of three forms: + # + # * String - This would normally take the form of a path, like + # <tt>pages/45/notes</tt>. + # * Hash - Treated as an implicit call to +url_for+, like + # <tt>{ controller: 'pages', action: 'notes', id: 45}</tt> + # * Regexp - Will remove any fragment that matches, so + # <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you + # don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because + # the actual filename matched looks like + # <tt>./cache/filename/path.cache</tt>. Note: Regexp expiration is + # only supported on caches that can iterate over all keys (unlike + # memcached). + # + # +options+ is passed through to the cache store's +delete+ + # method (or <tt>delete_matched</tt>, for Regexp keys). + def expire_fragment(key, options = nil) + return unless cache_configured? + key = fragment_cache_key(key) unless key.is_a?(Regexp) + + instrument_fragment_cache :expire_fragment, key do + if key.is_a?(Regexp) + cache_store.delete_matched(key, options) + else + cache_store.delete(key, options) + end + end + end + + def instrument_fragment_cache(name, key) # :nodoc: + payload = instrument_payload(key) + ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", payload) { yield } + end + end + end +end |