1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/actionpack/lib/abstract_controller/base.rb

255 lines
9 KiB
Ruby
Raw Normal View History

require 'erubis'
require 'active_support/configurable'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/module/anonymous'
2015-09-01 14:30:37 -04:00
require 'active_support/core_ext/module/attr_internal'
module AbstractController
2012-04-07 16:26:41 -04:00
class Error < StandardError #:nodoc:
end
# Raised when a non-existing controller action is triggered.
class ActionNotFound < StandardError
2012-04-07 16:26:41 -04:00
end
# AbstractController::Base is a low-level API. Nobody should be
2010-08-25 13:51:20 -04:00
# using it directly, and subclasses (like ActionController::Base) are
# expected to provide their own +render+ method, since rendering means
# different things depending on the context.
class Base
attr_internal :response_body
attr_internal :action_name
attr_internal :formats
include ActiveSupport::Configurable
extend ActiveSupport::DescendantsTracker
undef_method :not_implemented
class << self
attr_reader :abstract
2009-06-08 19:14:38 -04:00
alias_method :abstract?, :abstract
2009-06-08 19:14:38 -04:00
# Define a controller as abstract. See internal_methods for more
# details.
def abstract!
@abstract = true
end
2012-10-25 13:49:04 -04:00
def inherited(klass) # :nodoc:
# Define the abstract ivar on subclasses so that we don't get
2012-10-25 13:49:04 -04:00
# uninitialized ivar warnings
unless klass.instance_variable_defined?(:@abstract)
klass.instance_variable_set(:@abstract, false)
end
super
end
2009-06-08 19:14:38 -04:00
# A list of all internal methods for a controller. This finds the first
# abstract superclass of a controller, and gets a list of all public
# instance methods on that abstract class. Public instance methods of
2010-11-30 10:00:47 -05:00
# 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)
def internal_methods
controller = self
2012-10-25 13:49:04 -04:00
controller = controller.superclass until controller.abstract?
controller.public_instance_methods(true)
end
2009-06-08 19:14:38 -04:00
# A list of method names that should be considered actions. This
# includes all public instance methods on a controller, less
# any internal methods (see internal_methods), adding back in
2009-06-08 19:14:38 -04:00
# any methods that are internal, but still exist on the class
# itself.
2009-06-08 19:14:38 -04:00
#
# ==== Returns
# * <tt>Set</tt> - A set of all methods that should be considered actions.
def action_methods
@action_methods ||= begin
# All public instance methods of this class, including ancestors
2010-09-29 14:47:27 -04:00
methods = (public_instance_methods(true) -
# Except for public instance methods of Base and its ancestors
2010-09-29 14:47:27 -04:00
internal_methods +
# Be sure to include shadowed public instance methods of this class
public_instance_methods(false)).uniq.map(&:to_s)
methods.to_set
end
end
# 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
def clear_action_methods!
@action_methods = nil
end
# Returns the full controller name, underscored, without the ending Controller.
#
2015-01-03 10:36:33 -05:00
# class MyApp::MyPostsController < AbstractController::Base
#
# end
2015-01-03 10:36:33 -05:00
#
# MyApp::MyPostsController.controller_path # => "my_app/my_posts"
#
# ==== Returns
# * <tt>String</tt>
def controller_path
Freeze string literals when not mutated. I wrote a utility that helps find areas where you could optimize your program using a frozen string instead of a string literal, it's called [let_it_go](https://github.com/schneems/let_it_go). After going through the output and adding `.freeze` I was able to eliminate the creation of 1,114 string objects on EVERY request to [codetriage](codetriage.com). How does this impact execution? To look at memory: ```ruby require 'get_process_mem' mem = GetProcessMem.new GC.start GC.disable 1_114.times { " " } before = mem.mb after = mem.mb GC.enable puts "Diff: #{after - before} mb" ``` Creating 1,114 string objects results in `Diff: 0.03125 mb` of RAM allocated on every request. Or 1mb every 32 requests. To look at raw speed: ```ruby require 'benchmark/ips' number_of_objects_reduced = 1_114 Benchmark.ips do |x| x.report("freeze") { number_of_objects_reduced.times { " ".freeze } } x.report("no-freeze") { number_of_objects_reduced.times { " " } } end ``` We get the results ``` Calculating ------------------------------------- freeze 1.428k i/100ms no-freeze 609.000 i/100ms ------------------------------------------------- freeze 14.363k (± 8.5%) i/s - 71.400k no-freeze 6.084k (± 8.1%) i/s - 30.450k ``` Now we can do some maths: ```ruby ips = 6_226k # iterations / 1 second call_time_before = 1.0 / ips # seconds per iteration ips = 15_254 # iterations / 1 second call_time_after = 1.0 / ips # seconds per iteration diff = call_time_before - call_time_after number_of_objects_reduced * diff * 100 # => 0.4530373333993266 miliseconds saved per request ``` So we're shaving off 1 second of execution time for every 220 requests. Is this going to be an insane speed boost to any Rails app: nope. Should we merge it: yep. p.s. If you know of a method call that doesn't modify a string input such as [String#gsub](https://github.com/schneems/let_it_go/blob/b0e2da69f0cca87ab581022baa43291cdf48638c/lib/let_it_go/core_ext/string.rb#L37) please [give me a pull request to the appropriate file](https://github.com/schneems/let_it_go/blob/b0e2da69f0cca87ab581022baa43291cdf48638c/lib/let_it_go/core_ext/string.rb#L37), or open an issue in LetItGo so we can track and freeze more strings. Keep those strings Frozen ![](https://www.dropbox.com/s/z4dj9fdsv213r4v/let-it-go.gif?dl=1)
2015-07-19 17:19:15 -04:00
@controller_path ||= name.sub(/Controller$/, ''.freeze).underscore unless anonymous?
end
# Refresh the cached action_methods when a new action_method is added.
def method_added(name)
super
clear_action_methods!
end
end
abstract!
2009-06-08 19:14:38 -04:00
# Calls the action going through the entire action dispatch stack.
#
2009-06-08 19:14:38 -04:00
# The actual method that is called is determined by calling
# #method_for_action. If no method can handle the action, then an
# AbstractController::ActionNotFound error is raised.
2009-06-08 19:14:38 -04:00
#
# ==== Returns
# * <tt>self</tt>
def process(action, *args)
@_action_name = action.to_s
unless action_name = _find_action_name(@_action_name)
raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}"
end
2009-12-14 17:07:46 -05:00
@_response_body = nil
process_action(action_name, *args)
end
# Delegates to the class' ::controller_path
def controller_path
self.class.controller_path
end
# Delegates to the class' ::action_methods
def action_methods
self.class.action_methods
end
# Returns true if a method for the action is available and
# can be dispatched, false otherwise.
#
# Notice that <tt>action_methods.include?("foo")</tt> may return
# false and <tt>available_action?("foo")</tt> returns true because
2012-05-23 13:12:49 -04:00
# this method considers actions that are also available
# through other means, for example, implicit render ones.
#
# ==== Parameters
# * <tt>action_name</tt> - The name of an action to be tested
def available_action?(action_name)
_find_action_name(action_name).present?
end
# Returns true if the given controller is capable of rendering
# a path. A subclass of +AbstractController::Base+
# may return false. An Email controller for example does not
# support paths, only full URLs.
def self.supports_path?
true
end
private
# Returns true if the name can be considered an action because
# it has a method defined in the controller.
#
# ==== Parameters
# * <tt>name</tt> - The name of an action to be tested
#
# :api: private
def action_method?(name)
self.class.action_methods.include?(name)
end
# Call the action. Override this in a subclass to modify the
# behavior around processing an action. This, and not #process,
# is the intended way to override action dispatching.
#
# Notice that the first argument is the method to be dispatched
# which is *not* necessarily the same as the action name.
def process_action(method_name, *args)
send_action(method_name, *args)
end
# Actually call the method associated with the action. Override
# this method if you wish to change how action methods are called,
# not to add additional behavior around it. For example, you would
# override #send_action if you want to inject arguments into the
# method.
alias send_action send
# If the action name was not found, but a method called "action_missing"
# was found, #method_for_action will return "_handle_action_missing".
# This method calls #action_missing with the current action name.
def _handle_action_missing(*args)
action_missing(@_action_name, *args)
end
# Takes an action name and returns the name of the method that will
# handle the action.
#
# It checks if the action name is valid and returns false otherwise.
#
# See method_for_action for more information.
#
# ==== Parameters
# * <tt>action_name</tt> - An action name to find a method name for
#
# ==== Returns
# * <tt>string</tt> - The name of the method that handles the action
# * false - No valid method name could be found.
# Raise AbstractController::ActionNotFound.
def _find_action_name(action_name)
_valid_action_name?(action_name) && method_for_action(action_name)
end
# Takes an action name and returns the name of the method that will
# handle the action. In normal cases, this method returns the same
# name as it receives. By default, if #method_for_action receives
# a name that is not an action, it will look for an #action_missing
# method and return "_handle_action_missing" if one is found.
#
# Subclasses may override this method to add additional conditions
# that should be considered an action. For instance, an HTTP controller
# with a template matching the action name is considered to exist.
#
# If you override this method to handle additional cases, you may
# also provide a method (like _handle_method_missing) to handle
# the case.
#
# If none of these conditions are true, and method_for_action
# returns nil, an AbstractController::ActionNotFound exception will be raised.
#
# ==== Parameters
# * <tt>action_name</tt> - An action name to find a method name for
#
# ==== Returns
2010-08-25 13:51:20 -04:00
# * <tt>string</tt> - The name of the method that handles the action
# * <tt>nil</tt> - No method name could be found.
def method_for_action(action_name)
if action_method?(action_name)
action_name
elsif respond_to?(:action_missing, true)
"_handle_action_missing"
end
end
# Checks if the action name is valid and returns false otherwise.
def _valid_action_name?(action_name)
!action_name.to_s.include? File::SEPARATOR
end
end
end