Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 8546

Show
Ignore:
Timestamp:
01/03/08 21:05:12 (4 months ago)
Author:
david
Message:

Moved the caching stores from ActionController::Caching::Fragments::* to ActiveSupport::Cache::*. If you're explicitly referring to a store, like ActionController::Caching::Fragments::MemoryStore, you need to update that reference with ActiveSupport::Cache::MemoryStore [DHH] Deprecated ActionController::Base.fragment_cache_store for ActionController::Base.cache_store [DHH] All fragment cache keys are now by default prefixed with the 'views/' namespace [DHH] Added ActiveRecord::Base.cache_key to make it easier to cache Active Records in combination with the new ActiveSupport::Cache::* libraries [DHH] Added ActiveSupport::Gzip.decompress/compress(source) as an easy wrapper for Zlib [Tobias Luetke] Included MemCache-Client to make the improved ActiveSupport::Cache::MemCacheStore work out of the box [Bob Cottrell, Eric Hodel] Added config.cache_store to environment options to control the default cache store (default is FileStore if tmp/cache is present, otherwise MemoryStore is used) [DHH]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/CHANGELOG

    r8542 r8546  
    11*SVN* 
     2 
     3* All fragment cache keys are now by default prefixed with the "views/" namespace [DHH] 
     4 
     5* Moved the caching stores from ActionController::Caching::Fragments::* to ActiveSupport::Cache::*. If you're explicitly referring to a store, like ActionController::Caching::Fragments::MemoryStore, you need to update that reference with ActiveSupport::Cache::MemoryStore [DHH] 
     6 
     7* Deprecated ActionController::Base.fragment_cache_store for ActionController::Base.cache_store [DHH] 
    28 
    39* Made fragment caching in views work for rjs and builder as well #6642 [zsombor] 
  • trunk/actionpack/lib/action_controller/caching.rb

    r8542 r8546  
    22require 'uri' 
    33require 'set' 
     4 
     5require 'action_controller/caching/pages' 
     6require 'action_controller/caching/actions' 
     7require 'action_controller/caching/sql_cache' 
     8require 'action_controller/caching/sweeping' 
     9require 'action_controller/caching/fragments' 
     10 
    411 
    512module ActionController #:nodoc: 
     
    1017  # 
    1118  # Note: To turn off all caching and sweeping, set Base.perform_caching = false. 
     19  # 
     20  # 
     21  # == Caching stores 
     22  # 
     23  # All the caching stores from ActiveSupport::Cache is available to be used as backends for Action Controller caching. 
     24  # 
     25  # Configuration examples (MemoryStore is the default): 
     26  # 
     27  #   ActionController::Base.cache_store = :memory_store 
     28  #   ActionController::Base.cache_store = :file_store, "/path/to/cache/directory" 
     29  #   ActionController::Base.cache_store = :drb_store, "druby://localhost:9192" 
     30  #   ActionController::Base.cache_store = :mem_cache_store, "localhost" 
     31  #   ActionController::Base.cache_store = MyOwnStore.new("parameter") 
    1232  module Caching 
    1333    def self.included(base) #:nodoc: 
    1434      base.class_eval do 
     35        @@cache_store = nil 
     36        cattr_reader :cache_store 
     37 
     38        # Defines the storage option for cached fragments 
     39        def self.cache_store=(store_option) 
     40          @@cache_store = ActiveSupport::Cache.lookup_store(store_option) 
     41        end 
     42 
    1543        include Pages, Actions, Fragments 
    16  
    17         if defined? ActiveRecord 
    18           include Sweeping, SqlCache 
    19         end 
     44        include Sweeping, SqlCache if defined?(ActiveRecord) 
    2045 
    2146        @@perform_caching = true 
    2247        cattr_accessor :perform_caching 
     48 
     49        def self.cache_configured? 
     50          perform_caching && cache_store 
     51        end 
    2352      end 
    2453    end 
    2554 
    26     # Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server 
    27     # can serve without going through the Action Pack. This can be as much as 100 times faster than going through the process of dynamically 
    28     # generating the content. Unfortunately, this incredible speed-up is only available to stateless pages where all visitors 
    29     # are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are a great fit 
    30     # for this approach, but account-based systems where people log in and manipulate their own data are often less likely candidates. 
    31     # 
    32     # Specifying which actions to cache is done through the <tt>caches</tt> class method: 
    33     # 
    34     #   class WeblogController < ActionController::Base 
    35     #     caches_page :show, :new 
    36     #   end 
    37     # 
    38     # This will generate cache files such as weblog/show/5 and weblog/new, which match the URLs used to trigger the dynamic 
    39     # generation. This is how the web server is able pick up a cache file when it exists and otherwise let the request pass on to 
    40     # the Action Pack to generate it. 
    41     # 
    42     # Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache 
    43     # is not restored before another hit is made against it. The API for doing so mimics the options from url_for and friends: 
    44     # 
    45     #   class WeblogController < ActionController::Base 
    46     #     def update 
    47     #       List.update(params[:list][:id], params[:list]) 
    48     #       expire_page :action => "show", :id => params[:list][:id] 
    49     #       redirect_to :action => "show", :id => params[:list][:id] 
    50     #     end 
    51     #   end 
    52     # 
    53     # Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be 
    54     # expired. 
    55     # 
    56     # == Setting the cache directory 
    57     # 
    58     # The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root". 
    59     # For Rails, this directory has already been set to RAILS_ROOT + "/public". 
    60     # 
    61     # == Setting the cache extension 
    62     # 
    63     # By default, the cache extension is .html, which makes it easy for the cached files to be picked up by the web server. If you want 
    64     # something else, like .php or .shtml, just set Base.page_cache_extension. 
    65     module Pages 
    66       def self.included(base) #:nodoc: 
    67         base.extend(ClassMethods) 
    68         base.class_eval do 
    69           @@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : "" 
    70           cattr_accessor :page_cache_directory 
    71  
    72           @@page_cache_extension = '.html' 
    73           cattr_accessor :page_cache_extension 
    74         end 
    75       end 
    76  
    77       module ClassMethods 
    78         # Expires the page that was cached with the +path+ as a key. Example: 
    79         #   expire_page "/lists/show" 
    80         def expire_page(path) 
    81           return unless perform_caching 
    82  
    83           benchmark "Expired page: #{page_cache_file(path)}" do 
    84             File.delete(page_cache_path(path)) if File.exist?(page_cache_path(path)) 
    85           end 
    86         end 
    87  
    88         # Manually cache the +content+ in the key determined by +path+. Example: 
    89         #   cache_page "I'm the cached content", "/lists/show" 
    90         def cache_page(content, path) 
    91           return unless perform_caching 
    92  
    93           benchmark "Cached page: #{page_cache_file(path)}" do 
    94             FileUtils.makedirs(File.dirname(page_cache_path(path))) 
    95             File.open(page_cache_path(path), "wb+") { |f| f.write(content) } 
    96           end 
    97         end 
    98  
    99         # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that 
    100         # matches the triggering url. 
    101         def caches_page(*actions) 
    102           return unless perform_caching 
    103           actions = actions.map(&:to_s) 
    104           after_filter { |c| c.cache_page if actions.include?(c.action_name) } 
    105         end 
    106  
    107         private 
    108           def page_cache_file(path) 
    109             name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/')) 
    110             name << page_cache_extension unless (name.split('/').last || name).include? '.' 
    111             return name 
    112           end 
    113  
    114           def page_cache_path(path) 
    115             page_cache_directory + page_cache_file(path) 
    116           end 
    117       end 
    118  
    119       # Expires the page that was cached with the +options+ as a key. Example: 
    120       #   expire_page :controller => "lists", :action => "show" 
    121       def expire_page(options = {}) 
    122         return unless perform_caching 
    123  
    124         if options.is_a?(Hash) 
    125           if options[:action].is_a?(Array) 
    126             options[:action].dup.each do |action| 
    127               self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action))) 
    128             end 
    129           else 
    130             self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true))) 
    131           end 
     55    protected 
     56      # Convenience accessor 
     57      def cache(key, options = nil, &block) 
     58        if cache_configured? 
     59          cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) 
    13260        else 
    133           self.class.expire_page(options) 
    134         end 
    135       end 
    136  
    137       # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used 
    138       # If no options are provided, the requested url is used. Example: 
    139       #   cache_page "I'm the cached content", :controller => "lists", :action => "show" 
    140       def cache_page(content = nil, options = nil) 
    141         return unless perform_caching && caching_allowed 
    142  
    143         path = case options 
    144           when Hash 
    145             url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format])) 
    146           when String 
    147             options 
    148           else 
    149             request.path 
    150         end 
    151  
    152         self.class.cache_page(content || response.body, path) 
    153       end 
    154  
    155       private 
    156         def caching_allowed 
    157           request.get? && response.headers['Status'].to_i == 200 
    158         end 
    159     end 
    160  
    161     # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching, 
    162     # every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which 
    163     # allows for authentication and other restrictions on whether someone is allowed to see the cache. Example: 
    164     # 
    165     #   class ListsController < ApplicationController 
    166     #     before_filter :authenticate, :except => :public 
    167     #     caches_page   :public 
    168     #     caches_action :show, :feed 
    169     #   end 
    170     # 
    171     # In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the 
    172     # show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches. 
    173     # 
    174     # Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both 
    175     # the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named 
    176     # "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and 
    177     # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern. 
    178     # 
    179     # Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt> 
    180     # are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same 
    181     # as <tt>:action => 'list', :format => :xml</tt>. 
    182     # 
    183     # You can set modify the default action cache path by passing a :cache_path option.  This will be passed directly to ActionCachePath.path_for.  This is handy 
    184     # for actions with multiple possible routes that should be cached differently.  If a block is given, it is called with the current controller instance. 
    185     # 
    186     #   class ListsController < ApplicationController 
    187     #     before_filter :authenticate, :except => :public 
    188     #     caches_page   :public 
    189     #     caches_action :show, :cache_path => { :project => 1 } 
    190     #     caches_action :show, :cache_path => Proc.new { |controller|  
    191     #       controller.params[:user_id] ?  
    192     #         controller.send(:user_list_url, c.params[:user_id], c.params[:id]) : 
    193     #         controller.send(:list_url, c.params[:id]) } 
    194     #   end 
    195     module Actions 
    196       def self.included(base) #:nodoc: 
    197         base.extend(ClassMethods) 
    198           base.class_eval do 
    199             attr_accessor :rendered_action_cache, :action_cache_path 
    200             alias_method_chain :protected_instance_variables, :action_caching 
    201           end 
    202       end 
    203  
    204       module ClassMethods 
    205         # Declares that +actions+ should be cached. 
    206         # See ActionController::Caching::Actions for details. 
    207         def caches_action(*actions) 
    208           return unless perform_caching 
    209           around_filter(ActionCacheFilter.new(*actions)) 
    210         end 
    211       end 
    212  
    213       def protected_instance_variables_with_action_caching 
    214         protected_instance_variables_without_action_caching + %w(@action_cache_path) 
    215       end 
    216  
    217       def expire_action(options = {}) 
    218         return unless perform_caching 
    219         if options[:action].is_a?(Array) 
    220           options[:action].dup.each do |action| 
    221             expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }))) 
    222           end 
    223         else 
    224           expire_fragment(ActionCachePath.path_for(self, options)) 
    225         end 
    226       end 
    227  
    228       class ActionCacheFilter #:nodoc: 
    229         def initialize(*actions, &block) 
    230           @options = actions.extract_options! 
    231           @actions = Set.new actions 
    232         end 
    233  
    234         def before(controller) 
    235           return unless @actions.include?(controller.action_name.intern) 
    236           cache_path = ActionCachePath.new(controller, path_options_for(controller, @options)) 
    237           if cache = controller.read_fragment(cache_path.path) 
    238             controller.rendered_action_cache = true 
    239             set_content_type!(controller, cache_path.extension) 
    240             controller.send!(:render_for_text, cache) 
    241             false 
    242           else 
    243             controller.action_cache_path = cache_path 
    244           end 
    245         end 
    246  
    247         def after(controller) 
    248           return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || !caching_allowed(controller) 
    249           controller.write_fragment(controller.action_cache_path.path, controller.response.body) 
    250         end 
    251  
    252         private 
    253           def set_content_type!(controller, extension) 
    254             controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension 
    255           end 
    256  
    257           def path_options_for(controller, options) 
    258             ((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {} 
    259           end 
    260  
    261           def caching_allowed(controller) 
    262             controller.request.get? && controller.response.headers['Status'].to_i == 200 
    263           end 
    264       end 
    265        
    266       class ActionCachePath 
    267         attr_reader :path, :extension 
    268          
    269         class << self 
    270           def path_for(controller, options) 
    271             new(controller, options).path 
    272           end 
    273         end 
    274          
    275         def initialize(controller, options = {}) 
    276           @extension = extract_extension(controller.request.path) 
    277           path = controller.url_for(options).split('://').last 
    278           normalize!(path) 
    279           add_extension!(path, @extension) 
    280           @path = URI.unescape(path) 
    281         end 
    282          
    283         private 
    284           def normalize!(path) 
    285             path << 'index' if path[-1] == ?/ 
    286           end 
    287          
    288           def add_extension!(path, extension) 
    289             path << ".#{extension}" if extension 
    290           end 
    291            
    292           def extract_extension(file_path) 
    293             # Don't want just what comes after the last '.' to accommodate multi part extensions 
    294             # such as tar.gz. 
    295             file_path[/^[^.]+\.(.+)$/, 1] 
    296           end 
    297       end 
    298     end 
    299  
    300     # Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when 
    301     # certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple 
    302     # parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like: 
    303     # 
    304     #   <b>Hello <%= @name %></b> 
    305     #   <% cache do %> 
    306     #     All the topics in the system: 
    307     #     <%= render :partial => "topic", :collection => Topic.find(:all) %> 
    308     #   <% end %> 
    309     # 
    310     # This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would  
    311     # be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>.  
    312     #  
    313     # This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using  
    314     # <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like: 
    315     # 
    316     #   <% cache(:action => "list", :action_suffix => "all_topics") do %> 
    317     # 
    318     # That would result in a name such as "/topics/list/all_topics", avoiding conflicts with the action cache and with any fragments that use a  
    319     # different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique  
    320     # cache names that we can refer to when we need to expire the cache.  
    321     #  
    322     # The expiration call for this example is: 
    323     #  
    324     #   expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics") 
    325     # 
    326     # == Fragment stores 
    327     # 
    328     # By default, cached fragments are stored in memory. The available store options are: 
    329     # 
    330     # * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and allows all  
    331     #   processes running from the same application directory to access the cached content. 
    332     # * MemoryStore: Keeps the fragments in memory, which is fine for WEBrick and for FCGI (if you don't care that each FCGI process holds its 
    333     #   own fragment store). It's not suitable for CGI as the process is thrown away at the end of each request. It can potentially also take 
    334     #   up a lot of memory since each process keeps all the caches in memory. 
    335     # * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache 
    336     #   around for all processes, but requires that you run and manage a separate DRb process. 
    337     # * MemCacheStore: Works like DRbStore, but uses Danga's MemCache instead. 
    338     #   Requires the ruby-memcache library:  gem install ruby-memcache. 
    339     # 
    340     # Configuration examples (MemoryStore is the default): 
    341     # 
    342     #   ActionController::Base.fragment_cache_store = :memory_store 
    343     #   ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory" 
    344     #   ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192" 
    345     #   ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost" 
    346     #   ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter") 
    347     module Fragments 
    348       def self.included(base) #:nodoc: 
    349         base.class_eval do 
    350           @@fragment_cache_store = MemoryStore.new 
    351           cattr_reader :fragment_cache_store 
    352  
    353           # Defines the storage option for cached fragments 
    354           def self.fragment_cache_store=(store_option) 
    355             store, *parameters = *([ store_option ].flatten) 
    356             @@fragment_cache_store = if store.is_a?(Symbol) 
    357               store_class_name = (store == :drb_store ? "DRbStore" : store.to_s.camelize) 
    358               store_class = ActionController::Caching::Fragments.const_get(store_class_name) 
    359               store_class.new(*parameters) 
    360             else 
    361               store 
    362             end 
    363           end 
    364         end 
    365       end 
    366  
    367       # Given a name (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,  
    368       # writing, or expiring a cached fragment. If the name is a hash, the generated name is the return 
    369       # value of url_for on that hash (without the protocol). 
    370       def fragment_cache_key(name) 
    371         name.is_a?(Hash) ? url_for(name).split("://").last : name 
    372       end 
    373  
    374       def fragment_for(block, name = {}, options = nil) #:nodoc: 
    375         unless perform_caching then block.call; return end 
    376  
    377         buffer = yield 
    378  
    379         if cache = read_fragment(name, options) 
    380           buffer.concat(cache) 
    381         else 
    382           pos = buffer.length 
    383           block.call 
    384           write_fragment(name, buffer[pos..-1], options) 
    385         end 
    386       end 
    387  
    388       # Called by CacheHelper#cache 
    389       def cache_rxml_fragment(block, name = {}, options = nil) #:nodoc: 
    390         fragment_for(block, name, options) do 
    391           eval('xml.target!', block.binding) 
    392         end 
    393       end 
    394        
    395       # Called by CacheHelper#cache 
    396       def cache_rjs_fragment(block, name = {}, options = nil) #:nodoc: 
    397         fragment_for(block, name, options) do 
    398           begin 
    399             debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, false 
    400             eval('page.to_s', block.binding) 
    401           ensure 
    402             ActionView::Base.debug_rjs = debug_mode 
    403           end 
    404         end 
    405       end 
    406        
    407       # Called by CacheHelper#cache 
    408       def cache_erb_fragment(block, name = {}, options = nil) #:nodoc: 
    409         fragment_for(block, name, options) do 
    410           eval(ActionView::Base.erb_variable, block.binding) 
     61          yield 
    41162        end 
    41263      end 
    41364 
    41465 
    415       # Writes <tt>content</tt> to the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats) 
    416       def write_fragment(name, content, options = nil) 
    417         return unless perform_caching 
    418  
    419         key = fragment_cache_key(name) 
    420         self.class.benchmark "Cached fragment: #{key}" do 
    421           fragment_cache_store.write(key, content, options) 
    422         end 
    423         content 
     66    private     
     67      def cache_configured? 
     68        self.class.cache_configured? 
    42469      end 
    425  
    426       # Reads a cached fragment from the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats) 
    427       def read_fragment(name, options = nil) 
    428         return unless perform_caching 
    429  
    430         key = fragment_cache_key(name) 
    431         self.class.benchmark "Fragment read: #{key}" do 
    432           fragment_cache_store.read(key, options) 
    433         end 
    434       end 
    435  
    436       # Name can take one of three forms: 
    437       # * String: This would normally take the form of a path like "pages/45/notes" 
    438       # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 } 
    439       # * Regexp: Will destroy all the matched fragments, example: 
    440       #     %r{pages/\d*/notes} 
    441       #   Ensure you do not specify start and finish in the regex (^$) because 
    442       #   the actual filename matched looks like ./cache/filename/path.cache 
    443       #   Regexp expiration is only supported on caches that can iterate over 
    444       #   all keys (unlike memcached). 
    445       def expire_fragment(name, options = nil) 
    446         return unless perform_caching 
    447  
    448         key = fragment_cache_key(name) 
    449  
    450         if key.is_a?(Regexp) 
    451           self.class.benchmark "Expired fragments matching: #{key.source}" do 
    452             fragment_cache_store.delete_matched(key, options) 
    453           end 
    454         else 
    455           self.class.benchmark "Expired fragment: #{key}" do 
    456             fragment_cache_store.delete(key, options) 
    457           end 
    458         end 
    459       end 
    460  
    461  
    462       class UnthreadedMemoryStore #:nodoc: 
    463         def initialize #:nodoc: 
    464           @data = {} 
    465         end 
    466  
    467         def read(name, options=nil) #:nodoc: 
    468           @data[name] 
    469         end 
    470  
    471         def write(name, value, options=nil) #:nodoc: 
    472           @data[name] = value 
    473         end 
    474  
    475         def delete(name, options=nil) #:nodoc: 
    476           @data.delete(name) 
    477         end 
    478  
    479         def delete_matched(matcher, options=nil) #:nodoc: 
    480           @data.delete_if { |k,v| k =~ matcher } 
    481         end 
    482       end 
    483  
    484       module ThreadSafety #:nodoc: 
    485         def read(name, options=nil) #:nodoc: 
    486           @mutex.synchronize { super } 
    487         end 
    488  
    489         def write(name, value, options=nil) #:nodoc: 
    490           @mutex.synchronize { super } 
    491         end 
    492  
    493         def delete(name, options=nil) #:nodoc: 
    494           @mutex.synchronize { super } 
    495         end 
    496  
    497         def delete_matched(matcher, options=nil) #:nodoc: 
    498           @mutex.synchronize { super } 
    499         end 
    500       end 
    501  
    502       class MemoryStore < UnthreadedMemoryStore #:nodoc: 
    503         def initialize #:nodoc: 
    504           super 
    505           if ActionController::Base.allow_concurrency 
    506             @mutex = Mutex.new 
    507             MemoryStore.module_eval { include ThreadSafety } 
    508           end 
    509         end 
    510       end 
    511  
    512       class DRbStore < MemoryStore #:nodoc: 
    513         attr_reader :address 
    514  
    515         def initialize(address = 'druby://localhost:9192') 
    516           super() 
    517           @address = address 
    518           @data = DRbObject.new(nil, address) 
    519         end 
    520       end 
    521  
    522     begin 
    523       require_library_or_gem 'memcache' 
    524       class MemCacheStore < MemoryStore #:nodoc: 
    525         attr_reader :addresses 
    526  
    527         def initialize(*addresses) 
    528           super() 
    529           addresses = addresses.flatten 
    530           addresses = ["localhost"] if addresses.empty? 
    531           @addresses = addresses 
    532           @data = MemCache.new(*addresses) 
    533         end 
    534       end 
    535     rescue LoadError 
    536       # MemCache wasn't available so neither can the store be 
    537     end 
    538  
    539       class UnthreadedFileStore #:nodoc: 
    540         attr_reader :cache_path 
    541  
    542         def initialize(cache_path) 
    543           @cache_path = cache_path 
    544         end 
    545  
    546         def write(name, value, options = nil) #:nodoc: 
    547           ensure_cache_path(File.dirname(real_file_path(name))) 
    548           File.open(real_file_path(name), "wb+") { |f| f.write(value) } 
    549         rescue => e 
    550           Base.logger.error "Couldn't create cache directory: #{name} (#{e.message})" if Base.logger 
    551         end 
    552  
    553         def read(name, options = nil) #:nodoc: 
    554           File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil 
    555         end 
    556  
    557         def delete(name, options) #:nodoc: 
    558           File.delete(real_file_path(name)) 
    559         rescue SystemCallError => e 
    560           # If there's no cache, then there's nothing to complain about 
    561         end 
    562  
    563         def delete_matched(matcher, options) #:nodoc: 
    564           search_dir(@cache_path) do |f| 
    565             if f =~ matcher 
    566               begin 
    567                 File.delete(f) 
    568               rescue SystemCallError => e 
    569                 # If there's no cache, then there's nothing to complain about 
    570               end 
    571             end 
    572           end 
    573         end 
    574  
    575         private 
    576           def real_file_path(name) 
    577             '%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')] 
    578           end 
    579  
    580           def ensure_cache_path(path) 
    581             FileUtils.makedirs(path) unless File.exist?(path) 
    582           end 
    583  
    584           def search_dir(dir, &callback) 
    585             Dir.foreach(dir) do |d| 
    586               next if d == "." || d == ".." 
    587               name = File.join(dir, d) 
    588               if File.directory?(name) 
    589                 search_dir(name, &callback) 
    590               else 
    591                 callback.call name 
    592               end 
    593             end 
    594           end 
    595         end 
    596  
    597         class FileStore < UnthreadedFileStore #:nodoc: 
    598           def initialize(cache_path) 
    599             super(cache_path) 
    600             if ActionController::Base.allow_concurrency 
    601               @mutex = Mutex.new 
    602               FileStore.module_eval { include ThreadSafety } 
    603             end 
    604           end 
    605         end 
    606     end 
    607  
    608     # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change. 
    609     # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example: 
    610     # 
    611     #   class ListSweeper < ActionController::Caching::Sweeper 
    612     #     observe List, Item 
    613     # 
    614     #     def after_save(record) 
    615     #       list = record.is_a?(List) ? record : record.list 
    616     #       expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id) 
    617     #       expire_action(:controller => "lists", :action => "all") 
    618     #       list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) } 
    619     #     end 
    620     #   end 
    621     # 
    622     # The sweeper is assigned in the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method: 
    623     # 
    624     #   class ListsController < ApplicationController 
    625     #     caches_action :index, :show, :public, :feed 
    626     #     cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ] 
    627     #   end 
    628     # 
    629     # In the example above, four actions are cached and three actions are responsible for expiring those caches. 
    630     module Sweeping 
    631       def self.included(base) #:nodoc: 
    632         base.extend(ClassMethods) 
    633       end 
    634  
    635       module ClassMethods #:nodoc: 
    636         def cache_sweeper(*sweepers) 
    637           return unless perform_caching 
    638           configuration = sweepers.extract_options! 
    639           sweepers.each do |sweeper| 
    640             ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base) 
    641             sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance 
    642  
    643             if sweeper_instance.is_a?(Sweeper) 
    644               around_filter(sweeper_instance, :only => configuration[:only]) 
    645             else 
    646               after_filter(sweeper_instance, :only => configuration[:only]) 
    647             end 
    648           end 
    649         end 
    650       end 
    651     end 
    652  
    653     if defined?(ActiveRecord) and defined?(ActiveRecord::Observer) 
    654       class Sweeper < ActiveRecord::Observer #:nodoc: 
    655         attr_accessor :controller 
    656  
    657         def before(controller) 
    658           self.controller = controller 
    659           callback(:before) 
    660         end 
    661  
    662         def after(controller) 
    663           callback(:after) 
    664           # Clean up, so that the controller can be collected after this request 
    665           self.controller = nil 
    666         end 
    667  
    668         protected 
    669           # gets the action cache path for the given options. 
    670           def action_path_for(options) 
    671             ActionController::Caching::Actions::ActionCachePath.path_for(controller, options) 
    672           end 
    673  
    674           # Retrieve instance variables set in the controller. 
    675           def assigns(key) 
    676             controller.instance_variable_get("@#{key}") 
    677           end 
    678  
    679         private 
    680           def callback(timing) 
    681             controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}" 
    682             action_callback_method_name     = "#{controller_callback_method_name}_#{controller.action_name}" 
    683  
    684             send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true) 
    685             send!(action_callback_method_name)     if respond_to?(action_callback_method_name, true) 
    686           end 
    687  
    688           def method_missing(method, *arguments) 
    689             return if @controller.nil? 
    690             @controller.send!(method, *arguments) 
    691           end 
    692       end 
    693     end 
    694  
    695     module SqlCache 
    696       def self.included(base) #:nodoc: 
    697         if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache) 
    698           base.alias_method_chain :perform_action, :caching 
    699         end 
    700       end 
    701  
    702       def perform_action_with_caching 
    703         ActiveRecord::Base.cache do 
    704           perform_action_without_caching 
    705         end 
    706       end 
    707     end 
    70870  end 
    70971end 
  • trunk/actionpack/lib/action_view/helpers/cache_helper.rb

    r8542 r8546  
    3232      #      <i>Topics listed alphabetically</i> 
    3333      #    <% end %> 
    34       def cache(name = {}, &block) 
     34      def cache(name = {}, options = nil, &block) 
    3535        template_extension = first_render[/\.(\w+)$/, 1].to_sym 
     36 
    3637        case template_extension 
    3738        when :erb, :rhtml 
    38           @controller.cache_erb_fragment(block, name
     39          @controller.cache_erb_fragment(block, name, options
    3940        when :rjs 
    40           @controller.cache_rjs_fragment(block, name
     41          @controller.cache_rjs_fragment(block, name, options
    4142        when :builder, :rxml 
    42           @controller.cache_rxml_fragment(block, name
     43          @controller.cache_rxml_fragment(block, name, options
    4344        else 
    4445          # do a last ditch effort for those brave souls using 
    4546          # different template engines. This should give plugin 
    4647          # writters a simple hook. 
    47           raise "fragment caching not supported for #{template_extension} files." unless @controller.respond_to?("cache_#{template_extension}_fragment") 
    48           @controller.send "cache_#{template_extension}_fragment", block, name 
     48          unless @controller.respond_to?("cache_#{template_extension}_fragment") 
     49            raise "fragment caching not supported for #{template_extension} files." 
     50          end 
     51 
     52          @controller.send!("cache_#{template_extension}_fragment", block, name, options) 
    4953        end 
    5054      end 
  • trunk/actionpack/README

    r7884 r8546  
    9898    class WeblogController < ActionController::Base 
    9999      before_filter :authenticate, :cache, :audit 
    100       after_filter { |c| c.response.body = GZip::compress(c.response.body) } 
     100      after_filter { |c| c.response.body = Gzip::compress(c.response.body) } 
    101101      after_filter LocalizeFilter 
    102102       
  • trunk/actionpack/test/controller/caching_test.rb

    r8542 r8546  
    66FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR) 
    77ActionController::Base.page_cache_directory = FILE_STORE_PATH 
    8 ActionController::Base.fragment_cache_store = :file_store, FILE_STORE_PATH 
     8ActionController::Base.cache_store = :file_store, FILE_STORE_PATH 
    99 
    1010class PageCachingTestController < ActionController::Base 
     
    344344 
    345345    def assert_cache_exists(path) 
    346       full_path = File.join(FILE_STORE_PATH, path + '.cache') 
     346      full_path = File.join(FILE_STORE_PATH, "views", path + '.cache') 
    347347      assert File.exist?(full_path), "#{full_path.inspect} does not exist." 
    348348    end 
     
    356356  def setup 
    357357    ActionController::Base.perform_caching = true 
    358     @store = ActionController::Caching::Fragments::UnthreadedMemoryStore.new 
    359     ActionController::Base.fragment_cache_store = @store 
     358    @store = ActiveSupport::Cache::MemoryStore.new 
     359    ActionController::Base.cache_store = @store 
    360360    @controller = FragmentCachingTestController.new 
    361361    @params = {:controller => 'posts', :action => 'index'} 
     
    369369 
    370370  def test_fragement_cache_key 
    371     assert_equal 'what a key', @controller.fragment_cache_key('what a key') 
    372     assert_equal( "test.host/fragment_caching_test/some_action", 
     371    assert_equal 'views/what a key', @controller.fragment_cache_key('what a key') 
     372    assert_equal( "views/test.host/fragment_caching_test/some_action", 
    373373                  @controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action')) 
    374374  end 
    375375 
    376376  def test_read_fragment__with_caching_enabled 
    377     @store.write('name', 'value') 
     377    @store.write('views/name', 'value') 
    378378    assert_equal 'value', @controller.read_fragment('name') 
    379379  end 
     
    381381  def test_read_fragment__with_caching_disabled 
    382382    ActionController::Base.perform_caching = false 
    383     @store.write('name', 'value') 
     383    @store.write('views/name', 'value') 
    384384    assert_nil @controller.read_fragment('name') 
    385385  end 
    386386 
    387387  def test_write_fragment__with_caching_enabled 
    388     assert_nil @store.read('name') 
     388    assert_nil @store.read('views/name') 
    389389    assert_equal 'value', @controller.write_fragment('name', 'value') 
    390     assert_equal 'value', @store.read('name') 
     390    assert_equal 'value', @store.read('views/name') 
    391391  end 
    392392 
    393393  def test_write_fragment__with_caching_disabled 
    394     assert_nil @store.read('name') 
     394    assert_nil @store.read('views/name') 
    395395    ActionController::Base.perform_caching = false 
    396396    assert_equal nil, @controller.write_fragment('name', 'value') 
    397     assert_nil @store.read('name') 
     397    assert_nil @store.read('views/name') 
    398398  end 
    399399 
    400400  def test_expire_fragment__with_simple_key 
    401     @store.write('name', 'value') 
     401    @store.write('views/name', 'value') 
    402402    @controller.expire_fragment 'name' 
    403     assert_nil @store.read('name') 
     403    assert_nil @store.read('views/name') 
    404404  end 
    405405 
    406406  def test_expire_fragment__with__regexp 
    407     @store.write('name', 'value') 
    408     @store.write('another_name', 'another_value') 
    409     @store.write('primalgrasp', 'will not expire ;-)') 
     407    @store.write('views/name', 'value') 
     408    @store.write('views/another_name', 'another_value') 
     409    @store.write('views/primalgrasp', 'will not expire ;-)') 
    410410 
    411411    @controller.expire_fragment /name/ 
    412412 
    413     assert_nil @store.read('name') 
    414     assert_nil @store.read('another_name') 
    415     assert_equal 'will not expire ;-)', @store.read('primalgrasp') 
     413    assert_nil @store.read('views/name') 
     414    assert_nil @store.read('views/another_name') 
     415    assert_equal 'will not expire ;-)', @store.read('views/primalgrasp') 
    416416  end 
    417417 
     
    419419    ActionController::Base.perform_caching = false 
    420420 
    421     @store.write('expensive', 'fragment content')