halorgium-actionpack 3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. data/CHANGELOG +5179 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +409 -0
  4. data/lib/abstract_controller.rb +16 -0
  5. data/lib/abstract_controller/base.rb +158 -0
  6. data/lib/abstract_controller/callbacks.rb +113 -0
  7. data/lib/abstract_controller/exceptions.rb +12 -0
  8. data/lib/abstract_controller/helpers.rb +151 -0
  9. data/lib/abstract_controller/layouts.rb +250 -0
  10. data/lib/abstract_controller/localized_cache.rb +49 -0
  11. data/lib/abstract_controller/logger.rb +61 -0
  12. data/lib/abstract_controller/rendering_controller.rb +188 -0
  13. data/lib/action_controller.rb +72 -0
  14. data/lib/action_controller/base.rb +168 -0
  15. data/lib/action_controller/caching.rb +80 -0
  16. data/lib/action_controller/caching/actions.rb +163 -0
  17. data/lib/action_controller/caching/fragments.rb +116 -0
  18. data/lib/action_controller/caching/pages.rb +154 -0
  19. data/lib/action_controller/caching/sweeping.rb +97 -0
  20. data/lib/action_controller/deprecated.rb +4 -0
  21. data/lib/action_controller/deprecated/integration_test.rb +2 -0
  22. data/lib/action_controller/deprecated/performance_test.rb +1 -0
  23. data/lib/action_controller/dispatch/dispatcher.rb +57 -0
  24. data/lib/action_controller/metal.rb +129 -0
  25. data/lib/action_controller/metal/benchmarking.rb +73 -0
  26. data/lib/action_controller/metal/compatibility.rb +145 -0
  27. data/lib/action_controller/metal/conditional_get.rb +86 -0
  28. data/lib/action_controller/metal/configuration.rb +28 -0
  29. data/lib/action_controller/metal/cookies.rb +105 -0
  30. data/lib/action_controller/metal/exceptions.rb +55 -0
  31. data/lib/action_controller/metal/filter_parameter_logging.rb +77 -0
  32. data/lib/action_controller/metal/flash.rb +162 -0
  33. data/lib/action_controller/metal/head.rb +27 -0
  34. data/lib/action_controller/metal/helpers.rb +115 -0
  35. data/lib/action_controller/metal/hide_actions.rb +47 -0
  36. data/lib/action_controller/metal/http_authentication.rb +312 -0
  37. data/lib/action_controller/metal/layouts.rb +171 -0
  38. data/lib/action_controller/metal/mime_responds.rb +317 -0
  39. data/lib/action_controller/metal/rack_convenience.rb +27 -0
  40. data/lib/action_controller/metal/redirector.rb +22 -0
  41. data/lib/action_controller/metal/render_options.rb +103 -0
  42. data/lib/action_controller/metal/rendering_controller.rb +57 -0
  43. data/lib/action_controller/metal/request_forgery_protection.rb +108 -0
  44. data/lib/action_controller/metal/rescuable.rb +13 -0
  45. data/lib/action_controller/metal/responder.rb +200 -0
  46. data/lib/action_controller/metal/session.rb +15 -0
  47. data/lib/action_controller/metal/session_management.rb +45 -0
  48. data/lib/action_controller/metal/streaming.rb +188 -0
  49. data/lib/action_controller/metal/testing.rb +39 -0
  50. data/lib/action_controller/metal/url_for.rb +41 -0
  51. data/lib/action_controller/metal/verification.rb +130 -0
  52. data/lib/action_controller/middleware.rb +38 -0
  53. data/lib/action_controller/notifications.rb +10 -0
  54. data/lib/action_controller/polymorphic_routes.rb +183 -0
  55. data/lib/action_controller/record_identifier.rb +91 -0
  56. data/lib/action_controller/testing/process.rb +111 -0
  57. data/lib/action_controller/testing/test_case.rb +345 -0
  58. data/lib/action_controller/translation.rb +13 -0
  59. data/lib/action_controller/url_rewriter.rb +204 -0
  60. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  61. data/lib/action_controller/vendor/html-scanner/html/document.rb +68 -0
  62. data/lib/action_controller/vendor/html-scanner/html/node.rb +537 -0
  63. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +176 -0
  64. data/lib/action_controller/vendor/html-scanner/html/selector.rb +828 -0
  65. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +105 -0
  66. data/lib/action_controller/vendor/html-scanner/html/version.rb +11 -0
  67. data/lib/action_dispatch.rb +70 -0
  68. data/lib/action_dispatch/http/headers.rb +33 -0
  69. data/lib/action_dispatch/http/mime_type.rb +231 -0
  70. data/lib/action_dispatch/http/mime_types.rb +23 -0
  71. data/lib/action_dispatch/http/request.rb +539 -0
  72. data/lib/action_dispatch/http/response.rb +290 -0
  73. data/lib/action_dispatch/http/status_codes.rb +42 -0
  74. data/lib/action_dispatch/http/utils.rb +20 -0
  75. data/lib/action_dispatch/middleware/callbacks.rb +50 -0
  76. data/lib/action_dispatch/middleware/params_parser.rb +79 -0
  77. data/lib/action_dispatch/middleware/rescue.rb +26 -0
  78. data/lib/action_dispatch/middleware/session/abstract_store.rb +208 -0
  79. data/lib/action_dispatch/middleware/session/cookie_store.rb +235 -0
  80. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +47 -0
  81. data/lib/action_dispatch/middleware/show_exceptions.rb +143 -0
  82. data/lib/action_dispatch/middleware/stack.rb +116 -0
  83. data/lib/action_dispatch/middleware/static.rb +44 -0
  84. data/lib/action_dispatch/middleware/string_coercion.rb +29 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +24 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +26 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +10 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +29 -0
  89. data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +2 -0
  90. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +10 -0
  91. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +21 -0
  92. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +2 -0
  93. data/lib/action_dispatch/routing.rb +381 -0
  94. data/lib/action_dispatch/routing/deprecated_mapper.rb +878 -0
  95. data/lib/action_dispatch/routing/mapper.rb +327 -0
  96. data/lib/action_dispatch/routing/route.rb +49 -0
  97. data/lib/action_dispatch/routing/route_set.rb +497 -0
  98. data/lib/action_dispatch/testing/assertions.rb +8 -0
  99. data/lib/action_dispatch/testing/assertions/dom.rb +35 -0
  100. data/lib/action_dispatch/testing/assertions/model.rb +19 -0
  101. data/lib/action_dispatch/testing/assertions/response.rb +145 -0
  102. data/lib/action_dispatch/testing/assertions/routing.rb +144 -0
  103. data/lib/action_dispatch/testing/assertions/selector.rb +639 -0
  104. data/lib/action_dispatch/testing/assertions/tag.rb +123 -0
  105. data/lib/action_dispatch/testing/integration.rb +504 -0
  106. data/lib/action_dispatch/testing/performance_test.rb +15 -0
  107. data/lib/action_dispatch/testing/test_request.rb +83 -0
  108. data/lib/action_dispatch/testing/test_response.rb +131 -0
  109. data/lib/action_pack.rb +24 -0
  110. data/lib/action_pack/version.rb +9 -0
  111. data/lib/action_view.rb +58 -0
  112. data/lib/action_view/base.rb +308 -0
  113. data/lib/action_view/context.rb +44 -0
  114. data/lib/action_view/erb/util.rb +48 -0
  115. data/lib/action_view/helpers.rb +62 -0
  116. data/lib/action_view/helpers/active_model_helper.rb +306 -0
  117. data/lib/action_view/helpers/ajax_helper.rb +68 -0
  118. data/lib/action_view/helpers/asset_tag_helper.rb +830 -0
  119. data/lib/action_view/helpers/atom_feed_helper.rb +198 -0
  120. data/lib/action_view/helpers/cache_helper.rb +39 -0
  121. data/lib/action_view/helpers/capture_helper.rb +168 -0
  122. data/lib/action_view/helpers/date_helper.rb +988 -0
  123. data/lib/action_view/helpers/debug_helper.rb +38 -0
  124. data/lib/action_view/helpers/form_helper.rb +1102 -0
  125. data/lib/action_view/helpers/form_options_helper.rb +600 -0
  126. data/lib/action_view/helpers/form_tag_helper.rb +495 -0
  127. data/lib/action_view/helpers/javascript_helper.rb +208 -0
  128. data/lib/action_view/helpers/number_helper.rb +311 -0
  129. data/lib/action_view/helpers/prototype_helper.rb +1309 -0
  130. data/lib/action_view/helpers/raw_output_helper.rb +9 -0
  131. data/lib/action_view/helpers/record_identification_helper.rb +20 -0
  132. data/lib/action_view/helpers/record_tag_helper.rb +58 -0
  133. data/lib/action_view/helpers/sanitize_helper.rb +259 -0
  134. data/lib/action_view/helpers/scriptaculous_helper.rb +226 -0
  135. data/lib/action_view/helpers/tag_helper.rb +151 -0
  136. data/lib/action_view/helpers/text_helper.rb +594 -0
  137. data/lib/action_view/helpers/translation_helper.rb +39 -0
  138. data/lib/action_view/helpers/url_helper.rb +639 -0
  139. data/lib/action_view/locale/en.yml +117 -0
  140. data/lib/action_view/paths.rb +80 -0
  141. data/lib/action_view/render/partials.rb +342 -0
  142. data/lib/action_view/render/rendering.rb +134 -0
  143. data/lib/action_view/safe_buffer.rb +28 -0
  144. data/lib/action_view/template/error.rb +101 -0
  145. data/lib/action_view/template/handler.rb +36 -0
  146. data/lib/action_view/template/handlers.rb +52 -0
  147. data/lib/action_view/template/handlers/builder.rb +17 -0
  148. data/lib/action_view/template/handlers/erb.rb +53 -0
  149. data/lib/action_view/template/handlers/rjs.rb +18 -0
  150. data/lib/action_view/template/resolver.rb +165 -0
  151. data/lib/action_view/template/template.rb +131 -0
  152. data/lib/action_view/template/text.rb +38 -0
  153. data/lib/action_view/test_case.rb +163 -0
  154. 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