actionpack 2.0.5 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (210) hide show
  1. data/CHANGELOG +149 -7
  2. data/MIT-LICENSE +1 -1
  3. data/README +1 -1
  4. data/Rakefile +5 -6
  5. data/lib/action_controller.rb +2 -2
  6. data/lib/action_controller/assertions/model_assertions.rb +2 -1
  7. data/lib/action_controller/assertions/response_assertions.rb +4 -2
  8. data/lib/action_controller/assertions/routing_assertions.rb +3 -3
  9. data/lib/action_controller/assertions/selector_assertions.rb +30 -27
  10. data/lib/action_controller/assertions/tag_assertions.rb +3 -3
  11. data/lib/action_controller/base.rb +103 -129
  12. data/lib/action_controller/benchmarking.rb +3 -3
  13. data/lib/action_controller/caching.rb +41 -652
  14. data/lib/action_controller/caching/actions.rb +144 -0
  15. data/lib/action_controller/caching/fragments.rb +138 -0
  16. data/lib/action_controller/caching/pages.rb +154 -0
  17. data/lib/action_controller/caching/sql_cache.rb +18 -0
  18. data/lib/action_controller/caching/sweeping.rb +97 -0
  19. data/lib/action_controller/cgi_ext/cookie.rb +27 -23
  20. data/lib/action_controller/cgi_ext/stdinput.rb +1 -0
  21. data/lib/action_controller/cgi_process.rb +6 -4
  22. data/lib/action_controller/components.rb +7 -6
  23. data/lib/action_controller/cookies.rb +31 -19
  24. data/lib/action_controller/dispatcher.rb +51 -84
  25. data/lib/action_controller/filters.rb +295 -421
  26. data/lib/action_controller/flash.rb +1 -6
  27. data/lib/action_controller/headers.rb +31 -0
  28. data/lib/action_controller/helpers.rb +26 -9
  29. data/lib/action_controller/http_authentication.rb +1 -1
  30. data/lib/action_controller/integration.rb +65 -13
  31. data/lib/action_controller/layout.rb +24 -39
  32. data/lib/action_controller/mime_responds.rb +7 -3
  33. data/lib/action_controller/mime_type.rb +25 -9
  34. data/lib/action_controller/mime_types.rb +1 -1
  35. data/lib/action_controller/polymorphic_routes.rb +32 -17
  36. data/lib/action_controller/record_identifier.rb +10 -4
  37. data/lib/action_controller/request.rb +46 -30
  38. data/lib/action_controller/request_forgery_protection.rb +10 -9
  39. data/lib/action_controller/request_profiler.rb +29 -8
  40. data/lib/action_controller/rescue.rb +24 -24
  41. data/lib/action_controller/resources.rb +66 -23
  42. data/lib/action_controller/response.rb +2 -2
  43. data/lib/action_controller/routing.rb +113 -1229
  44. data/lib/action_controller/routing/builder.rb +204 -0
  45. data/lib/action_controller/{routing_optimisation.rb → routing/optimisations.rb} +13 -12
  46. data/lib/action_controller/routing/recognition_optimisation.rb +158 -0
  47. data/lib/action_controller/routing/route.rb +240 -0
  48. data/lib/action_controller/routing/route_set.rb +435 -0
  49. data/lib/action_controller/routing/routing_ext.rb +46 -0
  50. data/lib/action_controller/routing/segments.rb +283 -0
  51. data/lib/action_controller/session/active_record_store.rb +13 -8
  52. data/lib/action_controller/session/cookie_store.rb +20 -17
  53. data/lib/action_controller/session_management.rb +10 -3
  54. data/lib/action_controller/streaming.rb +45 -31
  55. data/lib/action_controller/test_case.rb +33 -23
  56. data/lib/action_controller/test_process.rb +39 -35
  57. data/lib/action_controller/url_rewriter.rb +18 -12
  58. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -1
  59. data/lib/action_pack.rb +1 -1
  60. data/lib/action_pack/version.rb +2 -2
  61. data/lib/action_view.rb +11 -3
  62. data/lib/action_view/base.rb +73 -390
  63. data/lib/action_view/helpers/active_record_helper.rb +83 -62
  64. data/lib/action_view/helpers/asset_tag_helper.rb +101 -44
  65. data/lib/action_view/helpers/atom_feed_helper.rb +35 -7
  66. data/lib/action_view/helpers/benchmark_helper.rb +5 -3
  67. data/lib/action_view/helpers/cache_helper.rb +3 -2
  68. data/lib/action_view/helpers/capture_helper.rb +1 -2
  69. data/lib/action_view/helpers/date_helper.rb +104 -82
  70. data/lib/action_view/helpers/form_helper.rb +148 -75
  71. data/lib/action_view/helpers/form_options_helper.rb +44 -23
  72. data/lib/action_view/helpers/form_tag_helper.rb +22 -13
  73. data/lib/action_view/helpers/javascripts/controls.js +1 -1
  74. data/lib/action_view/helpers/javascripts/dragdrop.js +1 -1
  75. data/lib/action_view/helpers/javascripts/effects.js +1 -1
  76. data/lib/action_view/helpers/number_helper.rb +10 -3
  77. data/lib/action_view/helpers/prototype_helper.rb +61 -29
  78. data/lib/action_view/helpers/record_tag_helper.rb +3 -3
  79. data/lib/action_view/helpers/sanitize_helper.rb +23 -17
  80. data/lib/action_view/helpers/scriptaculous_helper.rb +86 -60
  81. data/lib/action_view/helpers/text_helper.rb +153 -125
  82. data/lib/action_view/helpers/url_helper.rb +83 -28
  83. data/lib/action_view/inline_template.rb +20 -0
  84. data/lib/action_view/partial_template.rb +70 -0
  85. data/lib/action_view/partials.rb +31 -73
  86. data/lib/action_view/template.rb +127 -0
  87. data/lib/action_view/template_error.rb +8 -7
  88. data/lib/action_view/template_finder.rb +177 -0
  89. data/lib/action_view/template_handler.rb +18 -1
  90. data/lib/action_view/template_handlers/builder.rb +10 -2
  91. data/lib/action_view/template_handlers/compilable.rb +128 -0
  92. data/lib/action_view/template_handlers/erb.rb +37 -2
  93. data/lib/action_view/template_handlers/rjs.rb +14 -1
  94. data/lib/action_view/test_case.rb +58 -0
  95. data/test/abstract_unit.rb +1 -1
  96. data/test/active_record_unit.rb +3 -6
  97. data/test/activerecord/active_record_store_test.rb +1 -2
  98. data/test/activerecord/render_partial_with_record_identification_test.rb +158 -41
  99. data/test/adv_attr_test.rb +20 -0
  100. data/test/controller/action_pack_assertions_test.rb +16 -19
  101. data/test/controller/addresses_render_test.rb +1 -1
  102. data/test/controller/assert_select_test.rb +13 -6
  103. data/test/controller/base_test.rb +48 -2
  104. data/test/controller/benchmark_test.rb +1 -2
  105. data/test/controller/caching_test.rb +282 -21
  106. data/test/controller/capture_test.rb +1 -1
  107. data/test/controller/cgi_test.rb +1 -1
  108. data/test/controller/components_test.rb +1 -1
  109. data/test/controller/content_type_test.rb +2 -2
  110. data/test/controller/cookie_test.rb +13 -2
  111. data/test/controller/custom_handler_test.rb +14 -10
  112. data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -1
  113. data/test/controller/dispatcher_test.rb +31 -49
  114. data/test/controller/fake_controllers.rb +17 -0
  115. data/test/controller/fake_models.rb +6 -0
  116. data/test/controller/filter_params_test.rb +14 -8
  117. data/test/controller/filters_test.rb +44 -16
  118. data/test/controller/flash_test.rb +2 -2
  119. data/test/controller/header_test.rb +14 -0
  120. data/test/controller/helper_test.rb +19 -15
  121. data/test/controller/html-scanner/document_test.rb +1 -2
  122. data/test/controller/html-scanner/node_test.rb +1 -2
  123. data/test/controller/html-scanner/sanitizer_test.rb +8 -5
  124. data/test/controller/html-scanner/tag_node_test.rb +1 -2
  125. data/test/controller/html-scanner/text_node_test.rb +2 -3
  126. data/test/controller/html-scanner/tokenizer_test.rb +8 -2
  127. data/test/controller/http_authentication_test.rb +1 -1
  128. data/test/controller/integration_test.rb +14 -16
  129. data/test/controller/integration_upload_test.rb +43 -0
  130. data/test/controller/layout_test.rb +26 -6
  131. data/test/controller/mime_responds_test.rb +39 -7
  132. data/test/controller/mime_type_test.rb +29 -5
  133. data/test/controller/new_render_test.rb +105 -34
  134. data/test/controller/polymorphic_routes_test.rb +32 -20
  135. data/test/controller/record_identifier_test.rb +38 -2
  136. data/test/controller/redirect_test.rb +21 -1
  137. data/test/controller/render_test.rb +59 -15
  138. data/test/controller/request_forgery_protection_test.rb +92 -5
  139. data/test/controller/request_test.rb +64 -6
  140. data/test/controller/rescue_test.rb +22 -6
  141. data/test/controller/resources_test.rb +102 -14
  142. data/test/controller/routing_test.rb +231 -19
  143. data/test/controller/selector_test.rb +2 -2
  144. data/test/controller/send_file_test.rb +14 -3
  145. data/test/controller/session/cookie_store_test.rb +16 -4
  146. data/test/controller/session/mem_cache_store_test.rb +3 -4
  147. data/test/controller/session_fixation_test.rb +1 -1
  148. data/test/controller/session_management_test.rb +23 -1
  149. data/test/controller/test_test.rb +39 -18
  150. data/test/controller/url_rewriter_test.rb +35 -1
  151. data/test/controller/verification_test.rb +1 -1
  152. data/test/controller/view_paths_test.rb +15 -12
  153. data/test/controller/webservice_test.rb +48 -3
  154. data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
  155. data/test/fixtures/company.rb +1 -0
  156. data/test/fixtures/customers/_customer.html.erb +1 -0
  157. data/test/fixtures/db_definitions/sqlite.sql +6 -0
  158. data/test/fixtures/functional_caching/_partial.erb +3 -0
  159. data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
  160. data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
  161. data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
  162. data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
  163. data/test/fixtures/mascot.rb +3 -0
  164. data/test/fixtures/mascots.yml +4 -0
  165. data/test/fixtures/mascots/_mascot.html.erb +1 -0
  166. data/test/fixtures/multipart/boundary_problem_file +10 -0
  167. data/test/fixtures/public/javascripts/application.js +1 -0
  168. data/test/fixtures/public/javascripts/controls.js +1 -0
  169. data/test/fixtures/public/javascripts/dragdrop.js +1 -0
  170. data/test/fixtures/public/javascripts/effects.js +1 -0
  171. data/test/fixtures/public/javascripts/prototype.js +1 -0
  172. data/test/fixtures/public/javascripts/version.1.0.js +1 -0
  173. data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
  174. data/test/fixtures/reply.rb +1 -0
  175. data/test/fixtures/shared.html.erb +1 -0
  176. data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
  177. data/test/fixtures/test/_customer_counter.erb +1 -0
  178. data/test/fixtures/test/_form.erb +1 -0
  179. data/test/fixtures/test/_labelling_form.erb +1 -0
  180. data/test/fixtures/test/_raise.html.erb +1 -0
  181. data/test/fixtures/test/greeting.js.rjs +1 -0
  182. data/test/fixtures/topics/_topic.html.erb +1 -0
  183. data/test/template/active_record_helper_test.rb +25 -8
  184. data/test/template/asset_tag_helper_test.rb +100 -17
  185. data/test/template/atom_feed_helper_test.rb +29 -1
  186. data/test/template/benchmark_helper_test.rb +10 -22
  187. data/test/template/date_helper_test.rb +455 -153
  188. data/test/template/erb_util_test.rb +10 -42
  189. data/test/template/form_helper_test.rb +192 -66
  190. data/test/template/form_options_helper_test.rb +19 -8
  191. data/test/template/form_tag_helper_test.rb +11 -8
  192. data/test/template/javascript_helper_test.rb +3 -9
  193. data/test/template/number_helper_test.rb +6 -3
  194. data/test/template/prototype_helper_test.rb +27 -40
  195. data/test/template/record_tag_helper_test.rb +54 -0
  196. data/test/template/sanitize_helper_test.rb +5 -6
  197. data/test/template/scriptaculous_helper_test.rb +7 -13
  198. data/test/template/tag_helper_test.rb +3 -6
  199. data/test/template/template_finder_test.rb +73 -0
  200. data/test/template/template_object_test.rb +95 -0
  201. data/test/template/test_test.rb +56 -0
  202. data/test/template/text_helper_test.rb +46 -33
  203. data/test/template/url_helper_test.rb +8 -10
  204. metadata +65 -12
  205. data/lib/action_view/compiled_templates.rb +0 -69
  206. data/test/action_view_test.rb +0 -44
  207. data/test/activerecord/fixtures_test.rb +0 -24
  208. data/test/controller/fragment_store_setting_test.rb +0 -47
  209. data/test/template/compiled_templates_test.rb +0 -197
  210. data/test/template/deprecate_ivars_test.rb +0 -51
