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,86 @@
1
+ module ActionController
2
+ module ConditionalGet
3
+ extend ActiveSupport::Concern
4
+
5
+ include RackConvenience
6
+ include Head
7
+
8
+ # Sets the etag, last_modified, or both on the response and renders a
9
+ # "304 Not Modified" response if the request is already fresh.
10
+ #
11
+ # Parameters:
12
+ # * <tt>:etag</tt>
13
+ # * <tt>:last_modified</tt>
14
+ # * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches).
15
+ #
16
+ # Example:
17
+ #
18
+ # def show
19
+ # @article = Article.find(params[:id])
20
+ # fresh_when(:etag => @article, :last_modified => @article.created_at.utc, :public => true)
21
+ # end
22
+ #
23
+ # This will render the show template if the request isn't sending a matching etag or
24
+ # If-Modified-Since header and just a "304 Not Modified" response if there's a match.
25
+ #
26
+ def fresh_when(options)
27
+ options.assert_valid_keys(:etag, :last_modified, :public)
28
+
29
+ response.etag = options[:etag] if options[:etag]
30
+ response.last_modified = options[:last_modified] if options[:last_modified]
31
+ response.cache_control[:public] = true if options[:public]
32
+
33
+ head :not_modified if request.fresh?(response)
34
+ end
35
+
36
+ # Sets the etag and/or last_modified on the response and checks it against
37
+ # the client request. If the request doesn't match the options provided, the
38
+ # request is considered stale and should be generated from scratch. Otherwise,
39
+ # it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent.
40
+ #
41
+ # Parameters:
42
+ # * <tt>:etag</tt>
43
+ # * <tt>:last_modified</tt>
44
+ # * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches).
45
+ #
46
+ # Example:
47
+ #
48
+ # def show
49
+ # @article = Article.find(params[:id])
50
+ #
51
+ # if stale?(:etag => @article, :last_modified => @article.created_at.utc)
52
+ # @statistics = @article.really_expensive_call
53
+ # respond_to do |format|
54
+ # # all the supported formats
55
+ # end
56
+ # end
57
+ # end
58
+ def stale?(options)
59
+ fresh_when(options)
60
+ !request.fresh?(response)
61
+ end
62
+
63
+ # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that
64
+ # intermediate caches shouldn't cache the response.
65
+ #
66
+ # Examples:
67
+ # expires_in 20.minutes
68
+ # expires_in 3.hours, :public => true
69
+ # expires in 3.hours, 'max-stale' => 5.hours, :public => true
70
+ #
71
+ # This method will overwrite an existing Cache-Control header.
72
+ # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
73
+ def expires_in(seconds, options = {}) #:doc:
74
+ response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public))
75
+ options.delete(:private)
76
+
77
+ response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
78
+ end
79
+
80
+ # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or
81
+ # intermediate caches (like caching proxy servers).
82
+ def expires_now #:doc:
83
+ response.cache_control.replace(:no_cache => true)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,28 @@
1
+ module ActionController
2
+ module Configuration
3
+ extend ActiveSupport::Concern
4
+
5
+ def config
6
+ @config ||= self.class.config
7
+ end
8
+
9
+ def config=(config)
10
+ @config = config
11
+ end
12
+
13
+ module ClassMethods
14
+ def default_config
15
+ @default_config ||= {}
16
+ end
17
+
18
+ def config
19
+ self.config ||= default_config
20
+ end
21
+
22
+ def config=(config)
23
+ @config = ActiveSupport::OrderedHash.new
24
+ @config.merge!(config)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,105 @@
1
+ module ActionController #:nodoc:
2
+ # Cookies are read and written through ActionController#cookies.
3
+ #
4
+ # The cookies being read are the ones received along with the request, the cookies
5
+ # being written will be sent out with the response. Reading a cookie does not get
6
+ # the cookie object itself back, just the value it holds.
7
+ #
8
+ # Examples for writing:
9
+ #
10
+ # # Sets a simple session cookie.
11
+ # cookies[:user_name] = "david"
12
+ #
13
+ # # Sets a cookie that expires in 1 hour.
14
+ # cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
15
+ #
16
+ # Examples for reading:
17
+ #
18
+ # cookies[:user_name] # => "david"
19
+ # cookies.size # => 2
20
+ #
21
+ # Example for deleting:
22
+ #
23
+ # cookies.delete :user_name
24
+ #
25
+ # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie:
26
+ #
27
+ # cookies[:key] = {
28
+ # :value => 'a yummy cookie',
29
+ # :expires => 1.year.from_now,
30
+ # :domain => 'domain.com'
31
+ # }
32
+ #
33
+ # cookies.delete(:key, :domain => 'domain.com')
34
+ #
35
+ # The option symbols for setting cookies are:
36
+ #
37
+ # * <tt>:value</tt> - The cookie's value or list of values (as an array).
38
+ # * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
39
+ # of the application.
40
+ # * <tt>:domain</tt> - The domain for which this cookie applies.
41
+ # * <tt>:expires</tt> - The time at which this cookie expires, as a Time object.
42
+ # * <tt>:secure</tt> - Whether this cookie is a only transmitted to HTTPS servers.
43
+ # Default is +false+.
44
+ # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
45
+ # only HTTP. Defaults to +false+.
46
+ module Cookies
47
+ extend ActiveSupport::Concern
48
+
49
+ include RackConvenience
50
+
51
+ included do
52
+ helper_method :cookies
53
+ end
54
+
55
+ protected
56
+ # Returns the cookie container, which operates as described above.
57
+ def cookies
58
+ @cookies ||= CookieJar.build(request, response)
59
+ end
60
+ end
61
+
62
+ class CookieJar < Hash #:nodoc:
63
+ def self.build(request, response)
64
+ new.tap do |hash|
65
+ hash.update(request.cookies)
66
+ hash.response = response
67
+ end
68
+ end
69
+
70
+ attr_accessor :response
71
+
72
+ # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists.
73
+ def [](name)
74
+ super(name.to_s)
75
+ end
76
+
77
+ # Sets the cookie named +name+. The second argument may be the very cookie
78
+ # value, or a hash of options as documented above.
79
+ def []=(key, options)
80
+ if options.is_a?(Hash)
81
+ options.symbolize_keys!
82
+ value = options[:value]
83
+ else
84
+ value = options
85
+ options = { :value => value }
86
+ end
87
+
88
+ super(key.to_s, value)
89
+
90
+ options[:path] ||= "/"
91
+ response.set_cookie(key, options)
92
+ end
93
+
94
+ # Removes the cookie on the client machine by setting the value to an empty string
95
+ # and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
96
+ # an options hash to delete cookies with extra data such as a <tt>:path</tt>.
97
+ def delete(key, options = {})
98
+ options.symbolize_keys!
99
+ options[:path] ||= "/"
100
+ value = super(key.to_s)
101
+ response.delete_cookie(key, options)
102
+ value
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,55 @@
1
+ module ActionController
2
+ class ActionControllerError < StandardError #:nodoc:
3
+ end
4
+
5
+ class RenderError < ActionControllerError #:nodoc:
6
+ end
7
+
8
+ class RoutingError < ActionControllerError #:nodoc:
9
+ attr_reader :failures
10
+ def initialize(message, failures=[])
11
+ super(message)
12
+ @failures = failures
13
+ end
14
+ end
15
+
16
+ class MethodNotAllowed < ActionControllerError #:nodoc:
17
+ attr_reader :allowed_methods
18
+
19
+ def initialize(*allowed_methods)
20
+ super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
21
+ @allowed_methods = allowed_methods
22
+ end
23
+
24
+ def allowed_methods_header
25
+ allowed_methods.map { |method_symbol| method_symbol.to_s.upcase } * ', '
26
+ end
27
+
28
+ def handle_response!(response)
29
+ response.headers['Allow'] ||= allowed_methods_header
30
+ end
31
+ end
32
+
33
+ class NotImplemented < MethodNotAllowed #:nodoc:
34
+ end
35
+
36
+ class UnknownController < ActionControllerError #:nodoc:
37
+ end
38
+
39
+ class MissingFile < ActionControllerError #:nodoc:
40
+ end
41
+
42
+ class RenderError < ActionControllerError #:nodoc:
43
+ end
44
+
45
+ class SessionOverflowError < ActionControllerError #:nodoc:
46
+ DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
47
+
48
+ def initialize(message = nil)
49
+ super(message || DEFAULT_MESSAGE)
50
+ end
51
+ end
52
+
53
+ class UnknownHttpMethod < ActionControllerError #:nodoc:
54
+ end
55
+ end
@@ -0,0 +1,77 @@
1
+ module ActionController
2
+ module FilterParameterLogging
3
+ extend ActiveSupport::Concern
4
+
5
+ include AbstractController::Logger
6
+
7
+ module ClassMethods
8
+ # Replace sensitive parameter data from the request log.
9
+ # Filters parameters that have any of the arguments as a substring.
10
+ # Looks in all subhashes of the param hash for keys to filter.
11
+ # If a block is given, each key and value of the parameter hash and all
12
+ # subhashes is passed to it, the value or key
13
+ # can be replaced using String#replace or similar method.
14
+ #
15
+ # Examples:
16
+ #
17
+ # filter_parameter_logging :password
18
+ # => replaces the value to all keys matching /password/i with "[FILTERED]"
19
+ #
20
+ # filter_parameter_logging :foo, "bar"
21
+ # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
22
+ #
23
+ # filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i }
24
+ # => reverses the value to all keys matching /secret/i
25
+ #
26
+ # filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i }
27
+ # => reverses the value to all keys matching /secret/i, and
28
+ # replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
29
+ def filter_parameter_logging(*filter_words, &block)
30
+ raise "You must filter at least one word from logging" if filter_words.empty?
31
+
32
+ parameter_filter = Regexp.new(filter_words.join('|'), true)
33
+
34
+ define_method(:filter_parameters) do |original_params|
35
+ filtered_params = {}
36
+
37
+ original_params.each do |key, value|
38
+ if key =~ parameter_filter
39
+ value = '[FILTERED]'
40
+ elsif value.is_a?(Hash)
41
+ value = filter_parameters(value)
42
+ elsif value.is_a?(Array)
43
+ value = value.map { |item| filter_parameters(item) }
44
+ elsif block_given?
45
+ key = key.dup
46
+ value = value.dup if value.duplicable?
47
+ yield key, value
48
+ end
49
+
50
+ filtered_params[key] = value
51
+ end
52
+
53
+ filtered_params
54
+ end
55
+ protected :filter_parameters
56
+ end
57
+ end
58
+
59
+ INTERNAL_PARAMS = [:controller, :action, :format, :_method, :only_path]
60
+
61
+ def process(*)
62
+ response = super
63
+ if logger
64
+ parameters = filter_parameters(params).except!(*INTERNAL_PARAMS)
65
+ logger.info { " Parameters: #{parameters.inspect}" } unless parameters.empty?
66
+ end
67
+ response
68
+ end
69
+
70
+ protected
71
+
72
+ def filter_parameters(params)
73
+ params.dup
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,162 @@
1
+ module ActionController #:nodoc:
2
+ # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
3
+ # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
4
+ # action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can
5
+ # then expose the flash to its template. Actually, that exposure is automatically done. Example:
6
+ #
7
+ # class PostsController < ActionController::Base
8
+ # def create
9
+ # # save post
10
+ # flash[:notice] = "Successfully created post"
11
+ # redirect_to posts_path(@post)
12
+ # end
13
+ #
14
+ # def show
15
+ # # doesn't need to assign the flash notice to the template, that's done automatically
16
+ # end
17
+ # end
18
+ #
19
+ # show.html.erb
20
+ # <% if flash[:notice] %>
21
+ # <div class="notice"><%= flash[:notice] %></div>
22
+ # <% end %>
23
+ #
24
+ # This example just places a string in the flash, but you can put any object in there. And of course, you can put as
25
+ # many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
26
+ #
27
+ # See docs on the FlashHash class for more details about the flash.
28
+ module Flash
29
+ extend ActiveSupport::Concern
30
+
31
+ include Session
32
+
33
+ class FlashNow #:nodoc:
34
+ def initialize(flash)
35
+ @flash = flash
36
+ end
37
+
38
+ def []=(k, v)
39
+ @flash[k] = v
40
+ @flash.discard(k)
41
+ v
42
+ end
43
+
44
+ def [](k)
45
+ @flash[k]
46
+ end
47
+ end
48
+
49
+ class FlashHash < Hash
50
+ def initialize #:nodoc:
51
+ super
52
+ @used = Set.new
53
+ end
54
+
55
+ def []=(k, v) #:nodoc:
56
+ keep(k)
57
+ super
58
+ end
59
+
60
+ def update(h) #:nodoc:
61
+ h.keys.each { |k| keep(k) }
62
+ super
63
+ end
64
+
65
+ alias :merge! :update
66
+
67
+ def replace(h) #:nodoc:
68
+ @used = Set.new
69
+ super
70
+ end
71
+
72
+ # Sets a flash that will not be available to the next action, only to the current.
73
+ #
74
+ # flash.now[:message] = "Hello current action"
75
+ #
76
+ # This method enables you to use the flash as a central messaging system in your app.
77
+ # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
78
+ # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
79
+ # vanish when the current action is done.
80
+ #
81
+ # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
82
+ def now
83
+ FlashNow.new(self)
84
+ end
85
+
86
+ # Keeps either the entire current flash or a specific flash entry available for the next action:
87
+ #
88
+ # flash.keep # keeps the entire flash
89
+ # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
90
+ def keep(k = nil)
91
+ use(k, false)
92
+ end
93
+
94
+ # Marks the entire flash or a single flash entry to be discarded by the end of the current action:
95
+ #
96
+ # flash.discard # discard the entire flash at the end of the current action
97
+ # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
98
+ def discard(k = nil)
99
+ use(k)
100
+ end
101
+
102
+ # Mark for removal entries that were kept, and delete unkept ones.
103
+ #
104
+ # This method is called automatically by filters, so you generally don't need to care about it.
105
+ def sweep #:nodoc:
106
+ keys.each do |k|
107
+ unless @used.include?(k)
108
+ @used << k
109
+ else
110
+ delete(k)
111
+ @used.delete(k)
112
+ end
113
+ end
114
+
115
+ # clean up after keys that could have been left over by calling reject! or shift on the flash
116
+ (@used - keys).each{ |k| @used.delete(k) }
117
+ end
118
+
119
+ def store(session)
120
+ return if self.empty?
121
+ session["flash"] = self
122
+ end
123
+
124
+ private
125
+ # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
126
+ # use() # marks the entire flash as used
127
+ # use('msg') # marks the "msg" entry as used
128
+ # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
129
+ # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
130
+ # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
131
+ # if no key is passed.
132
+ def use(key = nil, used = true)
133
+ Array(key || keys).each { |k| used ? @used << k : @used.delete(k) }
134
+ return key ? self[key] : self
135
+ end
136
+ end
137
+
138
+ protected
139
+ def process_action(method_name)
140
+ super
141
+ @_flash.store(session) if @_flash
142
+ @_flash = nil
143
+ end
144
+
145
+ def reset_session
146
+ super
147
+ @_flash = nil
148
+ end
149
+
150
+ # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
151
+ # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
152
+ # to put a new one.
153
+ def flash #:doc:
154
+ unless @_flash
155
+ @_flash = session["flash"] || FlashHash.new
156
+ @_flash.sweep
157
+ end
158
+
159
+ @_flash
160
+ end
161
+ end
162
+ end