halorgium-actionpack 3.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5179 -0
- data/MIT-LICENSE +21 -0
- data/README +409 -0
- data/lib/abstract_controller.rb +16 -0
- data/lib/abstract_controller/base.rb +158 -0
- data/lib/abstract_controller/callbacks.rb +113 -0
- data/lib/abstract_controller/exceptions.rb +12 -0
- data/lib/abstract_controller/helpers.rb +151 -0
- data/lib/abstract_controller/layouts.rb +250 -0
- data/lib/abstract_controller/localized_cache.rb +49 -0
- data/lib/abstract_controller/logger.rb +61 -0
- data/lib/abstract_controller/rendering_controller.rb +188 -0
- data/lib/action_controller.rb +72 -0
- data/lib/action_controller/base.rb +168 -0
- data/lib/action_controller/caching.rb +80 -0
- data/lib/action_controller/caching/actions.rb +163 -0
- data/lib/action_controller/caching/fragments.rb +116 -0
- data/lib/action_controller/caching/pages.rb +154 -0
- data/lib/action_controller/caching/sweeping.rb +97 -0
- data/lib/action_controller/deprecated.rb +4 -0
- data/lib/action_controller/deprecated/integration_test.rb +2 -0
- data/lib/action_controller/deprecated/performance_test.rb +1 -0
- data/lib/action_controller/dispatch/dispatcher.rb +57 -0
- data/lib/action_controller/metal.rb +129 -0
- data/lib/action_controller/metal/benchmarking.rb +73 -0
- data/lib/action_controller/metal/compatibility.rb +145 -0
- data/lib/action_controller/metal/conditional_get.rb +86 -0
- data/lib/action_controller/metal/configuration.rb +28 -0
- data/lib/action_controller/metal/cookies.rb +105 -0
- data/lib/action_controller/metal/exceptions.rb +55 -0
- data/lib/action_controller/metal/filter_parameter_logging.rb +77 -0
- data/lib/action_controller/metal/flash.rb +162 -0
- data/lib/action_controller/metal/head.rb +27 -0
- data/lib/action_controller/metal/helpers.rb +115 -0
- data/lib/action_controller/metal/hide_actions.rb +47 -0
- data/lib/action_controller/metal/http_authentication.rb +312 -0
- data/lib/action_controller/metal/layouts.rb +171 -0
- data/lib/action_controller/metal/mime_responds.rb +317 -0
- data/lib/action_controller/metal/rack_convenience.rb +27 -0
- data/lib/action_controller/metal/redirector.rb +22 -0
- data/lib/action_controller/metal/render_options.rb +103 -0
- data/lib/action_controller/metal/rendering_controller.rb +57 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +108 -0
- data/lib/action_controller/metal/rescuable.rb +13 -0
- data/lib/action_controller/metal/responder.rb +200 -0
- data/lib/action_controller/metal/session.rb +15 -0
- data/lib/action_controller/metal/session_management.rb +45 -0
- data/lib/action_controller/metal/streaming.rb +188 -0
- data/lib/action_controller/metal/testing.rb +39 -0
- data/lib/action_controller/metal/url_for.rb +41 -0
- data/lib/action_controller/metal/verification.rb +130 -0
- data/lib/action_controller/middleware.rb +38 -0
- data/lib/action_controller/notifications.rb +10 -0
- data/lib/action_controller/polymorphic_routes.rb +183 -0
- data/lib/action_controller/record_identifier.rb +91 -0
- data/lib/action_controller/testing/process.rb +111 -0
- data/lib/action_controller/testing/test_case.rb +345 -0
- data/lib/action_controller/translation.rb +13 -0
- data/lib/action_controller/url_rewriter.rb +204 -0
- data/lib/action_controller/vendor/html-scanner.rb +16 -0
- data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
- data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +176 -0
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
- data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
- data/lib/action_dispatch.rb +70 -0
- data/lib/action_dispatch/http/headers.rb +33 -0
- data/lib/action_dispatch/http/mime_type.rb +231 -0
- data/lib/action_dispatch/http/mime_types.rb +23 -0
- data/lib/action_dispatch/http/request.rb +539 -0
- data/lib/action_dispatch/http/response.rb +290 -0
- data/lib/action_dispatch/http/status_codes.rb +42 -0
- data/lib/action_dispatch/http/utils.rb +20 -0
- data/lib/action_dispatch/middleware/callbacks.rb +50 -0
- data/lib/action_dispatch/middleware/params_parser.rb +79 -0
- data/lib/action_dispatch/middleware/rescue.rb +26 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +208 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +235 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +47 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +143 -0
- data/lib/action_dispatch/middleware/stack.rb +116 -0
- data/lib/action_dispatch/middleware/static.rb +44 -0
- data/lib/action_dispatch/middleware/string_coercion.rb +29 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +26 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +10 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +29 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +2 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +10 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +21 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +2 -0
- data/lib/action_dispatch/routing.rb +381 -0
- data/lib/action_dispatch/routing/deprecated_mapper.rb +878 -0
- data/lib/action_dispatch/routing/mapper.rb +327 -0
- data/lib/action_dispatch/routing/route.rb +49 -0
- data/lib/action_dispatch/routing/route_set.rb +497 -0
- data/lib/action_dispatch/testing/assertions.rb +8 -0
- data/lib/action_dispatch/testing/assertions/dom.rb +35 -0
- data/lib/action_dispatch/testing/assertions/model.rb +19 -0
- data/lib/action_dispatch/testing/assertions/response.rb +145 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +144 -0
- data/lib/action_dispatch/testing/assertions/selector.rb +639 -0
- data/lib/action_dispatch/testing/assertions/tag.rb +123 -0
- data/lib/action_dispatch/testing/integration.rb +504 -0
- data/lib/action_dispatch/testing/performance_test.rb +15 -0
- data/lib/action_dispatch/testing/test_request.rb +83 -0
- data/lib/action_dispatch/testing/test_response.rb +131 -0
- data/lib/action_pack.rb +24 -0
- data/lib/action_pack/version.rb +9 -0
- data/lib/action_view.rb +58 -0
- data/lib/action_view/base.rb +308 -0
- data/lib/action_view/context.rb +44 -0
- data/lib/action_view/erb/util.rb +48 -0
- data/lib/action_view/helpers.rb +62 -0
- data/lib/action_view/helpers/active_model_helper.rb +306 -0
- data/lib/action_view/helpers/ajax_helper.rb +68 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +830 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
- data/lib/action_view/helpers/cache_helper.rb +39 -0
- data/lib/action_view/helpers/capture_helper.rb +168 -0
- data/lib/action_view/helpers/date_helper.rb +988 -0
- data/lib/action_view/helpers/debug_helper.rb +38 -0
- data/lib/action_view/helpers/form_helper.rb +1102 -0
- data/lib/action_view/helpers/form_options_helper.rb +600 -0
- data/lib/action_view/helpers/form_tag_helper.rb +495 -0
- data/lib/action_view/helpers/javascript_helper.rb +208 -0
- data/lib/action_view/helpers/number_helper.rb +311 -0
- data/lib/action_view/helpers/prototype_helper.rb +1309 -0
- data/lib/action_view/helpers/raw_output_helper.rb +9 -0
- data/lib/action_view/helpers/record_identification_helper.rb +20 -0
- data/lib/action_view/helpers/record_tag_helper.rb +58 -0
- data/lib/action_view/helpers/sanitize_helper.rb +259 -0
- data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
- data/lib/action_view/helpers/tag_helper.rb +151 -0
- data/lib/action_view/helpers/text_helper.rb +594 -0
- data/lib/action_view/helpers/translation_helper.rb +39 -0
- data/lib/action_view/helpers/url_helper.rb +639 -0
- data/lib/action_view/locale/en.yml +117 -0
- data/lib/action_view/paths.rb +80 -0
- data/lib/action_view/render/partials.rb +342 -0
- data/lib/action_view/render/rendering.rb +134 -0
- data/lib/action_view/safe_buffer.rb +28 -0
- data/lib/action_view/template/error.rb +101 -0
- data/lib/action_view/template/handler.rb +36 -0
- data/lib/action_view/template/handlers.rb +52 -0
- data/lib/action_view/template/handlers/builder.rb +17 -0
- data/lib/action_view/template/handlers/erb.rb +53 -0
- data/lib/action_view/template/handlers/rjs.rb +18 -0
- data/lib/action_view/template/resolver.rb +165 -0
- data/lib/action_view/template/template.rb +131 -0
- data/lib/action_view/template/text.rb +38 -0
- data/lib/action_view/test_case.rb +163 -0
- metadata +236 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'uri'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module ActionController #:nodoc:
|
6
|
+
# Caching is a cheap way of speeding up slow applications by keeping the result of
|
7
|
+
# calculations, renderings, and database calls around for subsequent requests.
|
8
|
+
# Action Controller affords you three approaches in varying levels of granularity:
|
9
|
+
# Page, Action, Fragment.
|
10
|
+
#
|
11
|
+
# You can read more about each approach and the sweeping assistance by clicking the
|
12
|
+
# modules below.
|
13
|
+
#
|
14
|
+
# Note: To turn off all caching and sweeping, set
|
15
|
+
# config.action_controller.perform_caching = false.
|
16
|
+
#
|
17
|
+
# == Caching stores
|
18
|
+
#
|
19
|
+
# All the caching stores from ActiveSupport::Cache are available to be used as backends
|
20
|
+
# for Action Controller caching. This setting only affects action and fragment caching
|
21
|
+
# as page caching is always written to disk.
|
22
|
+
#
|
23
|
+
# Configuration examples (MemoryStore is the default):
|
24
|
+
#
|
25
|
+
# config.action_controller.cache_store = :memory_store
|
26
|
+
# config.action_controller.cache_store = :file_store, "/path/to/cache/directory"
|
27
|
+
# config.action_controller.cache_store = :drb_store, "druby://localhost:9192"
|
28
|
+
# config.action_controller.cache_store = :mem_cache_store, "localhost"
|
29
|
+
# config.action_controller.cache_store = MyOwnStore.new("parameter")
|
30
|
+
module Caching
|
31
|
+
extend ActiveSupport::Concern
|
32
|
+
|
33
|
+
autoload :Actions, 'action_controller/caching/actions'
|
34
|
+
autoload :Fragments, 'action_controller/caching/fragments'
|
35
|
+
autoload :Pages, 'action_controller/caching/pages'
|
36
|
+
autoload :Sweeper, 'action_controller/caching/sweeping'
|
37
|
+
autoload :Sweeping, 'action_controller/caching/sweeping'
|
38
|
+
|
39
|
+
included do
|
40
|
+
@@cache_store = nil
|
41
|
+
cattr_reader :cache_store
|
42
|
+
|
43
|
+
# Defines the storage option for cached fragments
|
44
|
+
def self.cache_store=(store_option)
|
45
|
+
@@cache_store = ActiveSupport::Cache.lookup_store(store_option)
|
46
|
+
end
|
47
|
+
|
48
|
+
include Pages, Actions, Fragments
|
49
|
+
include Sweeping if defined?(ActiveRecord)
|
50
|
+
|
51
|
+
@@perform_caching = true
|
52
|
+
cattr_accessor :perform_caching
|
53
|
+
end
|
54
|
+
|
55
|
+
module ClassMethods
|
56
|
+
def cache_configured?
|
57
|
+
perform_caching && cache_store
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def caching_allowed?
|
62
|
+
request.get? && response.status == 200
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
# Convenience accessor
|
67
|
+
def cache(key, options = {}, &block)
|
68
|
+
if cache_configured?
|
69
|
+
cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
|
70
|
+
else
|
71
|
+
yield
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
def cache_configured?
|
77
|
+
self.class.cache_configured?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ActionController #:nodoc:
|
4
|
+
module Caching
|
5
|
+
# Action caching is similar to page caching by the fact that the entire
|
6
|
+
# output of the response is cached, but unlike page caching, every
|
7
|
+
# request still goes through the Action Pack. The key benefit
|
8
|
+
# of this is that filters are run before the cache is served, which
|
9
|
+
# allows for authentication and other restrictions on whether someone
|
10
|
+
# is allowed to see the cache. Example:
|
11
|
+
#
|
12
|
+
# class ListsController < ApplicationController
|
13
|
+
# before_filter :authenticate, :except => :public
|
14
|
+
# caches_page :public
|
15
|
+
# caches_action :index, :show, :feed
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# In this example, the public action doesn't require authentication,
|
19
|
+
# so it's possible to use the faster page caching method. But both
|
20
|
+
# the show and feed action are to be shielded behind the authenticate
|
21
|
+
# filter, so we need to implement those as action caches.
|
22
|
+
#
|
23
|
+
# Action caching internally uses the fragment caching and an around
|
24
|
+
# filter to do the job. The fragment cache is named according to both
|
25
|
+
# the current host and the path. So a page that is accessed at
|
26
|
+
# http://david.somewhere.com/lists/show/1 will result in a fragment named
|
27
|
+
# "david.somewhere.com/lists/show/1". This allows the cacher to
|
28
|
+
# differentiate between "david.somewhere.com/lists/" and
|
29
|
+
# "jamis.somewhere.com/lists/" -- which is a helpful way of assisting
|
30
|
+
# the subdomain-as-account-key pattern.
|
31
|
+
#
|
32
|
+
# Different representations of the same resource, e.g.
|
33
|
+
# <tt>http://david.somewhere.com/lists</tt> and
|
34
|
+
# <tt>http://david.somewhere.com/lists.xml</tt>
|
35
|
+
# are treated like separate requests and so are cached separately.
|
36
|
+
# Keep in mind when expiring an action cache that
|
37
|
+
# <tt>:action => 'lists'</tt> is not the same as
|
38
|
+
# <tt>:action => 'list', :format => :xml</tt>.
|
39
|
+
#
|
40
|
+
# You can set modify the default action cache path by passing a
|
41
|
+
# :cache_path option. This will be passed directly to
|
42
|
+
# ActionCachePath.path_for. This is handy for actions with multiple
|
43
|
+
# possible routes that should be cached differently. If a block is
|
44
|
+
# given, it is called with the current controller instance.
|
45
|
+
#
|
46
|
+
# And you can also use :if (or :unless) to pass a Proc that
|
47
|
+
# specifies when the action should be cached.
|
48
|
+
#
|
49
|
+
# Finally, if you are using memcached, you can also pass :expires_in.
|
50
|
+
#
|
51
|
+
# class ListsController < ApplicationController
|
52
|
+
# before_filter :authenticate, :except => :public
|
53
|
+
# caches_page :public
|
54
|
+
# caches_action :index, :if => proc do |c|
|
55
|
+
# !c.request.format.json? # cache if is not a JSON request
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# caches_action :show, :cache_path => { :project => 1 },
|
59
|
+
# :expires_in => 1.hour
|
60
|
+
#
|
61
|
+
# caches_action :feed, :cache_path => proc do |controller|
|
62
|
+
# if controller.params[:user_id]
|
63
|
+
# controller.send(:user_list_url,
|
64
|
+
# controller.params[:user_id], controller.params[:id])
|
65
|
+
# else
|
66
|
+
# controller.send(:list_url, controller.params[:id])
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# If you pass :layout => false, it will only cache your action
|
72
|
+
# content. It is useful when your layout has dynamic information.
|
73
|
+
#
|
74
|
+
module Actions
|
75
|
+
extend ActiveSupport::Concern
|
76
|
+
|
77
|
+
module ClassMethods
|
78
|
+
# Declares that +actions+ should be cached.
|
79
|
+
# See ActionController::Caching::Actions for details.
|
80
|
+
def caches_action(*actions)
|
81
|
+
return unless cache_configured?
|
82
|
+
options = actions.extract_options!
|
83
|
+
filter_options = options.extract!(:if, :unless).merge(:only => actions)
|
84
|
+
cache_options = options.extract!(:layout, :cache_path).merge(:store_options => options)
|
85
|
+
|
86
|
+
around_filter ActionCacheFilter.new(cache_options), filter_options
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def _render_cache_fragment(cache, extension, layout)
|
91
|
+
render :text => cache, :layout => layout, :content_type => Mime[extension || :html]
|
92
|
+
end
|
93
|
+
|
94
|
+
def _save_fragment(name, layout, options)
|
95
|
+
return unless caching_allowed?
|
96
|
+
|
97
|
+
content = layout ? view_context.content_for(:layout) : response_body
|
98
|
+
write_fragment(name, content, options)
|
99
|
+
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
def expire_action(options = {})
|
103
|
+
return unless cache_configured?
|
104
|
+
|
105
|
+
actions = options[:action]
|
106
|
+
if actions.is_a?(Array)
|
107
|
+
actions.each {|action| expire_action(options.merge(:action => action)) }
|
108
|
+
else
|
109
|
+
expire_fragment(ActionCachePath.new(self, options, false).path)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class ActionCacheFilter #:nodoc:
|
114
|
+
def initialize(options, &block)
|
115
|
+
@cache_path, @store_options, @layout =
|
116
|
+
options.values_at(:cache_path, :store_options, :layout)
|
117
|
+
end
|
118
|
+
|
119
|
+
def filter(controller)
|
120
|
+
path_options = if @cache_path.respond_to?(:call)
|
121
|
+
controller.instance_exec(controller, &@cache_path)
|
122
|
+
else
|
123
|
+
@cache_path
|
124
|
+
end
|
125
|
+
|
126
|
+
cache_path = ActionCachePath.new(controller, path_options || {})
|
127
|
+
|
128
|
+
if cache = controller.read_fragment(cache_path.path, @store_options)
|
129
|
+
controller._render_cache_fragment(cache, cache_path.extension, @layout == false)
|
130
|
+
else
|
131
|
+
yield
|
132
|
+
controller._save_fragment(cache_path.path, @layout == false, @store_options)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class ActionCachePath
|
138
|
+
attr_reader :path, :extension
|
139
|
+
|
140
|
+
# If +infer_extension+ is true, the cache path extension is looked up from the request's
|
141
|
+
# path & format. This is desirable when reading and writing the cache, but not when
|
142
|
+
# expiring the cache - expire_action should expire the same files regardless of the
|
143
|
+
# request format.
|
144
|
+
def initialize(controller, options = {}, infer_extension = true)
|
145
|
+
if infer_extension
|
146
|
+
@extension = controller.params[:format]
|
147
|
+
options.reverse_merge!(:format => @extension) if options.is_a?(Hash)
|
148
|
+
end
|
149
|
+
|
150
|
+
path = controller.url_for(options).split(%r{://}).last
|
151
|
+
@path = normalize!(path)
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
def normalize!(path)
|
156
|
+
path << 'index' if path[-1] == ?/
|
157
|
+
path << ".#{extension}" if extension and !path.ends_with?(extension)
|
158
|
+
URI.unescape(path)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module ActionController #:nodoc:
|
2
|
+
module Caching
|
3
|
+
# Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
|
4
|
+
# certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
|
5
|
+
# parties. The caching is done using the cache helper available in the Action View. A template with caching might look something like:
|
6
|
+
#
|
7
|
+
# <b>Hello <%= @name %></b>
|
8
|
+
# <% cache do %>
|
9
|
+
# All the topics in the system:
|
10
|
+
# <%= render :partial => "topic", :collection => Topic.find(:all) %>
|
11
|
+
# <% end %>
|
12
|
+
#
|
13
|
+
# 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
|
14
|
+
# be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>.
|
15
|
+
#
|
16
|
+
# This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using
|
17
|
+
# <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like:
|
18
|
+
#
|
19
|
+
# <% cache(:action => "list", :action_suffix => "all_topics") do %>
|
20
|
+
#
|
21
|
+
# 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
|
22
|
+
# 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
|
23
|
+
# cache names that we can refer to when we need to expire the cache.
|
24
|
+
#
|
25
|
+
# The expiration call for this example is:
|
26
|
+
#
|
27
|
+
# expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
|
28
|
+
module Fragments
|
29
|
+
# Given a key (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
|
30
|
+
# writing, or expiring a cached fragment. If the key is a hash, the generated key is the return
|
31
|
+
# value of url_for on that hash (without the protocol). All keys are prefixed with "views/" and uses
|
32
|
+
# ActiveSupport::Cache.expand_cache_key for the expansion.
|
33
|
+
def fragment_cache_key(key)
|
34
|
+
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
|
35
|
+
end
|
36
|
+
|
37
|
+
def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
|
38
|
+
if perform_caching
|
39
|
+
if fragment_exist?(name,options)
|
40
|
+
buffer.concat(read_fragment(name, options))
|
41
|
+
else
|
42
|
+
pos = buffer.length
|
43
|
+
block.call
|
44
|
+
write_fragment(name, buffer[pos..-1], options)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
block.call
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
52
|
+
def write_fragment(key, content, options = nil)
|
53
|
+
return content unless cache_configured?
|
54
|
+
key = fragment_cache_key(key)
|
55
|
+
|
56
|
+
ActiveSupport::Notifications.instrument(:write_fragment, :key => key) do
|
57
|
+
cache_store.write(key, content, options)
|
58
|
+
end
|
59
|
+
content
|
60
|
+
end
|
61
|
+
|
62
|
+
# Reads a cached fragment from the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
|
63
|
+
def read_fragment(key, options = nil)
|
64
|
+
return unless cache_configured?
|
65
|
+
key = fragment_cache_key(key)
|
66
|
+
|
67
|
+
ActiveSupport::Notifications.instrument(:read_fragment, :key => key) do
|
68
|
+
cache_store.read(key, options)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Check if a cached fragment from the location signified by <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats)
|
73
|
+
def fragment_exist?(key, options = nil)
|
74
|
+
return unless cache_configured?
|
75
|
+
key = fragment_cache_key(key)
|
76
|
+
|
77
|
+
ActiveSupport::Notifications.instrument(:fragment_exist?, :key => key) do
|
78
|
+
cache_store.exist?(key, options)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Removes fragments from the cache.
|
83
|
+
#
|
84
|
+
# +key+ can take one of three forms:
|
85
|
+
# * String - This would normally take the form of a path, like
|
86
|
+
# <tt>"pages/45/notes"</tt>.
|
87
|
+
# * Hash - Treated as an implicit call to +url_for+, like
|
88
|
+
# <tt>{:controller => "pages", :action => "notes", :id => 45}</tt>
|
89
|
+
# * Regexp - Will remove any fragment that matches, so
|
90
|
+
# <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
|
91
|
+
# don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
|
92
|
+
# the actual filename matched looks like
|
93
|
+
# <tt>./cache/filename/path.cache</tt>. Note: Regexp expiration is
|
94
|
+
# only supported on caches that can iterate over all keys (unlike
|
95
|
+
# memcached).
|
96
|
+
#
|
97
|
+
# +options+ is passed through to the cache store's <tt>delete</tt>
|
98
|
+
# method (or <tt>delete_matched</tt>, for Regexp keys.)
|
99
|
+
def expire_fragment(key, options = nil)
|
100
|
+
return unless cache_configured?
|
101
|
+
key = fragment_cache_key(key) unless key.is_a?(Regexp)
|
102
|
+
message = nil
|
103
|
+
|
104
|
+
ActiveSupport::Notifications.instrument(:expire_fragment, :key => key) do
|
105
|
+
if key.is_a?(Regexp)
|
106
|
+
message = "Expired fragments matching: #{key.source}"
|
107
|
+
cache_store.delete_matched(key, options)
|
108
|
+
else
|
109
|
+
message = "Expired fragment: #{key}"
|
110
|
+
cache_store.delete(key, options)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module ActionController #:nodoc:
|
5
|
+
module Caching
|
6
|
+
# Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server
|
7
|
+
# can serve without going through Action Pack. This is the fastest way to cache your content as opposed to going dynamically
|
8
|
+
# through the process of generating the content. Unfortunately, this incredible speed-up is only available to stateless pages
|
9
|
+
# where all visitors are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are
|
10
|
+
# a great fit for this approach, but account-based systems where people log in and manipulate their own data are often less
|
11
|
+
# likely candidates.
|
12
|
+
#
|
13
|
+
# Specifying which actions to cache is done through the <tt>caches_page</tt> class method:
|
14
|
+
#
|
15
|
+
# class WeblogController < ActionController::Base
|
16
|
+
# caches_page :show, :new
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>,
|
20
|
+
# which match the URLs used to trigger the dynamic generation. This is how the web server is able
|
21
|
+
# pick up a cache file when it exists and otherwise let the request pass on to Action Pack to generate it.
|
22
|
+
#
|
23
|
+
# Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
|
24
|
+
# is not restored before another hit is made against it. The API for doing so mimics the options from +url_for+ and friends:
|
25
|
+
#
|
26
|
+
# class WeblogController < ActionController::Base
|
27
|
+
# def update
|
28
|
+
# List.update(params[:list][:id], params[:list])
|
29
|
+
# expire_page :action => "show", :id => params[:list][:id]
|
30
|
+
# redirect_to :action => "show", :id => params[:list][:id]
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
|
35
|
+
# expired.
|
36
|
+
module Pages
|
37
|
+
def self.included(base) #:nodoc:
|
38
|
+
base.extend(ClassMethods)
|
39
|
+
base.class_eval do
|
40
|
+
@@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
|
41
|
+
##
|
42
|
+
# :singleton-method:
|
43
|
+
# The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
|
44
|
+
# For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing
|
45
|
+
# this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
|
46
|
+
# web server to look in the new location for cached files.
|
47
|
+
cattr_accessor :page_cache_directory
|
48
|
+
|
49
|
+
@@page_cache_extension = '.html'
|
50
|
+
##
|
51
|
+
# :singleton-method:
|
52
|
+
# Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
|
53
|
+
# order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
|
54
|
+
# If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
|
55
|
+
# extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
|
56
|
+
cattr_accessor :page_cache_extension
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module ClassMethods
|
61
|
+
# Expires the page that was cached with the +path+ as a key. Example:
|
62
|
+
# expire_page "/lists/show"
|
63
|
+
def expire_page(path)
|
64
|
+
return unless perform_caching
|
65
|
+
path = page_cache_path(path)
|
66
|
+
|
67
|
+
ActiveSupport::Notifications.instrument(:expire_page, :path => path) do
|
68
|
+
File.delete(path) if File.exist?(path)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Manually cache the +content+ in the key determined by +path+. Example:
|
73
|
+
# cache_page "I'm the cached content", "/lists/show"
|
74
|
+
def cache_page(content, path)
|
75
|
+
return unless perform_caching
|
76
|
+
path = page_cache_path(path)
|
77
|
+
|
78
|
+
ActiveSupport::Notifications.instrument(:cache_page, :path => path) do
|
79
|
+
FileUtils.makedirs(File.dirname(path))
|
80
|
+
File.open(path, "wb+") { |f| f.write(content) }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
|
85
|
+
# matches the triggering url.
|
86
|
+
#
|
87
|
+
# Usage:
|
88
|
+
#
|
89
|
+
# # cache the index action
|
90
|
+
# caches_page :index
|
91
|
+
#
|
92
|
+
# # cache the index action except for JSON requests
|
93
|
+
# caches_page :index, :if => Proc.new { |c| !c.request.format.json? }
|
94
|
+
def caches_page(*actions)
|
95
|
+
return unless perform_caching
|
96
|
+
options = actions.extract_options!
|
97
|
+
after_filter({:only => actions}.merge(options)) { |c| c.cache_page }
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
def page_cache_file(path)
|
102
|
+
name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/'))
|
103
|
+
name << page_cache_extension unless (name.split('/').last || name).include? '.'
|
104
|
+
return name
|
105
|
+
end
|
106
|
+
|
107
|
+
def page_cache_path(path)
|
108
|
+
page_cache_directory + page_cache_file(path)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Expires the page that was cached with the +options+ as a key. Example:
|
113
|
+
# expire_page :controller => "lists", :action => "show"
|
114
|
+
def expire_page(options = {})
|
115
|
+
return unless perform_caching
|
116
|
+
|
117
|
+
if options.is_a?(Hash)
|
118
|
+
if options[:action].is_a?(Array)
|
119
|
+
options[:action].dup.each do |action|
|
120
|
+
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action)))
|
121
|
+
end
|
122
|
+
else
|
123
|
+
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
|
124
|
+
end
|
125
|
+
else
|
126
|
+
self.class.expire_page(options)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used
|
131
|
+
# If no options are provided, the requested url is used. Example:
|
132
|
+
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
|
133
|
+
def cache_page(content = nil, options = nil)
|
134
|
+
return unless perform_caching && caching_allowed
|
135
|
+
|
136
|
+
path = case options
|
137
|
+
when Hash
|
138
|
+
url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format]))
|
139
|
+
when String
|
140
|
+
options
|
141
|
+
else
|
142
|
+
request.path
|
143
|
+
end
|
144
|
+
|
145
|
+
self.class.cache_page(content || response.body, path)
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
def caching_allowed
|
150
|
+
request.get? && response.status.to_i == 200
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|