@@ -41,14 +41,14 @@ module ActionController #:nodoc:
41
41
  end
42
42
 
43
43
  protected
44
- def render_with_benchmark(options = nil, deprecated_status = nil, &block)
44
+ def render_with_benchmark(options = nil, extra_options = {}, &block)
45
45
  unless logger
46
- render_without_benchmark(options, &block)
46
+ render_without_benchmark(options, extra_options, &block)
47
47
  else
48
48
  db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
49
49
 
50
50
  render_output = nil
51
- @rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, &block) }.real
51
+ @rendering_runtime = Benchmark::realtime{ render_output = render_without_benchmark(options, extra_options, &block) }
52
52
 
53
53
  if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
54
54
  @db_rt_before_render = db_runtime
@@ -2,6 +2,13 @@ require 'fileutils'
2
2
  require 'uri'
3
3
  require 'set'
4
4
 
5
+ require 'action_controller/caching/pages'
6
+ require 'action_controller/caching/actions'
7
+ require 'action_controller/caching/sql_cache'
8
+ require 'action_controller/caching/sweeping'
9
+ require 'action_controller/caching/fragments'
10
+
11
+
5
12
  module ActionController #:nodoc:
6
13
  # Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
7
14
  # around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment.
@@ -9,675 +16,57 @@ module ActionController #:nodoc:
9
16
  # You can read more about each approach and the sweeping assistance by clicking the modules below.
