aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/abstract_controller
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/abstract_controller')
-rw-r--r--actionpack/lib/abstract_controller/base.rb4
-rw-r--r--actionpack/lib/abstract_controller/caching.rb62
-rw-r--r--actionpack/lib/abstract_controller/caching/fragments.rb143
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb13
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb8
5 files changed, 221 insertions, 9 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 7f349f2741..8edea0f52b 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -49,7 +49,7 @@ module AbstractController
# instance methods on that abstract class. Public instance methods of
# a controller would normally be considered action methods, so methods
# declared on abstract classes are being removed.
- # (ActionController::Metal and ActionController::Base are defined as abstract)
+ # (<tt>ActionController::Metal</tt> and ActionController::Base are defined as abstract)
def internal_methods
controller = self
@@ -80,7 +80,7 @@ module AbstractController
# action_methods are cached and there is sometimes need to refresh
# them. ::clear_action_methods! allows you to do that, so next time
- # you run action_methods, they will be recalculated
+ # you run action_methods, they will be recalculated.
def clear_action_methods!
@action_methods = nil
end
diff --git a/actionpack/lib/abstract_controller/caching.rb b/actionpack/lib/abstract_controller/caching.rb
new file mode 100644
index 0000000000..0dea50889a
--- /dev/null
+++ b/actionpack/lib/abstract_controller/caching.rb
@@ -0,0 +1,62 @@
+module AbstractController
+ module Caching
+ extend ActiveSupport::Concern
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :Fragments
+ end
+
+ module ConfigMethods
+ def cache_store
+ config.cache_store
+ end
+
+ def cache_store=(store)
+ config.cache_store = ActiveSupport::Cache.lookup_store(store)
+ end
+
+ private
+ def cache_configured?
+ perform_caching && cache_store
+ end
+ end
+
+ include ConfigMethods
+ include AbstractController::Caching::Fragments
+
+ included do
+ extend ConfigMethods
+
+ config_accessor :default_static_extension
+ self.default_static_extension ||= '.html'
+
+ config_accessor :perform_caching
+ self.perform_caching = true if perform_caching.nil?
+
+ class_attribute :_view_cache_dependencies
+ self._view_cache_dependencies = []
+ helper_method :view_cache_dependencies if respond_to?(:helper_method)
+ end
+
+ module ClassMethods
+ def view_cache_dependency(&dependency)
+ self._view_cache_dependencies += [dependency]
+ end
+ end
+
+ def view_cache_dependencies
+ self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact
+ end
+
+ protected
+ # Convenience accessor.
+ def cache(key, options = {}, &block)
+ if cache_configured?
+ cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block)
+ else
+ yield
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/abstract_controller/caching/fragments.rb b/actionpack/lib/abstract_controller/caching/fragments.rb
new file mode 100644
index 0000000000..3257a731ed
--- /dev/null
+++ b/actionpack/lib/abstract_controller/caching/fragments.rb
@@ -0,0 +1,143 @@
+module AbstractController
+ module Caching
+ # Fragment caching is used for caching various blocks within
+ # views without caching the entire action as a whole. This is
+ # useful when certain elements of an action change frequently or
+ # depend on complicated state while other parts rarely change or
+ # can be shared amongst multiple parties. The caching is done using
+ # the +cache+ helper available in the Action View. See
+ # ActionView::Helpers::CacheHelper for more information.
+ #
+ # While it's strongly recommended that you use key-based cache
+ # expiration (see links in CacheHelper for more information),
+ # it is also possible to manually expire caches. For example:
+ #
+ # expire_fragment('name_of_cache')
+ module Fragments
+ extend ActiveSupport::Concern
+
+ included do
+ if respond_to?(:class_attribute)
+ class_attribute :fragment_cache_keys
+ else
+ mattr_writer :fragment_cache_keys
+ end
+
+ self.fragment_cache_keys = []
+
+ helper_method :fragment_cache_key if respond_to?(:helper_method)
+ end
+
+ module ClassMethods
+ # Allows you to specify controller-wide key prefixes for
+ # cache fragments. Pass either a constant +value+, or a block
+ # which computes a value each time a cache key is generated.
+ #
+ # For example, you may want to prefix all fragment cache keys
+ # with a global version identifier, so you can easily
+ # invalidate all caches.
+ #
+ # class ApplicationController
+ # fragment_cache_key "v1"
+ # end
+ #
+ # When it's time to invalidate all fragments, simply change
+ # the string constant. Or, progressively roll out the cache
+ # invalidation using a computed value:
+ #
+ # class ApplicationController
+ # fragment_cache_key do
+ # @account.id.odd? ? "v1" : "v2"
+ # end
+ # end
+ def fragment_cache_key(value = nil, &key)
+ self.fragment_cache_keys += [key || ->{ value }]
+ end
+ end
+
+ # Given a key (as described in +expire_fragment+), returns
+ # a key suitable for use in reading, writing, or expiring a
+ # cached fragment. All keys begin with <tt>views/</tt>,
+ # followed by any controller-wide key prefix values, ending
+ # with the specified +key+ value. The key is expanded using
+ # ActiveSupport::Cache.expand_cache_key.
+ def fragment_cache_key(key)
+ head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
+ tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
+ ActiveSupport::Cache.expand_cache_key([*head, *tail], :views)
+ end
+
+ # Writes +content+ to the location signified by
+ # +key+ (see +expire_fragment+ for acceptable formats).
+ def write_fragment(key, content, options = nil)
+ return content unless cache_configured?
+
+ key = fragment_cache_key(key)
+ instrument_fragment_cache :write_fragment, key do
+ content = content.to_str
+ cache_store.write(key, content, options)
+ end
+ content
+ end
+
+ # Reads a cached fragment from the location signified by +key+
+ # (see +expire_fragment+ for acceptable formats).
+ def read_fragment(key, options = nil)
+ return unless cache_configured?
+
+ key = fragment_cache_key(key)
+ instrument_fragment_cache :read_fragment, key do
+ result = cache_store.read(key, options)
+ result.respond_to?(:html_safe) ? result.html_safe : result
+ end
+ end
+
+ # Check if a cached fragment from the location signified by
+ # +key+ exists (see +expire_fragment+ for acceptable formats).
+ def fragment_exist?(key, options = nil)
+ return unless cache_configured?
+ key = fragment_cache_key(key)
+
+ instrument_fragment_cache :exist_fragment?, key do
+ cache_store.exist?(key, options)
+ end
+ end
+
+ # Removes fragments from the cache.
+ #
+ # +key+ can take one of three forms:
+ #
+ # * String - This would normally take the form of a path, like
+ # <tt>pages/45/notes</tt>.
+ # * Hash - Treated as an implicit call to +url_for+, like
+ # <tt>{ controller: 'pages', action: 'notes', id: 45}</tt>
+ # * Regexp - Will remove any fragment that matches, so
+ # <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
+ # don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
+ # the actual filename matched looks like
+ # <tt>./cache/filename/path.cache</tt>. Note: Regexp expiration is
+ # only supported on caches that can iterate over all keys (unlike
+ # memcached).
+ #
+ # +options+ is passed through to the cache store's +delete+
+ # method (or <tt>delete_matched</tt>, for Regexp keys).
+ def expire_fragment(key, options = nil)
+ return unless cache_configured?
+ key = fragment_cache_key(key) unless key.is_a?(Regexp)
+
+ instrument_fragment_cache :expire_fragment, key do
+ if key.is_a?(Regexp)
+ cache_store.delete_matched(key, options)
+ else
+ cache_store.delete(key, options)
+ end
+ end
+ end
+
+ def instrument_fragment_cache(name, key) # :nodoc:
+ payload = instrument_payload(key)
+ ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", payload) { yield }
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 287550db42..d63ce9c1c3 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -39,8 +39,8 @@ module AbstractController
# except: :index, if: -> { true } # the :except option will be ignored.
#
# ==== Options
- # * <tt>only</tt> - The callback should be run only for this action
- # * <tt>except</tt> - The callback should be run for all actions except this action
+ # * <tt>only</tt> - The callback should be run only for this action.
+ # * <tt>except</tt> - The callback should be run for all actions except this action.
def _normalize_callback_options(options)
_normalize_callback_option(options, :only, :if)
_normalize_callback_option(options, :except, :unless)
@@ -48,7 +48,8 @@ module AbstractController
def _normalize_callback_option(options, from, to) # :nodoc:
if from = options[from]
- from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ")
+ _from = Array(from).map(&:to_s).to_set
+ from = proc {|c| _from.include? c.action_name }
options[to] = Array(options[to]).unshift(from)
end
end
@@ -59,7 +60,7 @@ module AbstractController
# * <tt>names</tt> - A list of valid names that could be used for
# callbacks. Note that skipping uses Ruby equality, so it's
# impossible to skip a callback defined using an anonymous proc
- # using #skip_action_callback
+ # using #skip_action_callback.
def skip_action_callback(*names)
ActiveSupport::Deprecation.warn('`skip_action_callback` is deprecated and will be removed in Rails 5.1. Please use skip_before_action, skip_after_action or skip_around_action instead.')
skip_before_action(*names, raise: false)
@@ -82,8 +83,8 @@ module AbstractController
# * <tt>block</tt> - A proc that should be added to the callbacks.
#
# ==== Block Parameters
- # * <tt>name</tt> - The callback to be added
- # * <tt>options</tt> - A hash of options to be used when adding the callback
+ # * <tt>name</tt> - The callback to be added.
+ # * <tt>options</tt> - A hash of options to be used when adding the callback.
def _insert_callbacks(callbacks, block = nil)
options = callbacks.extract_options!
_normalize_callback_options(options)
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index a73f188623..e765d73ce4 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -82,7 +82,13 @@ module AbstractController
# <tt>render :file => "foo/bar"</tt>.
# :api: plugin
def _normalize_args(action=nil, options={})
- if action.is_a? Hash
+ if action.respond_to?(:permitted?)
+ if action.permitted?
+ action
+ else
+ raise ArgumentError, "render parameters are not permitted"
+ end
+ elsif action.is_a?(Hash)
action
else
options