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

Changeset 8393

Show
Ignore:
Timestamp:
12/15/07 01:35:55 (7 months ago)
Author:
david
Message:

The initial, rough batch of work

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/2-1-caching/actionpack/lib/action_controller/caching.rb

    r8365 r8393  
    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::Caching 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 
    23       end 
    24     end 
    2548 
    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 
    132         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       # Called by CacheHelper#cache 
    375       def cache_erb_fragment(block, name = {}, options = nil) 
    376         unless perform_caching then block.call; return end 
    377  
    378         buffer = eval(ActionView::Base.erb_variable, block.binding) 
    379  
    380         if cache = read_fragment(name, options) 
    381           buffer.concat(cache) 
    382         else 
    383           pos = buffer.length 
    384           block.call 
    385           write_fragment(name, buffer[pos..-1], options) 
    386         end 
    387       end 
    388  
    389       # Writes <tt>content</tt> to the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats) 
    390       def write_fragment(name, content, options = nil) 
    391         return unless perform_caching 
    392  
    393         key = fragment_cache_key(name) 
    394         self.class.benchmark "Cached fragment: #{key}" do 
    395           fragment_cache_store.write(key, content, options) 
    396         end 
    397         content 
    398       end 
    399  
    400       # Reads a cached fragment from the location signified by <tt>name</tt> (see <tt>expire_fragment</tt> for acceptable formats) 
    401       def read_fragment(name, options = nil) 
    402         return unless perform_caching 
    403  
    404         key = fragment_cache_key(name) 
    405         self.class.benchmark "Fragment read: #{key}" do 
    406           fragment_cache_store.read(key, options) 
    407         end 
    408       end 
    409  
    410       # Name can take one of three forms: 
    411       # * String: This would normally take the form of a path like "pages/45/notes" 
    412       # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 } 
    413       # * Regexp: Will destroy all the matched fragments, example: 
    414       #     %r{pages/\d*/notes} 
    415       #   Ensure you do not specify start and finish in the regex (^$) because 
    416       #   the actual filename matched looks like ./cache/filename/path.cache 
    417       #   Regexp expiration is only supported on caches that can iterate over 
    418       #   all keys (unlike memcached). 
    419       def expire_fragment(name, options = nil) 
    420         return unless perform_caching 
    421  
    422         key = fragment_cache_key(name) 
    423  
    424         if key.is_a?(Regexp) 
    425           self.class.benchmark "Expired fragments matching: #{key.source}" do 
    426             fragment_cache_store.delete_matched(key, options) 
    427           end 
    428         else 
    429           self.class.benchmark "Expired fragment: #{key}" do 
    430             fragment_cache_store.delete(key, options) 
    431           end 
    432         end 
    433       end 
    434  
    435  
    436       class UnthreadedMemoryStore #:nodoc: 
    437         def initialize #:nodoc: 
    438           @data = {} 
    439         end 
    440  
    441         def read(name, options=nil) #:nodoc: 
    442           @data[name] 
    443         end 
    444  
    445         def write(name, value, options=nil) #:nodoc: 
    446           @data[name] = value 
    447         end 
    448  
    449         def delete(name, options=nil) #:nodoc: 
    450           @data.delete(name) 
    451         end 
    452  
    453         def delete_matched(matcher, options=nil) #:nodoc: 
    454           @data.delete_if { |k,v| k =~ matcher } 
    455         end 
    456       end 
    457  
    458       module ThreadSafety #:nodoc: 
    459         def read(name, options=nil) #:nodoc: 
    460           @mutex.synchronize { super } 
    461         end 
    462  
    463         def write(name, value, options=nil) #:nodoc: 
    464           @mutex.synchronize { super } 
    465         end 
    466  
    467         def delete(name, options=nil) #:nodoc: 
    468           @mutex.synchronize { super } 
    469         end 
    470  
    471         def delete_matched(matcher, options=nil) #:nodoc: 
    472           @mutex.synchronize { super } 
    473         end 
    474       end 
    475  
    476       class MemoryStore < UnthreadedMemoryStore #:nodoc: 
    477         def initialize #:nodoc: 
    478           super 
    479           if ActionController::Base.allow_concurrency 
    480             @mutex = Mutex.new 
    481             MemoryStore.module_eval { include ThreadSafety } 
    482           end 
    483         end 
    484       end 
    485  
    486       class DRbStore < MemoryStore #:nodoc: 
    487         attr_reader :address 
    488  
    489         def initialize(address = 'druby://localhost:9192') 
    490           super() 
    491           @address = address 
    492           @data = DRbObject.new(nil, address) 
    493         end 
    494       end 
    495  
    496     begin 
    497       require_library_or_gem 'memcache' 
    498       class MemCacheStore < MemoryStore #:nodoc: 
    499         attr_reader :addresses 
    500  
    501         def initialize(*addresses) 
    502           super() 
    503           addresses = addresses.flatten 
    504           addresses = ["localhost"] if addresses.empty? 
    505           @addresses = addresses 
    506           @data = MemCache.new(*addresses) 
    507         end 
    508       end 
    509     rescue LoadError 
    510       # MemCache wasn't available so neither can the store be 
    511     end 
    512  
    513       class UnthreadedFileStore #:nodoc: 
    514         attr_reader :cache_path 
    515  
    516         def initialize(cache_path) 
    517           @cache_path = cache_path 
    518         end 
    519  
    520         def write(name, value, options = nil) #:nodoc: 
    521           ensure_cache_path(File.dirname(real_file_path(name))) 
    522           File.open(real_file_path(name), "wb+") { |f| f.write(value) } 
    523         rescue => e 
    524           Base.logger.error "Couldn't create cache directory: #{name} (#{e.message})" if Base.logger 
    525         end 
    526  
    527         def read(name, options = nil) #:nodoc: 
    528           File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil 
    529         end 
    530  
    531         def delete(name, options) #:nodoc: 
    532           File.delete(real_file_path(name)) 
    533         rescue SystemCallError => e 
    534           # If there's no cache, then there's nothing to complain about 
    535         end 
    536  
    537         def delete_matched(matcher, options) #:nodoc: 
    538           search_dir(@cache_path) do |f| 
    539             if f =~ matcher 
    540               begin 
    541                 File.delete(f) 
    542               rescue SystemCallError => e 
    543                 # If there's no cache, then there's nothing to complain about 
    544               end 
    545             end 
    546           end 
    547         end 
    548  
    549         private 
    550           def real_file_path(name) 
    551             '%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')] 
    552           end 
    553  
    554           def ensure_cache_path(path) 
    555             FileUtils.makedirs(path) unless File.exist?(path) 
    556           end 
    557  
    558           def search_dir(dir, &callback) 
    559             Dir.foreach(dir) do |d| 
    560               next if d == "." || d == ".." 
    561               name = File.join(dir, d) 
    562               if File.directory?(name) 
    563                 search_dir(name, &callback) 
    564               else 
    565                 callback.call name 
    566               end 
    567             end 
    568           end 
    569         end 
    570  
    571         class FileStore < UnthreadedFileStore #:nodoc: 
    572           def initialize(cache_path) 
    573             super(cache_path) 
    574             if ActionController::Base.allow_concurrency 
    575               @mutex = Mutex.new 
    576               FileStore.module_eval { include ThreadSafety } 
    577             end 
    578           end 
    579         end 
    580     end 
    581  
    582     # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change. 
    583     # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example: 
    584     # 
    585     #   class ListSweeper < ActionController::Caching::Sweeper 
    586     #     observe List, Item 
    587     # 
    588     #     def after_save(record) 
    589     #       list = record.is_a?(List) ? record : record.list 
    590     #       expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id) 
    591     #       expire_action(:controller => "lists", :action => "all") 
    592     #       list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) } 
    593     #     end 
    594     #   end 
    595     # 
    596     # The sweeper is assigned in the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method: 
    597     # 
    598     #   class ListsController < ApplicationController 
    599     #     caches_action :index, :show, :public, :feed 
    600     #     cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ] 
    601     #   end 
    602     # 
    603     # In the example above, four actions are cached and three actions are responsible for expiring those caches. 
    604     module Sweeping 
    605       def self.included(base) #:nodoc: 
    606         base.extend(ClassMethods) 
    607       end 
    608  
    609       module ClassMethods #:nodoc: 
    610         def cache_sweeper(*sweepers) 
    611           return unless perform_caching 
    612           configuration = sweepers.extract_options! 
    613           sweepers.each do |sweeper| 
    614             ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base) 
    615             sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance 
    616  
    617             if sweeper_instance.is_a?(Sweeper) 
    618               around_filter(sweeper_instance, :only => configuration[:only]) 
    619             else 
    620               after_filter(sweeper_instance, :only => configuration[:only]) 
    621             end 
    622           end 
     49        def cache_configured? 
     50          perform_caching && cache_store 
    62351        end 
    62452      end 
    62553    end 
    62654 
    627     if defined?(ActiveRecord) and defined?(ActiveRecord::Observer) 
    628       class Sweeper < ActiveRecord::Observer #:nodoc: 
    629         attr_accessor :controller 
    630  
    631         def before(controller) 
    632           self.controller = controller 
    633           callback(:before) 
    634         end 
    635  
    636         def after(controller) 
    637           callback(:after) 
    638           # Clean up, so that the controller can be collected after this request 
    639           self.controller = nil 
    640         end 
    641  
    642         protected 
    643           # gets the action cache path for the given options. 
    644           def action_path_for(options) 
    645             ActionController::Caching::Actions::ActionCachePath.path_for(controller, options) 
    646           end 
    647  
    648           # Retrieve instance variables set in the controller. 
    649           def assigns(key) 
    650             controller.instance_variable_get("@#{key}") 
    651           end 
    652  
    653         private 
    654           def callback(timing) 
    655             controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}" 
    656             action_callback_method_name     = "#{controller_callback_method_name}_#{controller.action_name}" 
    657  
    658             send!(controller_callback_method_name) if respond_to?(controller_callback_method_name, true) 
    659             send!(action_callback_method_name)     if respond_to?(action_callback_method_name, true) 
    660           end 
    661  
    662           def method_missing(method, *arguments) 
    663             return if @controller.nil? 
    664             @controller.send!(method, *arguments) 
    665           end 
     55    # Convenience accessor 
     56    def cache(key, options = nil, &block) 
     57      if cache_configured? 
     58        cache_store.fetch(key, options, &block) 
     59      else 
     60        yield 
    66661      end 
    66762    end 
    66863 
    669     module SqlCache 
    670       def self.included(base) #:nodoc: 
    671         if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache) 
    672           base.alias_method_chain :perform_action, :caching 
    673         end 
    674       end 
    675  
    676       def perform_action_with_caching 
    677         ActiveRecord::Base.cache do 
    678           perform_action_without_caching 
    679         end 
    680       end 
     64    def cache_configured? 
     65      self.class.cache_configured? 
    68166    end 
    68267  end 
  • branches/2-1-caching/actionpack/lib/action_view/helpers/cache_helper.rb

    r7106 r8393  
    3232      #      <i>Topics listed alphabetically</i> 
    3333      #    <% end %> 
    34       def cache(name = {}, &block) 
    35         @controller.cache_erb_fragment(block, name
     34      def cache(name = {}, options = nil, &block) 
     35        @controller.cache_erb_fragment(block, name, options
    3636      end 
    3737    end 
  • branches/2-1-caching/actionpack/test/controller/caching_test.rb

    r8226 r8393  
    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 
  • branches/2-1-caching/activesupport/lib/active_support.rb

    r8022 r8393  
    3333require 'active_support/buffered_logger' 
    3434 
     35require 'active_support/gzip' 
     36require 'active_support/cache' 
     37 
    3538require 'active_support/dependencies' 
    3639require 'active_support/deprecation' 
  • branches/2-1-caching/activesupport/lib/active_support/vendor.rb

    r7828 r8393  
    1313  $:.unshift "#{File.dirname(__FILE__)}/vendor/xml-simple-1.0.11" 
    1414end 
     15 
     16begin 
     17  gem 'memcache-client', '~> 1.5.0' 
     18rescue Gem::LoadError 
     19  $:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.5.0" 
     20end 
  • branches/2-1-caching/railties/lib/initializer.rb

    r8301 r8393  
    5959 
    6060    # Sequentially step through all of the available initialization routines, 
    61     # in order: 
    62     # 
    63     # * #check_ruby_version 
    64     # * #set_load_path 
    65     # * #require_frameworks 
    66     # * #set_autoload_paths 
    67     # * add_plugin_load_paths 
    68     # * #load_environment 
    69     # * #initialize_encoding 
    70     # * #initialize_database 
    71     # * #initialize_logger 
    72     # * #initialize_framework_logging 
    73     # * #initialize_framework_views 
    74     # * #initialize_dependency_mechanism 
    75     # * #initialize_whiny_nils 
    76     # * #initialize_temporary_directories 
    77     # * #initialize_framework_settings 
    78     # * #add_support_load_paths 
    79     # * #load_plugins 
    80     # * #load_observers 
    81     # * #initialize_routing 
    82     # * #after_initialize 
    83     # * #load_application_initializers 
     61    # in order (view execution order in source). 
    8462    def process 
    8563      check_ruby_version 
     
    9371      initialize_encoding 
    9472      initialize_database 
     73 
     74      initialize_cache 
     75      initialize_framework_caches 
     76 
    9577      initialize_logger 
    9678      initialize_framework_logging 
     79 
    9780      initialize_framework_views 
    9881      initialize_dependency_mechanism 
    9982      initialize_whiny_nils 
    100       initialize_temporary_directories 
     83      initialize_temporary_session_directory 
    10184      initialize_framework_settings 
    10285 
     
    236219    end 
    237220 
     221    def initialize_cache 
     222      unless defined?(RAILS_CACHE) 
     223        silence_warnings { Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(configuration.cache_store) } 
     224      end 
     225    end 
     226 
     227    def initialize_framework_caches 
     228      if configuration.frameworks.include?(:action_controller) 
     229        ActionController::Base.cache_store ||= RAILS_CACHE 
     230      end 
     231    end 
     232 
    238233    # If the +RAILS_DEFAULT_LOGGER+ constant is already set, this initialization 
    239234    # routine does nothing. If the constant is not set, and Configuration#logger 
     
    274269        framework.to_s.camelize.constantize.const_get("Base").logger ||= RAILS_DEFAULT_LOGGER 
    275270      end 
     271       
     272      RAILS_CACHE.logger ||= RAILS_DEFAULT_LOGGER 
    276273    end 
    277274 
     
    306303    end 
    307304 
    308     def initialize_temporary_directories 
     305    def initialize_temporary_session_directory 
    309306      if configuration.frameworks.include?(:action_controller) 
    310307        session_path = "#{configuration.root_path}/tmp/sessions/" 
    311308        ActionController::Base.session_options[:tmpdir] = File.exist?(session_path) ? session_path : Dir::tmpdir 
    312  
    313         cache_path = "#{configuration.root_path}/tmp/cache/" 
    314         if File.exist?(cache_path) 
    315           ActionController::Base.fragment_cache_store = :file_store, cache_path 
    316         end 
    317309      end 
    318310    end 
     
    414406    # used directly. 
    415407    attr_accessor :logger 
     408 
     409    # The specific cache store to use. By default, the ActiveSupport::Cache::Store will be used. 
     410    attr_accessor :cache_store 
    416411 
    417412    # The root of the application's views. (Defaults to <tt>app/views</tt>.) 
     
    644639        Plugin::Loader 
    645640      end 
     641       
     642      def default_cache_store 
     643        if File.exist?("#{root_path}/tmp/cache/") 
     644          [ :file_store, "#{root_path}/tmp/cache/" ] 
     645        else 
     646          :memory_store 
     647        end 
     648      end 
    646649  end 
    647650end