10
17
  #
11
18
  # Note: To turn off all caching and sweeping, set Base.perform_caching = false.
19
+ #
20
+ #
21
+ # == Caching stores
22
+ #
23
+ # All the caching stores from ActiveSupport::Cache is available to be used as backends for Action Controller caching. This setting only
24
+ # affects action and fragment caching as page caching is always written to disk.
25
+ #
26
+ # Configuration examples (MemoryStore is the default):
27
+ #
28
+ # ActionController::Base.cache_store = :memory_store
29
+ # ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
30
+ # ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
31
+ # ActionController::Base.cache_store = :mem_cache_store, "localhost"
32
+ # ActionController::Base.cache_store = MyOwnStore.new("parameter")
12
33
  module Caching
13
34
  def self.included(base) #:nodoc:
14
35
  base.class_eval do
15
- include Pages, Actions, Fragments
36
+ @@cache_store = nil
37
+ cattr_reader :cache_store
16
38
 
17
- if defined? ActiveRecord
18
- include Sweeping, SqlCache
39
+ # Defines the storage option for cached fragments
40
+ def self.cache_store=(store_option)
41
+ @@cache_store = ActiveSupport::Cache.lookup_store(store_option)
19
42
  end
20
43
 
44
+ include Pages, Actions, Fragments
45
+ include Sweeping, SqlCache if defined?(ActiveRecord)
46
+
21
47
  @@perform_caching = true
22
48
  cattr_accessor :perform_caching
23
- end
24
- end
25
-
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
49
 
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
50
+ def self.cache_configured?
51
+ perform_caching && cache_store
97
52
  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
53
  end
154
-
155
- private
156
- def caching_allowed
157
- request.get? && response.headers['Status'].to_i == 200
158
- end
159
54
  end
160
55
 
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
56
+ protected
57
+ # Convenience accessor
58
+ def cache(key, options = {}, &block)
59
+ if cache_configured?
60
+ cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
223
61
  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)
62
+ yield
250
63
  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
64
  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
65
 
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
66
 
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
67
+ private
68
+ def cache_configured?
69
+ self.class.cache_configured?
372
70
  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
623
- end
624
- end
625
- end
626
-
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
666
- end
667
- end
668
-
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
681
- end
682
71
  end
683
- end
72
+ end