diff options
Diffstat (limited to 'actionpack/lib/action_dispatch/middleware/flash.rb')
-rw-r--r-- | actionpack/lib/action_dispatch/middleware/flash.rb | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb new file mode 100644 index 0000000000..e90f8b9ce6 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -0,0 +1,273 @@ +require 'active_support/core_ext/hash/keys' + +module ActionDispatch + class Request < Rack::Request + # Access the contents of the flash. Use <tt>flash["notice"]</tt> to + # read a notice you put there or <tt>flash["notice"] = "hello"</tt> + # to put a new one. + def flash + @env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"]) + end + end + + # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed + # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create + # action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can + # then expose the flash to its template. Actually, that exposure is automatically done. + # + # class PostsController < ActionController::Base + # def create + # # save post + # flash[:notice] = "Post successfully created" + # redirect_to @post + # end + # + # def show + # # doesn't need to assign the flash notice to the template, that's done automatically + # end + # end + # + # show.html.erb + # <% if flash[:notice] %> + # <div class="notice"><%= flash[:notice] %></div> + # <% end %> + # + # Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available: + # + # flash.alert = "You must be logged in" + # flash.notice = "Post successfully created" + # + # This example places a string in the flash. And of course, you can put as many as you like at a time too. If you want to pass + # non-primitive types, you will have to handle that in your application. Example: To show messages with links, you will have to + # use sanitize helper. + # + # Just remember: They'll be gone by the time the next action has been performed. + # + # See docs on the FlashHash class for more details about the flash. + class Flash + KEY = 'action_dispatch.request.flash_hash'.freeze + + class FlashNow #:nodoc: + attr_accessor :flash + + def initialize(flash) + @flash = flash + end + + def []=(k, v) + k = k.to_s + @flash[k] = v + @flash.discard(k) + v + end + + def [](k) + @flash[k.to_s] + end + + # Convenience accessor for <tt>flash.now[:alert]=</tt>. + def alert=(message) + self[:alert] = message + end + + # Convenience accessor for <tt>flash.now[:notice]=</tt>. + def notice=(message) + self[:notice] = message + end + end + + class FlashHash + include Enumerable + + def self.from_session_value(value) + flash = case value + when FlashHash # Rails 3.1, 3.2 + new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used)) + when Hash # Rails 4.0 + new(value['flashes'], value['discard']) + else + new + end + + flash.tap(&:sweep) + end + + def to_session_value + return nil if empty? + {'discard' => @discard.to_a, 'flashes' => @flashes} + end + + def initialize(flashes = {}, discard = []) #:nodoc: + @discard = Set.new(stringify_array(discard)) + @flashes = flashes.stringify_keys + @now = nil + end + + def initialize_copy(other) + if other.now_is_loaded? + @now = other.now.dup + @now.flash = self + end + super + end + + def []=(k, v) + k = k.to_s + @discard.delete k + @flashes[k] = v + end + + def [](k) + @flashes[k.to_s] + end + + def update(h) #:nodoc: + @discard.subtract stringify_array(h.keys) + @flashes.update h.stringify_keys + self + end + + def keys + @flashes.keys + end + + def key?(name) + @flashes.key? name + end + + def delete(key) + key = key.to_s + @discard.delete key + @flashes.delete key + self + end + + def to_hash + @flashes.dup + end + + def empty? + @flashes.empty? + end + + def clear + @discard.clear + @flashes.clear + end + + def each(&block) + @flashes.each(&block) + end + + alias :merge! :update + + def replace(h) #:nodoc: + @discard.clear + @flashes.replace h.stringify_keys + self + end + + # Sets a flash that will not be available to the next action, only to the current. + # + # flash.now[:message] = "Hello current action" + # + # This method enables you to use the flash as a central messaging system in your app. + # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>). + # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will + # vanish when the current action is done. + # + # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>. + # + # Also, brings two convenience accessors: + # + # flash.now.alert = "Beware now!" + # # Equivalent to flash.now[:alert] = "Beware now!" + # + # flash.now.notice = "Good luck now!" + # # Equivalent to flash.now[:notice] = "Good luck now!" + def now + @now ||= FlashNow.new(self) + end + + # Keeps either the entire current flash or a specific flash entry available for the next action: + # + # flash.keep # keeps the entire flash + # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded + def keep(k = nil) + k = k.to_s if k + @discard.subtract Array(k || keys) + k ? self[k] : self + end + + # Marks the entire flash or a single flash entry to be discarded by the end of the current action: + # + # flash.discard # discard the entire flash at the end of the current action + # flash.discard(:warning) # discard only the "warning" entry at the end of the current action + def discard(k = nil) + k = k.to_s if k + @discard.merge Array(k || keys) + k ? self[k] : self + end + + # Mark for removal entries that were kept, and delete unkept ones. + # + # This method is called automatically by filters, so you generally don't need to care about it. + def sweep #:nodoc: + @discard.each { |k| @flashes.delete k } + @discard.replace @flashes.keys + end + + # Convenience accessor for <tt>flash[:alert]</tt>. + def alert + self[:alert] + end + + # Convenience accessor for <tt>flash[:alert]=</tt>. + def alert=(message) + self[:alert] = message + end + + # Convenience accessor for <tt>flash[:notice]</tt>. + def notice + self[:notice] + end + + # Convenience accessor for <tt>flash[:notice]=</tt>. + def notice=(message) + self[:notice] = message + end + + protected + def now_is_loaded? + @now + end + + def stringify_array(array) + array.map do |item| + item.kind_of?(Symbol) ? item.to_s : item + end + end + end + + def initialize(app) + @app = app + end + + def call(env) + @app.call(env) + ensure + session = Request::Session.find(env) || {} + flash_hash = env[KEY] + + if flash_hash && (flash_hash.present? || session.key?('flash')) + session["flash"] = flash_hash.to_session_value + env[KEY] = flash_hash.dup + end + + if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?) + session.key?('flash') && session['flash'].nil? + session.delete('flash') + end + end + end +end |