aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionmailer/lib/action_mailer/helpers.rb17
-rw-r--r--actionpack/Rakefile4
-rw-r--r--actionpack/lib/action_controller/abstract/base.rb7
-rw-r--r--actionpack/lib/action_controller/abstract/helpers.rb52
-rw-r--r--actionpack/lib/action_controller/abstract/layouts.rb114
-rw-r--r--actionpack/lib/action_controller/abstract/logger.rb16
-rw-r--r--actionpack/lib/action_controller/abstract/renderer.rb3
-rw-r--r--actionpack/lib/action_controller/new_base/compatibility.rb5
-rw-r--r--actionpack/lib/action_controller/new_base/helpers.rb159
-rw-r--r--actionpack/lib/action_controller/new_base/hide_actions.rb44
-rw-r--r--actionpack/lib/action_controller/new_base/http.rb51
-rw-r--r--actionpack/lib/action_controller/new_base/layouts.rb171
-rw-r--r--actionpack/test/abstract_controller/abstract_controller_test.rb2
-rw-r--r--actionpack/test/abstract_controller/layouts_test.rb6
-rw-r--r--actionpack/test/controller/base_test.rb11
-rw-r--r--actionpack/test/controller/helper_test.rb19
-rwxr-xr-xactiverecord/lib/active_record/base.rb16
-rw-r--r--activesupport/lib/active_support/dependencies.rb15
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb14
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb10
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb18
-rw-r--r--railties/lib/rails/gem_dependency.rb2
-rw-r--r--railties/test/gem_dependency_test.rb7
23 files changed, 547 insertions, 216 deletions
diff --git a/actionmailer/lib/action_mailer/helpers.rb b/actionmailer/lib/action_mailer/helpers.rb
index 31f7de8d60..1bb8682315 100644
--- a/actionmailer/lib/action_mailer/helpers.rb
+++ b/actionmailer/lib/action_mailer/helpers.rb
@@ -48,13 +48,14 @@ module ActionMailer
file_name = arg.to_s.underscore + '_helper'
class_name = file_name.camelize
- begin
- require_dependency(file_name)
- rescue LoadError => load_error
- requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1]
- msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
- raise LoadError.new(msg).copy_blame!(load_error)
- end
+ require_dependency(file_name, "Missing helper file helpers/%s.rb")
+ # begin
+ # require_dependency(file_name)
+ # rescue LoadError => load_error
+ # requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1]
+ # msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
+ # raise LoadError.new(msg).copy_blame!(load_error)
+ # end
add_template_helper(class_name.constantize)
else
@@ -97,7 +98,7 @@ module ActionMailer
child.master_helper_module.__send__(:include, master_helper_module)
child.helper child.name.to_s.underscore
rescue MissingSourceFile => e
- raise unless e.is_missing?("helpers/#{child.name.to_s.underscore}_helper")
+ raise unless e.is_missing?("#{child.name.to_s.underscore}_helper")
end
end
end
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 074bc90ca9..53387d305c 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -24,7 +24,9 @@ task :default => [ :test ]
desc "Run all unit tests"
task :test => [:test_action_pack, :test_active_record_integration, :test_new_base, :test_new_base_on_old_tests]
-test_lib_dirs = [ENV["NEW"] ? "test/new_base" : "test", "test/lib"]
+test_lib_dirs = ENV["NEW"] ? ["test/new_base"] : []
+test_lib_dirs.push "test", "test/lib"
+# test_lib_dirs = [ENV["NEW"] ? "test/new_base" : "test", "test/lib"]
Rake::TestTask.new(:test_action_pack) do |t|
t.libs.concat test_lib_dirs
diff --git a/actionpack/lib/action_controller/abstract/base.rb b/actionpack/lib/action_controller/abstract/base.rb
index c3daef8759..a19a236ef7 100644
--- a/actionpack/lib/action_controller/abstract/base.rb
+++ b/actionpack/lib/action_controller/abstract/base.rb
@@ -95,11 +95,6 @@ module AbstractController
end
private
- # See AbstractController::Base.action_methods
- def action_methods
- self.class.action_methods
- end
-
# Returns true if the name can be considered an action. This can
# be overridden in subclasses to modify the semantics of what
# can be considered an action.
@@ -110,7 +105,7 @@ module AbstractController
# ==== Returns
# TrueClass, FalseClass
def action_method?(name)
- action_methods.include?(name)
+ self.class.action_methods.include?(name)
end
# Call the action. Override this in a subclass to modify the
diff --git a/actionpack/lib/action_controller/abstract/helpers.rb b/actionpack/lib/action_controller/abstract/helpers.rb
index 0a2776de9c..6b73f887c1 100644
--- a/actionpack/lib/action_controller/abstract/helpers.rb
+++ b/actionpack/lib/action_controller/abstract/helpers.rb
@@ -5,33 +5,26 @@ module AbstractController
include Renderer
included do
- extlib_inheritable_accessor :master_helper_module
- self.master_helper_module = Module.new
+ extlib_inheritable_accessor(:_helpers) { Module.new }
end
+ # Override AbstractController::Renderer's _action_view to include the
+ # helper module for this class into its helpers module.
def _action_view
- @_action_view ||= begin
- av = super
- av.helpers.send(:include, master_helper_module)
- av
- end
+ @_action_view ||= super.tap { |av| av.helpers.include(_helpers) }
end
module ClassMethods
+ # When a class is inherited, wrap its helper module in a new module.
+ # This ensures that the parent class's module can be changed
+ # independently of the child class's.
def inherited(klass)
- klass.master_helper_module = Module.new
- klass.master_helper_module.__send__ :include, master_helper_module
+ helpers = _helpers
+ klass._helpers = Module.new { include helpers }
super
end
- # Makes all the (instance) methods in the helper module available to templates rendered through this controller.
- # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
- # available to the templates.
- def add_template_helper(mod)
- master_helper_module.module_eval { include mod }
- end
-
# Declare a controller method as a helper. For example, the following
# makes the +current_user+ controller method available to the view:
# class ApplicationController < ActionController::Base
@@ -48,9 +41,13 @@ module AbstractController
#
# In a view:
# <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
+ #
+ # ==== Parameters
+ # meths<Array[#to_s]>:: The name of a method on the controller
+ # to be made available on the view.
def helper_method(*meths)
meths.flatten.each do |meth|
- master_helper_module.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
+ _helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
def #{meth}(*args, &blk)
controller.send(%(#{meth}), *args, &blk)
end
@@ -58,6 +55,14 @@ module AbstractController
end
end
+ # Make a number of helper modules part of this class' default
+ # helpers.
+ #
+ # ==== Parameters
+ # *args<Array[Module]>:: Modules to be included
+ # block<Block>:: Evalulate the block in the context
+ # of the helper module. Any methods defined in the block
+ # will be helpers.
def helper(*args, &block)
args.flatten.each do |arg|
case arg
@@ -65,7 +70,18 @@ module AbstractController
add_template_helper(arg)
end
end
- master_helper_module.module_eval(&block) if block_given?
+ _helpers.module_eval(&block) if block_given?
+ end
+
+ private
+ # Makes all the (instance) methods in the helper module available to templates
+ # rendered through this controller.
+ #
+ # ==== Parameters
+ # mod<Module>:: The module to include into the current helper module
+ # for the class
+ def add_template_helper(mod)
+ _helpers.module_eval { include mod }
end
end
end
diff --git a/actionpack/lib/action_controller/abstract/layouts.rb b/actionpack/lib/action_controller/abstract/layouts.rb
index 273063f74b..9ff8e9beb1 100644
--- a/actionpack/lib/action_controller/abstract/layouts.rb
+++ b/actionpack/lib/action_controller/abstract/layouts.rb
@@ -5,16 +5,26 @@ module AbstractController
include Renderer
included do
- extlib_inheritable_accessor :_layout_conditions
- self._layout_conditions = {}
+ extlib_inheritable_accessor(:_layout_conditions) { Hash.new }
end
module ClassMethods
+ # Specify the layout to use for this class.
+ #
+ # If the specified layout is a:
+ # String:: the String is the template name
+ # Symbol:: call the method specified by the symbol, which will return
+ # the template name
+ # false:: There is no layout
+ # true:: raise an ArgumentError
+ #
+ # ==== Parameters
+ # layout<String, Symbol, false)>:: The layout to use.
+ #
+ # ==== Options (conditions)
+ # :only<#to_s, Array[#to_s]>:: A list of actions to apply this layout to.
+ # :except<#to_s, Array[#to_s]>:: Apply this layout to all actions but this one
def layout(layout, conditions = {})
- unless [String, Symbol, FalseClass, NilClass].include?(layout.class)
- raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
- end
-
conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
self._layout_conditions = conditions
@@ -22,6 +32,11 @@ module AbstractController
_write_layout_method
end
+ # If no layout is supplied, look for a template named the return
+ # value of this method.
+ #
+ # ==== Returns
+ # String:: A template name
def _implied_layout_name
name.underscore
end
@@ -29,23 +44,31 @@ module AbstractController
# Takes the specified layout and creates a _layout method to be called
# by _default_layout
#
- # If the specified layout is a:
- # String:: return the string
- # Symbol:: call the method specified by the symbol
- # false:: return nil
- # none:: If a layout is found in the view paths with the controller's
- # name, return that string. Otherwise, use the superclass'
- # layout (which might also be implied)
+ # If there is no explicit layout specified:
+ # If a layout is found in the view paths with the controller's
+ # name, return that string. Otherwise, use the superclass'
+ # layout (which might also be implied)
def _write_layout_method
case @_layout
when String
self.class_eval %{def _layout(details) #{@_layout.inspect} end}
when Symbol
- self.class_eval %{def _layout(details) #{@_layout} end}
+ self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
+ def _layout(details)
+ #{@_layout}.tap do |layout|
+ unless layout.is_a?(String) || !layout
+ raise ArgumentError, "Your layout method :#{@_layout} returned \#{layout}. It " \
+ "should have returned a String, false, or nil"
+ end
+ end
+ end
+ ruby_eval
when false
self.class_eval %{def _layout(details) end}
- else
- self.class_eval %{
+ when true
+ raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
+ when nil
+ self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
def _layout(details)
if view_paths.find_by_parts?("#{_implied_layout_name}", details, "layouts")
"#{_implied_layout_name}"
@@ -53,33 +76,54 @@ module AbstractController
super
end
end
- }
+ ruby_eval
end
end
end
private
- # This will be overwritten
- def _layout(details)
- end
+ # This will be overwritten by _write_layout_method
+ def _layout(details) end
- # :api: plugin
- # ====
- # Override this to mutate the inbound layout name
+ # Determine the layout for a given name and details.
+ #
+ # ==== Parameters
+ # name<String>:: The name of the template
+ # details<Hash{Symbol => Object}>:: A list of details to restrict
+ # the lookup to. By default, layout lookup is limited to the
+ # formats specified for the current request.
def _layout_for_name(name, details = {:formats => formats})
- unless [String, FalseClass, NilClass].include?(name.class)
- raise ArgumentError, "String, false, or nil expected; you passed #{name.inspect}"
- end
-
- name && view_paths.find_by_parts(name, details, _layout_prefix(name))
+ name && _find_by_parts(name, details)
end
- # TODO: Decide if this is the best hook point for the feature
- def _layout_prefix(name)
- "layouts"
+ # Take in the name and details and find a Template.
+ #
+ # ==== Parameters
+ # name<String>:: The name of the template to retrieve
+ # details<Hash>:: A list of details to restrict the search by. This
+ # might include details like the format or locale of the template.
+ #
+ # ==== Returns
+ # Template:: A template object matching the name and details
+ def _find_by_parts(name, details)
+ # TODO: Make prefix actually part of details in ViewPath#find_by_parts
+ prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts"
+ view_paths.find_by_parts(name, details, prefix)
end
- def _default_layout(require_layout = false, details = {:formats => formats})
+ # Returns the default layout for this controller and a given set of details.
+ # Optionally raises an exception if the layout could not be found.
+ #
+ # ==== Parameters
+ # details<Hash>:: A list of details to restrict the search by. This
+ # might include details like the format or locale of the template.
+ # require_layout<Boolean>:: If this is true, raise an ArgumentError
+ # with details about the fact that the exception could not be
+ # found (defaults to false)
+ #
+ # ==== Returns
+ # Template:: The template object for the default layout (or nil)
+ def _default_layout(details, require_layout = false)
if require_layout && _action_has_layout? && !_layout(details)
raise ArgumentError,
"There was no default layout for #{self.class} in #{view_paths.inspect}"
@@ -93,6 +137,12 @@ module AbstractController
end
end
+ # Determines whether the current action has a layout by checking the
+ # action name against the :only and :except conditions set on the
+ # layout.
+ #
+ # ==== Returns
+ # Boolean:: True if the action has a layout, false otherwise.
def _action_has_layout?
conditions = _layout_conditions
if only = conditions[:only]
diff --git a/actionpack/lib/action_controller/abstract/logger.rb b/actionpack/lib/action_controller/abstract/logger.rb
index d6fa843485..b960e152e3 100644
--- a/actionpack/lib/action_controller/abstract/logger.rb
+++ b/actionpack/lib/action_controller/abstract/logger.rb
@@ -5,6 +5,13 @@ module AbstractController
module Logger
extend ActiveSupport::Concern
+ # A class that allows you to defer expensive processing
+ # until the logger actually tries to log. Otherwise, you are
+ # forced to do the processing in advance, and send the
+ # entire processed String to the logger, which might
+ # just discard the String if the log level is too low.
+ #
+ # TODO: Require that Rails loggers accept a block.
class DelayedLog
def initialize(&blk)
@blk = blk
@@ -20,8 +27,10 @@ module AbstractController
cattr_accessor :logger
end
- def process(action)
- ret = super
+ # Override process_action in the AbstractController::Base
+ # to log details about the method.
+ def process_action(action)
+ super
if logger
log = DelayedLog.new do
@@ -32,10 +41,9 @@ module AbstractController
logger.info(log)
end
-
- ret
end
+ private
def request_origin
# this *needs* to be cached!
# otherwise you'd get different results if calling it more than once
diff --git a/actionpack/lib/action_controller/abstract/renderer.rb b/actionpack/lib/action_controller/abstract/renderer.rb
index 28bdda4a75..611d3a16ce 100644
--- a/actionpack/lib/action_controller/abstract/renderer.rb
+++ b/actionpack/lib/action_controller/abstract/renderer.rb
@@ -29,6 +29,9 @@ module AbstractController
# partial<Boolean>:: Whether or not the template to render is a partial
# _partial:: If a partial, rather than a template, was rendered, return
# the partial.
+ # helpers:: A module containing the helpers to be used in the view. This
+ # module should respond_to include.
+ # controller:: The controller that initialized the ActionView
#
# Override this method in a to change the default behavior.
def _action_view
diff --git a/actionpack/lib/action_controller/new_base/compatibility.rb b/actionpack/lib/action_controller/new_base/compatibility.rb
index f278c2da14..29ba43a879 100644
--- a/actionpack/lib/action_controller/new_base/compatibility.rb
+++ b/actionpack/lib/action_controller/new_base/compatibility.rb
@@ -114,8 +114,9 @@ module ActionController
super || (respond_to?(:method_missing) && "_handle_method_missing")
end
- def _layout_prefix(name)
- super unless name =~ /\blayouts/
+ def _find_by_parts(name, details)
+ details[:prefix] = nil if name =~ /\blayouts/
+ super
end
def performed?
diff --git a/actionpack/lib/action_controller/new_base/helpers.rb b/actionpack/lib/action_controller/new_base/helpers.rb
index e8000be87b..2fa5ea6519 100644
--- a/actionpack/lib/action_controller/new_base/helpers.rb
+++ b/actionpack/lib/action_controller/new_base/helpers.rb
@@ -3,6 +3,51 @@ require 'active_support/core_ext/name_error'
require 'active_support/dependencies'
module ActionController
+ # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+,
+ # +numbers+ and model objects, to name a few. These helpers are available to all templates
+ # by default.
+ #
+ # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to
+ # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will
+ # include a helper whose name matches that of the controller, e.g., <tt>MyController</tt> will automatically
+ # include <tt>MyHelper</tt>.
+ #
+ # Additional helpers can be specified using the +helper+ class method in <tt>ActionController::Base</tt> or any
+ # controller which inherits from it.
+ #
+ # ==== Examples
+ # The +to_s+ method from the Time class can be wrapped in a helper method to display a custom message if
+ # the Time object is blank:
+ #
+ # module FormattedTimeHelper
+ # def format_time(time, format=:long, blank_message="&nbsp;")
+ # time.blank? ? blank_message : time.to_s(format)
+ # end
+ # end
+ #
+ # FormattedTimeHelper can now be included in a controller, using the +helper+ class method:
+ #
+ # class EventsController < ActionController::Base
+ # helper FormattedTimeHelper
+ # def index
+ # @events = Event.find(:all)
+ # end
+ # end
+ #
+ # Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called:
+ #
+ # <% @events.each do |event| -%>
+ # <p>
+ # <% format_time(event.time, :short, "N/A") %> | <%= event.name %>
+ # </p>
+ # <% end -%>
+ #
+ # Finally, assuming we have two event instances, one which has a time and one which does not,
+ # the output might look like this:
+ #
+ # 23 Aug 11:30 | Carolina Railhawks Soccer Match
+ # N/A | Carolina Railhaws Training Workshop
+ #
module Helpers
extend ActiveSupport::Concern
@@ -10,20 +55,22 @@ module ActionController
included do
# Set the default directory for helpers
- class_inheritable_accessor :helpers_dir
- self.helpers_dir = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers")
+ extlib_inheritable_accessor(:helpers_dir) do
+ defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers"
+ end
end
module ClassMethods
def inherited(klass)
- klass.__send__ :default_helper_module!
+ klass.class_eval { default_helper_module! unless name.blank? }
super
end
# The +helper+ class method can take a series of helper module names, a block, or both.
#
- # * <tt>*args</tt>: One or more modules, strings or symbols, or the special symbol <tt>:all</tt>.
- # * <tt>&block</tt>: A block defining helper methods.
+ # ==== Parameters
+ # *args<Array[Module, Symbol, String, :all]>
+ # block<Block>:: A block defining helper methods
#
# ==== Examples
# When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file
@@ -56,34 +103,7 @@ module ActionController
# helper(:three, BlindHelper) { def mice() 'mice' end }
#
def helper(*args, &block)
- args.flatten.each do |arg|
- case arg
- when :all
- helper all_application_helpers
- when String, Symbol
- file_name = arg.to_s.underscore + '_helper'
- class_name = file_name.camelize
-
- begin
- require_dependency(file_name)
- rescue LoadError => load_error
- requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1]
- if requiree == file_name
- msg = "Missing helper file helpers/#{file_name}.rb"
- raise LoadError.new(msg).copy_blame!(load_error)
- else
- raise
- end
- end
-
- super class_name.constantize
- else
- super args
- end
- end
-
- # Evaluate block in template class if given.
- master_helper_module.module_eval(&block) if block_given?
+ super(*_modules_for_helpers(args), &block)
end
# Declares helper accessors for controller attributes. For example, the
@@ -91,39 +111,68 @@ module ActionController
# controller and makes them available to the view:
# helper_attr :name
# attr_accessor :name
+ #
+ # ==== Parameters
+ # *attrs<Array[String, Symbol]>:: Names of attributes to be converted
+ # into helpers.
def helper_attr(*attrs)
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
end
# Provides a proxy to access helpers methods from outside the view.
def helpers
- unless @helper_proxy
- @helper_proxy = ActionView::Base.new
- @helper_proxy.extend master_helper_module
- else
- @helper_proxy
- end
+ @helper_proxy ||= ActionView::Base.new.extend(_helpers)
end
- private
- def default_helper_module!
- unless name.blank?
- module_name = name.sub(/Controller$|$/, 'Helper')
- module_path = module_name.split('::').map { |m| m.underscore }.join('/')
- require_dependency module_path
- helper module_name.constantize
+ private
+ # Returns a list of modules, normalized from the acceptable kinds of
+ # helpers with the following behavior:
+ # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper",
+ # and "foo_bar_helper.rb" is loaded using require_dependency.
+ # :all:: Loads all modules in the #helpers_dir
+ # Module:: No further processing
+ #
+ # After loading the appropriate files, the corresponding modules
+ # are returned.
+ #
+ # ==== Parameters
+ # args<Array[String, Symbol, Module, all]>:: A list of helpers
+ #
+ # ==== Returns
+ # Array[Module]:: A normalized list of modules for the list of
+ # helpers provided.
+ def _modules_for_helpers(args)
+ args.flatten.map! do |arg|
+ case arg
+ when :all
+ _modules_for_helpers all_application_helpers
+ when String, Symbol
+ file_name = "#{arg.to_s.underscore}_helper"
+ require_dependency(file_name, "Missing helper file helpers/%s.rb")
+ file_name.camelize.constantize
+ when Module
+ arg
+ else
+ raise ArgumentError, "helper must be a String, Symbol, or Module"
end
- rescue MissingSourceFile => e
- raise unless e.is_missing? module_path
- rescue NameError => e
- raise unless e.missing_name? module_name
end
+ end
- # Extract helper names from files in app/helpers/**/*.rb
- def all_application_helpers
- extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/
- Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
- end
+ def default_helper_module!
+ module_name = name.sub(/Controller$/, '')
+ module_path = module_name.underscore
+ helper module_path
+ rescue MissingSourceFile => e
+ raise e unless e.is_missing? "#{module_path}_helper"
+ rescue NameError => e
+ raise e unless e.missing_name? "#{module_name}Helper"
+ end
+
+ # Extract helper names from files in app/helpers/**/*.rb
+ def all_application_helpers
+ extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/
+ Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' }
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/new_base/hide_actions.rb b/actionpack/lib/action_controller/new_base/hide_actions.rb
index b45e520bee..af68c772b1 100644
--- a/actionpack/lib/action_controller/new_base/hide_actions.rb
+++ b/actionpack/lib/action_controller/new_base/hide_actions.rb
@@ -1,39 +1,35 @@
module ActionController
+ # ActionController::HideActions adds the ability to prevent public methods on a controller
+ # to be called as actions.
module HideActions
extend ActiveSupport::Concern
included do
- extlib_inheritable_accessor :hidden_actions
- self.hidden_actions ||= Set.new
+ extlib_inheritable_accessor(:hidden_actions) { Set.new }
end
- def action_methods
- self.class.action_names
- end
+ private
- def action_names
- action_methods
+ # Overrides AbstractController::Base#action_method? to return false if the
+ # action name is in the list of hidden actions.
+ def action_method?(action_name)
+ !hidden_actions.include?(action_name) && super
end
- private
- def action_method?(action_name)
- !hidden_actions.include?(action_name) && super
+ module ClassMethods
+ # Sets all of the actions passed in as hidden actions.
+ #
+ # ==== Parameters
+ # *args<#to_s>:: A list of actions
+ def hide_action(*args)
+ hidden_actions.merge(args.map! {|a| a.to_s })
end
- module ClassMethods
- def hide_action(*args)
- args.each do |arg|
- self.hidden_actions << arg.to_s
- end
- end
-
- def action_methods
- @action_names ||= Set.new(super.reject {|name| self.hidden_actions.include?(name.to_s)})
- end
-
- def self.action_names
- action_methods
- end
+ # Overrides AbstractController::Base#action_methods to remove any methods
+ # that are listed as hidden methods.
+ def action_methods
+ @action_methods ||= Set.new(super.reject {|name| hidden_actions.include?(name)})
end
+ end
end
end
diff --git a/actionpack/lib/action_controller/new_base/http.rb b/actionpack/lib/action_controller/new_base/http.rb
index c96aaaa865..2e73561f93 100644
--- a/actionpack/lib/action_controller/new_base/http.rb
+++ b/actionpack/lib/action_controller/new_base/http.rb
@@ -2,48 +2,48 @@ require 'action_controller/abstract'
require 'active_support/core_ext/module/delegation'
module ActionController
+ # ActionController::Http provides a way to get a valid Rack application from a controller.
+ #
+ # In AbstractController, dispatching is triggered directly by calling #process on a new controller.
+ # ActionController::Http provides an #action method that returns a valid Rack application for a
+ # given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the Rails router,
+ # can dispatch directly to the action returned by FooController.action(:index).
class Http < AbstractController::Base
abstract!
# :api: public
attr_internal :params, :env
- # :api: public
+ # Returns the last part of the controller's name, underscored, without the ending
+ # "Controller". For instance, MyApp::MyPostsController would return "my_posts" for
+ # controller_name
+ #
+ # ==== Returns
+ # String
def self.controller_name
@controller_name ||= controller_path.split("/").last
end
- # :api: public
+ # Delegates to the class' #controller_name
def controller_name
self.class.controller_name
end
- # :api: public
+ # Returns the full controller name, underscored, without the ending Controller.
+ # For instance, MyApp::MyPostsController would return "my_app/my_posts" for
+ # controller_name.
+ #
+ # ==== Returns
+ # String
def self.controller_path
@controller_path ||= self.name.sub(/Controller$/, '').underscore
end
- # :api: public
+ # Delegates to the class' #controller_path
def controller_path
self.class.controller_path
end
- # :api: private
- def self.action_names
- action_methods
- end
-
- # :api: private
- def action_names
- action_methods
- end
-
- # :api: plugin
- def self.call(env)
- controller = new
- controller.call(env).to_rack
- end
-
# The details below can be overridden to support a specific
# Request and Response object. The default ActionController::Base
# implementation includes RackConvenience, which makes a request
@@ -57,7 +57,7 @@ module ActionController
super
end
- # Basic implements for content_type=, location=, and headers are
+ # Basic implementations for content_type=, location=, and headers are
# provided to reduce the dependency on the RackConvenience module
# in Renderer and Redirector.
@@ -81,6 +81,15 @@ module ActionController
[status, headers, response_body]
end
+ # Return a rack endpoint for the given action. Memoize the endpoint, so
+ # multiple calls into MyController.action will return the same object
+ # for the same action.
+ #
+ # ==== Parameters
+ # action<#to_s>:: An action name
+ #
+ # ==== Returns
+ # Proc:: A rack application
def self.action(name)
@actions ||= {}
@actions[name.to_s] ||= proc do |env|
diff --git a/actionpack/lib/action_controller/new_base/layouts.rb b/actionpack/lib/action_controller/new_base/layouts.rb
index 0ff71587d6..ace4b148c9 100644
--- a/actionpack/lib/action_controller/new_base/layouts.rb
+++ b/actionpack/lib/action_controller/new_base/layouts.rb
@@ -1,4 +1,163 @@
module ActionController
+ # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
+ # repeated setups. The inclusion pattern has pages that look like this:
+ #
+ # <%= render "shared/header" %>
+ # Hello World
+ # <%= render "shared/footer" %>
+ #
+ # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
+ # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
+ #
+ # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
+ # that the header and footer are only mentioned in one place, like this:
+ #
+ # // The header part of this layout
+ # <%= yield %>
+ # // The footer part of this layout
+ #
+ # And then you have content pages that look like this:
+ #
+ # hello world
+ #
+ # At rendering time, the content page is computed and then inserted in the layout, like this:
+ #
+ # // The header part of this layout
+ # hello world
+ # // The footer part of this layout
+ #
+ # NOTE: The old notation for rendering the view from a layout was to expose the magic <tt>@content_for_layout</tt> instance
+ # variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
+ #
+ # == Accessing shared variables
+ #
+ # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
+ # references that won't materialize before rendering time:
+ #
+ # <h1><%= @page_title %></h1>
+ # <%= yield %>
+ #
+ # ...and content pages that fulfill these references _at_ rendering time:
+ #
+ # <% @page_title = "Welcome" %>
+ # Off-world colonies offers you a chance to start a new life
+ #
+ # The result after rendering is:
+ #
+ # <h1>Welcome</h1>
+ # Off-world colonies offers you a chance to start a new life
+ #
+ # == Layout assignment
+ #
+ # You can either specify a layout declaratively (using the #layout class method) or give
+ # it the same name as your controller, and place it in <tt>app/views/layouts</tt>.
+ # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.
+ #
+ # For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>,
+ # that template will be used for all actions in PostsController and controllers inheriting
+ # from PostsController.
+ #
+ # If you use a module, for instance Weblog::PostsController, you will need a template named
+ # <tt>app/views/layouts/weblog/posts.html.erb</tt>.
+ #
+ # Since all your controllers inherit from ApplicationController, they will use
+ # <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified
+ # or provided.
+ #
+ # == Inheritance Examples
+ #
+ # class BankController < ActionController::Base
+ # layout "bank_standard"
+ #
+ # class InformationController < BankController
+ #
+ # class TellerController < BankController
+ # # teller.html.erb exists
+ #
+ # class TillController < TellerController
+ #
+ # class VaultController < BankController
+ # layout :access_level_layout
+ #
+ # class EmployeeController < BankController
+ # layout nil
+ #
+ # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
+ # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
+ #
+ # The TellerController uses +teller.html.erb+, and TillController inherits that layout and
+ # uses it as well.
+ #
+ # == Types of layouts
+ #
+ # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
+ # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
+ # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
+ #
+ # The method reference is the preferred approach to variable layouts and is used like this:
+ #
+ # class WeblogController < ActionController::Base
+ # layout :writers_and_readers
+ #
+ # def index
+ # # fetching posts
+ # end
+ #
+ # private
+ # def writers_and_readers
+ # logged_in? ? "writer_layout" : "reader_layout"
+ # end
+ #
+ # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
+ # is logged in or not.
+ #
+ # If you want to use an inline method, such as a proc, do something like this:
+ #
+ # class WeblogController < ActionController::Base
+ # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
+ #
+ # Of course, the most common way of specifying a layout is still just as a plain template name:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard"
+ #
+ # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>.
+ # Otherwise, it will be looked up relative to the template root.
+ #
+ # == Conditional layouts
+ #
+ # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
+ # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
+ # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard", :except => :rss
+ #
+ # # ...
+ #
+ # end
+ #
+ # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
+ # around the rendered view.
+ #
+ # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
+ # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
+ #
+ # == Using a different layout in the action render call
+ #
+ # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
+ # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
+ # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
+ #
+ # class WeblogController < ActionController::Base
+ # layout "weblog_standard"
+ #
+ # def help
+ # render :action => "help", :layout => "help"
+ # end
+ # end
+ #
+ # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout.
module Layouts
extend ActiveSupport::Concern
@@ -6,6 +165,7 @@ module ActionController
include AbstractController::Layouts
module ClassMethods
+ # If no layout is provided, look for a layout with this name.
def _implied_layout_name
controller_path
end
@@ -14,16 +174,17 @@ module ActionController
private
def _determine_template(options)
super
- if (!options.key?(:text) && !options.key?(:inline) && !options.key?(:partial)) || options.key?(:layout)
- options[:_layout] = _layout_for_option(options.key?(:layout) ? options[:layout] : :none, options[:_template].details)
- end
+
+ return if (options.key?(:text) || options.key?(:inline) || options.key?(:partial)) && !options.key?(:layout)
+ layout = options.key?(:layout) ? options[:layout] : :none
+ options[:_layout] = _layout_for_option(layout, options[:_template].details)
end
def _layout_for_option(name, details)
case name
when String then _layout_for_name(name, details)
- when true then _default_layout(true, details)
- when :none then _default_layout(false, details)
+ when true then _default_layout(details, true)
+ when :none then _default_layout(details, false)
when false, nil then nil
else
raise ArgumentError,
diff --git a/actionpack/test/abstract_controller/abstract_controller_test.rb b/actionpack/test/abstract_controller/abstract_controller_test.rb
index c7eaaeb4ba..05b55216c8 100644
--- a/actionpack/test/abstract_controller/abstract_controller_test.rb
+++ b/actionpack/test/abstract_controller/abstract_controller_test.rb
@@ -154,7 +154,7 @@ module AbstractController
end
def render_to_body(options = {})
- options[:_layout] = options[:layout] || _default_layout
+ options[:_layout] = options[:layout] || _default_layout({})
super
end
end
diff --git a/actionpack/test/abstract_controller/layouts_test.rb b/actionpack/test/abstract_controller/layouts_test.rb
index 4b66f063f3..64c435abb7 100644
--- a/actionpack/test/abstract_controller/layouts_test.rb
+++ b/actionpack/test/abstract_controller/layouts_test.rb
@@ -25,7 +25,7 @@ module AbstractControllerTests
def controller_path() self.class.controller_path end
def render_to_body(options)
- options[:_layout] = _default_layout
+ options[:_layout] = _default_layout({})
super
end
end
@@ -221,11 +221,11 @@ module AbstractControllerTests
test "raises an exception when specifying layout true" do
assert_raises ArgumentError do
- Object.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ Object.class_eval do
class ::BadOmgFailLolLayout < AbstractControllerTests::Layouts::Base
layout true
end
- RUBY_EVAL
+ end
end
end
end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 3a4cdb81d9..03fd98a85c 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -87,11 +87,11 @@ class ControllerInstanceTests < Test::Unit::TestCase
def test_action_methods
@empty_controllers.each do |c|
hide_mocha_methods_from_controller(c)
- assert_equal Set.new, c.__send__(:action_methods), "#{c.controller_path} should be empty!"
+ assert_equal Set.new, c.class.__send__(:action_methods), "#{c.controller_path} should be empty!"
end
@non_empty_controllers.each do |c|
hide_mocha_methods_from_controller(c)
- assert_equal Set.new(%w(public_action)), c.__send__(:action_methods), "#{c.controller_path} should not be empty!"
+ assert_equal Set.new(%w(public_action)), c.class.__send__(:action_methods), "#{c.controller_path} should not be empty!"
end
end
@@ -145,7 +145,12 @@ class PerformActionTest < ActionController::TestCase
def test_method_missing_is_not_an_action_name
use_controller MethodMissingController
- assert ! @controller.__send__(:action_methods).include?('method_missing')
+
+ if defined?(ActionController::Http)
+ assert ! @controller.__send__(:action_method?, 'method_missing')
+ else
+ assert ! @controller.__send__(:action_methods).include?('method_missing')
+ end
get :method_missing
assert_response :success
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 342cbfbcd3..515c4c9f52 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -127,7 +127,11 @@ class HelperTest < Test::Unit::TestCase
end
def test_all_helpers
- methods = AllHelpersController.master_helper_module.instance_methods.map {|m| m.to_s}
+ methods = if defined?(ActionController::Http)
+ AllHelpersController._helpers.instance_methods.map {|m| m.to_s}
+ else
+ AllHelpersController.master_helper_module.instance_methods.map {|m| m.to_s}
+ end
# abc_helper.rb
assert methods.include?('bare_a')
@@ -143,7 +147,12 @@ class HelperTest < Test::Unit::TestCase
@controller_class.helpers_dir = File.dirname(__FILE__) + '/../fixtures/alternate_helpers'
# Reload helpers
- @controller_class.master_helper_module = Module.new
+ if defined?(ActionController::Http)
+ @controller_class._helpers = Module.new
+ else
+ @controller_class.master_helper_module = Module.new
+ end
+
@controller_class.helper :all
# helpers/abc_helper.rb should not be included
@@ -175,7 +184,11 @@ class HelperTest < Test::Unit::TestCase
end
def master_helper_methods
- @controller_class.master_helper_module.instance_methods.map {|m| m.to_s }
+ if defined?(ActionController::Http)
+ @controller_class._helpers.instance_methods.map {|m| m.to_s }
+ else
+ @controller_class.master_helper_module.instance_methods.map {|m| m.to_s }
+ end
end
def missing_methods
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 48db9fb942..4f0c11d883 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -3141,11 +3141,11 @@ module ActiveRecord #:nodoc:
def execute_callstack_for_multiparameter_attributes(callstack)
errors = []
callstack.each do |name, values|
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
- if values.empty?
- send(name + "=", nil)
- else
- begin
+ begin
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ if values.empty?
+ send(name + "=", nil)
+ else
value = if Time == klass
instantiate_time_object(name, values)
elsif Date == klass
@@ -3159,9 +3159,9 @@ module ActiveRecord #:nodoc:
end
send(name + "=", value)
- rescue => ex
- errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
end
+ rescue => ex
+ errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
end
end
unless errors.empty?
@@ -3187,7 +3187,7 @@ module ActiveRecord #:nodoc:
end
def type_cast_attribute_value(multiparameter_name, value)
- multiparameter_name =~ /\([0-9]*([a-z])\)/ ? value.send("to_" + $1) : value
+ multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
end
def find_parameter_position(multiparameter_name)
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 855b720ef1..7f6f012721 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -143,8 +143,8 @@ module ActiveSupport #:nodoc:
Dependencies.require_or_load(file_name)
end
- def require_dependency(file_name)
- Dependencies.depend_on(file_name)
+ def require_dependency(file_name, message = "No such file to load -- %s")
+ Dependencies.depend_on(file_name, false, message)
end
def require_association(file_name)
@@ -230,11 +230,16 @@ module ActiveSupport #:nodoc:
mechanism == :load
end
- def depend_on(file_name, swallow_load_errors = false)
+ def depend_on(file_name, swallow_load_errors = false, message = "No such file to load -- %s.rb")
path = search_for_file(file_name)
require_or_load(path || file_name)
- rescue LoadError
- raise unless swallow_load_errors
+ rescue LoadError => load_error
+ unless swallow_load_errors
+ if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1]
+ raise MissingSourceFile.new(message % file_name, load_error.path).copy_blame!(load_error)
+ end
+ raise
+ end
end
def associate_with(file_name)
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index d4c4dc7be5..2ae22c35fb 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -9,16 +9,18 @@ module ActiveSupport
# data::
# XML Document string or IO to parse
def parse(data)
- if data.respond_to?(:read)
- data = data.read
+ if !data.respond_to?(:read)
+ data = StringIO.new(data || '')
end
-
+
LibXML::XML.default_keep_blanks = false
-
- if data.blank?
+
+ char = data.getc
+ if char.nil?
{}
else
- LibXML::XML::Parser.string(data.strip).parse.to_hash
+ data.ungetc(char)
+ LibXML::XML::Parser.io(data).parse.to_hash
end
end
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
index 7337c143c9..622523a1b9 100644
--- a/activesupport/lib/active_support/xml_mini/nokogiri.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -9,13 +9,15 @@ module ActiveSupport
# data::
# XML Document string or IO to parse
def parse(data)
- if data.respond_to?(:read)
- data = data.read
+ if !data.respond_to?(:read)
+ data = StringIO.new(data || '')
end
-
- if data.blank?
+
+ char = data.getc
+ if char.nil?
{}
else
+ data.ungetc(char)
doc = Nokogiri::XML(data)
raise doc.errors.first if doc.errors.length > 0
doc.to_hash
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index 1184d2d6c9..aa2461535b 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -15,13 +15,19 @@ module ActiveSupport
# data::
# XML Document string or IO to parse
def parse(data)
- if data.respond_to?(:read)
- data = data.read
+ if !data.respond_to?(:read)
+ data = StringIO.new(data || '')
+ end
+
+ char = data.getc
+ if char.nil?
+ {}
+ else
+ data.ungetc(char)
+ require 'rexml/document' unless defined?(REXML::Document)
+ doc = REXML::Document.new(data)
+ merge_element!({}, doc.root)
end
-
- require 'rexml/document' unless defined?(REXML::Document)
- doc = REXML::Document.new(data)
- merge_element!({}, doc.root)
end
private
diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb
index 3a82202bd0..3cc75494e4 100644
--- a/railties/lib/rails/gem_dependency.rb
+++ b/railties/lib/rails/gem_dependency.rb
@@ -34,7 +34,7 @@ module Rails
name = directory_name_parts[0..-2].join('-')
version = directory_name_parts.last
result = self.new(name, :version => version)
- spec_filename = File.join(unpacked_path, directory_name, '.specification')
+ spec_filename = File.join(directory_name, '.specification')
if load_spec
raise "Missing specification file in #{File.dirname(spec_filename)}. Perhaps you need to do a 'rake gems:refresh_specs'?" unless File.exists?(spec_filename)
spec = YAML::load_file(spec_filename)
diff --git a/railties/test/gem_dependency_test.rb b/railties/test/gem_dependency_test.rb
index 195494a926..70f4496685 100644
--- a/railties/test/gem_dependency_test.rb
+++ b/railties/test/gem_dependency_test.rb
@@ -178,6 +178,13 @@ class GemDependencyTest < Test::Unit::TestCase
assert_equal '= 1.1', dummy_gem.version_requirements.to_s
end
+ def test_gem_from_directory_name_loads_specification_successfully
+ assert_nothing_raised do
+ dummy_gem = Rails::GemDependency.from_directory_name(File.join(Rails::GemDependency.unpacked_path, 'dummy-gem-g-1.0.0'))
+ assert_not_nil dummy_gem.specification
+ end
+ end
+
def test_gem_from_invalid_directory_name
assert_raises RuntimeError do
dummy_gem = Rails::GemDependency.from_directory_name('dummy-gem')