diff options
Diffstat (limited to 'actionpack')
160 files changed, 3245 insertions, 2848 deletions
diff --git a/actionpack/Rakefile b/actionpack/Rakefile index c45f88ed04..b9ace8658a 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -4,17 +4,6 @@ require 'rake/testtask' require 'rake/rdoctask' require 'rake/packagetask' require 'rake/gempackagetask' -require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version') - -PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' -PKG_NAME = 'actionpack' -PKG_VERSION = ActionPack::VERSION::STRING + PKG_BUILD -PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" - -RELEASE_NAME = "REL #{PKG_VERSION}" - -RUBY_FORGE_PROJECT = "actionpack" -RUBY_FORGE_USER = "webster132" desc "Default Task" task :default => :test @@ -115,26 +104,7 @@ task :update_js => [ :update_scriptaculous ] # Publishing ------------------------------------------------------ desc "Publish the API documentation" -task :pgem => [:package] do - require 'rake/contrib/sshpublisher' - Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload - `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` -end - -desc "Publish the API documentation" task :pdoc => [:rdoc] do require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload -end - -desc "Publish the release files to RubyForge." -task :release => [ :package ] do - require 'rubyforge' - require 'rake/contrib/rubyforgepublisher' - - packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" } - - rubyforge = RubyForge.new - rubyforge.login - rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages) -end +end
\ No newline at end of file diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 8a7169bfa1..ed5b7e1e93 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -1,9 +1,11 @@ +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip + Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'actionpack' - s.version = '3.0.0.beta1' + s.version = version s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).' - s.description = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).' + s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.' s.required_ruby_version = '>= 1.8.7' s.author = 'David Heinemeier Hansson' @@ -17,10 +19,10 @@ Gem::Specification.new do |s| s.has_rdoc = true - s.add_dependency('activesupport', '= 3.0.0.beta1') - s.add_dependency('activemodel', '= 3.0.0.beta1') + s.add_dependency('activesupport', version) + s.add_dependency('activemodel', version) s.add_dependency('rack', '~> 1.1.0') s.add_dependency('rack-test', '~> 0.5.0') - s.add_dependency('rack-mount', '~> 0.4.7') + s.add_dependency('rack-mount', '~> 0.6.0') s.add_dependency('erubis', '~> 2.6.5') end diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 1e15ab090c..2da4dc052c 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -3,8 +3,11 @@ $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.inc require 'active_support/ruby/shim' require 'active_support/dependencies/autoload' +require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/module/anonymous' +require 'active_support/i18n' module AbstractController extend ActiveSupport::Autoload @@ -12,11 +15,10 @@ module AbstractController autoload :Base autoload :Callbacks autoload :Collector - autoload :Compatibility autoload :Helpers autoload :Layouts - autoload :LocalizedCache autoload :Logger autoload :Rendering autoload :Translation + autoload :ViewPaths end diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 3119ee498b..c12b584144 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -1,3 +1,5 @@ +require 'active_support/ordered_options' + module AbstractController class Error < StandardError; end class ActionNotFound < StandardError; end @@ -5,7 +7,6 @@ module AbstractController class Base attr_internal :response_body attr_internal :action_name - attr_internal :formats class << self attr_reader :abstract @@ -28,6 +29,14 @@ module AbstractController @descendants ||= [] end + def config + @config ||= ActiveSupport::InheritableOptions.new(superclass < Base ? superclass.config : {}) + end + + def configure + yield config + end + # 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 @@ -84,15 +93,14 @@ module AbstractController # ==== Returns # String def controller_path - @controller_path ||= name && name.sub(/Controller$/, '').underscore + @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous? end end abstract! - # Initialize controller with nil formats. - def initialize #:nodoc: - @_formats = nil + def config + @config ||= ActiveSupport::InheritableOptions.new(self.class.config) end # Calls the action going through the entire action dispatch stack. diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index d429333661..81fb514770 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -1,3 +1,5 @@ +require "action_dispatch/http/mime_type" + module AbstractController module Collector def self.generate_method_for_mime(mime) diff --git a/actionpack/lib/abstract_controller/compatibility.rb b/actionpack/lib/abstract_controller/compatibility.rb deleted file mode 100644 index 7fb93a0eb5..0000000000 --- a/actionpack/lib/abstract_controller/compatibility.rb +++ /dev/null @@ -1,18 +0,0 @@ -module AbstractController - module Compatibility - extend ActiveSupport::Concern - - def _find_layout(name, details) - details[:prefix] = nil if name =~ /\blayouts/ - super - end - - # Move this into a "don't run in production" module - def _default_layout(details, require_layout = false) - super - rescue ActionView::MissingTemplate - _find_layout(_layout({}), {}) - nil - end - end -end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 578b884a4d..f875213afb 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -1,6 +1,4 @@ require 'active_support/dependencies' -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/module/delegation' module AbstractController module Helpers @@ -27,7 +25,7 @@ module AbstractController def inherited(klass) helpers = _helpers klass._helpers = Module.new { include helpers } - klass.class_eval { default_helper_module! unless name.blank? } + klass.class_eval { default_helper_module! unless anonymous? } super end @@ -99,7 +97,7 @@ module AbstractController def helper(*args, &block) self._helper_serial = AbstractController::Helpers.next_serial + 1 - _modules_for_helpers(args).each do |mod| + modules_for_helpers(args).each do |mod| add_template_helper(mod) end @@ -134,7 +132,7 @@ module AbstractController # ==== Returns # Array[Module]:: A normalized list of modules for the list of # helpers provided. - def _modules_for_helpers(args) + def modules_for_helpers(args) args.flatten.map! do |arg| case arg when String, Symbol diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 0d214396aa..2f9616124a 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -1,6 +1,3 @@ -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/module/delegation' - module AbstractController # 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: @@ -173,27 +170,7 @@ module AbstractController module ClassMethods def inherited(klass) super - klass.class_eval do - _write_layout_method - @found_layouts = {} - end - end - - def clear_template_caches! - @found_layouts.clear if defined? @found_layouts - super - end - - def cache_layout(details) - layout = @found_layouts - key = Thread.current[:format_locale_key] - - # Cache nil - if layout.key?(key) - return layout[key] - else - layout[key] = yield - end + klass._write_layout_method end # This module is mixed in if layout conditions are provided. This means @@ -262,10 +239,10 @@ module AbstractController def _write_layout_method case defined?(@_layout) ? @_layout : nil when String - self.class_eval %{def _layout(details) #{@_layout.inspect} end} + self.class_eval %{def _layout; #{@_layout.inspect} end} when Symbol self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 - def _layout(details) + def _layout #{@_layout}.tap do |layout| unless layout.is_a?(String) || !layout raise ArgumentError, "Your layout method :#{@_layout} returned \#{layout}. It " \ @@ -276,21 +253,21 @@ module AbstractController ruby_eval when Proc define_method :_layout_from_proc, &@_layout - self.class_eval %{def _layout(details) _layout_from_proc(self) end} + self.class_eval %{def _layout; _layout_from_proc(self) end} when false - self.class_eval %{def _layout(details) end} + self.class_eval %{def _layout; end} when true raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil" when nil if name + _prefix = "layouts" unless _implied_layout_name =~ /\blayouts/ + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def _layout(details) - self.class.cache_layout(details) do - if template_exists?("#{_implied_layout_name}", details, :_prefix => "layouts") - "#{_implied_layout_name}" - else - super - end + def _layout + if template_exists?("#{_implied_layout_name}", #{_prefix.inspect}) + "#{_implied_layout_name}" + else + super end end RUBY @@ -300,42 +277,20 @@ module AbstractController end end - def render_to_body(options = {}) - # In the case of a partial with a layout, handle the layout - # here, and make sure the view does not try to handle it - layout = options.delete(:layout) if options.key?(:partial) - - response = super + def _normalize_options(options) + super - # This is a little bit messy. We need to explicitly handle partial - # layouts here since the core lookup logic is in the view, but - # we need to determine the layout based on the controller - # - # TODO: An easier way to handle this would probably be to override - # render_template - if layout - layout = _layout_for_option(layout, options[:_template].details) - response = layout.render(view_context, options[:locals] || {}) { response } + if _include_layout?(options) + layout = options.key?(:layout) ? options.delete(:layout) : :default + value = _layout_for_option(layout) + options[:layout] = (value =~ /\blayouts/ ? value : "layouts/#{value}") if value end - - response end private # This will be overwritten by _write_layout_method - def _layout(details) end - - # 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) - name && _find_layout(name, details) - end + def _layout; end # Determine the layout for a given name and details, taking into account # the name type. @@ -345,11 +300,11 @@ module AbstractController # 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_option(name, details) + def _layout_for_option(name) case name - when String then _layout_for_name(name, details) - when true then _default_layout(details, true) - when :default then _default_layout(details, false) + when String then name + when true then _default_layout(true) + when :default then _default_layout(false) when false, nil then nil else raise ArgumentError, @@ -357,29 +312,6 @@ module AbstractController end end - def _determine_template(options) - super - - return unless (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) - layout = options.key?(:layout) ? options[:layout] : :default - options[:_layout] = _layout_for_option(layout, options[:_template].details) - end - - # 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_layout(name, details) - # TODO: Make prefix actually part of details in ViewPath#find_by_parts - prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts" - find_template(name, details, :_prefix => prefix) - end - # Returns the default layout for this controller and a given set of details. # Optionally raises an exception if the layout could not be found. # @@ -392,18 +324,24 @@ module AbstractController # # ==== 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}" - end - + def _default_layout(require_layout = false) begin - _layout_for_name(_layout(details), details) if _action_has_layout? + layout_name = _layout if _action_has_layout? rescue NameError => e raise NoMethodError, "You specified #{@_layout.inspect} as the layout, but no such method was found" end + + if require_layout && _action_has_layout? && !layout_name + raise ArgumentError, + "There was no default layout for #{self.class} in #{view_paths.inspect}" + end + + layout_name + end + + def _include_layout?(options) + (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) end def _action_has_layout? diff --git a/actionpack/lib/abstract_controller/localized_cache.rb b/actionpack/lib/abstract_controller/localized_cache.rb deleted file mode 100644 index 5e3efa002c..0000000000 --- a/actionpack/lib/abstract_controller/localized_cache.rb +++ /dev/null @@ -1,49 +0,0 @@ -module AbstractController - class HashKey - @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|sh,sk| sh[sk] = {} } } - - def self.get(klass, formats, locale) - @hash_keys[klass][formats][locale] ||= new(klass, formats, locale) - end - - attr_accessor :hash - def initialize(klass, formats, locale) - @formats, @locale = formats, locale - @hash = [formats, locale].hash - end - - alias_method :eql?, :equal? - - def inspect - "#<HashKey -- formats: #{@formats.inspect} locale: #{@locale.inspect}>" - end - end - - module LocalizedCache - extend ActiveSupport::Concern - - module ClassMethods - def clear_template_caches! - ActionView::Partials::PartialRenderer::TEMPLATES.clear - template_cache.clear - super - end - - def template_cache - @template_cache ||= Hash.new {|h,k| h[k] = {} } - end - end - - def render(*args) - Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale) - super - end - - private - - def with_template_cache(name) - self.class.template_cache[Thread.current[:format_locale_key]][name] ||= super - end - - end -end diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb index a23a13e1d6..9318f5e369 100644 --- a/actionpack/lib/abstract_controller/logger.rb +++ b/actionpack/lib/abstract_controller/logger.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/logger' -require 'active_support/benchmarkable' +require "active_support/core_ext/logger" +require "active_support/benchmarkable" module AbstractController module Logger diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 619a49571b..42f4939108 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,7 +1,4 @@ require "abstract_controller/base" -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/array/wrap' module AbstractController class DoubleRenderError < Error @@ -12,32 +9,47 @@ module AbstractController end end + # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request, + # it will trigger the lookup_context and consequently expire the cache. + # TODO Add some deprecation warnings to remove I18n.locale from controllers + class I18nProxy < ::I18n::Config #:nodoc: + attr_reader :i18n_config, :lookup_context + + def initialize(i18n_config, lookup_context) + @i18n_config, @lookup_context = i18n_config, lookup_context + end + + def locale + @i18n_config.locale + end + + def locale=(value) + @i18n_config.locale = value + @lookup_context.update_details(:locale => @i18n_config.locale) + end + end + module Rendering extend ActiveSupport::Concern + include AbstractController::ViewPaths - included do - class_attribute :_view_paths - delegate :_view_paths, :to => :'self.class' - self._view_paths = ActionView::PathSet.new + # Overwrite process to setup I18n proxy. + def process(*) #:nodoc: + old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) + super + ensure + I18n.config = old_config end # An instance of a view class. The default view class is ActionView::Base # # The view class must have the following methods: - # View.for_controller[controller] Create a new ActionView instance for a - # controller - # View#render_partial[options] - # - responsible for setting options[:_template] - # - Returns String with the rendered partial - # options<Hash>:: see _render_partial in ActionView::Base - # View#render_template[template, layout, options, partial] - # - Returns String with the rendered template - # template<ActionView::Template>:: The template to render - # layout<ActionView::Template>:: The layout to render around the template - # options<Hash>:: See _render_template_with_layout in ActionView::Base - # partial<Boolean>:: Whether or not the template to render is a partial + # View.for_controller[controller] + # Create a new ActionView instance for a controller + # View#render_template[options] + # Returns String with the rendered template # - # Override this method in a to change the default behavior. + # Override this method in a module to change the default behavior. def view_context @_view_context ||= ActionView::Base.for_controller(self) end @@ -45,57 +57,29 @@ module AbstractController # Mostly abstracts the fact that calling render twice is a DoubleRenderError. # Delegates render_to_body and sticks the result in self.response_body. def render(*args, &block) - options = _normalize_options(*args, &block) + options = _normalize_args(*args, &block) + _normalize_options(options) self.response_body = render_to_body(options) end # Raw rendering of a template to a Rack-compatible body. - # - # ==== Options - # _partial_object<Object>:: The object that is being rendered. If this - # exists, we are in the special case of rendering an object as a partial. - # # :api: plugin def render_to_body(options = {}) - # TODO: Refactor so we can just use the normal template logic for this - if options.key?(:partial) - _render_partial(options) - else - _determine_template(options) - _render_template(options) - end + _process_options(options) + _render_template(options) end # Raw rendering of a template to a string. Just convert the results of # render_to_body into a String. - # # :api: plugin - def render_to_string(*args) - options = _normalize_options(*args) + def render_to_string(options={}) + _normalize_options(options) AbstractController::Rendering.body_to_s(render_to_body(options)) end - # Renders the template from an object. - # - # ==== Options - # _template<ActionView::Template>:: The template to render - # _layout<ActionView::Template>:: The layout to wrap the template in (optional) + # Find and renders a template based on the options given. def _render_template(options) - view_context.render_template(options) - end - - # Renders the given partial. - # - # ==== Options - # partial<String|Object>:: The partial name or the object to be rendered - def _render_partial(options) - view_context.render_partial(options) - end - - # The list of view paths for this controller. See ActionView::ViewPathSet for - # more details about writing custom view paths. - def view_paths - _view_paths + view_context.render_template(options) { |template| _with_template_hook(template) } end # The prefix used in render "foo" shortcuts. @@ -117,122 +101,42 @@ module AbstractController private - # Normalize options, by converting render "foo" to render :template => "prefix/foo" - # and render "/foo" to render :file => "/foo". - def _normalize_options(action=nil, options={}) + # Normalize options by converting render "foo" to render :action => "foo" and + # render "foo/bar" to render :file => "foo/bar". + def _normalize_args(action=nil, options={}) case action + when NilClass when Hash options, action = action, nil when String, Symbol action = action.to_s - case action.index("/") - when NilClass - options[:_prefix] = _prefix - options[:_template_name] = action - when 0 - options[:file] = action - else - options[:template] = action - end + key = action.include?(?/) ? :file : :action + options[key] = action + else + options.merge!(:partial => action) end options end - # Take in a set of options and determine the template to render - # - # ==== Options - # _template<ActionView::Template>:: If this is provided, the search is over - # _template_name<#to_s>:: The name of the template to look up. Otherwise, - # use the current action name. - # _prefix<String>:: The prefix to look inside of. In a file system, this corresponds - # to a directory. - # _partial<TrueClass, FalseClass>:: Whether or not the file to look up is a partial - def _determine_template(options) - if options.key?(:text) - options[:_template] = ActionView::Template::Text.new(options[:text], format_for_text) - elsif options.key?(:inline) - handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb") - template = ActionView::Template.new(options[:inline], "inline template", handler, {}) - options[:_template] = template - elsif options.key?(:template) - options[:_template_name] = options[:template] - elsif options.key?(:file) - options[:_template_name] = options[:file] + def _normalize_options(options) + if options[:partial] == true + options[:partial] = action_name end - name = (options[:_template_name] || options[:action] || action_name).to_s - options[:_prefix] ||= _prefix if (options.keys & [:partial, :file, :template]).empty? - - details = _normalize_details(options) - options[:_template] ||= with_template_cache(name) do - find_template(name, details, options) + if (options.keys & [:partial, :file, :template]).empty? + options[:prefix] ||= _prefix end - end - - def _normalize_details(options) - details = { :formats => formats } - details[:formats] = Array(options[:format]) if options[:format] - details[:locale] = Array(options[:locale]) if options[:locale] - details - end - def find_template(name, details, options) - view_paths.find(name, details, options[:_prefix], options[:_partial]) - end - - def template_exists?(name, details, options) - view_paths.exists?(name, details, options[:_prefix], options[:_partial]) - end - - def with_template_cache(name) - yield + options[:template] ||= (options[:action] || action_name).to_s + options end - def format_for_text - Mime[:text] + def _process_options(options) end - module ClassMethods - def clear_template_caches! - end - - # Append a path to the list of view paths for this controller. - # - # ==== Parameters - # path<String, ViewPath>:: If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::ViewPathSet for more information) - def append_view_path(path) - self.view_paths = view_paths.dup + Array.wrap(path) - end - - # Prepend a path to the list of view paths for this controller. - # - # ==== Parameters - # path<String, ViewPath>:: If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::ViewPathSet for more information) - def prepend_view_path(path) - clear_template_caches! - self.view_paths = Array.wrap(path) + view_paths.dup - end - - # A list of all of the default view paths for this controller. - def view_paths - _view_paths - end - - # Set the view paths. - # - # ==== Parameters - # paths<ViewPathSet, Object>:: If a ViewPathSet is provided, use that; - # otherwise, process the parameter into a ViewPathSet. - def view_paths=(paths) - clear_template_caches! - self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths) - _view_paths.freeze - end + def _with_template_hook(template) + self.formats = template.formats end end end diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb new file mode 100644 index 0000000000..c2a9f6336d --- /dev/null +++ b/actionpack/lib/abstract_controller/view_paths.rb @@ -0,0 +1,73 @@ +module AbstractController + module ViewPaths + extend ActiveSupport::Concern + + included do + class_attribute :_view_paths + self._view_paths = ActionView::PathSet.new + end + + delegate :template_exists?, :view_paths, :formats, :formats=, + :locale, :locale=, :to => :lookup_context + + # LookupContext is the object responsible to hold all information required to lookup + # templates, i.e. view paths and details. Check ActionView::LookupContext for more + # information. + def lookup_context + @lookup_context ||= ActionView::LookupContext.new(self.class._view_paths, details_for_lookup) + end + + def details_for_lookup + { } + end + + def append_view_path(path) + lookup_context.view_paths.push(*path) + end + + def prepend_view_path(path) + lookup_context.view_paths.unshift(*path) + end + + def template_exists?(*args) + lookup_context.exists?(*args) + end + + module ClassMethods + # Append a path to the list of view paths for this controller. + # + # ==== Parameters + # path<String, ViewPath>:: If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::ViewPathSet for more information) + def append_view_path(path) + self.view_paths = view_paths.dup + Array(path) + end + + # Prepend a path to the list of view paths for this controller. + # + # ==== Parameters + # path<String, ViewPath>:: If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::ViewPathSet for more information) + def prepend_view_path(path) + self.view_paths = Array(path) + view_paths.dup + end + + # A list of all of the default view paths for this controller. + def view_paths + _view_paths + end + + # Set the view paths. + # + # ==== Parameters + # paths<ViewPathSet, Object>:: If a ViewPathSet is provided, use that; + # otherwise, process the parameter into a ViewPathSet. + def view_paths=(paths) + self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths) + self._view_paths.freeze + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 759e52b135..536154fc6b 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -13,13 +13,13 @@ module ActionController autoload_under "metal" do autoload :Compatibility autoload :ConditionalGet - autoload :Configuration autoload :Cookies autoload :Flash autoload :Head autoload :Helpers autoload :HideActions autoload :HttpAuthentication + autoload :ImplicitRender autoload :Instrumentation autoload :MimeResponds autoload :RackDelegation @@ -46,7 +46,6 @@ module ActionController eager_autoload do autoload :RecordIdentifier - autoload :UrlRewriter # TODO: Don't autoload exceptions, setup explicit # requires for files that need them diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 10244f8216..fcd3cb9bd3 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -15,7 +15,6 @@ module ActionController include ActionController::Renderers::All include ActionController::ConditionalGet include ActionController::RackDelegation - include ActionController::Configuration # Legacy modules include SessionManagement @@ -30,35 +29,13 @@ module ActionController include ActionController::Verification include ActionController::RequestForgeryProtection include ActionController::Streaming + include ActionController::RecordIdentifier include ActionController::HttpAuthentication::Basic::ControllerMethods include ActionController::HttpAuthentication::Digest::ControllerMethods # Add instrumentations hooks at the bottom, to ensure they instrument # all the methods properly. include ActionController::Instrumentation - - # TODO: Extract into its own module - # This should be moved together with other normalizing behavior - module ImplicitRender - def send_action(*) - ret = super - default_render unless response_body - ret - end - - def default_render - render - end - - def method_for_action(action_name) - super || begin - if template_exists?(action_name.to_s, {:formats => formats}, :_prefix => controller_path) - "default_render" - end - end - end - end - include ImplicitRender include ActionController::Rescue @@ -82,12 +59,9 @@ module ActionController filter end - protected + ActionController.run_base_hooks(self) - # Overwrite url rewriter to use request. - def _url_rewriter - return ActionController::UrlRewriter unless request - @_url_rewriter ||= ActionController::UrlRewriter.new(request, params) - end end end + +require "action_controller/deprecated/base" diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index d784138ebe..b3fa129929 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -40,28 +40,34 @@ module ActionController #:nodoc: autoload :Sweeping, 'action_controller/caching/sweeping' end - included do - @@cache_store = nil - cattr_reader :cache_store - - # Defines the storage option for cached fragments - def self.cache_store=(store_option) - @@cache_store = ActiveSupport::Cache.lookup_store(store_option) + module ConfigMethods + def cache_store + config.cache_store end - include Pages, Actions, Fragments - include Sweeping if defined?(ActiveRecord) + def cache_store=(store) + config.cache_store = ActiveSupport::Cache.lookup_store(store) + end - @@perform_caching = true - cattr_accessor :perform_caching - end + private - module ClassMethods def cache_configured? perform_caching && cache_store end end + include ConfigMethods + include Pages, Actions, Fragments + include Sweeping if defined?(ActiveRecord) + + included do + extend ConfigMethods + + @@perform_caching = true + cattr_accessor :perform_caching + end + + def caching_allowed? request.get? && response.status == 200 end @@ -75,10 +81,5 @@ module ActionController #:nodoc: yield end end - - private - def cache_configured? - self.class.cache_configured? - end end end diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 00a7f034d3..bb5ff95a67 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -41,7 +41,9 @@ module ActionController #:nodoc: else pos = buffer.length block.call - write_fragment(name, buffer[pos..-1], options) + content = buffer[pos..-1] + content = content.as_str if content.respond_to?(:as_str) + write_fragment(name, content, options) end else block.call diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 5797eeebd6..fe95f0e0d7 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -1,5 +1,6 @@ require 'fileutils' require 'uri' +require 'active_support/core_ext/class/attribute_accessors' module ActionController #:nodoc: module Caching @@ -34,27 +35,26 @@ module ActionController #:nodoc: # Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be # expired. module Pages - def self.included(base) #:nodoc: - base.extend(ClassMethods) - base.class_eval do - @@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : "" - ## - # :singleton-method: - # The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>. - # For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing - # this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your - # web server to look in the new location for cached files. - cattr_accessor :page_cache_directory - - @@page_cache_extension = '.html' - ## - # :singleton-method: - # Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in - # order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>. - # If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an - # extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps. - cattr_accessor :page_cache_extension - end + extend ActiveSupport::Concern + + included do + @@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : "" + ## + # :singleton-method: + # The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>. + # For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing + # this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your + # web server to look in the new location for cached files. + cattr_accessor :page_cache_directory + + @@page_cache_extension = '.html' + ## + # :singleton-method: + # Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in + # order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>. + # If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an + # extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps. + cattr_accessor :page_cache_extension end module ClassMethods @@ -121,10 +121,10 @@ module ActionController #:nodoc: if options.is_a?(Hash) if options[:action].is_a?(Array) options[:action].dup.each do |action| - self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action))) + self.class.expire_page(url_for(options.merge(:only_path => true, :action => action))) end else - self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true))) + self.class.expire_page(url_for(options.merge(:only_path => true))) end else self.class.expire_page(options) @@ -139,7 +139,7 @@ module ActionController #:nodoc: path = case options when Hash - url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format])) + url_for(options.merge(:only_path => true, :format => params[:format])) when String options else diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index 871f41bfdd..cf16417e84 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -30,9 +30,7 @@ module ActionController #:nodoc: # cache_sweeper OpenBar::Sweeper, :only => [ :edit, :destroy, :share ] # end module Sweeping - def self.included(base) #:nodoc: - base.extend(ClassMethods) - end + extend ActiveSupport::Concern module ClassMethods #:nodoc: def cache_sweeper(*sweepers) diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb index a4eef07841..9f2de57033 100644 --- a/actionpack/lib/action_controller/deprecated.rb +++ b/actionpack/lib/action_controller/deprecated.rb @@ -1,5 +1,3 @@ ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response ActionController::Routing = ActionDispatch::Routing -ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new -ActionController::UrlWriter = ActionController::UrlFor diff --git a/actionpack/lib/action_controller/deprecated/base.rb b/actionpack/lib/action_controller/deprecated/base.rb new file mode 100644 index 0000000000..1d05b3fbd6 --- /dev/null +++ b/actionpack/lib/action_controller/deprecated/base.rb @@ -0,0 +1,131 @@ +module ActionController + class Base + class << self + def deprecated_config_accessor(option, message = nil) + deprecated_config_reader(option, message) + deprecated_config_writer(option, message) + end + + def deprecated_config_reader(option, message = nil) + message ||= "Reading #{option} directly from ActionController::Base is deprecated. " \ + "Please read it from config.#{option}" + + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{option} + ActiveSupport::Deprecation.warn #{message.inspect}, caller + config.#{option} + end + RUBY + end + + def deprecated_config_writer(option, message = nil) + message ||= "Setting #{option} directly on ActionController::Base is deprecated. " \ + "Please set it on config.action_controller.#{option}" + + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{option}=(val) + ActiveSupport::Deprecation.warn #{message.inspect}, caller + config.#{option} = val + end + RUBY + end + + def consider_all_requests_local + ActiveSupport::Deprecation.warn "ActionController::Base.consider_all_requests_local is deprecated, " << + "use Rails.application.config.consider_all_requests_local instead", caller + Rails.application.config.consider_all_requests_local + end + + def consider_all_requests_local=(value) + ActiveSupport::Deprecation.warn "ActionController::Base.consider_all_requests_local= is deprecated. " << + "Please configure it on your application with config.consider_all_requests_local=", caller + Rails.application.config.consider_all_requests_local = value + end + + def allow_concurrency + ActiveSupport::Deprecation.warn "ActionController::Base.allow_concurrency is deprecated, " << + "use Rails.application.config.allow_concurrency instead", caller + Rails.application.config.allow_concurrency + end + + def allow_concurrency=(value) + ActiveSupport::Deprecation.warn "ActionController::Base.allow_concurrency= is deprecated. " << + "Please configure it on your application with config.allow_concurrency=", caller + Rails.application.config.allow_concurrency = value + end + + def ip_spoofing_check=(value) + ActiveSupport::Deprecation.warn "ActionController::Base.ip_spoofing_check= is deprecated. " << + "Please configure it on your application with config.action_dispatch.ip_spoofing_check=", caller + Rails.application.config.action_dispatch.ip_spoofing_check = value + end + + def ip_spoofing_check + ActiveSupport::Deprecation.warn "ActionController::Base.ip_spoofing_check is deprecated. " << + "Configuring ip_spoofing_check on the application configures a middleware.", caller + Rails.application.config.action_disaptch.ip_spoofing_check + end + + def trusted_proxies=(value) + ActiveSupport::Deprecation.warn "ActionController::Base.trusted_proxies= is deprecated. " << + "Please configure it on your application with config.action_dispatch.trusted_proxies=", caller + Rails.application.config.action_dispatch.ip_spoofing_check = value + end + + def trusted_proxies + ActiveSupport::Deprecation.warn "ActionController::Base.trusted_proxies is deprecated. " << + "Configuring trusted_proxies on the application configures a middleware.", caller + Rails.application.config.action_dispatch.ip_spoofing_check = value + end + + def session(*args) + ActiveSupport::Deprecation.warn( + "Disabling sessions for a single controller has been deprecated. " + + "Sessions are now lazy loaded. So if you don't access them, " + + "consider them off. You can still modify the session cookie " + + "options with request.session_options.", caller) + end + + def session=(value) + ActiveSupport::Deprecation.warn "ActionController::Base.session= is deprecated. " << + "Please configure it on your application with config.session_store :cookie_store, :key => '....'", caller + if value.delete(:disabled) + Rails.application.config.session_store :disabled + else + store = Rails.application.config.session_store + Rails.application.config.session_store store, value + end + end + + # Controls the resource action separator + def resource_action_separator + @resource_action_separator ||= "/" + end + + def resource_action_separator=(val) + ActiveSupport::Deprecation.warn "ActionController::Base.resource_action_separator is deprecated and only " \ + "works with the deprecated router DSL." + @resource_action_separator = val + end + + def use_accept_header + ActiveSupport::Deprecation.warn "ActionController::Base.use_accept_header doesn't do anything anymore. " \ + "The accept header is always taken into account." + end + + def use_accept_header=(val) + use_accept_header + end + end + + deprecated_config_writer :session_store + deprecated_config_writer :session_options + deprecated_config_accessor :relative_url_root, "relative_url_root is ineffective. Please stop using it" + deprecated_config_accessor :assets_dir + deprecated_config_accessor :javascripts_dir + deprecated_config_accessor :stylesheets_dir + + delegate :consider_all_requests_local, :consider_all_requests_local=, + :allow_concurrency, :allow_concurrency=, :to => :"self.class" + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 2b35e111ec..eebd2c943a 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -34,7 +34,8 @@ module ActionController # and response object available. You might wish to control the # environment and response manually for performance reasons. - attr_internal :status, :headers, :content_type, :response + attr_internal :status, :headers, :content_type, :response, :request + delegate :session, :to => "@_request" def initialize(*) @_headers = {} @@ -49,6 +50,14 @@ module ActionController headers["Content-Type"] = type.to_s end + def content_type + headers["Content-Type"] + end + + def location + headers["Location"] + end + def location=(url) headers["Location"] = url end @@ -58,8 +67,9 @@ module ActionController end # :api: private - def dispatch(name, env) - @_env = env + def dispatch(name, request) + @_request = request + @_env = request.env @_env['action_controller.instance'] = self process(name) to_a @@ -70,31 +80,12 @@ module ActionController response ? response.to_a : [status, headers, response_body] end - class ActionEndpoint - @@endpoints = Hash.new {|h,k| h[k] = Hash.new {|sh,sk| sh[sk] = {} } } - - def self.for(controller, action, stack) - @@endpoints[controller][action][stack] ||= begin - endpoint = new(controller, action) - stack.build(endpoint) - end - end - - def initialize(controller, action) - @controller, @action = controller, action - @_formats = [Mime::HTML] - end - - def call(env) - @controller.new.dispatch(@action, env) - end - end - class_attribute :middleware_stack self.middleware_stack = ActionDispatch::MiddlewareStack.new def self.inherited(base) self.middleware_stack = base.middleware_stack.dup + super end def self.use(*args) @@ -118,8 +109,10 @@ module ActionController # # ==== Returns # Proc:: A rack application - def self.action(name) - ActionEndpoint.for(self, name, middleware_stack) + def self.action(name, klass = ActionDispatch::Request) + middleware_stack.build do |env| + new.dispatch(name, klass.new(env)) + end end end end diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index a1cfa32d4d..ab8d87b2c4 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -2,26 +2,23 @@ module ActionController module Compatibility extend ActiveSupport::Concern - include AbstractController::Compatibility - class ::ActionController::ActionControllerError < StandardError #:nodoc: end + module ClassMethods + end + # Temporary hax included do ::ActionController::UnknownAction = ::AbstractController::ActionNotFound ::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError - cattr_accessor :session_options - self.session_options = {} - - cattr_accessor :relative_url_root - self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT'] + # ROUTES TODO: This should be handled by a middleware and route generation + # should be able to handle SCRIPT_NAME + self.config.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT'] class << self delegate :default_charset=, :to => "ActionDispatch::Response" - delegate :resources_path_names, :to => "ActionController::Routing::Routes" - delegate :resources_path_names=, :to => "ActionController::Routing::Routes" end # cattr_reader :protected_instance_variables @@ -32,31 +29,17 @@ module ActionController @before_filter_chain_aborted @_headers @_params @_response) - # Controls the resource action separator - cattr_accessor :resource_action_separator - self.resource_action_separator = "/" - - cattr_accessor :use_accept_header - self.use_accept_header = true + def rescue_action(env) + raise env["action_dispatch.rescue.exception"] + end self.page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : "" - - # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, - # and images to a dedicated asset server away from the main web server. Example: - # ActionController::Base.asset_host = "http://assets.example.com" - cattr_accessor :asset_host - - cattr_accessor :ip_spoofing_check - self.ip_spoofing_check = true - - cattr_accessor :trusted_proxies end # For old tests def initialize_template_class(*) end def assign_shortcuts(*) end - # TODO: Remove this after we flip def template @template ||= view_context end @@ -66,52 +49,20 @@ module ActionController super end - module ClassMethods - def consider_all_requests_local - ActiveSupport::Deprecation.warn "ActionController::Base.consider_all_requests_local is deprecated, " << - "use Rails.application.config.consider_all_requests_local instead" - Rails.application.config.consider_all_requests_local - end - - def consider_all_requests_local=(value) - ActiveSupport::Deprecation.warn "ActionController::Base.consider_all_requests_local= is no longer effective. " << - "Please configure it on your application with config.consider_all_requests_local=" - Rails.application.config.consider_all_requests_local = value - end - - def allow_concurrency - ActiveSupport::Deprecation.warn "ActionController::Base.allow_concurrency is deprecated, " << - "use Rails.application.config.allow_concurrency instead" - Rails.application.config.allow_concurrency - end - - def allow_concurrency=(value) - ActiveSupport::Deprecation.warn "ActionController::Base.allow_concurrency= is no longer effective. " << - "Please configure it on your application with config.allow_concurrency=" - Rails.application.config.allow_concurrency = value - end - - def rescue_action(env) - raise env["action_dispatch.rescue.exception"] - end - - # Defines the storage option for cached fragments - def cache_store=(store_option) - @@cache_store = ActiveSupport::Cache.lookup_store(store_option) - end - end - - delegate :consider_all_requests_local, :consider_all_requests_local=, - :allow_concurrency, :allow_concurrency=, :to => :"self.class" - - def render_to_body(options) - if options.is_a?(Hash) && options.key?(:template) - options[:template].sub!(/^\//, '') + def _normalize_options(options) + if options[:action] && options[:action].to_s.include?(?/) + ActiveSupport::Deprecation.warn "Giving a path to render :action is deprecated. " << + "Please use render :template instead", caller + options[:template] = options.delete(:action) end options[:text] = nil if options.delete(:nothing) == true options[:text] = " " if options.key?(:text) && options[:text].nil? + super + end + def render_to_body(options) + options[:template].sub!(/^\//, '') if options.key?(:template) super || " " end @@ -126,18 +77,5 @@ module ActionController def performed? response_body end - - # ==== Request only view path switching ==== - def append_view_path(path) - view_paths.push(*path) - end - - def prepend_view_path(path) - view_paths.unshift(*path) - end - - def view_paths - view_context.view_paths - end end end diff --git a/actionpack/lib/action_controller/metal/configuration.rb b/actionpack/lib/action_controller/metal/configuration.rb deleted file mode 100644 index 5c829853b7..0000000000 --- a/actionpack/lib/action_controller/metal/configuration.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ActionController - module Configuration - extend ActiveSupport::Concern - - def config - @config ||= self.class.config - end - - def config=(config) - @config = config - end - - module ClassMethods - def default_config - @default_config ||= {} - end - - def config - self.config ||= default_config - end - - def config=(config) - @config = ActiveSupport::OrderedHash.new - @config.merge!(config) - end - end - end -end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 37be8b3999..a5c9910d68 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -1,6 +1,7 @@ module ActionController module Head extend ActiveSupport::Concern + include ActionController::UrlFor # Return a response that has no content (merely headers). The options diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 1b5a4c3080..8efe01e37b 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -86,7 +86,7 @@ module ActionController end private - # Overwrite _modules_for_helpers to accept :all as argument, which loads + # Overwrite modules_for_helpers to accept :all as argument, which loads # all helpers in helpers_dir. # # ==== Parameters @@ -95,7 +95,7 @@ module ActionController # ==== Returns # Array[Module]:: A normalized list of modules for the list of # helpers provided. - def _modules_for_helpers(args) + def modules_for_helpers(args) args += all_application_helpers if args.delete(:all) super(args) end diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb index e893acffdf..3358d80c35 100644 --- a/actionpack/lib/action_controller/metal/hide_actions.rb +++ b/actionpack/lib/action_controller/metal/hide_actions.rb @@ -15,10 +15,8 @@ module ActionController # Overrides AbstractController::Base#action_method? to return false if the # action name is in the list of hidden actions. - def action_method?(action_name) - self.class.visible_action?(action_name) do - !self.class.hidden_actions.include?(action_name) && super - end + def method_for_action(action_name) + self.class.visible_action?(action_name) && super end module ClassMethods @@ -31,13 +29,13 @@ module ActionController end def inherited(klass) - klass.instance_variable_set("@visible_actions", {}) + klass.class_eval { @visible_actions = {} } super end def visible_action?(action_name) return @visible_actions[action_name] if @visible_actions.key?(action_name) - @visible_actions[action_name] = yield + @visible_actions[action_name] = !hidden_actions.include?(action_name) end # Overrides AbstractController::Base#action_methods to remove any methods diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 0f35a7c040..6ec788f302 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -124,7 +124,7 @@ module ActionController end def authenticate(request, &login_procedure) - unless authorization(request).blank? + unless request.authorization.blank? login_procedure.call(*user_name_and_password(request)) end end @@ -133,15 +133,8 @@ module ActionController decode_credentials(request).split(/:/, 2) end - def authorization(request) - request.env['HTTP_AUTHORIZATION'] || - request.env['X-HTTP_AUTHORIZATION'] || - request.env['X_HTTP_AUTHORIZATION'] || - request.env['REDIRECT_X_HTTP_AUTHORIZATION'] - end - def decode_credentials(request) - ActiveSupport::Base64.decode64(authorization(request).split(' ', 2).last || '') + ActiveSupport::Base64.decode64(request.authorization.split(' ', 2).last || '') end def encode_credentials(user_name, password) @@ -165,7 +158,7 @@ module ActionController # Authenticate with HTTP Digest, returns true or false def authenticate_with_http_digest(realm = "Application", &password_procedure) - HttpAuthentication::Digest.authenticate(request, realm, &password_procedure) + HttpAuthentication::Digest.authenticate(config.secret, request, realm, &password_procedure) end # Render output including the HTTP Digest authentication header @@ -175,30 +168,23 @@ module ActionController end # Returns false on a valid response, true otherwise - def authenticate(request, realm, &password_procedure) - authorization(request) && validate_digest_response(request, realm, &password_procedure) - end - - def authorization(request) - request.env['HTTP_AUTHORIZATION'] || - request.env['X-HTTP_AUTHORIZATION'] || - request.env['X_HTTP_AUTHORIZATION'] || - request.env['REDIRECT_X_HTTP_AUTHORIZATION'] + def authenticate(secret_key, request, realm, &password_procedure) + request.authorization && validate_digest_response(secret_key, request, realm, &password_procedure) end # Returns false unless the request credentials response value matches the expected value. # First try the password as a ha1 digest password. If this fails, then try it as a plain # text password. - def validate_digest_response(request, realm, &password_procedure) + def validate_digest_response(secret_key, request, realm, &password_procedure) credentials = decode_credentials_header(request) - valid_nonce = validate_nonce(request, credentials[:nonce]) + valid_nonce = validate_nonce(secret_key, request, credentials[:nonce]) - if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque] + if valid_nonce && realm == credentials[:realm] && opaque(secret_key) == credentials[:opaque] password = password_procedure.call(credentials[:username]) return false unless password method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] - uri = credentials[:uri][0,1] == '/' ? request.request_uri : request.url + uri = credentials[:uri][0,1] == '/' ? request.fullpath : request.url [true, false].any? do |password_is_ha1| expected = expected_response(method, uri, credentials, password, password_is_ha1) @@ -226,7 +212,7 @@ module ActionController end def decode_credentials_header(request) - decode_credentials(authorization(request)) + decode_credentials(request.authorization) end def decode_credentials(header) @@ -238,6 +224,9 @@ module ActionController end def authentication_header(controller, realm) + secret_key = controller.config.secret + nonce = self.nonce(secret_key) + opaque = opaque(secret_key) controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}") end @@ -280,7 +269,7 @@ module ActionController # The nonce is opaque to the client. Composed of Time, and hash of Time with secret # key from the Rails session secret generated upon creation of project. Ensures # the time cannot be modified by client. - def nonce(time = Time.now) + def nonce(secret_key, time = Time.now) t = time.to_i hashed = [t, secret_key] digest = ::Digest::MD5.hexdigest(hashed.join(":")) @@ -292,21 +281,16 @@ module ActionController # Can be much shorter if the Stale directive is implemented. This would # allow a user to use new nonce without prompting user again for their # username and password. - def validate_nonce(request, value, seconds_to_timeout=5*60) + def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60) t = ActiveSupport::Base64.decode64(value).split(":").first.to_i - nonce(t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout + nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout end # Opaque based on random generation - but changing each request? - def opaque() + def opaque(secret_key) ::Digest::MD5.hexdigest(secret_key) end - # Set in /initializers/session_store.rb, and loaded even if sessions are not in use. - def secret_key - ActionController::Base.session_options[:secret] - end - end end end diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb new file mode 100644 index 0000000000..282dcf66b3 --- /dev/null +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -0,0 +1,21 @@ +module ActionController + module ImplicitRender + def send_action(*) + ret = super + default_render unless response_body + ret + end + + def default_render + render + end + + def method_for_action(action_name) + super || begin + if template_exists?(action_name.to_s, _prefix) + "default_render" + end + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 85035dc09c..d69de65f28 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -20,7 +20,7 @@ module ActionController :params => request.filtered_parameters, :formats => request.formats.map(&:to_sym), :method => request.method, - :path => (request.request_uri rescue "unknown") + :path => (request.fullpath rescue "unknown") } ActiveSupport::Notifications.instrument("action_controller.start_processing", raw_payload.dup) diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index bb55383631..37106733cb 100644 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -6,14 +6,11 @@ module ActionController extend ActiveSupport::Concern included do - delegate :session, :to => "@_request" delegate :headers, :status=, :location=, :content_type=, :status, :location, :content_type, :to => "@_response" - attr_internal :request end - def dispatch(action, env) - @_request = ActionDispatch::Request.new(env) + def dispatch(action, request) @_response = ActionDispatch::Response.new @_response.request = request super diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index faf0589fd2..25e4e18493 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -11,6 +11,7 @@ module ActionController extend ActiveSupport::Concern include AbstractController::Logger + include ActionController::RackDelegation include ActionController::UrlFor # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 639b508746..49d3d6b466 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -19,7 +19,7 @@ module ActionController <<-RUBY_EVAL if options.key?(:#{name}) _process_options(options) - return _render_option_#{name}(options[:#{name}], options) + return _render_option_#{name}(options.delete(:#{name}), options) end RUBY_EVAL end diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 0aae9f8579..f892bd9b91 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -2,68 +2,54 @@ module ActionController module Rendering extend ActiveSupport::Concern - included do - include AbstractController::Rendering - include AbstractController::LocalizedCache - end + include ActionController::RackDelegation + include AbstractController::Rendering - def process_action(*) - self.formats = request.formats.map {|x| x.to_sym} + def process(*) + self.formats = request.formats.map { |x| x.to_sym } super end def render(*args) - if response_body - raise ::AbstractController::DoubleRenderError - end - - args << {} unless args.last.is_a?(Hash) - super(*args) - self.content_type ||= args.last[:_template].mime_type.to_s - response_body - end - - def render_to_body(options) - _process_options(options) + raise ::AbstractController::DoubleRenderError if response_body super + response_body end private - def _render_partial(options) - options[:partial] = action_name if options[:partial] == true - options[:_details] = {:formats => formats} - super + def _normalize_args(action=nil, options={}, &blk) + options = super + options[:update] = blk if block_given? + options end - def format_for_text - formats.first + def _normalize_options(options) + if options.key?(:text) && options[:text].respond_to?(:to_text) + options[:text] = options[:text].to_text + end + + if options[:status] + options[:status] = Rack::Utils.status_code(options[:status]) + end + + super end def _process_options(options) status, content_type, location = options.values_at(:status, :content_type, :location) + self.status = status if status self.content_type = content_type if content_type self.headers["Location"] = url_for(location) if location - end - def _normalize_options(action=nil, options={}, &blk) - case action - when NilClass - when Hash - options = super(action.delete(:action), action) - when String, Symbol - options = super - else - options.merge! :partial => action - end - - if options[:status] - options[:status] = Rack::Utils.status_code(options[:status]) - end + super + end - options[:update] = blk if block_given? - options + def _with_template_hook(template) + super + self.content_type ||= template.mime_type.to_s end + end end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 276c703307..39a809657b 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -12,11 +12,10 @@ module ActionController #:nodoc: included do # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ # sets it to <tt>:authenticity_token</tt> by default. - cattr_accessor :request_forgery_protection_token + config.request_forgery_protection_token ||= :authenticity_token # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode. - class_attribute :allow_forgery_protection - self.allow_forgery_protection = true + config.allow_forgery_protection ||= true helper_method :form_authenticity_token helper_method :protect_against_forgery? @@ -80,9 +79,47 @@ module ActionController #:nodoc: self.request_forgery_protection_token ||= :authenticity_token before_filter :verify_authenticity_token, options end + + def request_forgery_protection_token + config.request_forgery_protection_token + end + + def request_forgery_protection_token=(val) + config.request_forgery_protection_token = val + end + + def allow_forgery_protection + config.allow_forgery_protection + end + + def allow_forgery_protection=(val) + config.allow_forgery_protection = val + end end protected + + def protect_from_forgery(options = {}) + self.request_forgery_protection_token ||= :authenticity_token + before_filter :verify_authenticity_token, options + end + + def request_forgery_protection_token + config.request_forgery_protection_token + end + + def request_forgery_protection_token=(val) + config.request_forgery_protection_token = val + end + + def allow_forgery_protection + config.allow_forgery_protection + end + + def allow_forgery_protection=(val) + config.allow_forgery_protection = val + end + # The actual before_filter that is used. Modify this to change how you handle unverified requests. def verify_authenticity_token verified_request? || raise(ActionController::InvalidAuthenticityToken) @@ -109,7 +146,7 @@ module ActionController #:nodoc: end def protect_against_forgery? - self.class.allow_forgery_protection + config.allow_forgery_protection end end end diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb index d70f40ce7a..91d89ff9a4 100644 --- a/actionpack/lib/action_controller/metal/session_management.rb +++ b/actionpack/lib/action_controller/metal/session_management.rb @@ -2,44 +2,8 @@ module ActionController #:nodoc: module SessionManagement #:nodoc: extend ActiveSupport::Concern - include ActionController::Configuration - module ClassMethods - # Set the session store to be used for keeping the session data between requests. - # By default, sessions are stored in browser cookies (<tt>:cookie_store</tt>), - # but you can also specify one of the other included stores (<tt>:active_record_store</tt>, - # <tt>:mem_cache_store</tt>, or your own custom class. - def session_store=(store) - if store == :active_record_store - self.session_store = ActiveRecord::SessionStore - else - @@session_store = store.is_a?(Symbol) ? - ActionDispatch::Session.const_get(store.to_s.camelize) : - store - end - end - - # Returns the session store class currently used. - def session_store - if defined? @@session_store - @@session_store - else - ActionDispatch::Session::CookieStore - end - end - - def session=(options = {}) - self.session_store = nil if options.delete(:disabled) - session_options.merge!(options) - end - def session(*args) - ActiveSupport::Deprecation.warn( - "Disabling sessions for a single controller has been deprecated. " + - "Sessions are now lazy loaded. So if you don't access them, " + - "consider them off. You can still modify the session cookie " + - "options with request.session_options.", caller) - end end end end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 8f03b8bb17..753af3dc58 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -9,18 +9,13 @@ module ActionController #:nodoc: DEFAULT_SEND_FILE_OPTIONS = { :type => 'application/octet-stream'.freeze, :disposition => 'attachment'.freeze, - :stream => true, - :buffer_size => 4096, - :x_sendfile => false }.freeze - X_SENDFILE_HEADER = 'X-Sendfile'.freeze - protected - # Sends the file, by default streaming it 4096 bytes at a time. This way the - # whole file doesn't need to be read into memory at once. This makes it - # feasible to send even large files. You can optionally turn off streaming - # and send the whole file at once. + # Sends the file. This uses a server-appropriate method (such as X-Sendfile) + # via the Rack::Sendfile middleware. The header to use is set via + # config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile". + # Your server can also configure this for you by setting the X-Sendfile-Type header. # # Be careful to sanitize the path parameter if it is coming from a web # page. <tt>send_file(params[:path])</tt> allows a malicious user to @@ -31,24 +26,12 @@ module ActionController #:nodoc: # Defaults to <tt>File.basename(path)</tt>. # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json - # * <tt>:length</tt> - used to manually override the length (in bytes) of the content that - # is going to be sent to the client. Defaults to <tt>File.size(path)</tt>. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). - # * <tt>:stream</tt> - whether to send the file to the user agent as it is read (+true+) - # or to read the entire file before sending (+false+). Defaults to +true+. - # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file. - # Defaults to 4096. # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'. # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from # the URL, which is necessary for i18n filenames on certain browsers # (setting <tt>:filename</tt> overrides this option). - # * <tt>:x_sendfile</tt> - uses X-Sendfile to send the file when set to +true+. This is currently - # only available with Lighttpd/Apache2 and specific modules installed and activated. Since this - # uses the web server to send the file, this may lower memory consumption on your server and - # it will not block your application for further requests. - # See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and - # http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+. # # The default Content-Type and Content-Disposition headers are # set to download arbitrary binary files in as many browsers as @@ -81,29 +64,16 @@ module ActionController #:nodoc: def send_file(path, options = {}) #:doc: raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path) - options[:length] ||= File.size(path) options[:filename] ||= File.basename(path) unless options[:url_based_filename] send_file_headers! options - @performed_render = false - if options[:x_sendfile] - head options[:status], X_SENDFILE_HEADER => path - else - if options[:stream] - # TODO : Make render :text => proc {} work with the new base - render :status => options[:status], :text => Proc.new { |response, output| - len = options[:buffer_size] || 4096 - File.open(path, 'rb') do |file| - while buf = file.read(len) - output.write(buf) - end - end - } - else - File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read } - end + ActiveSupport::Deprecation.warn(":x_sendfile is no longer needed in send_file", caller) end + + self.status = options[:status] || 200 + self.content_type = options[:content_type] if options.key?(:content_type) + self.response_body = File.open(path, "rb") end # Sends the given binary data to the browser. This method is similar to @@ -138,32 +108,35 @@ module ActionController #:nodoc: # data to the browser, then use <tt>render :text => proc { ... }</tt> # instead. See ActionController::Base#render for more information. def send_data(data, options = {}) #:doc: - send_file_headers! options.merge(:length => data.bytesize) - render :status => options[:status], :text => data + send_file_headers! options.dup + render options.slice(:status, :content_type).merge(:text => data) end private def send_file_headers!(options) options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) - [:length, :type, :disposition].each do |arg| + [:type, :disposition].each do |arg| raise ArgumentError, ":#{arg} option required" if options[arg].nil? end - disposition = options[:disposition].dup || 'attachment' + if options.key?(:length) + ActiveSupport::Deprecation.warn("You do not need to provide the file's length", caller) + end - disposition <<= %(; filename="#{options[:filename]}") if options[:filename] + disposition = options[:disposition] + disposition += %(; filename="#{options[:filename]}") if options[:filename] content_type = options[:type] if content_type.is_a?(Symbol) - raise ArgumentError, "Unknown MIME type #{options[:type]}" unless Mime::EXTENSION_LOOKUP.key?(content_type.to_s) - self.content_type = Mime::Type.lookup_by_extension(content_type.to_s) + extension = Mime[content_type] + raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension + self.content_type = extension else self.content_type = content_type end headers.merge!( - 'Content-Length' => options[:length].to_s, 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary' ) diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index 707ad968f4..4b8c452d50 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -13,7 +13,6 @@ module ActionController if cookies = @_request.env['action_dispatch.cookies'] cookies.write(@_response) end - @_response.body ||= self.response_body @_response.prepare! set_test_assigns ret diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 4f3ad07be5..10c7ca9021 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -1,165 +1,20 @@ -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/module/attribute_accessors' - module ActionController - # In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse - # is also possible: an URL can be generated from one of your routing definitions. - # URL generation functionality is centralized in this module. - # - # See ActionController::Routing and ActionController::Resources for general - # information about routing and routes.rb. - # - # <b>Tip:</b> If you need to generate URLs from your models or some other place, - # then ActionController::UrlFor is what you're looking for. Read on for - # an introduction. - # - # == URL generation from parameters - # - # As you may know, some functions - such as ActionController::Base#url_for - # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set - # of parameters. For example, you've probably had the chance to write code - # like this in one of your views: - # - # <%= link_to('Click here', :controller => 'users', - # :action => 'new', :message => 'Welcome!') %> - # - # #=> Generates a link to: /users/new?message=Welcome%21 - # - # link_to, and all other functions that require URL generation functionality, - # actually use ActionController::UrlFor under the hood. And in particular, - # they use the ActionController::UrlFor#url_for method. One can generate - # the same path as the above example by using the following code: - # - # include UrlFor - # url_for(:controller => 'users', - # :action => 'new', - # :message => 'Welcome!', - # :only_path => true) - # # => "/users/new?message=Welcome%21" - # - # Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no - # information about the website hostname that your Rails app is serving. So if you - # want to include the hostname as well, then you must also pass the <tt>:host</tt> - # argument: - # - # include UrlFor - # url_for(:controller => 'users', - # :action => 'new', - # :message => 'Welcome!', - # :host => 'www.example.com') # Changed this. - # # => "http://www.example.com/users/new?message=Welcome%21" - # - # By default, all controllers and views have access to a special version of url_for, - # that already knows what the current hostname is. So if you use url_for in your - # controllers or your views, then you don't need to explicitly pass the <tt>:host</tt> - # argument. - # - # For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for. - # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for' - # in full. However, mailers don't have hostname information, and what's why you'll still - # have to specify the <tt>:host</tt> argument when generating URLs in mailers. - # - # - # == URL generation for named routes - # - # UrlFor also allows one to access methods that have been auto-generated from - # named routes. For example, suppose that you have a 'users' resource in your - # <b>routes.rb</b>: - # - # map.resources :users - # - # This generates, among other things, the method <tt>users_path</tt>. By default, - # this method is accessible from your controllers, views and mailers. If you need - # to access this auto-generated method from other places (such as a model), then - # you can do that by including ActionController::UrlFor in your class: - # - # class User < ActiveRecord::Base - # include ActionController::UrlFor - # - # def base_uri - # user_path(self) - # end - # end - # - # User.find(1).base_uri # => "/users/1" - # module UrlFor extend ActiveSupport::Concern - included do - ActionController::Routing::Routes.install_helpers(self) - - # Including in a class uses an inheritable hash. Modules get a plain hash. - if respond_to?(:class_attribute) - class_attribute :default_url_options - else - mattr_accessor :default_url_options - end + include ActionDispatch::Routing::UrlFor - self.default_url_options = {} + def url_options + super.reverse_merge( + :host => request.host_with_port, + :protocol => request.protocol, + :_path_segments => request.symbolized_path_parameters + ).merge(:script_name => request.script_name) end - # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in - # the form of a hash, just like the one you would use for url_for directly. Example: - # - # def default_url_options(options) - # { :project => @project.active? ? @project.url_name : "unknown" } - # end - # - # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the - # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set - # by this method. - def default_url_options(options = nil) - self.class.default_url_options - end - - def rewrite_options(options) #:nodoc: - if options.delete(:use_defaults) != false && (defaults = default_url_options(options)) - defaults.merge(options) - else - options - end - end - - # Generate a url based on the options provided, default_url_options and the - # routes defined in routes.rb. The following options are supported: - # - # * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+. - # * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'. - # * <tt>:host</tt> - Specifies the host the link should be targeted at. - # If <tt>:only_path</tt> is false, this option must be - # provided either explicitly, or via +default_url_options+. - # * <tt>:port</tt> - Optionally specify the port to connect to. - # * <tt>:anchor</tt> - An anchor name to be appended to the path. - # * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the - # +relative_url_root+ set in ActionController::Base.relative_url_root. - # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/" - # - # Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to - # +url_for+ is forwarded to the Routes module. - # - # Examples: - # - # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing' - # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok' - # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/' - # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33' - def url_for(options = {}) - options ||= {} - case options - when String - options - when Hash - _url_rewriter.rewrite(rewrite_options(options)) - else - polymorphic_url(options) - end - end - - protected - - def _url_rewriter - ActionController::UrlRewriter + def _router + raise "In order to use #url_for, you must include the helpers of a particular " \ + "router. For instance, `include Rails.application.routes.url_helpers" end end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/middleware.rb b/actionpack/lib/action_controller/middleware.rb index 17275793b7..2115b07b3e 100644 --- a/actionpack/lib/action_controller/middleware.rb +++ b/actionpack/lib/action_controller/middleware.rb @@ -6,7 +6,8 @@ module ActionController end def call(env) - @controller.build(@app).dispatch(:index, env) + request = ActionDispatch::Request.new(env) + @controller.build(@app).dispatch(:index, request) end end diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index eaed00cfb7..ae363e300c 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -92,8 +92,7 @@ module ActionController inflection = if options[:action].to_s == "new" args.pop :singular - elsif (record.respond_to?(:new_record?) && record.new_record?) || - (record.respond_to?(:destroyed?) && record.destroyed?) + elsif (record.respond_to?(:persisted?) && !record.persisted?) args.pop :plural elsif record.is_a?(Class) diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 55a5c22ac0..6a3afbb157 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -1,30 +1,80 @@ -require "action_controller" require "rails" +require "action_controller" require "action_view/railtie" +require "active_support/core_ext/class/subclasses" +require "active_support/deprecation/proxy_wrappers" +require "active_support/deprecation" module ActionController class Railtie < Rails::Railtie railtie_name :action_controller - require "action_controller/railties/subscriber" - subscriber ActionController::Railties::Subscriber.new + require "action_controller/railties/log_subscriber" + require "action_controller/railties/url_helpers" + + ad = config.action_dispatch + config.action_controller.singleton_class.send(:define_method, :session) do + ActiveSupport::Deprecation.warn "config.action_controller.session has been " \ + "renamed to config.action_dispatch.session.", caller + ad.session + end + + config.action_controller.singleton_class.send(:define_method, :session=) do |val| + ActiveSupport::Deprecation.warn "config.action_controller.session has been " \ + "renamed to config.action_dispatch.session.", caller + ad.session = val + end + + config.action_controller.singleton_class.send(:define_method, :session_store) do + ActiveSupport::Deprecation.warn "config.action_controller.session_store has been " \ + "renamed to config.action_dispatch.session_store.", caller + ad.session_store + end + + config.action_controller.singleton_class.send(:define_method, :session_store=) do |val| + ActiveSupport::Deprecation.warn "config.action_controller.session_store has been " \ + "renamed to config.action_dispatch.session_store.", caller + ad.session_store = val + end + + log_subscriber ActionController::Railties::LogSubscriber.new initializer "action_controller.logger" do - ActionController::Base.logger ||= Rails.logger + ActionController.base_hook { self.logger ||= Rails.logger } end initializer "action_controller.set_configs" do |app| - app.config.action_controller.each do |k,v| - ActionController::Base.send "#{k}=", v - end + paths = app.config.paths + ac = app.config.action_controller + + ac.assets_dir = paths.public.to_a.first + ac.javascripts_dir = paths.public.javascripts.to_a.first + ac.stylesheets_dir = paths.public.stylesheets.to_a.first + ac.secret = app.config.cookie_secret + + ActionController.base_hook { self.config.replace(ac) } end initializer "action_controller.initialize_framework_caches" do - ActionController::Base.cache_store ||= RAILS_CACHE + ActionController.base_hook { self.cache_store ||= RAILS_CACHE } end initializer "action_controller.set_helpers_path" do |app| - ActionController::Base.helpers_path = app.config.paths.app.helpers.to_a + ActionController.base_hook do + self.helpers_path = app.config.paths.app.helpers.to_a + end + end + + initializer "action_controller.url_helpers" do |app| + ActionController.base_hook do + extend ::ActionController::Railtie::UrlHelpers.with(app.routes) + end + + message = "ActionController::Routing::Routes is deprecated. " \ + "Instead, use Rails.application.routes" + + proxy = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(app.routes, message) + ActionController::Routing::Routes = proxy end end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/railties/subscriber.rb b/actionpack/lib/action_controller/railties/log_subscriber.rb index 4499e82292..c2299d0b05 100644 --- a/actionpack/lib/action_controller/railties/subscriber.rb +++ b/actionpack/lib/action_controller/railties/log_subscriber.rb @@ -1,6 +1,6 @@ module ActionController module Railties - class Subscriber < Rails::Subscriber + class LogSubscriber < Rails::LogSubscriber INTERNAL_PARAMS = %w(controller action format _method only_path) def start_processing(event) @@ -22,15 +22,7 @@ module ActionController end def send_file(event) - message = if event.payload[:x_sendfile] - header = ActionController::Streaming::X_SENDFILE_HEADER - "Sent #{header} header %s" - elsif event.payload[:stream] - "Streamed file %s" - else - "Sent file %s" - end - + message = "Sent file %s" message << " (%.1fms)" info(message % [event.payload[:path], event.duration]) end diff --git a/actionpack/lib/action_controller/railties/url_helpers.rb b/actionpack/lib/action_controller/railties/url_helpers.rb new file mode 100644 index 0000000000..ad2a8d4ef3 --- /dev/null +++ b/actionpack/lib/action_controller/railties/url_helpers.rb @@ -0,0 +1,14 @@ +module ActionController + class Railtie + module UrlHelpers + def self.with(router) + Module.new do + define_method(:inherited) do |klass| + super(klass) + klass.send(:include, router.url_helpers) + end + end + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 1165c3b7c5..3f966b1b64 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -60,13 +60,32 @@ module ActionController # # dom_id(Post.find(45), :edit) # => "edit_post_45" def dom_id(record, prefix = nil) - if record_id = record.id + if record_id = record_key_for_dom_id(record) "#{dom_class(record, prefix)}#{JOIN}#{record_id}" else dom_class(record, prefix || NEW) end end + # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id. + # This can be overwritten to customize the default generated string representation if desired. + # If you need to read back a key from a dom_id in order to query for the underlying database record, + # you should write a helper like 'person_record_from_dom_id' that will extract the key either based + # on the default implementation (which just joins all key attributes with '-') or on your own + # overwritten version of the method. By default, this implementation passes the key string through a + # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to + # make sure yourself that your dom ids are valid, in case you overwrite this method. + def record_key_for_dom_id(record) + return record.id unless record.respond_to?(:to_model) + key = record.to_model.to_key + key ? sanitize_dom_id(key.join('_')) : key + end + + # Replaces characters that are invalid in HTML DOM ids with valid ones. + def sanitize_dom_id(candidate_id) + candidate_id # TODO implement conversion to valid DOM id values + end + # Returns the plural class name of a record or class. Examples: # # plural_class_name(post) # => "posts" diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 14557ca782..cdb5db32aa 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -17,9 +17,9 @@ module ActionController end end - def assign_parameters(controller_path, action, parameters = {}) + def assign_parameters(router, controller_path, action, parameters = {}) parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) - extra_keys = ActionController::Routing::Routes.extra_keys(parameters) + extra_keys = router.extra_keys(parameters) non_path_parameters = get? ? query_parameters : request_parameters parameters.each do |key, value| if value.is_a? Fixnum @@ -220,7 +220,7 @@ module ActionController def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET') # Sanity check for required instance variables so we can give an # understandable error message. - %w(@controller @request @response).each do |iv_name| + %w(@router @controller @request @response).each do |iv_name| if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil? raise "#{iv_name} is nil: make sure you set it in your test's setup method." end @@ -236,7 +236,7 @@ module ActionController @request.env['REQUEST_METHOD'] = http_method parameters ||= {} - @request.assign_parameters(@controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters) + @request.assign_parameters(@router, @controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters) @request.session = ActionController::TestSession.new(session) unless session.nil? @request.session["flash"] = @request.flash.update(flash || {}) @@ -322,6 +322,8 @@ module ActionController @controller ||= klass.new rescue nil end + @request.env.delete('PATH_INFO') + if @controller @controller.request = @request @controller.params = {} @@ -335,13 +337,20 @@ module ActionController private def build_request_uri(action, parameters) - unless @request.env['REQUEST_URI'] - options = @controller.__send__(:rewrite_options, parameters) - options.update(:only_path => true, :action => action) - - url = ActionController::UrlRewriter.new(@request, parameters) - @request.request_uri = url.rewrite(options) + unless @request.env["PATH_INFO"] + options = @controller.__send__(:url_options).merge(parameters) + options.update( + :only_path => true, + :action => action, + :relative_url_root => nil, + :_path_segments => @request.symbolized_path_parameters) + + url, query_string = @router.url_for(options).split("?", 2) + + @request.env["SCRIPT_NAME"] = @controller.config.relative_url_root + @request.env["PATH_INFO"] = url + @request.env["QUERY_STRING"] = query_string || "" end end - end + end end diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb deleted file mode 100644 index 933a1fa8f9..0000000000 --- a/actionpack/lib/action_controller/url_rewriter.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'active_support/core_ext/hash/except' - -module ActionController - # Rewrites URLs for Base.redirect_to and Base.url_for in the controller. - class UrlRewriter #:nodoc: - RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root] - - def initialize(request, parameters) - @request, @parameters = request, parameters - end - - def rewrite(options = {}) - options[:host] ||= @request.host_with_port - options[:protocol] ||= @request.protocol - - self.class.rewrite(options, @request.symbolized_path_parameters) do |options| - process_path_options(options) - end - end - - def to_str - "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}" - end - - alias_method :to_s, :to_str - - def self.rewrite(options, path_segments=nil) - rewritten_url = "" - - unless options[:only_path] - rewritten_url << (options[:protocol] || "http") - rewritten_url << "://" unless rewritten_url.match("://") - rewritten_url << rewrite_authentication(options) - - raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host] - - rewritten_url << options[:host] - rewritten_url << ":#{options.delete(:port)}" if options.key?(:port) - end - - path_options = options.except(*RESERVED_OPTIONS) - path_options = yield(path_options) if block_given? - path = Routing::Routes.generate(path_options, path_segments || {}) - - rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root] - rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) - rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor] - - rewritten_url - end - - protected - - def self.rewrite_authentication(options) - if options[:user] && options[:password] - "#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@" - else - "" - end - end - - # Given a Hash of options, generates a route - def process_path_options(options) - options = options.symbolize_keys - options.update(options[:params].symbolize_keys) if options[:params] - - if (overwrite = options.delete(:overwrite_params)) - options.update(@parameters.symbolize_keys) - options.update(overwrite.symbolize_keys) - end - - options - end - - end -end diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 479ea959e6..dfb8919561 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -48,6 +48,7 @@ module ActionDispatch autoload :Flash autoload :Head autoload :ParamsParser + autoload :RemoteIp autoload :Rescue autoload :ShowExceptions autoload :Static diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 428e62dc6b..d2404e63c5 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -37,8 +37,21 @@ module ActionDispatch end module Response - def cache_control - @cache_control ||= {} + attr_reader :cache_control + + def initialize(*) + status, header, body = super + + @cache_control = {} + @etag = self["ETag"] + + if cache_control = self["Cache-Control"] + cache_control.split(/,\s*/).each do |segment| + first, last = segment.split("=") + last ||= true + @cache_control[first.to_sym] = last + end + end end def last_modified @@ -65,7 +78,7 @@ module ActionDispatch def etag=(etag) key = ActiveSupport::Cache.expand_cache_key(etag) - @etag = %("#{Digest::MD5.hexdigest(key)}") + @etag = self["ETag"] = %("#{Digest::MD5.hexdigest(key)}") end private @@ -100,6 +113,8 @@ module ActionDispatch def set_conditional_cache_control! control = @cache_control + return if self["Cache-Control"].present? + if control.empty? headers["Cache-Control"] = DEFAULT_CACHE_CONTROL elsif @cache_control[:no_cache] diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 1958e1668d..451b79b190 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -25,9 +25,16 @@ module ActionDispatch module FilterParameters extend ActiveSupport::Concern + mattr_reader :compiled_parameter_filter_for + @@compiled_parameter_filter_for = {} + # Return a hash of parameters with all sensitive data replaced. def filtered_parameters - @filtered_parameters ||= process_parameter_filter(parameters) + @filtered_parameters ||= if filtering_parameters? + process_parameter_filter(parameters) + else + parameters.dup + end end alias :fitered_params :filtered_parameters @@ -46,10 +53,18 @@ module ActionDispatch protected - def compile_parameter_filter #:nodoc: + def filtering_parameters? #:nodoc: + @env["action_dispatch.parameter_filter"].present? + end + + def process_parameter_filter(params) #:nodoc: + compiled_parameter_filter_for(@env["action_dispatch.parameter_filter"]).call(params) + end + + def compile_parameter_filter(filters) #:nodoc: strings, regexps, blocks = [], [], [] - Array(@env["action_dispatch.parameter_filter"]).each do |item| + filters.each do |item| case item when NilClass when Proc @@ -65,34 +80,34 @@ module ActionDispatch [regexps, blocks] end - def filtering_parameters? #:nodoc: - @env["action_dispatch.parameter_filter"].present? - end + def compiled_parameter_filter_for(filters) #:nodoc: + @@compiled_parameter_filter_for[filters] ||= begin + regexps, blocks = compile_parameter_filter(filters) - def process_parameter_filter(original_params) #:nodoc: - return original_params.dup unless filtering_parameters? + lambda do |original_params| + filtered_params = {} - filtered_params = {} - regexps, blocks = compile_parameter_filter + original_params.each do |key, value| + if regexps.find { |r| key =~ r } + value = '[FILTERED]' + elsif value.is_a?(Hash) + value = process_parameter_filter(value) + elsif value.is_a?(Array) + value = value.map { |v| v.is_a?(Hash) ? process_parameter_filter(v) : v } + elsif blocks.present? + key = key.dup + value = value.dup if value.duplicable? + blocks.each { |b| b.call(key, value) } + end - original_params.each do |key, value| - if regexps.find { |r| key =~ r } - value = '[FILTERED]' - elsif value.is_a?(Hash) - value = process_parameter_filter(value) - elsif value.is_a?(Array) - value = value.map { |i| process_parameter_filter(i) } - elsif blocks.present? - key = key.dup - value = value.dup if value.duplicable? - blocks.each { |b| b.call(key, value) } - end + filtered_params[key] = value + end - filtered_params[key] = value + filtered_params + end end - - filtered_params end + end end end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 40617e239a..fec250e928 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -67,21 +67,6 @@ module ActionDispatch @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])] end - # Returns a symbolized version of the <tt>:format</tt> parameter of the request. - # If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt> - # otherwise. - def template_format - parameter_format = parameters[:format] - - if parameter_format - parameter_format - elsif xhr? - :js - else - :html - end - end - # Receives an array of mimes and return the first user sent mime that # matches the order array. # diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 7a17023ed2..ea9f0f99c2 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -30,6 +30,14 @@ module ActionDispatch METHOD end + def self.new(env) + if request = env["action_dispatch.request"] && request.instance_of?(self) + return request + end + + super + end + def key?(key) @env.key?(key) end @@ -119,36 +127,7 @@ module ActionDispatch # delimited list in the case of multiple chained proxies; the last # address which is not trusted is the originating IP. def remote_ip - remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/) - - unless remote_addr_list.blank? - not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES || addr =~ ActionController::Base.trusted_proxies} - return not_trusted_addrs.first unless not_trusted_addrs.empty? - end - remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',') - - if @env.include? 'HTTP_CLIENT_IP' - if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP']) - # We don't know which came from the proxy, and which from the user - raise ActionController::ActionControllerError.new <<EOM -IP spoofing attack?! -HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} -HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect} -EOM - end - - return @env['HTTP_CLIENT_IP'] - end - - if remote_ips - while remote_ips.size > 1 && (TRUSTED_PROXIES =~ remote_ips.last.strip || ActionController::Base.trusted_proxies =~ remote_ips.last.strip) - remote_ips.pop - end - - return remote_ips.last.strip - end - - @env['REMOTE_ADDR'] + (@env["action_dispatch.remote_ip"] || ip).to_s end # Returns the lowercase name of the HTTP server software. diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index f299306ff4..9cfe5a5ea9 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -32,31 +32,38 @@ module ActionDispatch # :nodoc: # end # end class Response < Rack::Response - include ActionDispatch::Http::Cache::Response - attr_accessor :request, :blank attr_writer :header, :sending_file alias_method :headers=, :header= - def initialize - @status = 200 - @header = {} - @cache_control = {} + module Setup + def initialize(status = 200, header = {}, body = []) + @writer = lambda { |x| @body << x } + @block = nil + @length = 0 - @writer = lambda { |x| @body << x } - @block = nil - @length = 0 + @status, @header = status, header + self.body = body - @body, @cookie = [], [] - @sending_file = false + @cookie = [] + @sending_file = false - @blank = false - @etag = nil + @blank = false + + if content_type = self["Content-Type"] + type, charset = content_type.split(/;\s*charset=/) + @content_type = Mime::Type.lookup(type) + @charset = charset || "UTF-8" + end - yield self if block_given? + yield self if block_given? + end end + include Setup + include ActionDispatch::Http::Cache::Response + def status=(status) @status = Rack::Utils.status_code(status) end @@ -76,6 +83,18 @@ module ActionDispatch # :nodoc: end alias_method :status_message, :message + def respond_to?(method) + if method.to_sym == :to_path + @body.respond_to?(:to_path) + else + super + end + end + + def to_path + @body.to_path + end + def body str = '' each { |part| str << part.to_s } @@ -120,7 +139,7 @@ module ActionDispatch # :nodoc: assign_default_content_type_and_charset! handle_conditional_get! self["Set-Cookie"] = @cookie.join("\n") unless @cookie.blank? - self["ETag"] = @etag if @etag + self["ETag"] = @_etag if @_etag super end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 40ceb5a9b6..b64a83c62e 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -3,7 +3,7 @@ module ActionDispatch module URL # Returns the complete URL used for this request. def url - protocol + host_with_port + request_uri + protocol + host_with_port + fullpath end # Returns 'https://' if this is an SSL request and 'http://' otherwise. @@ -81,42 +81,15 @@ module ActionDispatch parts[0..-(tld_length+2)] end - # Returns the query string, accounting for server idiosyncrasies. - def query_string - @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '') + def subdomain(tld_length = 1) + subdomains(tld_length).join('.') end # Returns the request URI, accounting for server idiosyncrasies. # WEBrick includes the full URL. IIS leaves REQUEST_URI blank. def request_uri - if uri = @env['REQUEST_URI'] - # Remove domain, which webrick puts into the request_uri. - (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri - else - # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO. - uri = @env['PATH_INFO'].to_s - - if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$}) - uri = uri.sub(/#{script_filename}\//, '') - end - - env_qs = @env['QUERY_STRING'].to_s - uri += "?#{env_qs}" unless env_qs.empty? - - if uri.blank? - @env.delete('REQUEST_URI') - else - @env['REQUEST_URI'] = uri - end - end - end - - # Returns the interpreted \path to requested resource after all the installation - # directory of this application was taken into account. - def path - path = request_uri.to_s[/\A[^\?]*/] - path.sub!(/\A#{ActionController::Base.relative_url_root}/, '') - path + ActiveSupport::Deprecation.warn "Using #request_uri is deprecated. Use fullpath instead.", caller + fullpath end private diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 7cf75ffe63..d07841218a 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -37,7 +37,7 @@ module ActionDispatch def initialize(app, prepare_each_request = false) @app, @prepare_each_request = app, prepare_each_request - run_callbacks(:prepare) unless @prepare_each_request + run_callbacks(:prepare) end def call(env) diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 522982e202..f4c4324fb0 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -1,4 +1,3 @@ -require 'active_support/json' require 'action_dispatch/http/request' module ActionDispatch diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb new file mode 100644 index 0000000000..c7d710b98e --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -0,0 +1,51 @@ +module ActionDispatch + class RemoteIp + class IpSpoofAttackError < StandardError ; end + + class RemoteIpGetter + def initialize(env, check_ip_spoofing, trusted_proxies) + @env = env + @check_ip_spoofing = check_ip_spoofing + @trusted_proxies = trusted_proxies + end + + def remote_addrs + @remote_addrs ||= begin + list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : [] + list.reject { |addr| addr =~ @trusted_proxies } + end + end + + def to_s + return remote_addrs.first if remote_addrs.any? + + forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : [] + + if client_ip = @env['HTTP_CLIENT_IP'] + if @check_ip_spoofing && !forwarded_ips.include?(client_ip) + # We don't know which came from the proxy, and which from the user + raise IpSpoofAttackError, "IP spoofing attack?!" \ + "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \ + "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" + end + return client_ip + end + + return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"] + end + end + + def initialize(app, check_ip_spoofing = true, trusted_proxies = nil) + @app = app + @check_ip_spoofing = check_ip_spoofing + regex = '(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)' + regex << "|(#{trusted_proxies})" if trusted_proxies + @trusted_proxies = Regexp.new(regex, "i") + end + + def call(env) + env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies) + @app.call(env) + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 04a101dbb2..22da82479e 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/hash/keys' -require 'rack/request' module ActionDispatch module Session @@ -177,9 +176,8 @@ module ActionDispatch if key.blank? raise ArgumentError, 'A key is required to write a ' + 'cookie containing the session data. Use ' + - 'config.action_controller.session = { :key => ' + - '"_myapp_session", :secret => "some secret phrase" } in ' + - 'config/application.rb' + 'config.action_controller.session_store :cookie_store, { :key => ' + + '"_myapp_session" } in config/application.rb' end end @@ -193,10 +191,9 @@ module ActionDispatch if secret.blank? raise ArgumentError, "A secret is required to generate an " + "integrity hash for cookie session data. Use " + - "config.action_controller.session = { :key => " + - "\"_myapp_session\", :secret => \"some secret phrase of at " + - "least #{SECRET_MIN_LENGTH} characters\" } " + - "in config/environment.rb" + "config.cookie_secret = \"some secret phrase of at " + + "least #{SECRET_MIN_LENGTH} characters\"" + + "in config/application.rb" end if secret.length < SECRET_MIN_LENGTH diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 18a2922fa7..5c5362ce4a 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -58,7 +58,7 @@ module ActionDispatch if lazy_compare?(@klass) && lazy_compare?(middleware) normalize(@klass) == normalize(middleware) else - klass == ActiveSupport::Inflector.constantize(middleware.to_s) + klass.name == middleware.to_s end end end @@ -122,7 +122,11 @@ module ActionDispatch find_all { |middleware| middleware.active? } end - def build(app) + def build(app = nil, &blk) + app ||= blk + + raise "MiddlewareStack#build requires an app" unless app + active.reverse.inject(app) { |a, e| e.build(a) } end end diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 335daafc01..e486bd4079 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -5,6 +5,9 @@ module ActionDispatch class Railtie < Rails::Railtie railtie_name :action_dispatch + config.action_dispatch.x_sendfile_header = "X-Sendfile" + config.action_dispatch.ip_spoofing_check = true + # Prepare dispatcher callbacks and run 'prepare' callbacks initializer "action_dispatch.prepare_dispatcher" do |app| # TODO: This used to say unless defined?(Dispatcher). Find out why and fix. diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 335c9edb98..5bc3205c51 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -205,6 +205,7 @@ module ActionDispatch autoload :Mapper, 'action_dispatch/routing/mapper' autoload :Route, 'action_dispatch/routing/route' autoload :RouteSet, 'action_dispatch/routing/route_set' + autoload :UrlFor, 'action_dispatch/routing/url_for' SEPARATORS = %w( / . ? ) HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb index 8ce6b2f6d5..dd650e83d9 100644 --- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb +++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb @@ -1,5 +1,30 @@ module ActionDispatch module Routing + class RouteSet + attr_accessor :controller_namespaces + + CONTROLLER_REGEXP = /[_a-zA-Z0-9]+/ + + def controller_constraints + @controller_constraints ||= begin + namespaces = controller_namespaces + in_memory_controller_namespaces + source = namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" } + source << CONTROLLER_REGEXP.source + Regexp.compile(source.sort.reverse.join('|')) + end + end + + def in_memory_controller_namespaces + namespaces = Set.new + ActionController::Base.subclasses.each do |klass| + controller_name = klass.underscore + namespaces << controller_name.split('/')[0...-1].join('/') + end + namespaces.delete('') + namespaces + end + end + # Mapper instances are used to build routes. The object passed to the draw # block in config/routes.rb is a Mapper instance. # @@ -244,14 +269,15 @@ module ActionDispatch attr_reader :collection_methods, :member_methods, :new_methods attr_reader :path_prefix, :name_prefix, :path_segment attr_reader :plural, :singular - attr_reader :options + attr_reader :options, :defaults - def initialize(entities, options) + def initialize(entities, options, defaults) @plural ||= entities @singular ||= options[:singular] || plural.to_s.singularize @path_segment = options.delete(:as) || @plural @options = options + @defaults = defaults arrange_actions add_default_actions @@ -280,7 +306,7 @@ module ActionDispatch def new_path new_action = self.options[:path_names][:new] if self.options[:path_names] - new_action ||= ActionController::Base.resources_path_names[:new] + new_action ||= self.defaults[:path_names][:new] @new_path ||= "#{path}/#{new_action}" end @@ -370,7 +396,7 @@ module ActionDispatch end class SingletonResource < Resource #:nodoc: - def initialize(entity, options) + def initialize(entity, options, defaults) @singular = @plural = entity options[:controller] ||= @singular.to_s.pluralize super @@ -717,7 +743,7 @@ module ActionDispatch private def map_resource(entities, options = {}, &block) - resource = Resource.new(entities, options) + resource = Resource.new(entities, options, :path_names => @set.resources_path_names) with_options :controller => resource.controller do |map| map_associations(resource, options) @@ -734,7 +760,7 @@ module ActionDispatch end def map_singleton_resource(entities, options = {}, &block) - resource = SingletonResource.new(entities, options) + resource = SingletonResource.new(entities, options, :path_names => @set.resources_path_names) with_options :controller => resource.controller do |map| map_associations(resource, options) @@ -826,7 +852,7 @@ module ActionDispatch actions.each do |action| [method].flatten.each do |m| action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) - action_path ||= ActionController::Base.resources_path_names[action] || action + action_path ||= @set.resources_path_names[action] || action map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true }) end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 8de68b3174..0b7b09ee7a 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/hash/except' + module ActionDispatch module Routing class Mapper @@ -36,7 +38,7 @@ module ActionDispatch end def to_route - [ app, conditions, requirements, defaults, @options[:as] ] + [ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ] end private @@ -64,7 +66,7 @@ module ActionDispatch # match "account/overview" def using_match_shorthand?(args, options) - args.present? && options.except(:via).empty? && !args.first.include?(':') + args.present? && options.except(:via, :anchor).empty? && !args.first.include?(':') end def normalize_path(path) @@ -85,10 +87,9 @@ module ActionDispatch end def requirements - @requirements ||= returning(@options[:constraints] || {}) do |requirements| + @requirements ||= (@options[:constraints] || {}).tap do |requirements| requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints] @options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) } - requirements[:controller] ||= @set.controller_constraints end end @@ -175,9 +176,15 @@ module ActionDispatch end def match(*args) - @set.add_route(*Mapping.new(@set, @scope, args).to_route) + mapping = Mapping.new(@set, @scope, args).to_route + @set.add_route(*mapping) self end + + def default_url_options=(options) + @set.default_url_options = options + end + alias_method :default_url_options, :default_url_options= end module HttpHelpers @@ -283,7 +290,7 @@ module ActionDispatch end def namespace(path) - scope(path.to_s, :name_prefix => path.to_s, :namespace => path.to_s) { yield } + scope(path.to_s, :name_prefix => path.to_s, :controller_namespace => path.to_s) { yield } end def constraints(constraints = {}) @@ -294,6 +301,7 @@ module ActionDispatch options = args.extract_options! options = (@scope[:options] || {}).merge(options) + options[:anchor] = true unless options.key?(:anchor) if @scope[:name_prefix] && !options[:as].blank? options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}" @@ -318,12 +326,12 @@ module ActionDispatch parent ? "#{parent}_#{child}" : child end - def merge_namespace_scope(parent, child) + def merge_controller_namespace_scope(parent, child) parent ? "#{parent}/#{child}" : child end def merge_controller_scope(parent, child) - @scope[:namespace] ? "#{@scope[:namespace]}/#{child}" : child + @scope[:controller_namespace] ? "#{@scope[:controller_namespace]}/#{child}" : child end def merge_resources_path_names_scope(parent, child) @@ -367,9 +375,9 @@ module ActionDispatch def actions if only = options[:only] - only.map(&:to_sym) + Array(only).map(&:to_sym) elsif except = options[:except] - default_actions - except.map(&:to_sym) + default_actions - Array(except).map(&:to_sym) else default_actions end @@ -443,7 +451,7 @@ module ActionDispatch def resource(*resources, &block) options = resources.extract_options! - if verify_common_behavior_for(:resource, resources, options, &block) + if apply_common_behavior_for(:resource, resources, options, &block) return self end @@ -451,7 +459,10 @@ module ActionDispatch scope(:path => resource.name.to_s, :controller => resource.controller) do with_scope_level(:resource, resource) do - yield if block_given? + + scope(:name_prefix => resource.name.to_s) do + yield if block_given? + end get :show if resource.actions.include?(:show) post :create if resource.actions.include?(:create) @@ -468,7 +479,7 @@ module ActionDispatch def resources(*resources, &block) options = resources.extract_options! - if verify_common_behavior_for(:resources, resources, options, &block) + if apply_common_behavior_for(:resources, resources, options, &block) return self end @@ -534,6 +545,21 @@ module ActionDispatch end end + def mount(app, options = nil) + if options + path = options.delete(:at) + else + options = app + app, path = options.find { |k, v| k.respond_to?(:call) } + options.delete(app) if app + end + + raise "A rack application must be specified" unless path + + match(path, options.merge(:to => app, :anchor => false)) + self + end + def match(*args) options = args.extract_options! @@ -591,7 +617,7 @@ module ActionDispatch path_names[name.to_sym] || name.to_s end - def verify_common_behavior_for(method, resources, options, &block) + def apply_common_behavior_for(method, resources, options, &block) if resources.length > 1 resources.each { |r| send(method, r, options, &block) } return true diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb index e6e44d3546..6f37eadd6b 100644 --- a/actionpack/lib/action_dispatch/routing/route.rb +++ b/actionpack/lib/action_dispatch/routing/route.rb @@ -4,7 +4,7 @@ module ActionDispatch attr_reader :app, :conditions, :defaults, :name attr_reader :path, :requirements - def initialize(app, conditions = {}, requirements = {}, defaults = {}, name = nil) + def initialize(app, conditions, requirements, defaults, name, anchor) @app = app @defaults = defaults @name = name @@ -17,7 +17,7 @@ module ActionDispatch if path = conditions[:path_info] @path = path - conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS) + conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS, anchor) end @conditions = conditions.inject({}) { |h, (k, v)| diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index dcf98b729b..722be432c7 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,5 +1,6 @@ require 'rack/mount' require 'forwardable' +require 'action_dispatch/routing/deprecated_mapper' module ActionDispatch module Routing @@ -11,8 +12,8 @@ module ActionDispatch PARAMETERS_KEY = 'action_dispatch.request.path_parameters' class Dispatcher - def initialize(options = {}) - defaults = options[:defaults] + def initialize(options={}) + @defaults = options[:defaults] @glob_param = options.delete(:glob) end @@ -20,7 +21,8 @@ module ActionDispatch params = env[PARAMETERS_KEY] prepare_params!(params) - unless controller = controller(params) + # Just raise undefined constant errors if a controller was specified as default. + unless controller = controller(params, @defaults.key?(:controller)) return [404, {'X-Cascade' => 'pass'}, []] end @@ -39,14 +41,13 @@ module ActionDispatch end end - def controller(params) + def controller(params, raise_error=true) if params && params.has_key?(:controller) controller = "#{params[:controller].camelize}Controller" ActiveSupport::Inflector.constantize(controller) end rescue NameError => e - raise unless e.message.include?(controller) - nil + raise ActionController::RoutingError, e.message, e.backtrace if raise_error end private @@ -59,7 +60,6 @@ module ActionDispatch end end - # A NamedRouteCollection instance is a collection of named routes, and also # maintains an anonymous module that can be used to install helpers for the # named routes. @@ -168,63 +168,25 @@ module ActionDispatch selector = url_helper_name(name, kind) hash_access_method = hash_access_name(name, kind) - # We use module_eval to avoid leaks. - # - # def users_url(*args) - # if args.empty? || Hash === args.first - # options = hash_for_users_url(args.first || {}) - # else - # options = hash_for_users_url(args.extract_options!) - # default = default_url_options(options) if self.respond_to?(:default_url_options, true) - # options = (default ||= {}).merge(options) - # - # keys = [] - # keys -= options.keys if args.size < keys.size - 1 - # - # args = args.zip(keys).inject({}) do |h, (v, k)| - # h[k] = v - # h - # end - # - # # Tell url_for to skip default_url_options - # options[:use_defaults] = false - # options.merge!(args) - # end - # - # url_for(options) - # end @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 def #{selector}(*args) - if args.empty? || Hash === args.first - options = #{hash_access_method}(args.first || {}) - else - options = #{hash_access_method}(args.extract_options!) - default = default_url_options(options) if self.respond_to?(:default_url_options, true) - options = (default ||= {}).merge(options) - - keys = #{route.segment_keys.inspect} - keys -= options.keys if args.size < keys.size - 1 # take format into account - - args = args.zip(keys).inject({}) do |h, (v, k)| - h[k] = v - h - end - - # Tell url_for to skip default_url_options - options[:use_defaults] = false - options.merge!(args) + options = #{hash_access_method}(args.extract_options!) + + if args.any? + options[:_positional_args] = args + options[:_positional_keys] = #{route.segment_keys.inspect} end url_for(options) end - protected :#{selector} END_EVAL helpers << selector end end - attr_accessor :routes, :named_routes, :controller_namespaces + attr_accessor :routes, :named_routes attr_accessor :disable_clear_and_finalize, :resources_path_names + attr_accessor :default_url_options def self.default_resources_path_names { :new => 'new', :edit => 'edit' } @@ -235,8 +197,10 @@ module ActionDispatch self.named_routes = NamedRouteCollection.new self.resources_path_names = self.class.default_resources_path_names.dup self.controller_namespaces = Set.new + self.default_url_options = {} @disable_clear_and_finalize = false + clear! end def draw(&block) @@ -273,61 +237,155 @@ module ActionDispatch named_routes.install(destinations, regenerate_code) end - def empty? - routes.empty? - end + def url_helpers + @url_helpers ||= begin + router = self - CONTROLLER_REGEXP = /[_a-zA-Z0-9]+/ + Module.new do + extend ActiveSupport::Concern + include UrlFor - def controller_constraints - @controller_constraints ||= begin - namespaces = controller_namespaces + in_memory_controller_namespaces - source = namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" } - source << CONTROLLER_REGEXP.source - Regexp.compile(source.sort.reverse.join('|')) + # ROUTES TODO: install_helpers isn't great... can we make a module with the stuff that + # we can include? + # Yes plz - JP + included do + router.install_helpers(self) + end + + define_method(:_router) { router } + end end end - def in_memory_controller_namespaces - namespaces = Set.new - ActionController::Base.subclasses.each do |klass| - controller_name = klass.underscore - namespaces << controller_name.split('/')[0...-1].join('/') - end - namespaces.delete('') - namespaces + def empty? + routes.empty? end - def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil) - route = Route.new(app, conditions, requirements, defaults, name) + def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true) + route = Route.new(app, conditions, requirements, defaults, name, anchor) @set.add_route(*route) named_routes[name] = route if name routes << route route end - def options_as_params(options) - # If an explicit :controller was given, always make :action explicit - # too, so that action expiry works as expected for things like - # - # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) - # - # (the above is from the unit tests). In the above case, because the - # controller was explicitly given, but no action, the action is implied to - # be "index", not the recalled action of "show". - # - # great fun, eh? - - options_as_params = options.clone - options_as_params[:action] ||= 'index' if options[:controller] - options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action] - options_as_params - end + class Generator + attr_reader :options, :recall, :set, :script_name, :named_route + + def initialize(options, recall, set, extras = false) + @script_name = options.delete(:script_name) + @named_route = options.delete(:use_route) + @options = options.dup + @recall = recall.dup + @set = set + @extras = extras + + normalize_options! + normalize_controller_action_id! + use_relative_controller! + controller.sub!(%r{^/}, '') if controller + handle_nil_action! + end + + def controller + @controller ||= @options[:controller] + end + + def current_controller + @recall[:controller] + end + + def use_recall_for(key) + if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key]) + @options[key] = @recall.delete(key) + end + end + + def normalize_options! + # If an explicit :controller was given, always make :action explicit + # too, so that action expiry works as expected for things like + # + # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) + # + # (the above is from the unit tests). In the above case, because the + # controller was explicitly given, but no action, the action is implied to + # be "index", not the recalled action of "show". + + if options[:controller] + options[:action] ||= 'index' + options[:controller] = options[:controller].to_s + end + + if options[:action] + options[:action] = options[:action].to_s + end + end + + # This pulls :controller, :action, and :id out of the recall. + # The recall key is only used if there is no key in the options + # or if the key in the options is identical. If any of + # :controller, :action or :id is not found, don't pull any + # more keys from the recall. + def normalize_controller_action_id! + @recall[:action] ||= 'index' if current_controller + + use_recall_for(:controller) or return + use_recall_for(:action) or return + use_recall_for(:id) + end + + # if the current controller is "foo/bar/baz" and :controller => "baz/bat" + # is specified, the controller becomes "foo/baz/bat" + def use_relative_controller! + if !named_route && different_controller? + old_parts = current_controller.split('/') + size = controller.count("/") + 1 + parts = old_parts[0...-size] << controller + @controller = @options[:controller] = parts.join("/") + end + end + + # This handles the case of :action => nil being explicitly passed. + # It is identical to :action => "index" + def handle_nil_action! + if options.has_key?(:action) && options[:action].nil? + options[:action] = 'index' + end + recall[:action] = options.delete(:action) if options[:action] == 'index' + end - def build_expiry(options, recall) - recall.inject({}) do |expiry, (key, recalled_value)| - expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param) - expiry + def generate + error = ActionController::RoutingError.new("No route matches #{options.inspect}") + path, params = @set.generate(:path_info, named_route, options, recall, opts) + + raise error unless path + + params.reject! {|k,v| !v } + + return [path, params.keys] if @extras + + path << "?#{params.to_query}" if params.any? + "#{script_name}#{path}" + rescue Rack::Mount::RoutingError + raise error + end + + def opts + parameterize = lambda do |name, value| + if name == :controller + value + elsif value.is_a?(Array) + value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/') + else + Rack::Mount::Utils.escape_uri(value.to_param) + end + end + {:parameterize => parameterize} + end + + def different_controller? + return false unless current_controller + controller.to_param != current_controller.to_param end end @@ -338,82 +396,44 @@ module ActionDispatch end def generate_extras(options, recall={}) - generate(options, recall, :generate_extras) + generate(options, recall, true) end - def generate(options, recall = {}, method = :generate) - options, recall = options.dup, recall.dup - named_route = options.delete(:use_route) + def generate(options, recall = {}, extras = false) + Generator.new(options, recall, @set, extras).generate + end - options = options_as_params(options) - expire_on = build_expiry(options, recall) + RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash] - recall[:action] ||= 'index' if options[:controller] || recall[:controller] + def url_for(options) + options = default_url_options.merge(options || {}) - if recall[:controller] && (!options.has_key?(:controller) || options[:controller] == recall[:controller]) - options[:controller] = recall.delete(:controller) + handle_positional_args(options) - if recall[:action] && (!options.has_key?(:action) || options[:action] == recall[:action]) - options[:action] = recall.delete(:action) + rewritten_url = "" - if recall[:id] && (!options.has_key?(:id) || options[:id] == recall[:id]) - options[:id] = recall.delete(:id) - end - end - end - - options[:controller] = options[:controller].to_s if options[:controller] + path_segments = options.delete(:_path_segments) - if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/ - old_parts = recall[:controller].split('/') - new_parts = options[:controller].split('/') - parts = old_parts[0..-(new_parts.length + 1)] + new_parts - options[:controller] = parts.join('/') - end + unless options[:only_path] + rewritten_url << (options[:protocol] || "http") + rewritten_url << "://" unless rewritten_url.match("://") + rewritten_url << rewrite_authentication(options) - options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/ + raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host] - merged = options.merge(recall) - if options.has_key?(:action) && options[:action].nil? - options.delete(:action) - recall[:action] = 'index' + rewritten_url << options[:host] + rewritten_url << ":#{options.delete(:port)}" if options.key?(:port) end - recall[:action] = options.delete(:action) if options[:action] == 'index' - - opts = {} - opts[:parameterize] = lambda { |name, value| - if name == :controller - value - elsif value.is_a?(Array) - value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/') - else - Rack::Mount::Utils.escape_uri(value.to_param) - end - } - unless result = @set.generate(:path_info, named_route, options, recall, opts) - raise ActionController::RoutingError, "No route matches #{options.inspect}" - end + path_options = options.except(*RESERVED_OPTIONS) + path_options = yield(path_options) if block_given? + path = generate(path_options, path_segments || {}) - path, params = result - params.each do |k, v| - if v - params[k] = v - else - params.delete(k) - end - end + # ROUTES TODO: This can be called directly, so script_name should probably be set in the router + rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) + rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor] - if path && method == :generate_extras - [path, params.keys] - elsif path - path << "?#{params.to_query}" if params.any? - path - else - raise ActionController::RoutingError, "No route matches #{options.inspect}" - end - rescue Rack::Mount::RoutingError - raise ActionController::RoutingError, "No route matches #{options.inspect}" + rewritten_url end def call(env) @@ -431,9 +451,9 @@ module ActionDispatch end req = Rack::Request.new(env) - @set.recognize(req) do |route, params| + @set.recognize(req) do |route, matches, params| dispatcher = route.app - if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params) + if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params, false) dispatcher.prepare_params!(params) return params end @@ -441,6 +461,30 @@ module ActionDispatch raise ActionController::RoutingError, "No route matches #{path.inspect}" end + + private + def handle_positional_args(options) + return unless args = options.delete(:_positional_args) + + keys = options.delete(:_positional_keys) + keys -= options.keys if args.size < keys.size - 1 # take format into account + + args = args.zip(keys).inject({}) do |h, (v, k)| + h[k] = v + h + end + + # Tell url_for to skip default_url_options + options.merge!(args) + end + + def rewrite_authentication(options) + if options[:user] && options[:password] + "#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@" + else + "" + end + end end end end diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb new file mode 100644 index 0000000000..ec78f53fa6 --- /dev/null +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -0,0 +1,139 @@ +module ActionDispatch + module Routing + # In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse + # is also possible: an URL can be generated from one of your routing definitions. + # URL generation functionality is centralized in this module. + # + # See ActionDispatch::Routing and ActionController::Resources for general + # information about routing and routes.rb. + # + # <b>Tip:</b> If you need to generate URLs from your models or some other place, + # then ActionController::UrlFor is what you're looking for. Read on for + # an introduction. + # + # == URL generation from parameters + # + # As you may know, some functions - such as ActionController::Base#url_for + # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set + # of parameters. For example, you've probably had the chance to write code + # like this in one of your views: + # + # <%= link_to('Click here', :controller => 'users', + # :action => 'new', :message => 'Welcome!') %> + # + # #=> Generates a link to: /users/new?message=Welcome%21 + # + # link_to, and all other functions that require URL generation functionality, + # actually use ActionController::UrlFor under the hood. And in particular, + # they use the ActionController::UrlFor#url_for method. One can generate + # the same path as the above example by using the following code: + # + # include UrlFor + # url_for(:controller => 'users', + # :action => 'new', + # :message => 'Welcome!', + # :only_path => true) + # # => "/users/new?message=Welcome%21" + # + # Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no + # information about the website hostname that your Rails app is serving. So if you + # want to include the hostname as well, then you must also pass the <tt>:host</tt> + # argument: + # + # include UrlFor + # url_for(:controller => 'users', + # :action => 'new', + # :message => 'Welcome!', + # :host => 'www.example.com') # Changed this. + # # => "http://www.example.com/users/new?message=Welcome%21" + # + # By default, all controllers and views have access to a special version of url_for, + # that already knows what the current hostname is. So if you use url_for in your + # controllers or your views, then you don't need to explicitly pass the <tt>:host</tt> + # argument. + # + # For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for. + # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for' + # in full. However, mailers don't have hostname information, and what's why you'll still + # have to specify the <tt>:host</tt> argument when generating URLs in mailers. + # + # + # == URL generation for named routes + # + # UrlFor also allows one to access methods that have been auto-generated from + # named routes. For example, suppose that you have a 'users' resource in your + # <b>routes.rb</b>: + # + # map.resources :users + # + # This generates, among other things, the method <tt>users_path</tt>. By default, + # this method is accessible from your controllers, views and mailers. If you need + # to access this auto-generated method from other places (such as a model), then + # you can do that by including ActionController::UrlFor in your class: + # + # class User < ActiveRecord::Base + # include ActionController::UrlFor + # + # def base_uri + # user_path(self) + # end + # end + # + # User.find(1).base_uri # => "/users/1" + # + module UrlFor + extend ActiveSupport::Concern + + included do + # TODO: with_routing extends @controller with url_helpers, trickling down to including this module which overrides its default_url_options + unless method_defined?(:default_url_options) + # Including in a class uses an inheritable hash. Modules get a plain hash. + if respond_to?(:class_attribute) + class_attribute :default_url_options + else + mattr_accessor :default_url_options + remove_method :default_url_options + end + + self.default_url_options = {} + end + end + + def url_options + default_url_options + end + + # Generate a url based on the options provided, default_url_options and the + # routes defined in routes.rb. The following options are supported: + # + # * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+. + # * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'. + # * <tt>:host</tt> - Specifies the host the link should be targeted at. + # If <tt>:only_path</tt> is false, this option must be + # provided either explicitly, or via +default_url_options+. + # * <tt>:port</tt> - Optionally specify the port to connect to. + # * <tt>:anchor</tt> - An anchor name to be appended to the path. + # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/" + # + # Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to + # +url_for+ is forwarded to the Routes module. + # + # Examples: + # + # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing' + # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok' + # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/' + # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33' + def url_for(options = nil) + case options + when String + options + when nil, Hash + _router.url_for(url_options.merge(options || {})) + else + polymorphic_url(options) + end + end + end + end +end diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index c2486d3730..937c9f48d2 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -132,16 +132,21 @@ module ActionDispatch end def normalize_argument_to_redirection(fragment) - after_routing = @controller.url_for(fragment) - if after_routing =~ %r{^\w+://.*} - after_routing - else - # FIXME - this should probably get removed. - if after_routing.first != '/' - after_routing = '/' + after_routing + case fragment + when %r{^\w[\w\d+.-]*:.*} + fragment + when String + if fragment =~ %r{^\w[\w\d+.-]*:.*} + fragment + else + @request.protocol + @request.host_with_port + fragment end - @request.protocol + @request.host_with_port + after_routing - end + when :back + raise RedirectBackError unless refer = @request.headers["Referer"] + refer + else + @controller.url_for(fragment) + end.gsub(/[\r\n]/, '') end def validate_request! diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 0c33539b4a..1d7e8090e4 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -80,7 +80,7 @@ module ActionDispatch expected_path = "/#{expected_path}" unless expected_path[0] == ?/ # Load routes.rb if it hasn't been loaded. - generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, defaults) + generated_path, extra_keys = @router.generate_extras(options, defaults) found_extras = options.reject {|k, v| ! extra_keys.include? k} msg = build_message(message, "found extras <?>, not <?>", found_extras, extras) @@ -125,7 +125,7 @@ module ActionDispatch end # A helper to make it easier to test different route configurations. - # This method temporarily replaces ActionController::Routing::Routes + # This method temporarily replaces @router # with a new RouteSet instance. # # The new instance is yielded to the passed block. Typically the block @@ -142,22 +142,19 @@ module ActionDispatch # end # def with_routing - real_routes = ActionController::Routing::Routes - ActionController::Routing.module_eval { remove_const :Routes } - - temporary_routes = ActionController::Routing::RouteSet.new - ActionController::Routing.module_eval { const_set :Routes, temporary_routes } - - yield temporary_routes + old_routes, @router = @router, ActionDispatch::Routing::RouteSet.new + old_controller, @controller = @controller, @controller.clone if @controller + _router = @router + @controller.singleton_class.send(:send, :include, @router.url_helpers) if @controller + yield @router ensure - if ActionController::Routing.const_defined? :Routes - ActionController::Routing.module_eval { remove_const :Routes } - end - ActionController::Routing.const_set(:Routes, real_routes) if real_routes + @router = old_routes + @controller = old_controller if @controller end + # ROUTES TODO: These assertions should really work in an integration context def method_missing(selector, *args, &block) - if @controller && ActionController::Routing::Routes.named_routes.helpers.include?(selector) + if @controller && @router.named_routes.helpers.include?(selector) @controller.send(selector, *args, &block) else super @@ -174,7 +171,7 @@ module ActionDispatch request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method request.path = path - params = ActionController::Routing::Routes.recognize_path(path, { :method => request.method }) + params = @router.recognize_path(path, { :method => request.method }) request.path_parameters = params.with_indifferent_access request diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 2093bb3a0e..0aff4250c1 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -1,6 +1,6 @@ require 'stringio' require 'uri' -require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/object/singleton_class' require 'rack/test' module ActionDispatch @@ -162,12 +162,31 @@ module ActionDispatch # A running counter of the number of requests processed. attr_accessor :request_count + include ActionDispatch::Routing::UrlFor + # Create and initialize a new Session instance. def initialize(app) @app = app + + # If the app is a Rails app, make url_helpers available on the session + # This makes app.url_for and app.foo_path available in the console + if app.respond_to?(:routes) && app.routes.respond_to?(:url_helpers) + singleton_class.class_eval { include app.routes.url_helpers } + end + reset! end + def url_options + opts = super.reverse_merge( + :host => host, + :protocol => https? ? "https" : "http" + ) + + opts.merge!(:port => 443) if !opts.key?(:port) && https? + opts + end + # Resets the instance. This can be used to reset the state information # in an existing session instance, so it can be used from a clean-slate # condition. @@ -187,12 +206,10 @@ module ActionDispatch unless defined? @named_routes_configured # install the named routes in this session instance. - klass = metaclass - ActionController::Routing::Routes.install_helpers(klass) + klass = singleton_class # the helpers are made protected by default--we make them public for # easier access during testing and troubleshooting. - klass.module_eval { public *ActionController::Routing::Routes.named_routes.helpers } @named_routes_configured = true end end @@ -221,14 +238,6 @@ module ActionDispatch @host = name end - # Returns the URL for the given options, according to the rules specified - # in the application's routes. - def url_for(options) - controller ? - controller.url_for(options) : - generic_url_rewriter.rewrite(options) - end - private # Performs the actual request. @@ -273,26 +282,14 @@ module ActionDispatch @request_count += 1 @request = ActionDispatch::Request.new(session.last_request.env) - @response = ActionDispatch::TestResponse.from_response(@mock_session.last_response) + response = @mock_session.last_response + @response = ActionDispatch::TestResponse.new(response.status, response.headers, response.body) @html_document = nil @controller = session.last_request.env['action_controller.instance'] return response.status end - - # Get a temporary URL writer object - def generic_url_rewriter - env = { - 'REQUEST_METHOD' => "GET", - 'QUERY_STRING' => "", - "REQUEST_URI" => "/", - "HTTP_HOST" => host, - "SERVER_PORT" => https? ? "443" : "80", - "HTTPS" => https? ? "on" : "off" - } - ActionController::UrlRewriter.new(ActionDispatch::Request.new(env), {}) - end end module Runner @@ -364,6 +361,14 @@ module ActionDispatch end end + extend ActiveSupport::Concern + include ActionDispatch::Routing::UrlFor + + def url_options + reset! unless @integration_session + @integration_session.url_options + end + # Delegate unhandled messages to the current session instance. def method_missing(sym, *args, &block) reset! unless @integration_session diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index eae703e1b6..d4eecac2de 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -1,3 +1,5 @@ +require 'action_dispatch/middleware/flash' + module ActionDispatch module TestProcess def assigns(key = nil) diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 67f89e1627..72a1cd30ad 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,9 +1,10 @@ -module ActionPack #:nodoc: +module ActionPack module VERSION #:nodoc: MAJOR = 3 MINOR = 0 - TINY = "0.beta1" + TINY = 0 + BUILD = "beta1" - STRING = [MAJOR, MINOR, TINY].join('.') + STRING = [MAJOR, MINOR, TINY, BUILD].join('.') end end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index f5035fe45a..afe6386105 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -37,15 +37,18 @@ module ActionView autoload :Helpers autoload_under "render" do + autoload :Layouts autoload :Partials autoload :Rendering end - autoload :MissingTemplate, 'action_view/base' - autoload :Resolver, 'action_view/template/resolver' - autoload :PathResolver, 'action_view/template/resolver' - autoload :PathSet, 'action_view/paths' - autoload :FileSystemResolverWithFallback, 'action_view/template/resolver' + autoload :Base + autoload :LookupContext + autoload :MissingTemplate, 'action_view/base' + autoload :Resolver, 'action_view/template/resolver' + autoload :PathResolver, 'action_view/template/resolver' + autoload :FileSystemResolver, 'action_view/template/resolver' + autoload :PathSet, 'action_view/paths' autoload :TemplateError, 'action_view/template/error' autoload :TemplateHandler, 'action_view/template' @@ -55,7 +58,7 @@ module ActionView autoload :TestCase, 'action_view/test_case' end +require 'active_support/i18n' require 'active_support/core_ext/string/output_safety' -require 'action_view/base' I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml" diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 4096c296c3..ffe3060404 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -11,7 +11,7 @@ module ActionView #:nodoc: def initialize(paths, path, details, partial) @path = path - display_paths = paths.compact.join(":") + display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ") template_type = if partial "partial" elsif path =~ /layouts/i @@ -20,7 +20,7 @@ module ActionView #:nodoc: 'template' end - super("Missing #{template_type} #{path} with #{details.inspect} in view path #{display_paths}") + super("Missing #{template_type} #{path} with #{details.inspect} in view paths #{display_paths}") end end @@ -173,81 +173,43 @@ module ActionView #:nodoc: module Subclasses end - include Helpers, Rendering, Partials, ::ERB::Util + include Helpers, Rendering, Partials, Layouts, ::ERB::Util, Context + extend ActiveSupport::Memoizable - def config - self.config = DEFAULT_CONFIG unless @config - @config - end - - def config=(config) - @config = ActiveSupport::OrderedOptions.new.merge(config) - end - - extend ActiveSupport::Memoizable - - attr_accessor :base_path, :assigns, :template_extension, :formats - attr_internal :captures + ActionView.run_base_hooks(self) - def reset_formats(formats) - @formats = formats - - if defined?(AbstractController::HashKey) - # This is expensive, but we need to reset this when the format is updated, - # which currently only happens - Thread.current[:format_locale_key] = - AbstractController::HashKey.get(self.class, formats, I18n.locale) - end - end - - class << self - delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB' - delegate :logger, :to => 'ActionController::Base', :allow_nil => true - end - - @@debug_rjs = false - ## - # :singleton-method: # Specify whether RJS responses should be wrapped in a try/catch block # that alert()s the caught exception (and then re-raises it). cattr_accessor :debug_rjs + @@debug_rjs = false - # Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed. - # Automatically reloading templates are not thread safe and should only be used in development mode. - @@cache_template_loading = nil - cattr_accessor :cache_template_loading - - # :nodoc: - def self.xss_safe? - true - end + class_attribute :helpers + attr_reader :helpers - def self.cache_template_loading? - ActionController::Base.allow_concurrency || (cache_template_loading.nil? ? !ActiveSupport::Dependencies.load? : cache_template_loading) + class << self + delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB' + delegate :logger, :to => 'ActionController::Base', :allow_nil => true end - attr_internal :request, :layout + attr_accessor :base_path, :assigns, :template_extension, :lookup_context + attr_internal :captures, :request, :layout, :controller, :template, :config - def controller_path - @controller_path ||= controller && controller.controller_path - end + delegate :find, :exists?, :formats, :formats=, :locale, :locale=, + :view_paths, :view_paths=, :with_fallbacks, :update_details, :to => :lookup_context delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers, :flash, :action_name, :controller_name, :to => :controller delegate :logger, :to => :controller, :allow_nil => true - delegate :find, :to => :view_paths - - include Context + def self.xss_safe? #:nodoc: + true + end def self.process_view_paths(value) ActionView::PathSet.new(Array(value)) end - class_attribute :helpers - attr_reader :helpers - def self.for_controller(controller) @views ||= {} @@ -275,26 +237,26 @@ module ActionView #:nodoc: klass = self end - klass.new(controller.class.view_paths, {}, controller) + klass.new(controller.lookup_context, {}, controller) end - def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil, formats = nil)#:nodoc: + def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc: @config = nil - @formats = formats @assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) } @helpers = self.class.helpers || Module.new @_controller = controller - @_content_for = Hash.new {|h,k| h[k] = ActiveSupport::SafeBuffer.new } + @_config = ActiveSupport::InheritableOptions.new(controller.config) if controller + @_content_for = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new } @_virtual_path = nil - self.view_paths = view_paths - end - attr_internal :controller, :template - attr_reader :view_paths + @lookup_context = lookup_context.is_a?(ActionView::LookupContext) ? + lookup_context : ActionView::LookupContext.new(lookup_context) + @lookup_context.formats = formats if formats + end - def view_paths=(paths) - @view_paths = self.class.process_view_paths(paths) + def controller_path + @controller_path ||= controller && controller.controller_path end def punctuate_body!(part) diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index b4f649385a..e359b0bdac 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -10,6 +10,7 @@ module ActionView #:nodoc: autoload :CsrfHelper, 'action_view/helpers/csrf_helper' autoload :DateHelper, 'action_view/helpers/date_helper' autoload :DebugHelper, 'action_view/helpers/debug_helper' + autoload :DeprecatedBlockHelpers, 'action_view/helpers/deprecated_block_helpers' autoload :FormHelper, 'action_view/helpers/form_helper' autoload :FormOptionsHelper, 'action_view/helpers/form_options_helper' autoload :FormTagHelper, 'action_view/helpers/form_tag_helper' diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index e106bb0897..4e12cdab54 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -5,9 +5,11 @@ require 'active_support/core_ext/enumerable' require 'active_support/core_ext/kernel/reporting' module ActionView - class Base - @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>".html_safe } - cattr_accessor :field_error_proc + ActionView.base_hook do + class ActionView::Base + @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>".html_safe } + cattr_accessor :field_error_proc + end end module Helpers @@ -80,13 +82,13 @@ module ActionView record = convert_to_model(record) options = options.symbolize_keys - options[:action] ||= record.new_record? ? "create" : "update" + options[:action] ||= record.persisted? ? "update" : "create" action = url_for(:action => options[:action], :id => record) submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize contents = form_tag({:action => action}, :method =>(options[:method] || 'post'), :enctype => options[:multipart] ? 'multipart/form-data': nil) - contents.safe_concat hidden_field(record_name, :id) unless record.new_record? + contents.safe_concat hidden_field(record_name, :id) if record.persisted? contents.safe_concat all_input_tags(record, record_name, options) yield contents if block_given? contents.safe_concat submit_tag(submit_value) @@ -127,7 +129,7 @@ module ActionView if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) && (errors = obj.errors[method]) content_tag("div", - "#{options[:prepend_text]}#{ERB::Util.html_escape(errors.first)}#{options[:append_text]}", + (options[:prepend_text].html_safe << errors.first).safe_concat(options[:append_text]), :class => options[:css_class] ) else @@ -226,16 +228,16 @@ module ActionView error_messages = objects.sum do |object| object.errors.full_messages.map do |msg| - content_tag(:li, ERB::Util.html_escape(msg)) + content_tag(:li, msg) end - end.join + end.join.html_safe contents = '' contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank? contents << content_tag(:p, message) unless message.blank? contents << content_tag(:ul, error_messages) - content_tag(:div, contents, html) + content_tag(:div, contents.html_safe, html) end else '' diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 96976ce45f..0c488b6793 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -11,7 +11,7 @@ module ActionView # the assets exist before linking to them: # # image_tag("rails.png") - # # => <img alt="Rails src="/images/rails.png?1230601161" /> + # # => <img alt="Rails" src="/images/rails.png?1230601161" /> # stylesheet_link_tag("application") # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" /> # @@ -133,13 +133,6 @@ module ActionView # change. You can use something like Live HTTP Headers for Firefox to verify # that the cache is indeed working. module AssetTagHelper - assets_dir = defined?(Rails.public_path) ? Rails.public_path : "public" - ActionView::DEFAULT_CONFIG = { - :assets_dir => assets_dir, - :javascripts_dir => "#{assets_dir}/javascripts", - :stylesheets_dir => "#{assets_dir}/stylesheets", - } - JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls', 'rails'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES) # Returns a link tag that browsers and news readers can use to auto-detect @@ -530,7 +523,7 @@ module ActionView options.symbolize_keys! src = options[:src] = path_to_image(source) - options[:alt] ||= File.basename(src, '.*').split('.').first.to_s.capitalize + options[:alt] ||= File.basename(src, '.*').capitalize if size = options.delete(:size) options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} @@ -648,8 +641,8 @@ module ActionView source = rewrite_asset_path(source) if has_request && include_host - unless source =~ %r{^#{ActionController::Base.relative_url_root}/} - source = "#{ActionController::Base.relative_url_root}#{source}" + unless source =~ %r{^#{controller.config.relative_url_root}/} + source = "#{controller.config.relative_url_root}#{source}" end end end @@ -677,7 +670,7 @@ module ActionView # or the value returned from invoking the proc if it's a proc or the value from # invoking call if it's an object responding to call. def compute_asset_host(source) - if host = ActionController::Base.asset_host + if host = config.asset_host if host.is_a?(Proc) || host.respond_to?(:call) case host.is_a?(Proc) ? host.arity : host.method(:call).arity when 2 diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index 9951e11a37..58c3a8752e 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -8,7 +8,7 @@ module ActionView # Full usage example: # # config/routes.rb: - # ActionController::Routing::Routes.draw do |map| + # Basecamp::Application.routes.draw do |map| # map.resources :posts # map.root :controller => "posts" # end @@ -114,7 +114,7 @@ module ActionView feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)} xml.feed(feed_opts) do - xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}") + xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}") xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port)) xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url) diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 8c48300ed3..75fc2fddeb 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -30,14 +30,10 @@ module ActionView # <b><%= @greeting %></b> # </body></html> # - def capture(*args, &block) - # Return captured buffer in erb. - if block_called_from_erb?(block) - with_output_buffer { block.call(*args) } - else - # Return block result otherwise, but protect buffer also. - with_output_buffer { return block.call(*args) } - end + def capture(*args) + value = nil + buffer = with_output_buffer { value = yield *args } + buffer.presence || value end # Calling content_for stores a block of markup in an identifier for later use. @@ -143,7 +139,7 @@ module ActionView # Defaults to a new empty string. def with_output_buffer(buf = nil) #:nodoc: unless buf - buf = ActiveSupport::SafeBuffer.new + buf = ActionView::OutputBuffer.new buf.force_encoding(output_buffer.encoding) if buf.respond_to?(:force_encoding) end self.output_buffer, old_buffer = buf, output_buffer diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 8be2f76bd6..42018ee261 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -1,5 +1,6 @@ require "date" require 'action_view/helpers/tag_helper' +require 'active_support/core_ext/hash/slice' module ActionView module Helpers @@ -815,7 +816,7 @@ module ActionView tag_options[:selected] = "selected" if selected == i select_options << content_tag(:option, value, tag_options) end - select_options.join("\n") + "\n" + (select_options.join("\n") + "\n").html_safe end # Builds select tag from date type and html select options @@ -833,9 +834,9 @@ module ActionView select_html = "\n" select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank] select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt] - select_html << select_options_as_html.to_s + select_html << select_options_as_html - (content_tag(:select, select_html, select_options) + "\n").html_safe + (content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe end # Builds a prompt option tag with supplied options or from default options @@ -865,7 +866,7 @@ module ActionView :id => input_id_from_type(type), :name => input_name_from_type(type), :value => value - }) + "\n").html_safe + }.merge(@html_options.slice(:disabled))) + "\n").html_safe end # Returns the name attribute for the input tag @@ -907,7 +908,7 @@ module ActionView when :hour (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator] when :minute - @options[:time_separator] + @options[:discard_minute] ? "" : @options[:time_separator] when :second @options[:include_seconds] ? @options[:time_separator] : "" end diff --git a/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb b/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb new file mode 100644 index 0000000000..3d0657e873 --- /dev/null +++ b/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb @@ -0,0 +1,52 @@ +module ActionView + module Helpers + module DeprecatedBlockHelpers + extend ActiveSupport::Concern + + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TextHelper + include ActionView::Helpers::JavaScriptHelper + include ActionView::Helpers::FormHelper + + def content_tag(*, &block) + block_called_from_erb?(block) ? safe_concat(super) : super + end + + def javascript_tag(*, &block) + block_called_from_erb?(block) ? safe_concat(super) : super + end + + def form_for(*, &block) + block_called_from_erb?(block) ? safe_concat(super) : super + end + + def form_tag(*, &block) + block_called_from_erb?(block) ? safe_concat(super) : super + end + + def fields_for(*, &block) + block_called_from_erb?(block) ? safe_concat(super) : super + end + + def field_set_tag(*, &block) + block_called_from_erb?(block) ? safe_concat(super) : super + end + + BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template' + + if RUBY_VERSION < '1.9.0' + # Check whether we're called from an erb template. + # We'd return a string in any other case, but erb <%= ... %> + # can't take an <% end %> later on, so we have to use <% ... %> + # and implicitly concat. + def block_called_from_erb?(block) + block && eval(BLOCK_CALLED_FROM_ERB, block) + end + else + def block_called_from_erb?(block) + block && eval(BLOCK_CALLED_FROM_ERB, block.binding) + end + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 305d6b3128..7293145964 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -92,6 +92,10 @@ module ActionView # link:classes/ActionView/Helpers/DateHelper.html, and # link:classes/ActionView/Helpers/ActiveRecordHelper.html module FormHelper + extend ActiveSupport::Concern + + include FormTagHelper + # Creates a form and a scope around a specific model object that is used # as a base for questioning about values for the fields. # @@ -309,21 +313,20 @@ module ActionView options[:html][:remote] = true if options.delete(:remote) - safe_concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {})) - fields_for(object_name, *(args << options), &proc) - safe_concat('</form>') + output = form_tag(options.delete(:url) || {}, options.delete(:html) || {}) + output << fields_for(object_name, *(args << options), &proc) + output.safe_concat('</form>') end def apply_form_for_options!(object_or_array, options) #:nodoc: object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array - object = convert_to_model(object) html_options = - if object.respond_to?(:new_record?) && object.new_record? - { :class => dom_class(object, :new), :id => dom_id(object), :method => :post } - else + if object.respond_to?(:persisted?) && object.persisted? { :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put } + else + { :class => dom_class(object, :new), :id => dom_id(object), :method => :post } end options[:html] ||= {} @@ -529,7 +532,10 @@ module ActionView end builder = options[:builder] || ActionView::Base.default_form_builder - yield builder.new(object_name, object, self, options, block) + + with_output_buffer do + yield builder.new(object_name, object, self, options, block) + end end # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object @@ -1150,7 +1156,7 @@ module ActionView def submit_default_value object = @object.respond_to?(:to_model) ? @object.to_model : @object - key = object ? (object.new_record? ? :create : :update) : :submit + key = object ? (object.persisted? ? :update : :create) : :submit model = if object.class.respond_to?(:model_name) object.class.model_name.human @@ -1176,7 +1182,7 @@ module ActionView association = args.shift association = association.to_model if association.respond_to?(:to_model) - if association.respond_to?(:new_record?) + if association.respond_to?(:persisted?) association = [association] if @object.send(association_name).is_a?(Array) elsif !association.is_a?(Array) association = @object.send(association_name) @@ -1184,9 +1190,11 @@ module ActionView if association.is_a?(Array) explicit_child_index = options[:child_index] - association.map do |child| - fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, options, block) - end.join + output = ActiveSupport::SafeBuffer.new + association.each do |child| + output << fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, options, block) + end + output elsif association fields_for_nested_model(name, association, options, block) end @@ -1195,13 +1203,13 @@ module ActionView def fields_for_nested_model(name, object, options, block) object = object.to_model if object.respond_to?(:to_model) - if object.new_record? - @template.fields_for(name, object, options, &block) - else + if object.persisted? @template.fields_for(name, object, options) do |builder| block.call(builder) @template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id? end + else + @template.fields_for(name, object, options, &block) end end @@ -1212,8 +1220,10 @@ module ActionView end end - class Base - cattr_accessor :default_form_builder - @@default_form_builder = ::ActionView::Helpers::FormBuilder + ActionView.base_hook do + class ActionView::Base + cattr_accessor :default_form_builder + @@default_form_builder = ::ActionView::Helpers::FormBuilder + end end end diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 21acfbbee8..4c523d4b20 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -97,7 +97,9 @@ module ActionView # </select> # module FormOptionsHelper + # ERB::Util can mask some helpers like textilize. Make sure to include them. include ERB::Util + include TextHelper # Create a select tag and a series of contained option tags for the provided object and method. # The option currently held by the object will be selected, provided that the object is available. @@ -572,10 +574,9 @@ module ActionView end if value.blank? && options[:prompt] prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select') - "<option value=\"\">#{prompt}</option>\n" + option_tags - else - option_tags + option_tags = "<option value=\"\">#{prompt}</option>\n" + option_tags end + option_tags.html_safe end end diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 9d76870646..07694f5ebb 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -10,6 +10,11 @@ module ActionView # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying # <tt>:disabled => true</tt> will give <tt>disabled="disabled"</tt>. module FormTagHelper + extend ActiveSupport::Concern + + include UrlHelper + include TextHelper + # Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like # ActionController::Base#url_for. The method for the form defaults to POST. # @@ -90,9 +95,9 @@ module ActionView html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name if blank = options.delete(:include_blank) if blank.kind_of?(String) - option_tags = "<option value=\"\">#{blank}</option>" + option_tags + option_tags = "<option value=\"\">#{blank}</option>".html_safe + option_tags else - option_tags = "<option value=\"\"></option>" + option_tags + option_tags = "<option value=\"\"></option>".html_safe + option_tags end end content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) @@ -279,7 +284,7 @@ module ActionView escape = options.key?("escape") ? options.delete("escape") : true content = html_escape(content) if escape - content_tag :textarea, content, { "name" => name, "id" => sanitize_to_id(name) }.update(options) + content_tag :textarea, content.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options) end # Creates a check box form input tag. @@ -441,10 +446,10 @@ module ActionView # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset> def field_set_tag(legend = nil, options = nil, &block) content = capture(&block) - safe_concat(tag(:fieldset, options, true)) - safe_concat(content_tag(:legend, legend)) unless legend.blank? - concat(content) - safe_concat("</fieldset>") + output = tag(:fieldset, options, true) + output.safe_concat(content_tag(:legend, legend)) unless legend.blank? + output.concat(content) + output.safe_concat("</fieldset>") end private @@ -477,9 +482,10 @@ module ActionView def form_tag_in_block(html_options, &block) content = capture(&block) - safe_concat(form_tag_html(html_options)) - concat(content) - safe_concat("</form>") + output = ActiveSupport::SafeBuffer.new + output.safe_concat(form_tag_html(html_options)) + output << content + output.safe_concat("</form>") end def token_tag diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index 0a901b2d2b..07eee3b399 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -83,17 +83,11 @@ module ActionView content_or_options_with_block end - tag = content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS)) - - if block_called_from_erb?(block) - safe_concat(tag) - else - tag - end + content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS)) end def javascript_cdata_section(content) #:nodoc: - "\n//#{cdata_section("\n#{content}\n//")}\n" + "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe end end end diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 3d3502a08b..46e41bc406 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -28,27 +28,25 @@ module ActionView # number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".") # => +1.123.555.1234 x 1343 def number_to_phone(number, options = {}) - number = number.to_s.strip unless number.nil? + return nil if number.nil? + + number = number.to_s.strip options = options.symbolize_keys area_code = options[:area_code] || nil delimiter = options[:delimiter] || "-" extension = options[:extension].to_s.strip || nil country_code = options[:country_code] || nil - begin - str = "" - str << "+#{country_code}#{delimiter}" unless country_code.blank? - str << if area_code - number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4}$)/,"(\\1) \\2#{delimiter}\\3") - else - number.gsub!(/([0-9]{0,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") - number.starts_with?('-') ? number.slice!(1..-1) : number - end - str << " x #{extension}" unless extension.blank? - str - rescue - number + str = "" + str << "+#{country_code}#{delimiter}" unless country_code.blank? + str << if area_code + number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4}$)/,"(\\1) \\2#{delimiter}\\3") + else + number.gsub!(/([0-9]{0,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") + number.starts_with?('-') ? number.slice!(1..-1) : number end + str << " x #{extension}" unless extension.blank? + str end # Formats a +number+ into a currency string (e.g., $13.65). You can customize the format @@ -87,13 +85,14 @@ module ActionView format = options[:format] || defaults[:format] separator = '' if precision == 0 - begin - format.gsub(/%n/, number_with_precision(number, - :precision => precision, - :delimiter => delimiter, - :separator => separator) - ).gsub(/%u/, unit).html_safe - rescue + value = number_with_precision(number, + :precision => precision, + :delimiter => delimiter, + :separator => separator) + + if value + format.gsub(/%n/, value).gsub(/%u/, unit).html_safe + else number end end @@ -122,14 +121,11 @@ module ActionView separator = options[:separator] || defaults[:separator] delimiter = options[:delimiter] || defaults[:delimiter] - begin - number_with_precision(number, - :precision => precision, - :separator => separator, - :delimiter => delimiter) + "%" - rescue - number - end + value = number_with_precision(number, + :precision => precision, + :separator => separator, + :delimiter => delimiter) + value ? value + "%" : number end # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can @@ -168,11 +164,11 @@ module ActionView delimiter ||= (options[:delimiter] || defaults[:delimiter]) separator ||= (options[:separator] || defaults[:separator]) - begin - parts = number.to_s.split('.') + parts = number.to_s.split('.') + if parts[0] parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}") parts.join(separator) - rescue + else number end end @@ -216,11 +212,17 @@ module ActionView delimiter ||= (options[:delimiter] || defaults[:delimiter]) begin + value = Float(number) + rescue ArgumentError, TypeError + value = nil + end + + if value rounded_number = BigDecimal.new((Float(number) * (10 ** precision)).to_s).round.to_f / 10 ** precision number_with_delimiter("%01.#{precision}f" % rounded_number, :separator => separator, :delimiter => delimiter) - rescue + else number end end @@ -293,17 +295,13 @@ module ActionView unit_key = STORAGE_UNITS[exponent] unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true) - begin - escaped_separator = Regexp.escape(separator) - formatted_number = number_with_precision(number, - :precision => precision, - :separator => separator, - :delimiter => delimiter - ).sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') - storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit) - rescue - number - end + escaped_separator = Regexp.escape(separator) + formatted_number = number_with_precision(number, + :precision => precision, + :separator => separator, + :delimiter => delimiter + ).sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') + storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit) end end end diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 51510449bd..4d6ea7dfb2 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -182,9 +182,11 @@ module ActionView def initialize(context, &block) #:nodoc: context._evaluate_assigns_and_ivars @context, @lines = context, [] - include_helpers_from_context - @context.with_output_buffer(@lines) do - @context.instance_exec(self, &block) + @context.update_details(:formats => [:js, :html]) do + include_helpers_from_context + @context.with_output_buffer(@lines) do + @context.instance_exec(self, &block) + end end end @@ -569,15 +571,19 @@ module ActionView end end - def render(*options_for_render) - old_formats = @context && @context.formats + def render(*options) + with_formats(:html) do + case option = options.first + when Hash + @context.render(*options) + else + option.to_s + end + end + end - @context.reset_formats([:html]) if @context - Hash === options_for_render.first ? - @context.render(*options_for_render) : - options_for_render.first.to_s - ensure - @context.reset_formats(old_formats) if @context + def with_formats(*args) + @context ? @context.update_details(:formats => args) { yield } : yield end def javascript_object_for(object) diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index ee10f8f3bb..8ae2e5f28f 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -7,6 +7,9 @@ module ActionView module TagHelper include ERB::Util + extend ActiveSupport::Concern + include CaptureHelper + BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer autoplay controls loop selected hidden scoped async defer reversed ismap seemless muted required @@ -69,13 +72,7 @@ module ActionView def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) if block_given? options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) - content_tag = content_tag_string(name, capture(&block), options, escape) - - if block_called_from_erb?(block) - safe_concat(content_tag) - else - content_tag - end + content_tag_string(name, capture(&block), options, escape) else content_tag_string(name, content_or_options_with_block, options, escape) end @@ -109,25 +106,10 @@ module ActionView end private - BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template' - - if RUBY_VERSION < '1.9.0' - # Check whether we're called from an erb template. - # We'd return a string in any other case, but erb <%= ... %> - # can't take an <% end %> later on, so we have to use <% ... %> - # and implicitly concat. - def block_called_from_erb?(block) - block && eval(BLOCK_CALLED_FROM_ERB, block) - end - else - def block_called_from_erb?(block) - block && eval(BLOCK_CALLED_FROM_ERB, block.binding) - end - end def content_tag_string(name, content, options, escape = true) tag_options = tag_options(options, escape) if options - "<#{name}#{tag_options}>#{content}</#{name}>".html_safe + ("<#{name}#{tag_options}>".html_safe << content.to_s).safe_concat("</#{name}>") end def tag_options(options, escape = true) diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index c348ea7a0d..8a89ee58a0 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -12,9 +12,10 @@ module ActionView # prepend the key with a period, nothing is converted. def translate(key, options = {}) options[:raise] = true - I18n.translate(scope_key_by_partial(key), options).html_safe + translation = I18n.translate(scope_key_by_partial(key), options) + translation.is_a?(Array) ? translation.map { |entry| entry.html_safe } : translation.html_safe rescue I18n::MissingTranslationData => e - keys = I18n.send(:normalize_translation_keys, e.locale, e.key, e.options[:scope]) + keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) content_tag('span', keys.join(', '), :class => 'translation_missing') end alias :t :translate @@ -28,7 +29,7 @@ module ActionView private def scope_key_by_partial(key) - if key.to_s.first == "." + if (key.respond_to?(:join) ? key.join : key.to_s).first == "." if @_virtual_path @_virtual_path.gsub(%r{/_?}, ".") + key.to_s else @@ -40,4 +41,4 @@ module ActionView end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index fde4bfa4ce..ae1385f3b7 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -5,7 +5,7 @@ require 'active_support/core_ext/hash/keys' module ActionView module Helpers #:nodoc: # Provides a set of methods for making links and getting URLs that - # depend on the routing subsystem (see ActionController::Routing). + # depend on the routing subsystem (see ActionDispatch::Routing). # This allows you to use the same format for links in views # and controllers. module UrlHelper @@ -63,7 +63,7 @@ module ActionView # # => /testing/jump/#tax&ship # # <%= url_for(Workshop.new) %> - # # relies on Workshop answering a new_record? call (and in this case returning true) + # # relies on Workshop answering a persisted? call (and in this case returning false) # # => /workshops # # <%= url_for(@workshop) %> @@ -202,8 +202,6 @@ module ActionView # # link_to("Destroy", "http://www.example.com", :method => :delete, :confirm => "Are you sure?") # # => <a href='http://www.example.com' rel="nofollow" data-method="delete" data-confirm="Are you sure?">Destroy</a> - - # def link_to(*args, &block) if block_given? options = args.first || {} @@ -226,7 +224,7 @@ module ActionView end href_attr = "href=\"#{url}\"" unless href - "<a #{href_attr}#{tag_options}>#{ERB::Util.h(name || url)}</a>".html_safe + ("<a #{href_attr}#{tag_options}>".html_safe << (name || url)).safe_concat("</a>") end end @@ -469,14 +467,12 @@ module ActionView extras << "subject=#{Rack::Utils.escape(subject).gsub("+", "%20")}&" unless subject.nil? extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty? - email_address = email_address.to_s - - email_address_obfuscated = email_address.dup + email_address_obfuscated = html_escape(email_address) email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at") email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot") if encode == "javascript" - "document.write('#{content_tag("a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c| + "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c| string << sprintf("%%%x", c) end "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>" @@ -493,9 +489,9 @@ module ActionView char = c.chr string << (char =~ /\w/ ? sprintf("%%%x", c) : char) end - content_tag "a", name || email_address_encoded, html_options.merge({ "href" => "#{string}#{extras}" }) + content_tag "a", name || email_address_encoded.html_safe, html_options.merge({ "href" => "#{string}#{extras}" }) else - content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" }) + content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" }) end end @@ -548,10 +544,11 @@ module ActionView # submitted url doesn't have any either. This lets the function # work with things like ?order=asc if url_string.index("?") - request_uri = request.request_uri + request_uri = request.fullpath else - request_uri = request.request_uri.split('?').first + request_uri = request.path end + if url_string =~ /^\w+:\/\// url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}" else diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb new file mode 100644 index 0000000000..27ee8b23c9 --- /dev/null +++ b/actionpack/lib/action_view/lookup_context.rb @@ -0,0 +1,154 @@ +require 'active_support/core_ext/object/try' +require 'active_support/core_ext/object/blank' + +module ActionView + # LookupContext is the object responsible to hold all information required to lookup + # templates, i.e. view paths and details. The LookupContext is also responsible to + # generate a key, given to view paths, used in the resolver cache lookup. Since + # this key is generated just once during the request, it speeds up all cache accesses. + class LookupContext #:nodoc: + mattr_accessor :fallbacks + @@fallbacks = [FileSystemResolver.new(""), FileSystemResolver.new("/")] + + mattr_accessor :registered_details + self.registered_details = [] + + def self.register_detail(name, options = {}, &block) + self.registered_details << name + + Setters.send :define_method, :"_#{name}_defaults", &block + Setters.module_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{name}=(value) + value = Array(value.presence || _#{name}_defaults) + #{"value << nil unless value.include?(nil)" unless options[:allow_nil] == false} + + unless value == @details[:#{name}] + @details_key, @details = nil, @details.merge(:#{name} => value) + @details.freeze + end + end + METHOD + end + + # Holds raw setters for the registered details. + module Setters #:nodoc: + end + + register_detail(:formats) { Mime::SET.symbols } + register_detail(:locale) { [I18n.locale] } + + class DetailsKey #:nodoc: + attr_reader :details + alias :eql? :equal? + + @details_keys = Hash.new + + def self.get(details) + @details_keys[details] ||= new(details) + end + + def initialize(details) + @details, @hash = details, details.hash + end + end + + def initialize(view_paths, details = {}) + @details, @details_key = {}, nil + self.view_paths = view_paths + self.details = details + end + + module ViewPaths + attr_reader :view_paths + + # Whenever setting view paths, makes a copy so we can manipulate then in + # instance objects as we wish. + def view_paths=(paths) + @view_paths = ActionView::Base.process_view_paths(paths) + end + + def find(name, prefix = nil, partial = false) + @view_paths.find(name, prefix, partial, details, details_key) + end + + def find_all(name, prefix = nil, partial = false) + @view_paths.find_all(name, prefix, partial, details, details_key) + end + + def exists?(name, prefix = nil, partial = false) + @view_paths.exists?(name, prefix, partial, details, details_key) + end + + # Add fallbacks to the view paths. Useful in cases you are rendering a :file. + def with_fallbacks + added_resolvers = 0 + self.class.fallbacks.each do |resolver| + next if view_paths.include?(resolver) + view_paths.push(resolver) + added_resolvers += 1 + end + yield + ensure + added_resolvers.times { view_paths.pop } + end + end + + module Details + attr_reader :details + + def details=(given_details) + registered_details.each { |key| send(:"#{key}=", given_details[key]) } + end + + def details_key + @details_key ||= DetailsKey.get(@details) + end + + # Shortcut to read formats from details. + def formats + @details[:formats].compact + end + + # Overload formats= to reject [:"*/*"] values. + def formats=(value, freeze=true) + value = nil if value == [:"*/*"] + super(value) + end + + # Shortcut to read locale. + def locale + I18n.locale + end + + # Overload locale= to also set the I18n.locale. If the current I18n.config object responds + # to i18n_config, it means that it's has a copy of the original I18n configuration and it's + # acting as proxy, which we need to skip. + def locale=(value) + value = value.first if value.is_a?(Array) + config = I18n.config.respond_to?(:i18n_config) ? I18n.config.i18n_config : I18n.config + config.locale = value if value + super(I18n.locale) + end + + # Update the details keys by merging the given hash into the current + # details hash. If a block is given, the details are modified just during + # the execution of the block and reverted to the previous value after. + def update_details(new_details) + old_details = @details + self.details = old_details.merge(new_details) + + if block_given? + begin + yield + ensure + @details = old_details + end + end + end + end + + include Setters + include Details + include ViewPaths + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index 6e55d69d00..35927d09d1 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -1,80 +1,39 @@ module ActionView #:nodoc: class PathSet < Array #:nodoc: - def self.type_cast(obj, cache = nil) - # TODO: Clean this up - if obj.is_a?(String) - if cache.nil? - cache = !defined?(Rails.application) || Rails.application.config.cache_classes + %w(initialize << concat insert push unshift).each do |method| + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{method}(*args) + super + typecast! end - FileSystemResolverWithFallback.new(obj, :cache => cache) - else - obj - end - end - - def initialize(*args) - super(*args).map! { |obj| self.class.type_cast(obj) } - end - - def <<(obj) - super(self.class.type_cast(obj)) - end - - def concat(array) - super(array.map! { |obj| self.class.type_cast(obj) }) - end - - def insert(index, obj) - super(index, self.class.type_cast(obj)) - end - - def push(*objs) - super(*objs.map { |obj| self.class.type_cast(obj) }) + METHOD end - def unshift(*objs) - super(*objs.map { |obj| self.class.type_cast(obj) }) + def find(path, prefix = nil, partial = false, details = {}, key = nil) + template = find_all(path, prefix, partial, details, key).first + raise MissingTemplate.new(self, "#{prefix}/#{path}", details, partial) unless template + template end - def find(path, details = {}, prefix = nil, partial = false) - template_path = path - - each do |load_path| - if template = load_path.find(template_path, details, prefix, partial) - return template - end + def find_all(*args) + each do |resolver| + templates = resolver.find_all(*args) + return templates unless templates.empty? end - - raise ActionView::MissingTemplate.new(self, "#{prefix}/#{path}", details, partial) + [] end - - def exists?(path, extension = nil, prefix = nil, partial = false) - template_path = path.sub(/^\//, '') - each do |load_path| - return true if template = load_path.find(template_path, extension, prefix, partial) - end - false + def exists?(*args) + find_all(*args).any? end - def find_template(original_template_path, format = nil, html_fallback = true) - return original_template_path if original_template_path.respond_to?(:render) - template_path = original_template_path.sub(/^\//, '') + protected - each do |load_path| - if template = load_path.find(template_path, format) - return template - # Try to find html version if the format is javascript - elsif format == :js && html_fallback && template = load_path["#{template_path}.#{I18n.locale}.html"] - return template - elsif format == :js && html_fallback && template = load_path["#{template_path}.html"] - return template - end + def typecast! + each_with_index do |path, i| + next unless path.is_a?(String) + self[i] = FileSystemResolver.new(path) end - - return Template.new(original_template_path, original_template_path.to_s =~ /\A\// ? "" : ".") if File.file?(original_template_path) - - raise MissingTemplate.new(self, original_template_path, format) end end end diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb index d9e2557d89..2e5d115630 100644 --- a/actionpack/lib/action_view/railtie.rb +++ b/actionpack/lib/action_view/railtie.rb @@ -5,12 +5,14 @@ module ActionView class Railtie < Rails::Railtie railtie_name :action_view - require "action_view/railties/subscriber" - subscriber ActionView::Railties::Subscriber.new + require "action_view/railties/log_subscriber" + log_subscriber ActionView::Railties::LogSubscriber.new initializer "action_view.cache_asset_timestamps" do |app| unless app.config.cache_classes - ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false + ActionView.base_hook do + ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false + end end end end diff --git a/actionpack/lib/action_view/railties/subscriber.rb b/actionpack/lib/action_view/railties/log_subscriber.rb index 803f19379c..9487a10706 100644 --- a/actionpack/lib/action_view/railties/subscriber.rb +++ b/actionpack/lib/action_view/railties/log_subscriber.rb @@ -1,6 +1,6 @@ module ActionView module Railties - class Subscriber < Rails::Subscriber + class LogSubscriber < Rails::LogSubscriber def render_template(event) message = "Rendered #{from_rails_root(event.payload[:identifier])}" message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] diff --git a/actionpack/lib/action_view/render/layouts.rb b/actionpack/lib/action_view/render/layouts.rb new file mode 100644 index 0000000000..8688de3d18 --- /dev/null +++ b/actionpack/lib/action_view/render/layouts.rb @@ -0,0 +1,65 @@ +require 'active_support/core_ext/object/try' + +module ActionView + module Layouts + + # You can think of a layout as a method that is called with a block. _layout_for + # returns the contents that are yielded to the layout. If the user calls yield + # :some_name, the block, by default, returns content_for(:some_name). If the user + # calls yield, the default block returns content_for(:layout). + # + # The user can override this default by passing a block to the layout. + # + # ==== Example + # + # # The template + # <% render :layout => "my_layout" do %>Content<% end %> + # + # # The layout + # <html><% yield %></html> + # + # In this case, instead of the default block, which would return content_for(:layout), + # this method returns the block that was passed in to render layout, and the response + # would be <html>Content</html>. + # + # Finally, the block can take block arguments, which can be passed in by yield. + # + # ==== Example + # + # # The template + # <% render :layout => "my_layout" do |customer| %>Hello <%= customer.name %><% end %> + # + # # The layout + # <html><% yield Struct.new(:name).new("David") %></html> + # + # In this case, the layout would receive the block passed into <tt>render :layout</tt>, + # and the Struct specified in the layout would be passed into the block. The result + # would be <html>Hello David</html>. + def _layout_for(name = nil, &block) #:nodoc: + if !block || name + @_content_for[name || :layout] + else + capture(&block) + end + end + + # This is the method which actually finds the layout using details in the lookup + # context object. If no layout is found, it checkes if at least a layout with + # the given name exists across all details before raising the error. + def _find_layout(layout) #:nodoc: + begin + layout =~ /^\// ? + with_fallbacks { find(layout) } : find(layout) + rescue ActionView::MissingTemplate => e + update_details(:formats => nil) do + raise unless exists?(layout) + end + end + end + + # Contains the logic that actually renders the layout. + def _render_layout(layout, locals, &block) #:nodoc: + layout.render(self, locals){ |*name| _layout_for(*name, &block) } + end + end +end diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 62d31662db..d34ab0354c 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -174,17 +174,11 @@ module ActionView class PartialRenderer PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} } - TEMPLATES = Hash.new {|h,k| h[k] = {} } - - attr_reader :template def initialize(view_context, options, block) @view = view_context @partial_names = PARTIAL_NAMES[@view.controller.class] - key = Thread.current[:format_locale_key] - @templates = TEMPLATES[key] if key - setup(options, block) end @@ -228,6 +222,7 @@ module ActionView if !@block && (layout = @options[:layout]) content = @view._render_layout(find_template(layout), @locals){ content } end + content end end @@ -244,9 +239,9 @@ module ActionView end def collection_with_template(template = @template) - segments, locals, as = [], @locals, @options[:as] || template.variable_name + segments, locals, as, template = [], @locals, @options[:as] || @template.variable_name, @template - counter_name = template.counter_name + counter_name = template.counter_name locals[counter_name] = -1 @collection.each do |object| @@ -256,7 +251,6 @@ module ActionView segments << template.render(@view, locals) end - @template = template segments end @@ -277,7 +271,7 @@ module ActionView end def render_partial(object = @object) - locals, view = @locals, @view + locals, view, template = @locals, @view, @template object ||= locals[template.variable_name] locals[@options[:as] || template.variable_name] = object @@ -288,6 +282,7 @@ module ActionView end private + def collection if @object.respond_to?(:to_ary) @object @@ -296,20 +291,10 @@ module ActionView end end - def find_template(path = @path) - unless @templates - path && _find_template(path) - else - path && @templates[path] ||= _find_template(path) - end - end - - def _find_template(path) - if controller = @view.controller - prefix = controller.controller_path unless path.include?(?/) - end - - @view.find(path, {:formats => @view.formats}, prefix, true) + def find_template(path=@path) + return path unless path.is_a?(String) + prefix = @view.controller_path unless path.include?(?/) + @view.find(path, prefix, true) end def partial_path(object = @object) @@ -324,21 +309,8 @@ module ActionView end end - def render_partial(options) - _evaluate_assigns_and_ivars - - details = options[:_details] - - # Is this needed - self.formats = details[:formats] if details - renderer = PartialRenderer.new(self, options, nil) - text = renderer.render - options[:_template] = renderer.template - text - end - def _render_partial(options, &block) #:nodoc: - if defined? @renderer + if defined?(@renderer) @renderer.setup(options, block) else @renderer = PartialRenderer.new(self, options, block) diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index abc7c09991..47ea70f5ad 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -15,27 +15,11 @@ module ActionView def render(options = {}, locals = {}, &block) #:nodoc: case options when Hash - layout = options[:layout] - options[:locals] ||= {} - if block_given? - return safe_concat(_render_partial(options.merge(:partial => layout), &block)) - elsif options.key?(:partial) - return _render_partial(options) - end - - template = if options[:file] - find(options[:file], {:formats => formats}) - elsif options[:inline] - handler = Template.handler_class_for_extension(options[:type] || "erb") - Template.new(options[:inline], "inline template", handler, {}) - elsif options[:text] - Template::Text.new(options[:text]) - end - - if template - layout = find(layout, {:formats => formats}) if layout - _render_template(template, layout, :locals => options[:locals]) + content = _render_partial(options.merge(:partial => options[:layout]), &block) + safe_concat(content) + else + _render(options) end when :update update_page(&block) @@ -44,61 +28,57 @@ module ActionView end end - # You can think of a layout as a method that is called with a block. _layout_for - # returns the contents that are yielded to the layout. If the user calls yield - # :some_name, the block, by default, returns content_for(:some_name). If the user - # calls yield, the default block returns content_for(:layout). - # - # The user can override this default by passing a block to the layout. - # - # ==== Example - # - # # The template - # <% render :layout => "my_layout" do %>Content<% end %> - # - # # The layout - # <html><% yield %></html> - # - # In this case, instead of the default block, which would return content_for(:layout), - # this method returns the block that was passed in to render layout, and the response - # would be <html>Content</html>. - # - # Finally, the block can take block arguments, which can be passed in by yield. - # - # ==== Example - # - # # The template - # <% render :layout => "my_layout" do |customer| %>Hello <%= customer.name %><% end %> - # - # # The layout - # <html><% yield Struct.new(:name).new("David") %></html> - # - # In this case, the layout would receive the block passed into <tt>render :layout</tt>, - # and the Struct specified in the layout would be passed into the block. The result - # would be <html>Hello David</html>. - def _layout_for(name = nil, &block) - return @_content_for[name || :layout] if !block_given? || name - capture(&block) - end - # This is the API to render a ViewContext's template from a controller. - # - # Internal Options: - # _template:: The Template object to render - # _layout:: The layout, if any, to wrap the Template in - def render_template(options) + def render_template(options, &block) _evaluate_assigns_and_ivars - template, layout = options.values_at(:_template, :_layout) - _render_template(template, layout, options) + + # TODO Layout for partials should be handled here, because inside the + # partial renderer it looks for the layout as a partial. + if options.key?(:partial) && options[:layout] + options[:layout] = _find_layout(options[:layout]) + end + + _render(options, &block) + end + + # This method holds the common render logic for both controllers and + # views rendering stacks. + def _render(options) #:nodoc: + if options.key?(:partial) + _render_partial(options) + else + template = _determine_template(options) + yield template if block_given? + _render_template(template, options[:layout], options) + end end - def _render_template(template, layout = nil, options = {}) + # Determine the template to be rendered using the given options. + def _determine_template(options) #:nodoc: + if options.key?(:inline) + handler = Template.handler_class_for_extension(options[:type] || "erb") + Template.new(options[:inline], "inline template", handler, {}) + elsif options.key?(:text) + Template::Text.new(options[:text], self.formats.try(:first)) + elsif options.key?(:_template) + options[:_template] + elsif options.key?(:file) + with_fallbacks { find(options[:file], options[:prefix]) } + elsif options.key?(:template) + find(options[:template], options[:prefix]) + end + end + + # Renders the given template. An string representing the layout can be + # supplied as well. + def _render_template(template, layout = nil, options = {}) #:nodoc: locals = options[:locals] || {} + layout = _find_layout(layout) if layout ActiveSupport::Notifications.instrument("action_view.render_template", :identifier => template.identifier, :layout => layout.try(:identifier)) do - content = template.render(self, locals) + content = template.render(self, locals) { |*name| _layout_for(*name) } @_content_for[:layout] = content if layout @@ -110,8 +90,5 @@ module ActionView end end - def _render_layout(layout, locals, &block) - layout.render(self, locals){ |*name| _layout_for(*name, &block) } - end end end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index cd6b1930a1..b4fdb49d3b 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -16,23 +16,22 @@ module ActionView end extend Template::Handlers - attr_reader :source, :identifier, :handler, :mime_type, :formats, :details + + attr_reader :source, :identifier, :handler, :virtual_path, :formats def initialize(source, identifier, handler, details) @source = source @identifier = identifier @handler = handler - @details = details + + @partial = details[:partial] + @virtual_path = details[:virtual_path] @method_names = {} - format = details.delete(:format) || begin - # TODO: Clean this up - handler.respond_to?(:default_format) ? handler.default_format.to_sym.to_s : "html" - end - @mime_type = Mime::Type.lookup_by_extension(format.to_s) - @formats = [format.to_sym] - @formats << :html if format == :js - @details[:formats] = Array.wrap(format.to_sym) + format = details[:format] + format ||= handler.default_format.to_sym if handler.respond_to?(:default_format) + format ||= :html + @formats = [format.to_sym] end def render(view, locals, &block) @@ -47,19 +46,20 @@ module ActionView end end - # TODO: Figure out how to abstract this + def mime_type + @mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first + end + def variable_name - @variable_name ||= identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym + @variable_name ||= @virtual_path[%r'_?(\w+)(\.\w+)*$', 1].to_sym end - # TODO: Figure out how to abstract this def counter_name @counter_name ||= "#{variable_name}_counter".to_sym end - # TODO: kill hax def partial? - @details[:partial] + @partial end def inspect @@ -87,7 +87,7 @@ module ActionView source = <<-end_src def #{method_name}(local_assigns) - _old_virtual_path, @_virtual_path = @_virtual_path, #{@details[:virtual_path].inspect};_old_output_buffer = output_buffer;#{locals_code};#{code} + _old_virtual_path, @_virtual_path = @_virtual_path, #{@virtual_path.inspect};_old_output_buffer = output_buffer;#{locals_code};#{code} ensure @_virtual_path, self.output_buffer = _old_virtual_path, _old_output_buffer end diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index 4573a440d1..ac5902cc0e 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -3,10 +3,17 @@ require 'active_support/core_ext/string/output_safety' require 'erubis' module ActionView + class OutputBuffer < ActiveSupport::SafeBuffer + def <<(value) + super(value.to_s) + end + alias :append= :<< + end + module Template::Handlers class Erubis < ::Erubis::Eruby def add_preamble(src) - src << "@output_buffer = ActiveSupport::SafeBuffer.new;" + src << "@output_buffer = ActionView::OutputBuffer.new;" end def add_text(src, text) @@ -15,15 +22,15 @@ module ActionView end def add_expr_literal(src, code) - if code =~ /\s*raw\s+(.*)/ - src << "@output_buffer.safe_concat((" << $1 << ").to_s);" + if code =~ /(do|\{)(\s*\|[^|]*\|)?\s*\Z/ + src << '@output_buffer.append= ' << code else - src << '@output_buffer << ((' << code << ').to_s);' + src << '@output_buffer.append= (' << code << ');' end end def add_expr_escaped(src, code) - src << '@output_buffer << ' << escaped_expr(code) << ';' + src << '@output_buffer.append= ' << escaped_expr(code) << ';' end def add_postamble(src) @@ -42,14 +49,14 @@ module ActionView self.erb_trim_mode = '-' self.default_format = Mime::HTML - - cattr_accessor :erubis_implementation - self.erubis_implementation = Erubis + + cattr_accessor :erb_implementation + self.erb_implementation = Erubis def compile(template) source = template.source.gsub(/\A(<%(#.*coding[:=]\s*(\S+)\s*)-?%>)\s*\n?/, '') erb = "<% __in_erb_template=true %>#{source}" - result = self.class.erubis_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src + result = self.class.erb_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src result = "#{$2}\n#{result}" if $2 result end diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 8acfe6cad0..a43597e728 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -4,64 +4,47 @@ require "active_support/core_ext/array/wrap" require "action_view/template" module ActionView - # Abstract superclass class Resolver - - class_inheritable_accessor(:registered_details) - self.registered_details = {} - - def self.register_detail(name, options = {}) - registered_details[name] = lambda do |val| - val = Array.wrap(val || yield) - val |= [nil] unless options[:allow_nil] == false - val - end - end - - register_detail(:locale) { [I18n.locale] } - register_detail(:formats) { Mime::SET.symbols } - register_detail(:handlers, :allow_nil => false) do - Template::Handlers.extensions + def initialize + @cached = Hash.new { |h1,k1| h1[k1] = + Hash.new { |h2,k2| h2[k2] = Hash.new { |h3, k3| h3[k3] = {} } } } end - def initialize(options = {}) - @cache = options[:cache] - @cached = {} + def clear_cache + @cached.clear end - # Normalizes the arguments and passes it on to find_template def find(*args) find_all(*args).first end - def find_all(name, details = {}, prefix = nil, partial = nil) - details = normalize_details(details) + # Normalizes the arguments and passes it on to find_template. + def find_all(name, prefix=nil, partial=false, details={}, key=nil) name, prefix = normalize_name(name, prefix) + details = details.merge(:handlers => default_handlers) - cached([name, details, prefix, partial]) do - find_templates(name, details, prefix, partial) + cached(key, prefix, name, partial) do + find_templates(name, prefix, partial, details) end end private + def caching? + @caching ||= !defined?(Rails.application) || Rails.application.config.cache_classes + end + + def default_handlers + Template::Handlers.extensions + [nil] + end + # This is what child classes implement. No defaults are needed # because Resolver guarantees that the arguments are present and # normalized. - def find_templates(name, details, prefix, partial) + def find_templates(name, prefix, partial, details) raise NotImplementedError end - def normalize_details(details) - details = details.dup - # TODO: Refactor this concern out of the resolver - details.delete(:formats) if details[:formats] == [:"*/*"] - registered_details.each do |k, v| - details[k] = v.call(details[k]) - end - details - end - # Support legacy foo.erb names even though we now ignore .erb # as well as incorrectly putting part of the path in the template # name instead of the prefix. @@ -73,92 +56,73 @@ module ActionView return parts.pop, [prefix, *parts].compact.join("/") end - def cached(key) - return yield unless @cache - return @cached[key] if @cached.key?(key) - @cached[key] = yield + def cached(key, prefix, name, partial) + return yield unless key && caching? + scope = @cached[key][prefix][name] + if scope.key?(partial) + scope[partial] + else + scope[partial] = yield + end end end class PathResolver < Resolver - EXTENSION_ORDER = [:locale, :formats, :handlers] def to_s @path.to_s end - alias to_path to_s - - def find_templates(name, details, prefix, partial) - path = build_path(name, details, prefix, partial) - query(path, EXTENSION_ORDER.map { |ext| details[ext] }) - end + alias :to_path :to_s private - def build_path(name, details, prefix, partial) + def find_templates(name, prefix, partial, details) + path = build_path(name, prefix, partial, details) + query(partial, path, EXTENSION_ORDER.map { |ext| details[ext] }) + end + + def build_path(name, prefix, partial, details) path = "" path << "#{prefix}/" unless prefix.empty? path << (partial ? "_#{name}" : name) path end - def query(path, exts) + def query(partial, path, exts) query = File.join(@path, path) + exts.each do |ext| query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << '}' end Dir[query].reject { |p| File.directory?(p) }.map do |p| - Template.new(File.read(p), File.expand_path(p), *path_to_details(p)) + handler, format = extract_handler_and_format(p) + Template.new(File.read(p), File.expand_path(p), handler, + :partial => partial, :virtual_path => path, :format => format) end end - # # TODO: fix me - # # :api: plugin - def path_to_details(path) - # [:erb, :format => :html, :locale => :en, :partial => true/false] - if m = path.match(%r'((^|.*/)(_)?[\w-]+)((?:\.[\w-]+)*)\.(\w+)$') - partial = m[3] == '_' - details = (m[4]||"").split('.').reject { |e| e.empty? } - handler = Template.handler_class_for_extension(m[5]) + def extract_handler_and_format(path) + pieces = File.basename(path).split(".") + pieces.shift - format = Mime[details.last] && details.pop.to_sym - locale = details.last && details.pop.to_sym - - virtual_path = (m[1].gsub("#{@path}/", "") << details.join(".")) - - return handler, :format => format, :locale => locale, :partial => partial, - :virtual_path => virtual_path - end + handler = Template.handler_class_for_extension(pieces.pop) + format = pieces.last && Mime[pieces.last] && pieces.pop.to_sym + [handler, format] end end class FileSystemResolver < PathResolver - def initialize(path, options = {}) + def initialize(path) raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver) - super(options) + super() @path = Pathname.new(path).expand_path end - end - - # TODO: remove hack - class FileSystemResolverWithFallback < Resolver - def initialize(path, options = {}) - super(options) - @paths = [FileSystemResolver.new(path, options), FileSystemResolver.new("", options), FileSystemResolver.new("/", options)] - end - def find_templates(*args) - @paths.each do |p| - template = p.find_templates(*args) - return template unless template.empty? - end - [] - end - - def to_s - @paths.first.to_s + def eql?(resolver) + self.class.equal?(resolver.class) && to_path == resolver.to_path end + alias :== :eql? end end diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index 2abb352d4e..df394b0fb0 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -1,15 +1,10 @@ module ActionView #:nodoc: class Template class Text < String #:nodoc: - HTML = Mime[:html] - - def initialize(string, content_type = HTML) + def initialize(string, content_type = nil) super(string.to_s) - @content_type = Mime[content_type] || content_type - end - - def details - {:formats => [@content_type.to_sym]} + @content_type = Mime[content_type] || content_type if content_type + @content_type ||= Mime::TEXT end def identifier @@ -29,7 +24,7 @@ module ActionView #:nodoc: end def formats - [mime_type] + [@content_type.to_sym] end def partial? diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index fc29acea6d..1578ac9479 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -1,4 +1,5 @@ require 'action_controller/test_case' +require 'action_view' module ActionView class Base @@ -34,6 +35,8 @@ module ActionView @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new + @request.env.delete('PATH_INFO') + @params = {} end end @@ -60,6 +63,10 @@ module ActionView make_test_case_available_to_view! end + def config + @controller.config + end + def render(options = {}, local_assigns = {}, &block) @rendered << output = _view.render(options, local_assigns, &block) output @@ -152,7 +159,7 @@ module ActionView end def method_missing(selector, *args) - if ActionController::Routing::Routes.named_routes.helpers.include?(selector) + if @controller._router.named_routes.helpers.include?(selector) @controller.__send__(selector, *args) else super diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb index 4ad87d9762..f70d497481 100644 --- a/actionpack/test/abstract/abstract_controller_test.rb +++ b/actionpack/test/abstract/abstract_controller_test.rb @@ -59,11 +59,11 @@ module AbstractController end def rendering_to_body - self.response_body = render_to_body :_template_name => "naked_render.erb" + self.response_body = render_to_body :template => "naked_render.erb" end def rendering_to_string - self.response_body = render_to_string :_template_name => "naked_render.erb" + self.response_body = render_to_string :template => "naked_render.erb" end end diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb index b6d89ea489..65a50807fd 100644 --- a/actionpack/test/abstract/layouts_test.rb +++ b/actionpack/test/abstract/layouts_test.rb @@ -8,41 +8,52 @@ module AbstractControllerTests include AbstractController::Rendering include AbstractController::Layouts + def _prefix + "template" + end + self.view_paths = [ActionView::FixtureResolver.new( - "layouts/hello.erb" => "With String <%= yield %>", - "layouts/hello_override.erb" => "With Override <%= yield %>", - "layouts/abstract_controller_tests/layouts/with_string_implied_child.erb" => - "With Implied <%= yield %>", - "layouts/overwrite.erb" => "Overwrite <%= yield %>", - "layouts/with_false_layout.erb" => "False Layout <%= yield %>" + "abstract_controller_tests/layouts/with_string_implied_child.erb" => + "With Implied <%= yield %>", + "layouts/hello.erb" => "With String <%= yield %>", + "layouts/hello_override.erb" => "With Override <%= yield %>", + "layouts/overwrite.erb" => "Overwrite <%= yield %>", + "layouts/with_false_layout.erb" => "False Layout <%= yield %>" )] end class Blank < Base - self.view_paths = [] - + self.view_paths = ActionView::FixtureResolver.new("template/index.erb" => "Hello blank!") + def index - render :_template => ActionView::Template::Text.new("Hello blank!") + render end end class WithString < Base layout "hello" - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello string!", + "template/overwrite_default.erb" => "Hello string!", + "template/overwrite_false.erb" => "Hello string!", + "template/overwrite_string.erb" => "Hello string!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello string!") + render end def overwrite_default - render :_template => ActionView::Template::Text.new("Hello string!"), :layout => :default + render :layout => :default end def overwrite_false - render :_template => ActionView::Template::Text.new("Hello string!"), :layout => false + render :layout => false end def overwrite_string - render :_template => ActionView::Template::Text.new("Hello string!"), :layout => "overwrite" + render :layout => "overwrite" end def overwrite_skip @@ -70,18 +81,28 @@ module AbstractControllerTests class WithProc < Base layout proc { |c| "overwrite" } + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello proc!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello proc!") + render end end class WithSymbol < Base layout :hello - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello symbol!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello symbol!") + render end - private + + private + def hello "overwrite" end @@ -89,11 +110,17 @@ module AbstractControllerTests class WithSymbolReturningString < Base layout :no_hello - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello missing symbol!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello missing symbol!") + render end - private + + private + def no_hello nil end @@ -101,19 +128,28 @@ module AbstractControllerTests class WithSymbolReturningNil < Base layout :nilz - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello nilz!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello nilz!") + render end - def nilz() end + def nilz + end end class WithSymbolReturningObj < Base layout :objekt - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello object!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello nilz!") + render end def objekt @@ -123,33 +159,49 @@ module AbstractControllerTests class WithSymbolAndNoMethod < Base layout :no_method - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello boom!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello boom!") + render end end class WithMissingLayout < Base layout "missing" - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello missing!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello missing!") + render end end class WithFalseLayout < Base layout false - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello false!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello false!") + render end end class WithNilLayout < Base layout nil + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello nil!" + ) def index - render :_template => ActionView::Template::Text.new("Hello nil!") + render end end diff --git a/actionpack/test/abstract/localized_cache_test.rb b/actionpack/test/abstract/localized_cache_test.rb deleted file mode 100644 index 8b0b0fff03..0000000000 --- a/actionpack/test/abstract/localized_cache_test.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'abstract_unit' - -module AbstractController - module Testing - - class CachedController < AbstractController::Base - include AbstractController::Rendering - include AbstractController::LocalizedCache - - self.view_paths = [ActionView::FixtureResolver.new( - "default.erb" => "With Default", - "template.erb" => "With Template", - "some/file.erb" => "With File", - "template_name.erb" => "With Template Name" - )] - end - - class TestLocalizedCache < ActiveSupport::TestCase - - def setup - @controller = CachedController.new - CachedController.clear_template_caches! - end - - def test_templates_are_cached - @controller.render :template => "default.erb" - assert_equal "With Default", @controller.response_body - - cached = @controller.class.template_cache - assert_equal 1, cached.size - assert_kind_of ActionView::Template, cached.values.first["default.erb"] - end - - def test_cache_is_used - CachedController.new.render :template => "default.erb" - - @controller.expects(:find_template).never - @controller.render :template => "default.erb" - - assert_equal 1, @controller.class.template_cache.size - end - - def test_cache_changes_with_locale - CachedController.new.render :template => "default.erb" - - I18n.locale = :es - @controller.render :template => "default.erb" - - assert_equal 2, @controller.class.template_cache.size - ensure - I18n.locale = :en - end - - end - - end -end diff --git a/actionpack/test/abstract/render_test.rb b/actionpack/test/abstract/render_test.rb index db924633ca..25dc8bd804 100644 --- a/actionpack/test/abstract/render_test.rb +++ b/actionpack/test/abstract/render_test.rb @@ -15,13 +15,8 @@ module AbstractController "renderer/default.erb" => "With Default", "renderer/string.erb" => "With String", "renderer/symbol.erb" => "With Symbol", - "renderer/template_name.erb" => "With Template Name", "string/with_path.erb" => "With String With Path", - "some/file.erb" => "With File", - "with_format.html.erb" => "With html format", - "with_format.xml.erb" => "With xml format", - "with_locale.en.erb" => "With en locale", - "with_locale.pl.erb" => "With pl locale" + "some/file.erb" => "With File" )] def template @@ -55,30 +50,6 @@ module AbstractController def symbol render :symbol end - - def template_name - render :_template_name => :template_name - end - - def object - render :_template => ActionView::Template::Text.new("With Object") - end - - def with_html_format - render :template => "with_format", :format => :html - end - - def with_xml_format - render :template => "with_format", :format => :xml - end - - def with_en_locale - render :template => "with_locale" - end - - def with_pl_locale - render :template => "with_locale", :locale => :pl - end end class TestRenderer < ActiveSupport::TestCase @@ -126,36 +97,6 @@ module AbstractController @controller.process(:string_with_path) assert_equal "With String With Path", @controller.response_body end - - def test_render_template_name - @controller.process(:template_name) - assert_equal "With Template Name", @controller.response_body - end - - def test_render_object - @controller.process(:object) - assert_equal "With Object", @controller.response_body - end - - def test_render_with_html_format - @controller.process(:with_html_format) - assert_equal "With html format", @controller.response_body - end - - def test_render_with_xml_format - @controller.process(:with_xml_format) - assert_equal "With xml format", @controller.response_body - end - - def test_render_with_en_locale - @controller.process(:with_en_locale) - assert_equal "With en locale", @controller.response_body - end - - def test_render_with_pl_locale - @controller.process(:with_pl_locale) - assert_equal "With pl locale", @controller.response_body - end end end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 867e50d5b9..67aa412d3d 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -1,5 +1,11 @@ require File.expand_path('../../../load_paths', __FILE__) +lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) + +activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) +$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) + $:.unshift(File.dirname(__FILE__) + '/lib') $:.unshift(File.dirname(__FILE__) + '/fixtures/helpers') $:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers') @@ -10,7 +16,6 @@ require 'test/unit' require 'abstract_controller' require 'action_controller' require 'action_view' -require 'action_view/base' require 'action_dispatch' require 'fixture_template' require 'active_support/dependencies' @@ -64,29 +69,66 @@ module SetupOnce end end -class ActiveSupport::TestCase - include SetupOnce +SharedTestRoutes = ActionDispatch::Routing::RouteSet.new + +module ActiveSupport + class TestCase + include SetupOnce + # Hold off drawing routes until all the possible controller classes + # have been loaded. + setup_once do + SharedTestRoutes.draw do |map| + # FIXME: match ':controller(/:action(/:id))' + map.connect ':controller/:action/:id' + end + + ActionController::IntegrationTest.app.router.draw do |map| + # FIXME: match ':controller(/:action(/:id))' + map.connect ':controller/:action/:id' + end + end + end +end + +class RoutedRackApp + attr_reader :router + alias routes router + + def initialize(router, &blk) + @router = router + @stack = ActionDispatch::MiddlewareStack.new(&blk).build(@router) + end + + def call(env) + @stack.call(env) + end +end - # Hold off drawing routes until all the possible controller classes - # have been loaded. - setup_once do - ActionController::Routing::Routes.draw do |map| - match ':controller(/:action(/:id))' +class BasicController + attr_accessor :request + + def config + @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config| + # VIEW TODO: View tests should not require a controller + public_dir = File.expand_path("../fixtures/public", __FILE__) + config.assets_dir = public_dir + config.javascripts_dir = "#{public_dir}/javascripts" + config.stylesheets_dir = "#{public_dir}/stylesheets" + config end end end class ActionController::IntegrationTest < ActiveSupport::TestCase def self.build_app(routes = nil) - ActionDispatch::Flash - ActionDispatch::MiddlewareStack.new { |middleware| + RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware| middleware.use "ActionDispatch::ShowExceptions" middleware.use "ActionDispatch::Callbacks" middleware.use "ActionDispatch::ParamsParser" middleware.use "ActionDispatch::Cookies" middleware.use "ActionDispatch::Flash" middleware.use "ActionDispatch::Head" - }.build(routes || ActionController::Routing::Routes) + end end self.app = build_app @@ -112,28 +154,22 @@ class ActionController::IntegrationTest < ActiveSupport::TestCase end def with_routing(&block) - real_routes = ActionController::Routing::Routes - ActionController::Routing.module_eval { remove_const :Routes } - - temporary_routes = ActionController::Routing::RouteSet.new - self.class.app = self.class.build_app(temporary_routes) - ActionController::Routing.module_eval { const_set :Routes, temporary_routes } + temporary_routes = ActionDispatch::Routing::RouteSet.new + old_app, self.class.app = self.class.app, self.class.build_app(temporary_routes) + old_routes = SharedTestRoutes + silence_warnings { Object.const_set(:SharedTestRoutes, temporary_routes) } yield temporary_routes ensure - if ActionController::Routing.const_defined? :Routes - ActionController::Routing.module_eval { remove_const :Routes } - end - ActionController::Routing.const_set(:Routes, real_routes) if real_routes - self.class.app = self.class.build_app + self.class.app = old_app + silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) } end end # Temporary base class class Rack::TestCase < ActionController::IntegrationTest setup do - ActionController::Base.session_options[:key] = "abc" - ActionController::Base.session_options[:secret] = ("*" * 30) + ActionController::Base.config.secret = "abc" * 30 end def self.testing(klass = nil) @@ -180,6 +216,16 @@ end class ::ApplicationController < ActionController::Base end +module ActionView + class TestCase + # Must repeat the setup because AV::TestCase is a duplication + # of AC::TestCase + setup do + @router = SharedTestRoutes + end + end +end + module ActionController class Base include ActionController::Testing @@ -190,6 +236,10 @@ module ActionController class TestCase include ActionDispatch::TestProcess + setup do + @router = SharedTestRoutes + end + def assert_template(options = {}, message = nil) validate_request! @@ -232,3 +282,10 @@ module ActionController end end end + +# This stub emulates the Railtie including the URL helpers from a Rails application +module ActionController + class Base + include SharedTestRoutes.url_helpers + end +end
\ No newline at end of file diff --git a/actionpack/test/activerecord/controller_runtime_test.rb b/actionpack/test/activerecord/controller_runtime_test.rb index ed8e324938..331f861d8f 100644 --- a/actionpack/test/activerecord/controller_runtime_test.rb +++ b/actionpack/test/activerecord/controller_runtime_test.rb @@ -1,37 +1,37 @@ require 'active_record_unit' require 'active_record/railties/controller_runtime' require 'fixtures/project' -require 'rails/subscriber/test_helper' -require 'action_controller/railties/subscriber' +require 'rails/log_subscriber/test_helper' +require 'action_controller/railties/log_subscriber' ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime -class ControllerRuntimeSubscriberTest < ActionController::TestCase - class SubscriberController < ActionController::Base +class ControllerRuntimeLogSubscriberTest < ActionController::TestCase + class LogSubscriberController < ActionController::Base def show render :inline => "<%= Project.all %>" end end - - include Rails::Subscriber::TestHelper - tests SubscriberController + + include Rails::LogSubscriber::TestHelper + tests LogSubscriberController def setup - @old_logger = ActionController::Base.logger - Rails::Subscriber.add(:action_controller, ActionController::Railties::Subscriber.new) super + @old_logger = ActionController::Base.logger + Rails::LogSubscriber.add(:action_controller, ActionController::Railties::LogSubscriber.new) end def teardown super - Rails::Subscriber.subscribers.clear + Rails::LogSubscriber.log_subscribers.clear ActionController::Base.logger = @old_logger end def set_logger(logger) ActionController::Base.logger = logger end - + def test_log_with_active_record get :show wait diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index ea82758cf5..5643ad5ad6 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -26,7 +26,7 @@ class Series < ActiveRecord::Base end class PolymorphicRoutesTest < ActionController::TestCase - include ActionController::UrlFor + include SharedTestRoutes.url_helpers self.default_url_options[:host] = 'example.com' def setup @@ -400,7 +400,7 @@ class PolymorphicRoutesTest < ActionController::TestCase map.resources :series end - ActionController::Routing::Routes.install_helpers(self.class) + self.class.send(:include, @router.url_helpers) yield end end @@ -422,7 +422,7 @@ class PolymorphicRoutesTest < ActionController::TestCase end end - ActionController::Routing::Routes.install_helpers(self.class) + self.class.send(:include, @router.url_helpers) yield end end @@ -441,7 +441,7 @@ class PolymorphicRoutesTest < ActionController::TestCase end end - ActionController::Routing::Routes.install_helpers(self.class) + self.class.send(:include, @router.url_helpers) yield end end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index d54be9bdc0..26e0d6d844 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -253,12 +253,14 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase end def test_assert_redirect_to_nested_named_route + @controller = Admin::InnerModuleController.new + with_routing do |set| set.draw do |map| match 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module - match ':controller/:action' + # match ':controller/:action' + map.connect ':controller/:action/:id' end - @controller = Admin::InnerModuleController.new process :redirect_to_index # redirection is <{"action"=>"index", "controller"=>"admin/admin/inner_module"}> assert_redirected_to admin_inner_module_path @@ -266,12 +268,14 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase end def test_assert_redirected_to_top_level_named_route_from_nested_controller + @controller = Admin::InnerModuleController.new + with_routing do |set| set.draw do |map| match '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level - match ':controller/:action' + # match ':controller/:action' + map.connect ':controller/:action/:id' end - @controller = Admin::InnerModuleController.new process :redirect_to_top_level_named_route # assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return assert_redirected_to "/action_pack_assertions/foo" @@ -279,13 +283,15 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase end def test_assert_redirected_to_top_level_named_route_with_same_controller_name_in_both_namespaces + @controller = Admin::InnerModuleController.new + with_routing do |set| set.draw do |map| # this controller exists in the admin namespace as well which is the only difference from previous test match '/user/:id', :to => 'user#index', :as => :top_level - match ':controller/:action' + # match ':controller/:action' + map.connect ':controller/:action/:id' end - @controller = Admin::InnerModuleController.new process :redirect_to_top_level_named_route # assert_redirected_to top_level_url('foo') would pass because of exact match early return assert_redirected_to top_level_path('foo') @@ -463,9 +469,11 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase assert_redirected_to '/some/path' end - def test_redirected_to_url_no_leadling_slash + def test_redirected_to_url_no_leading_slash_fails process :redirect_to_path - assert_redirected_to 'some/path' + assert_raise ActiveSupport::TestCase::Assertion do + assert_redirected_to 'some/path' + end end def test_redirected_to_url_full_url diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index 612827dd41..cb3e848dfa 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -6,18 +6,7 @@ require 'abstract_unit' require 'controller/fake_controllers' - -unless defined?(ActionMailer) - begin - $:.unshift("#{File.dirname(__FILE__)}/../../../actionmailer/lib") - require 'action_mailer' - rescue LoadError => e - raise unless e.message =~ /action_mailer/ - require 'rubygems' - gem 'actionmailer' - end -end - +require 'action_mailer' ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH class AssertSelectTest < ActionController::TestCase diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 4fcfbacf4e..f047e7da30 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -66,6 +66,19 @@ class DefaultUrlOptionsController < ActionController::Base end end +class UrlOptionsController < ActionController::Base + def from_view + render :inline => "<%= #{params[:route]} %>" + end + + def url_options + super.merge(:host => 'www.override.com', :action => 'new', :locale => 'en') + end +end + +class RecordIdentifierController < ActionController::Base +end + class ControllerClassTests < ActiveSupport::TestCase def test_controller_path assert_equal 'empty', EmptyController.controller_path @@ -92,6 +105,11 @@ class ControllerClassTests < ActiveSupport::TestCase assert_equal [:password], parameters end + + def test_record_identifier + assert_respond_to RecordIdentifierController.new, :dom_id + assert_respond_to RecordIdentifierController.new, :dom_class + end end class ControllerInstanceTests < Test::Unit::TestCase @@ -113,6 +131,15 @@ class ControllerInstanceTests < Test::Unit::TestCase assert_equal Set.new(%w(public_action)), c.class.__send__(:action_methods), "#{c.controller_path} should not be empty!" end end + + def test_temporary_anonymous_controllers + name = 'ExamplesController' + klass = Class.new(ActionController::Base) + Object.const_set(name, klass) + + controller = klass.new + assert_equal "examples", controller.controller_path + end end class PerformActionTest < ActionController::TestCase @@ -153,6 +180,31 @@ class PerformActionTest < ActionController::TestCase end end +class UrlOptionsTest < ActionController::TestCase + tests UrlOptionsController + + def setup + super + @request.host = 'www.example.com' + rescue_action_in_public! + end + + def test_url_options_override + with_routing do |set| + set.draw do |map| + match 'from_view', :to => 'url_options#from_view', :as => :from_view + match ':controller/:action' + end + + get :from_view, :route => "from_view_url" + + assert_equal 'http://www.override.com/from_view?locale=en', @response.body + assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url) + assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options') + end + end +end + class DefaultUrlOptionsTest < ActionController::TestCase tests DefaultUrlOptionsController @@ -162,7 +214,7 @@ class DefaultUrlOptionsTest < ActionController::TestCase rescue_action_in_public! end - def test_default_url_options_are_used_if_set + def test_default_url_options_override with_routing do |set| set.draw do |map| match 'from_view', :to => 'default_url_options#from_view', :as => :from_view @@ -219,12 +271,15 @@ class EmptyUrlOptionsTest < ActionController::TestCase end def test_named_routes_with_path_without_doing_a_request_first + @controller = EmptyController.new + @controller.request = @request + with_routing do |set| set.draw do |map| resources :things end - assert_equal '/things', EmptyController.new.send(:things_path) + assert_equal '/things', @controller.send(:things_path) end end end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index de92fc56fd..a3c8fdbb6e 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -6,12 +6,18 @@ CACHE_DIR = 'test_cache' # Don't change '/../temp/' cavalierly or you might hose something you don't want hosed FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR) ActionController::Base.page_cache_directory = FILE_STORE_PATH -ActionController::Base.cache_store = :file_store, FILE_STORE_PATH -class PageCachingTestController < ActionController::Base +class CachingController < ActionController::Base + abstract! + + self.cache_store = :file_store, FILE_STORE_PATH +end + +class PageCachingTestController < CachingController caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? } caches_page :found, :not_found + def ok head :ok end @@ -51,12 +57,13 @@ class PageCachingTest < ActionController::TestCase @request = ActionController::TestRequest.new @request.host = 'hostname.com' + @request.env.delete('PATH_INFO') @response = ActionController::TestResponse.new @controller = PageCachingTestController.new + @controller.cache_store = :file_store, FILE_STORE_PATH - @params = {:controller => 'posts', :action => 'index', :only_path => true, :skip_relative_url_root => true} - @rewriter = ActionController::UrlRewriter.new(@request, @params) + @params = {:controller => 'posts', :action => 'index', :only_path => true} FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) FileUtils.mkdir_p(FILE_STORE_PATH) @@ -74,9 +81,9 @@ class PageCachingTest < ActionController::TestCase match '/', :to => 'posts#index', :as => :main end @params[:format] = 'rss' - assert_equal '/posts.rss', @rewriter.rewrite(@params) + assert_equal '/posts.rss', @router.url_for(@params) @params[:format] = nil - assert_equal '/', @rewriter.rewrite(@params) + assert_equal '/', @router.url_for(@params) end end @@ -110,7 +117,7 @@ class PageCachingTest < ActionController::TestCase end def test_should_cache_ok_at_custom_path - @request.request_uri = "/index.html" + @request.env['PATH_INFO'] = '/index.html' get :ok assert_response :ok assert File.exist?("#{FILE_STORE_PATH}/index.html") @@ -147,7 +154,7 @@ class PageCachingTest < ActionController::TestCase end end -class ActionCachingTestController < ActionController::Base +class ActionCachingTestController < CachingController rescue_from(Exception) { head 500 } if defined? ActiveRecord rescue_from(ActiveRecord::RecordNotFound) { head :not_found } @@ -305,12 +312,9 @@ class ActionCacheTest < ActionController::TestCase end def test_action_cache_conditional_options - old_use_accept_header = ActionController::Base.use_accept_header - ActionController::Base.use_accept_header = true @request.env['HTTP_ACCEPT'] = 'application/json' get :index assert !fragment_exist?('hostname.com/action_caching_test') - ActionController::Base.use_accept_header = old_use_accept_header end def test_action_cache_with_store_options @@ -511,6 +515,7 @@ class ActionCacheTest < ActionController::TestCase @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @controller = ActionCachingTestController.new + @controller.singleton_class.send(:include, @router.url_helpers) @request.host = 'hostname.com' end @@ -523,7 +528,7 @@ class ActionCacheTest < ActionController::TestCase end end -class FragmentCachingTestController < ActionController::Base +class FragmentCachingTestController < CachingController def some_action; end; end @@ -532,8 +537,8 @@ class FragmentCachingTest < ActionController::TestCase super ActionController::Base.perform_caching = true @store = ActiveSupport::Cache::MemoryStore.new - ActionController::Base.cache_store = @store @controller = FragmentCachingTestController.new + @controller.cache_store = @store @params = {:controller => 'posts', :action => 'index'} @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @@ -631,7 +636,7 @@ class FragmentCachingTest < ActionController::TestCase end -class FunctionalCachingController < ActionController::Base +class FunctionalCachingController < CachingController def fragment_cached end @@ -665,8 +670,8 @@ class FunctionalFragmentCachingTest < ActionController::TestCase super ActionController::Base.perform_caching = true @store = ActiveSupport::Cache::MemoryStore.new - ActionController::Base.cache_store = @store @controller = FunctionalCachingController.new + @controller.cache_store = @store @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new end diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb index e5ffe20ecc..967107853b 100644 --- a/actionpack/test/controller/content_type_test.rb +++ b/actionpack/test/controller/content_type_test.rb @@ -145,18 +145,6 @@ end class AcceptBasedContentTypeTest < ActionController::TestCase tests OldContentTypeController - def setup - super - @_old_accept_header = ActionController::Base.use_accept_header - ActionController::Base.use_accept_header = true - end - - def teardown - super - ActionController::Base.use_accept_header = @_old_accept_header - end - - def test_render_default_content_types_for_respond_to @request.accept = Mime::HTML.to_s get :render_default_content_types_for_respond_to diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index f5ccef8aaf..908967a110 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -157,7 +157,7 @@ class CookieTest < ActionController::TestCase def assert_cookie_header(expected) header = @response.headers["Set-Cookie"] if header.respond_to?(:to_str) - assert_equal expected, header + assert_equal expected.split("\n").sort, header.split("\n").sort else assert_equal expected.split("\n"), header end diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index 7e9a2625f1..eb2af523a2 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -40,11 +40,13 @@ class HttpDigestAuthenticationTest < ActionController::TestCase setup do # Used as secret in generating nonce to prevent tampering of timestamp - @old_secret, ActionController::Base.session_options[:secret] = ActionController::Base.session_options[:secret], "session_options_secret" + @secret = "session_options_secret" + @controller.config.secret = @secret + # @old_secret, ActionController::Base.config.secret[:secret] = ActionController::Base.session_options[:secret], @secret end teardown do - ActionController::Base.session_options[:secret] = @old_secret + # ActionController::Base.session_options[:secret] = @old_secret end AUTH_HEADERS.each do |header| @@ -138,7 +140,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase test "authentication request with request-uri that doesn't match credentials digest-uri" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') - @request.env['REQUEST_URI'] = "/http_digest_authentication_test/dummy_digest/altered/uri" + @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/altered/uri" get :display assert_response :unauthorized @@ -147,7 +149,8 @@ class HttpDigestAuthenticationTest < ActionController::TestCase test "authentication request with absolute request uri (as in webrick)" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') - @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest" + @request.env["SERVER_NAME"] = "test.host" + @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" get :display @@ -170,7 +173,8 @@ class HttpDigestAuthenticationTest < ActionController::TestCase test "authentication request with absolute uri in both request and credentials (as in Webrick with IE)" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest", :username => 'pretty', :password => 'please') - @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest" + @request.env['SERVER_NAME'] = "test.host" + @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" get :display @@ -202,7 +206,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase test "validate_digest_response should fail with nil returning password_procedure" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => nil, :password => nil) - assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret"){nil} + assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@secret, @request, "SuperSecret"){nil} end private @@ -225,7 +229,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase credentials = decode_credentials(@response.headers['WWW-Authenticate']) credentials.merge!(options) - credentials.merge!(:uri => @request.env['REQUEST_URI'].to_s) + credentials.merge!(:uri => @request.env['PATH_INFO'].to_s) ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1]) end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 683ab5236c..2180466ca7 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -75,23 +75,6 @@ class SessionTest < Test::Unit::TestCase @session.delete_via_redirect(path, args, headers) end - def test_url_for_with_controller - options = {:action => 'show'} - mock_controller = mock() - mock_controller.expects(:url_for).with(options).returns('/show') - @session.stubs(:controller).returns(mock_controller) - assert_equal '/show', @session.url_for(options) - end - - def test_url_for_without_controller - options = {:action => 'show'} - mock_rewriter = mock() - mock_rewriter.expects(:rewrite).with(options).returns('/show') - @session.stubs(:generic_url_rewriter).returns(mock_rewriter) - @session.stubs(:controller).returns(nil) - assert_equal '/show', @session.url_for(options) - end - def test_get path = "/index"; params = "blah"; headers = {:location => 'blah'} @session.expects(:process).with(:get,path,params,headers) @@ -195,8 +178,8 @@ class IntegrationTestTest < Test::Unit::TestCase session1 = @test.open_session { |sess| } session2 = @test.open_session # implicit session - assert_equal ::ActionController::Integration::Session, session1.class - assert_equal ::ActionController::Integration::Session, session2.class + assert_kind_of ::ActionController::Integration::Session, session1 + assert_kind_of ::ActionController::Integration::Session, session2 assert_not_equal session1, session2 end @@ -301,18 +284,13 @@ class IntegrationProcessTest < ActionController::IntegrationTest end end - def test_cookie_monster + test 'response cookies are added to the cookie jar for the next request' do with_test_route_set do self.cookies['cookie_1'] = "sugar" self.cookies['cookie_2'] = "oatmeal" get '/cookie_monster' - assert_equal 410, status - assert_equal "Gone", status_message - assert_response 410 - assert_response :gone assert_equal "cookie_1=; path=/\ncookie_3=chocolate; path=/", headers["Set-Cookie"] assert_equal({"cookie_1"=>"", "cookie_2"=>"oatmeal", "cookie_3"=>"chocolate"}, cookies.to_hash) - assert_equal "Gone", response.body end end @@ -350,7 +328,7 @@ class IntegrationProcessTest < ActionController::IntegrationTest with_test_route_set do get '/get_with_params?foo=bar' assert_equal '/get_with_params?foo=bar', request.env["REQUEST_URI"] - assert_equal '/get_with_params?foo=bar', request.request_uri + assert_equal '/get_with_params?foo=bar', request.fullpath assert_equal "foo=bar", request.env["QUERY_STRING"] assert_equal 'foo=bar', request.query_string assert_equal 'bar', request.parameters['foo'] @@ -363,8 +341,8 @@ class IntegrationProcessTest < ActionController::IntegrationTest def test_get_with_parameters with_test_route_set do get '/get_with_params', :foo => "bar" - assert_equal '/get_with_params', request.env["REQUEST_URI"] - assert_equal '/get_with_params', request.request_uri + assert_equal '/get_with_params', request.env["PATH_INFO"] + assert_equal '/get_with_params', request.path_info assert_equal 'foo=bar', request.env["QUERY_STRING"] assert_equal 'foo=bar', request.query_string assert_equal 'bar', request.parameters['foo'] @@ -401,16 +379,26 @@ class IntegrationProcessTest < ActionController::IntegrationTest private def with_test_route_set with_routing do |set| + controller = ::IntegrationProcessTest::IntegrationController.clone + controller.class_eval do + include set.url_helpers + end + set.draw do |map| - match ':action', :to => ::IntegrationProcessTest::IntegrationController - get 'get/:action', :to => ::IntegrationProcessTest::IntegrationController + match ':action', :to => controller + get 'get/:action', :to => controller end + + self.singleton_class.send(:include, set.url_helpers) + yield end end end class MetalIntegrationTest < ActionController::IntegrationTest + include SharedTestRoutes.url_helpers + class Poller def self.call(env) if env["PATH_INFO"] =~ /^\/success/ diff --git a/actionpack/test/controller/subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index d7c1166f14..20d3e77b6e 100644 --- a/actionpack/test/controller/subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -1,9 +1,9 @@ require "abstract_unit" -require "rails/subscriber/test_helper" -require "action_controller/railties/subscriber" +require "rails/log_subscriber/test_helper" +require "action_controller/railties/log_subscriber" module Another - class SubscribersController < ActionController::Base + class LogSubscribersController < ActionController::Base def show render :nothing => true end @@ -35,24 +35,24 @@ module Another end end -class ACSubscriberTest < ActionController::TestCase - tests Another::SubscribersController - include Rails::Subscriber::TestHelper +class ACLogSubscriberTest < ActionController::TestCase + tests Another::LogSubscribersController + include Rails::LogSubscriber::TestHelper def setup + super + @old_logger = ActionController::Base.logger @cache_path = File.expand_path('../temp/test_cache', File.dirname(__FILE__)) ActionController::Base.page_cache_directory = @cache_path - ActionController::Base.cache_store = :file_store, @cache_path - - Rails::Subscriber.add(:action_controller, ActionController::Railties::Subscriber.new) - super + @controller.cache_store = :file_store, @cache_path + Rails::LogSubscriber.add(:action_controller, ActionController::Railties::LogSubscriber.new) end def teardown super - Rails::Subscriber.subscribers.clear + Rails::LogSubscriber.log_subscribers.clear FileUtils.rm_rf(@cache_path) ActionController::Base.logger = @old_logger end @@ -65,7 +65,7 @@ class ACSubscriberTest < ActionController::TestCase get :show wait assert_equal 2, logs.size - assert_equal "Processing by Another::SubscribersController#show as HTML", logs.first + assert_equal "Processing by Another::LogSubscribersController#show as HTML", logs.first end def test_process_action @@ -134,11 +134,11 @@ class ACSubscriberTest < ActionController::TestCase end def test_send_xfile - get :xfile_sender + assert_deprecated { get :xfile_sender } wait assert_equal 3, logs.size - assert_match /Sent X\-Sendfile header/, logs[1] + assert_match /Sent file/, logs[1] assert_match /test\/fixtures\/company\.rb/, logs[1] end diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 3bd3369242..5c1eaf453c 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -155,13 +155,11 @@ class RespondToControllerTest < ActionController::TestCase def setup super - ActionController::Base.use_accept_header = true @request.host = "www.example.com" end def teardown super - ActionController::Base.use_accept_header = false end def test_html @@ -513,7 +511,7 @@ class RespondWithController < ActionController::Base protected def resource - Customer.new("david", 13) + Customer.new("david", request.delete? ? nil : 13) end def _render_js(js, options) @@ -544,13 +542,11 @@ class RespondWithControllerTest < ActionController::TestCase def setup super - ActionController::Base.use_accept_header = true @request.host = "www.example.com" end def teardown super - ActionController::Base.use_accept_header = false end def test_using_resource @@ -717,7 +713,7 @@ class RespondWithControllerTest < ActionController::TestCase delete :using_resource assert_equal "text/html", @response.content_type assert_equal 302, @response.status - assert_equal "http://www.example.com/customers/13", @response.location + assert_equal "http://www.example.com/customers", @response.location end end diff --git a/actionpack/test/controller/new_base/render_rjs_test.rb b/actionpack/test/controller/new_base/render_rjs_test.rb index 8c47b38ab6..f4516ade63 100644 --- a/actionpack/test/controller/new_base/render_rjs_test.rb +++ b/actionpack/test/controller/new_base/render_rjs_test.rb @@ -17,7 +17,7 @@ module RenderRjs end def index_locale - old_locale, I18n.locale = I18n.locale, :da + self.locale = :da end end diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb index 6b6d154faa..813dedc80d 100644 --- a/actionpack/test/controller/record_identifier_test.rb +++ b/actionpack/test/controller/record_identifier_test.rb @@ -5,6 +5,7 @@ class Comment include ActiveModel::Conversion attr_reader :id + def to_key; id ? [id] : nil end def save; @id = 1 end def new_record?; @id.nil? end def name diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index 570ff4a41b..441bc47908 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -6,14 +6,14 @@ end class Workshop extend ActiveModel::Naming include ActiveModel::Conversion - attr_accessor :id, :new_record + attr_accessor :id - def initialize(id, new_record) - @id, @new_record = id, new_record + def initialize(id) + @id = id end - def new_record? - @new_record + def persisted? + id.present? end def to_s @@ -88,11 +88,11 @@ class RedirectController < ActionController::Base end def redirect_to_existing_record - redirect_to Workshop.new(5, false) + redirect_to Workshop.new(5) end def redirect_to_new_record - redirect_to Workshop.new(5, true) + redirect_to Workshop.new(nil) end def redirect_to_nil @@ -239,11 +239,11 @@ class RedirectTest < ActionController::TestCase get :redirect_to_existing_record assert_equal "http://test.host/workshops/5", redirect_to_url - assert_redirected_to Workshop.new(5, false) + assert_redirected_to Workshop.new(5) get :redirect_to_new_record assert_equal "http://test.host/workshops", redirect_to_url - assert_redirected_to Workshop.new(5, true) + assert_redirected_to Workshop.new(nil) end end diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb index 3938fc7061..2580ada88b 100644 --- a/actionpack/test/controller/render_json_test.rb +++ b/actionpack/test/controller/render_json_test.rb @@ -18,6 +18,10 @@ class RenderJsonTest < ActionController::TestCase render :json => ActiveSupport::JSON.encode(:hello => 'world') end + def render_json_hello_world_with_status + render :json => ActiveSupport::JSON.encode(:hello => 'world'), :status => 401 + end + def render_json_hello_world_with_callback render :json => ActiveSupport::JSON.encode(:hello => 'world'), :callback => 'alert' end @@ -58,6 +62,12 @@ class RenderJsonTest < ActionController::TestCase assert_equal 'application/json', @response.content_type end + def test_render_json_with_status + get :render_json_hello_world_with_status + assert_equal '{"hello":"world"}', @response.body + assert_equal 401, @response.status + end + def test_render_json_with_callback get :render_json_hello_world_with_callback assert_equal 'alert({"hello":"world"})', @response.body diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 2c3dc2a72d..e3c4869391 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -21,6 +21,10 @@ class TestController < ActionController::Base def hello_world end + def hello_world_file + render :file => File.expand_path("../../fixtures/hello.html", __FILE__) + end + def conditional_hello if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123]) render :action => 'hello_world' @@ -214,6 +218,10 @@ class TestController < ActionController::Base render :text => false end + def render_text_with_resource + render :text => Customer.new("David") + end + # :ported: def render_nothing_with_appendix render :text => "appended" @@ -347,7 +355,7 @@ class TestController < ActionController::Base @before = "i'm before the render" render_to_string :text => "foo" @after = "i'm after the render" - render :action => "test/hello_world" + render :template => "test/hello_world" end def render_to_string_with_exception @@ -361,7 +369,7 @@ class TestController < ActionController::Base rescue end @after = "i'm after the render" - render :action => "test/hello_world" + render :template => "test/hello_world" end def accessing_params_in_template_with_layout @@ -506,7 +514,7 @@ class TestController < ActionController::Base def render_to_string_with_partial @partial_only = render_to_string :partial => "partial_only" @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") } - render :action => "test/hello_world" + render :template => "test/hello_world" end def partial_with_counter @@ -747,6 +755,11 @@ class RenderTest < ActionController::TestCase assert_equal "The secret is in the sauce\n", @response.body end + def test_render_file + get :hello_world_file + assert_equal "Hello world!", @response.body + end + # :ported: def test_render_file_as_string_with_instance_variables get :render_file_as_string_with_instance_variables @@ -817,6 +830,11 @@ class RenderTest < ActionController::TestCase assert_equal 'appended', @response.body end + def test_render_text_with_resource + get :render_text_with_resource + assert_equal 'name: "David"', @response.body + end + # :ported: def test_attempt_to_access_object_method assert_raise(ActionController::UnknownAction, "No action responded to [clone]") { get :clone } diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb index b5b0d0b9d5..4da6c954cf 100644 --- a/actionpack/test/controller/render_xml_test.rb +++ b/actionpack/test/controller/render_xml_test.rb @@ -62,7 +62,8 @@ class RenderXmlTest < ActionController::TestCase with_routing do |set| set.draw do |map| resources :customers - match ':controller/:action' + # match ':controller/:action' + map.connect ':controller/:action/:id' end get :render_with_object_location diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 37367eaafc..dd991898a8 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -326,7 +326,7 @@ class RescueTest < ActionController::IntegrationTest end test 'rescue routing exceptions' do - @app = ActionDispatch::Rescue.new(ActionController::Routing::Routes) do + @app = ActionDispatch::Rescue.new(SharedTestRoutes) do rescue_from ActionController::RoutingError, lambda { |env| [200, {"Content-Type" => "text/html"}, ["Gotcha!"]] } end @@ -335,7 +335,7 @@ class RescueTest < ActionController::IntegrationTest end test 'unrescued exception' do - @app = ActionDispatch::Rescue.new(ActionController::Routing::Routes) + @app = ActionDispatch::Rescue.new(SharedTestRoutes) assert_raise(ActionController::RoutingError) { get '/b00m' } end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 01ed491732..f60045bba6 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -31,10 +31,10 @@ end class ResourcesTest < ActionController::TestCase def test_should_arrange_actions - resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, { :collection => { :rss => :get, :reorder => :post, :csv => :post }, :member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post }, - :new => { :preview => :get, :draft => :get }) + :new => { :preview => :get, :draft => :get }}, {}) assert_resource_methods [:rss], resource, :collection, :get assert_resource_methods [:csv, :reorder], resource, :collection, :post @@ -44,18 +44,18 @@ class ResourcesTest < ActionController::TestCase end def test_should_resource_controller_name_equal_resource_name_by_default - resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, {}) + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, {}, {}) assert_equal 'messages', resource.controller end def test_should_resource_controller_name_equal_controller_option - resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, :controller => 'posts') + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, {:controller => 'posts'}, {}) assert_equal 'posts', resource.controller end def test_should_all_singleton_paths_be_the_same [ :path, :nesting_path_prefix, :member_path ].each do |method| - resource = ActionDispatch::Routing::DeprecatedMapper::SingletonResource.new(:messages, :path_prefix => 'admin') + resource = ActionDispatch::Routing::DeprecatedMapper::SingletonResource.new(:messages, {:path_prefix => 'admin'}, {}) assert_equal 'admin/messages', resource.send(method) end end @@ -111,8 +111,8 @@ class ResourcesTest < ActionController::TestCase end def test_override_paths_for_default_restful_actions - resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, - :path_names => {:new => 'nuevo', :edit => 'editar'}) + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, { + :path_names => {:new => 'nuevo', :edit => 'editar'}}, {}) assert_equal resource.new_path, "#{resource.path}/nuevo" end @@ -125,7 +125,7 @@ class ResourcesTest < ActionController::TestCase def test_with_custom_conditions with_restful_routing :messages, :conditions => { :subdomain => 'app' } do - assert ActionDispatch::Routing::Routes.recognize_path("/messages", :method => :get, :subdomain => 'app') + assert @router.recognize_path("/messages", :method => :get, :subdomain => 'app') end end @@ -394,7 +394,7 @@ class ResourcesTest < ActionController::TestCase assert_restful_routes_for :messages do |options| assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :get) assert_raise(ActionController::RoutingError) do - ActionController::Routing::Routes.recognize_path("/messages/new", :method => :post) + @router.recognize_path("/messages/new", :method => :post) end end end @@ -504,7 +504,7 @@ class ResourcesTest < ActionController::TestCase def test_restful_routes_dont_generate_duplicates with_restful_routing :messages do - routes = ActionController::Routing::Routes.routes + routes = @router.routes routes.each do |route| routes.each do |r| next if route === r # skip the comparison instance @@ -1162,8 +1162,9 @@ class ResourcesTest < ActionController::TestCase options[:shallow_options] = options[:options] end - new_action = ActionController::Base.resources_path_names[:new] || "new" - edit_action = ActionController::Base.resources_path_names[:edit] || "edit" + new_action = @router.resources_path_names[:new] || "new" + edit_action = @router.resources_path_names[:edit] || "edit" + if options[:path_names] new_action = options[:path_names][:new] if options[:path_names][:new] edit_action = options[:path_names][:edit] if options[:path_names][:edit] @@ -1229,6 +1230,7 @@ class ResourcesTest < ActionController::TestCase end @controller = "#{options[:options][:controller].camelize}Controller".constantize.new + @controller.singleton_class.send(:include, @router.url_helpers) @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new get :index, options[:options] @@ -1298,6 +1300,7 @@ class ResourcesTest < ActionController::TestCase def assert_singleton_named_routes_for(singleton_name, options = {}) (options[:options] ||= {})[:controller] ||= singleton_name.to_s.pluralize @controller = "#{options[:options][:controller].camelize}Controller".constantize.new + @controller.singleton_class.send(:include, @router.url_helpers) @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new get :show, options[:options] diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index f390bbdc89..fc85b01394 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -52,29 +52,17 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase end class MockController - attr_accessor :routes + def self.build(helpers) + Class.new do + def url_for(options) + options[:protocol] ||= "http" + options[:host] ||= "test.host" - def initialize(routes) - self.routes = routes - end - - def url_for(options) - only_path = options.delete(:only_path) - - port = options.delete(:port) || 80 - port_string = port == 80 ? '' : ":#{port}" - - protocol = options.delete(:protocol) || "http" - host = options.delete(:host) || "test.host" - anchor = "##{options.delete(:anchor)}" if options.key?(:anchor) - - path = routes.generate(options) - - only_path ? "#{path}#{anchor}" : "#{protocol}://#{host}#{port_string}#{path}#{anchor}" - end + super(options) + end - def request - @request ||= ActionController::TestRequest.new + include helpers + end end end @@ -268,9 +256,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase end def setup_for_named_route - klass = Class.new(MockController) - rs.install_helpers(klass) - klass.new(rs) + MockController.build(rs.url_helpers).new end def test_named_route_without_hash @@ -759,9 +745,7 @@ class RouteSetTest < ActiveSupport::TestCase map.users '/admin/users', :controller => 'admin/users', :action => 'index' end - klass = Class.new(MockController) - set.install_helpers(klass) - klass.new(set) + MockController.build(set.url_helpers).new end def test_named_route_hash_access_method diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index 0afebac68c..30c9a65b7c 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -46,44 +46,32 @@ class SendFileTest < ActionController::TestCase response = nil assert_nothing_raised { response = process('file') } assert_not_nil response - assert_kind_of String, response.body - assert_equal file_data, response.body + body = response.body + assert_kind_of String, body + assert_equal file_data, body end def test_file_stream - pending do - response = nil - assert_nothing_raised { response = process('file') } - assert_not_nil response - assert_kind_of Array, response.body_parts - - require 'stringio' - output = StringIO.new - output.binmode - output.string.force_encoding(file_data.encoding) if output.string.respond_to?(:force_encoding) - assert_nothing_raised { response.body_parts.each { |part| output << part.to_s } } - assert_equal file_data, output.string - end - end - - def test_file_url_based_filename - @controller.options = { :url_based_filename => true } response = nil assert_nothing_raised { response = process('file') } assert_not_nil response - assert_equal "attachment", response.headers["Content-Disposition"] - end + assert response.body_parts.respond_to?(:each) + assert response.body_parts.respond_to?(:to_path) - def test_x_sendfile_header - @controller.options = { :x_sendfile => true } + require 'stringio' + output = StringIO.new + output.binmode + output.string.force_encoding(file_data.encoding) if output.string.respond_to?(:force_encoding) + assert_nothing_raised { response.body_parts.each { |part| output << part.to_s } } + assert_equal file_data, output.string + end + def test_file_url_based_filename + @controller.options = { :url_based_filename => true } response = nil assert_nothing_raised { response = process('file') } assert_not_nil response - - assert_equal @controller.file_path, response.headers['X-Sendfile'] - assert response.body.blank? - assert !response.etag? + assert_equal "attachment", response.headers["Content-Disposition"] end def test_data @@ -104,9 +92,8 @@ class SendFileTest < ActionController::TestCase end # Test that send_file_headers! is setting the correct HTTP headers. - def test_send_file_headers! + def test_send_file_headers_bang options = { - :length => 1, :type => Mime::PNG, :disposition => 'disposition', :filename => 'filename' @@ -121,13 +108,11 @@ class SendFileTest < ActionController::TestCase @controller.send(:send_file_headers!, options) h = @controller.headers - assert_equal '1', h['Content-Length'] assert_equal 'image/png', @controller.content_type assert_equal 'disposition; filename="filename"', h['Content-Disposition'] assert_equal 'binary', h['Content-Transfer-Encoding'] # test overriding Cache-Control: no-cache header to fix IE open/save dialog - @controller.headers = { 'Cache-Control' => 'no-cache' } @controller.send(:send_file_headers!, options) @controller.response.prepare! assert_equal 'private', h['Cache-Control'] @@ -135,7 +120,6 @@ class SendFileTest < ActionController::TestCase def test_send_file_headers_with_mime_lookup_with_symbol options = { - :length => 1, :type => :png } @@ -148,7 +132,6 @@ class SendFileTest < ActionController::TestCase def test_send_file_headers_with_bad_symbol options = { - :length => 1, :type => :this_type_is_not_registered } @@ -163,17 +146,16 @@ class SendFileTest < ActionController::TestCase assert_equal 500, @response.status end + define_method "test_send_#{method}_content_type" do + @controller.options = { :stream => false, :content_type => "application/x-ruby" } + assert_nothing_raised { assert_not_nil process(method) } + assert_equal "application/x-ruby", @response.content_type + end + define_method "test_default_send_#{method}_status" do @controller.options = { :stream => false } assert_nothing_raised { assert_not_nil process(method) } assert_equal 200, @response.status end end - - def test_send_data_content_length_header - @controller.headers = {} - @controller.options = { :type => :text, :filename => 'file_with_utf8_text' } - process('multibyte_text_data') - assert_equal '29', @controller.headers['Content-Length'] - end end diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 0f074b32e6..f6ba275849 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -42,7 +42,7 @@ class TestTest < ActionController::TestCase end def test_uri - render :text => request.request_uri + render :text => request.fullpath end def test_query_string @@ -128,6 +128,7 @@ XML @controller = TestController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new + @request.env['PATH_INFO'] = nil end def test_raw_post_handling @@ -199,7 +200,7 @@ XML end def test_process_with_request_uri_with_params_with_explicit_uri - @request.request_uri = "/explicit/uri" + @request.env['PATH_INFO'] = "/explicit/uri" process :test_uri, :id => 7 assert_equal "/explicit/uri", @response.body end @@ -210,7 +211,8 @@ XML end def test_process_with_query_string_with_explicit_uri - @request.request_uri = "/explicit/uri?q=test?extra=question" + @request.env['PATH_INFO'] = '/explicit/uri' + @request.env['QUERY_STRING'] = 'q=test?extra=question' process :test_query_string assert_equal "q=test?extra=question", @response.body end @@ -476,8 +478,8 @@ XML end def test_with_routing_places_routes_back - assert ActionController::Routing::Routes - routes_id = ActionController::Routing::Routes.object_id + assert @router + routes_id = @router.object_id begin with_routing { raise 'fail' } @@ -485,8 +487,8 @@ XML rescue RuntimeError end - assert ActionController::Routing::Routes - assert_equal routes_id, ActionController::Routing::Routes.object_id + assert @router + assert_equal routes_id, @router.object_id end def test_remote_addr diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 749fa5861f..fc7773dffe 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -5,7 +5,7 @@ module AbstractController class UrlForTests < ActionController::TestCase class W - include ActionController::UrlFor + include SharedTestRoutes.url_helpers end def teardown @@ -113,15 +113,13 @@ module AbstractController end def test_relative_url_root_is_respected - orig_relative_url_root = ActionController::Base.relative_url_root - ActionController::Base.relative_url_root = '/subdir' + # ROUTES TODO: Tests should not have to pass :relative_url_root directly. This + # should probably come from the router. add_host! assert_equal('https://www.basecamphq.com/subdir/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https', :script_name => '/subdir') ) - ensure - ActionController::Base.relative_url_root = orig_relative_url_root end def test_named_routes @@ -132,7 +130,8 @@ module AbstractController end # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlFor } + kls = Class.new { include set.url_helpers } + controller = kls.new assert controller.respond_to?(:home_url) assert_equal 'http://www.basecamphq.com/home/sweet/home/again', @@ -145,22 +144,17 @@ module AbstractController end def test_relative_url_root_is_respected_for_named_routes - orig_relative_url_root = ActionController::Base.relative_url_root - ActionController::Base.relative_url_root = '/subdir' - with_routing do |set| set.draw do |map| match '/home/sweet/home/:user', :to => 'home#index', :as => :home end - kls = Class.new { include ActionController::UrlFor } + kls = Class.new { include set.url_helpers } controller = kls.new assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again', - controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again') + controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again', :script_name => "/subdir") end - ensure - ActionController::Base.relative_url_root = orig_relative_url_root end def test_only_path @@ -171,7 +165,7 @@ module AbstractController end # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlFor } + kls = Class.new { include set.url_helpers } controller = kls.new assert controller.respond_to?(:home_url) assert_equal '/brave/new/world', @@ -239,7 +233,7 @@ module AbstractController end # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlFor } + kls = Class.new { include set.url_helpers } kls.default_url_options[:host] = 'www.basecamphq.com' controller = kls.new diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index c2b8cd85d8..7b46a48a1d 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -1,102 +1,85 @@ require 'abstract_unit' require 'controller/fake_controllers' -ActionController::UrlRewriter - class UrlRewriterTests < ActionController::TestCase + class Rewriter + def initialize(request) + @options = { + :host => request.host_with_port, + :protocol => request.protocol + } + end + + def rewrite(router, options) + router.url_for(@options.merge(options)) + end + end + def setup @request = ActionController::TestRequest.new @params = {} - @rewriter = ActionController::UrlRewriter.new(@request, @params) + @rewriter = Rewriter.new(@request) #.new(@request, @params) end def test_port assert_equal('http://test.host:1271/c/a/i', - @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :port => 1271) + @rewriter.rewrite(@router, :controller => 'c', :action => 'a', :id => 'i', :port => 1271) ) end def test_protocol_with_and_without_separator assert_equal('https://test.host/c/a/i', - @rewriter.rewrite(:protocol => 'https', :controller => 'c', :action => 'a', :id => 'i') + @rewriter.rewrite(@router, :protocol => 'https', :controller => 'c', :action => 'a', :id => 'i') ) assert_equal('https://test.host/c/a/i', - @rewriter.rewrite(:protocol => 'https://', :controller => 'c', :action => 'a', :id => 'i') + @rewriter.rewrite(@router, :protocol => 'https://', :controller => 'c', :action => 'a', :id => 'i') ) end def test_user_name_and_password assert_equal( 'http://david:secret@test.host/c/a/i', - @rewriter.rewrite(:user => "david", :password => "secret", :controller => 'c', :action => 'a', :id => 'i') + @rewriter.rewrite(@router, :user => "david", :password => "secret", :controller => 'c', :action => 'a', :id => 'i') ) end def test_user_name_and_password_with_escape_codes assert_equal( 'http://openid.aol.com%2Fnextangler:one+two%3F@test.host/c/a/i', - @rewriter.rewrite(:user => "openid.aol.com/nextangler", :password => "one two?", :controller => 'c', :action => 'a', :id => 'i') + @rewriter.rewrite(@router, :user => "openid.aol.com/nextangler", :password => "one two?", :controller => 'c', :action => 'a', :id => 'i') ) end def test_anchor assert_equal( 'http://test.host/c/a/i#anchor', - @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor') + @rewriter.rewrite(@router, :controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor') ) end def test_anchor_should_call_to_param assert_equal( 'http://test.host/c/a/i#anchor', - @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anchor')) + @rewriter.rewrite(@router, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anchor')) ) end def test_anchor_should_be_cgi_escaped assert_equal( 'http://test.host/c/a/i#anc%2Fhor', - @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor')) + @rewriter.rewrite(@router, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor')) ) end - def test_overwrite_params - @params[:controller] = 'hi' - @params[:action] = 'bye' - @params[:id] = '2' - - assert_equal '/hi/hi/2', @rewriter.rewrite(:only_path => true, :overwrite_params => {:action => 'hi'}) - u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:action => 'hi'}) - assert_match %r(/hi/hi/2$), u - end - - def test_overwrite_removes_original - @params[:controller] = 'search' - @params[:action] = 'list' - @params[:list_page] = 1 - - assert_equal '/search/list?list_page=2', @rewriter.rewrite(:only_path => true, :overwrite_params => {"list_page" => 2}) - u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:list_page => 2}) - assert_equal 'http://test.host/search/list?list_page=2', u - end - - def test_to_str - @params[:controller] = 'hi' - @params[:action] = 'bye' - @request.parameters[:id] = '2' - - assert_equal 'http://, test.host, /, hi, bye, {"id"=>"2"}', @rewriter.to_str - end - def test_trailing_slash options = {:controller => 'foo', :action => 'bar', :id => '3', :only_path => true} - assert_equal '/foo/bar/3', @rewriter.rewrite(options) - assert_equal '/foo/bar/3?query=string', @rewriter.rewrite(options.merge({:query => 'string'})) + assert_equal '/foo/bar/3', @rewriter.rewrite(@router, options) + assert_equal '/foo/bar/3?query=string', @rewriter.rewrite(@router, options.merge({:query => 'string'})) options.update({:trailing_slash => true}) - assert_equal '/foo/bar/3/', @rewriter.rewrite(options) + assert_equal '/foo/bar/3/', @rewriter.rewrite(@router, options) options.update({:query => 'string'}) - assert_equal '/foo/bar/3/?query=string', @rewriter.rewrite(options) + assert_equal '/foo/bar/3/?query=string', @rewriter.rewrite(@router, options) end end diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb index 56821332c5..b8972b04b6 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionpack/test/controller/view_paths_test.rb @@ -36,9 +36,12 @@ class ViewLoadPathsTest < ActionController::TestCase @old_behavior = ActiveSupport::Deprecation.behavior @last_message = nil ActiveSupport::Deprecation.behavior = Proc.new { |message, callback| @last_message = message } + + @paths = TestController.view_paths end def teardown + TestController.view_paths = @paths ActiveSupport::Deprecation.behavior = @old_behavior end diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 5882a8cfa3..05545395fb 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -245,7 +245,7 @@ class WebServiceTest < ActionController::IntegrationTest private def with_params_parsers(parsers = {}) old_session = @integration_session - @app = ActionDispatch::ParamsParser.new(ActionController::Routing::Routes, parsers) + @app = ActionDispatch::ParamsParser.new(app.router, parsers) reset! yield ensure diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb new file mode 100644 index 0000000000..00ca5ec9dc --- /dev/null +++ b/actionpack/test/dispatch/mount_test.rb @@ -0,0 +1,36 @@ +require 'abstract_unit' + +class TestRoutingMount < ActionDispatch::IntegrationTest + Router = ActionDispatch::Routing::RouteSet.new + Router.draw do + SprocketsApp = lambda { |env| + [200, {"Content-Type" => "text/html"}, ["#{env["SCRIPT_NAME"]} -- #{env["PATH_INFO"]}"]] + } + + mount SprocketsApp, :at => "/sprockets" + mount SprocketsApp => "/shorthand" + + scope "/its_a" do + mount SprocketsApp, :at => "/sprocket" + end + end + + def app + Router + end + + def test_mounting_sets_script_name + get "/sprockets/omg" + assert_equal "/sprockets -- /omg", response.body + end + + def test_mounting_works_with_scope + get "/its_a/sprocket/omg" + assert_equal "/its_a/sprocket -- /omg", response.body + end + + def test_mounting_with_shorthand + get "/shorthand/omg" + assert_equal "/shorthand -- /omg", response.body + end +end
\ No newline at end of file diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 2b5c19361a..badef4e92e 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -1,14 +1,6 @@ require 'abstract_unit' class RequestTest < ActiveSupport::TestCase - def setup - ActionController::Base.relative_url_root = nil - end - - def teardown - ActionController::Base.relative_url_root = nil - end - test "remote ip" do request = stub_request 'REMOTE_ADDR' => '1.2.3.4' assert_equal '1.2.3.4', request.remote_ip @@ -50,7 +42,7 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', 'HTTP_CLIENT_IP' => '2.2.2.2' - e = assert_raise(ActionController::ActionControllerError) { + e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) { request.remote_ip } assert_match /IP spoofing attack/, e.message @@ -62,18 +54,17 @@ class RequestTest < ActiveSupport::TestCase # example is WAP. Since the cellular network is not IP based, it's a # leap of faith to assume that their proxies are ever going to set the # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly. - ActionController::Base.ip_spoofing_check = false request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', - 'HTTP_CLIENT_IP' => '2.2.2.2' + 'HTTP_CLIENT_IP' => '2.2.2.2', + :ip_spoofing_check => false assert_equal '2.2.2.2', request.remote_ip - ActionController::Base.ip_spoofing_check = true request = stub_request 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9' assert_equal '9.9.9.9', request.remote_ip end test "remote ip with user specified trusted proxies" do - ActionController::Base.trusted_proxies = /^67\.205\.106\.73$/i + @trusted_proxies = /^67\.205\.106\.73$/i request = stub_request 'REMOTE_ADDR' => '67.205.106.73', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' @@ -96,8 +87,6 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73' assert_equal '3.4.5.6', request.remote_ip - - ActionController::Base.trusted_proxies = nil end test "domains" do @@ -151,104 +140,34 @@ class RequestTest < ActiveSupport::TestCase assert_equal ":8080", request.port_string end - test "request uri" do - request = stub_request 'REQUEST_URI' => "http://www.rubyonrails.org/path/of/some/uri?mapped=1" - assert_equal "/path/of/some/uri?mapped=1", request.request_uri - assert_equal "/path/of/some/uri", request.path - - request = stub_request 'REQUEST_URI' => "http://www.rubyonrails.org/path/of/some/uri" - assert_equal "/path/of/some/uri", request.request_uri - assert_equal "/path/of/some/uri", request.path - - request = stub_request 'REQUEST_URI' => "/path/of/some/uri" - assert_equal "/path/of/some/uri", request.request_uri - assert_equal "/path/of/some/uri", request.path - - request = stub_request 'REQUEST_URI' => "/" - assert_equal "/", request.request_uri - assert_equal "/", request.path - - request = stub_request 'REQUEST_URI' => "/?m=b" - assert_equal "/?m=b", request.request_uri - assert_equal "/", request.path - - request = stub_request 'REQUEST_URI' => "/", 'SCRIPT_NAME' => '/dispatch.cgi' - assert_equal "/", request.request_uri - assert_equal "/", request.path - - ActionController::Base.relative_url_root = "/hieraki" - request = stub_request 'REQUEST_URI' => "/hieraki/", 'SCRIPT_NAME' => "/hieraki/dispatch.cgi" - assert_equal "/hieraki/", request.request_uri - assert_equal "/", request.path - ActionController::Base.relative_url_root = nil - - ActionController::Base.relative_url_root = "/collaboration/hieraki" - request = stub_request 'REQUEST_URI' => "/collaboration/hieraki/books/edit/2", - 'SCRIPT_NAME' => "/collaboration/hieraki/dispatch.cgi" - assert_equal "/collaboration/hieraki/books/edit/2", request.request_uri - assert_equal "/books/edit/2", request.path - ActionController::Base.relative_url_root = nil - - # The following tests are for when REQUEST_URI is not supplied (as in IIS) - request = stub_request 'PATH_INFO' => "/path/of/some/uri?mapped=1", - 'SCRIPT_NAME' => nil, - 'REQUEST_URI' => nil - assert_equal "/path/of/some/uri?mapped=1", request.request_uri - assert_equal "/path/of/some/uri", request.path - - ActionController::Base.relative_url_root = '/path' - request = stub_request 'PATH_INFO' => "/path/of/some/uri?mapped=1", - 'SCRIPT_NAME' => "/path/dispatch.rb", - 'REQUEST_URI' => nil - assert_equal "/path/of/some/uri?mapped=1", request.request_uri - assert_equal "/of/some/uri", request.path - ActionController::Base.relative_url_root = nil - - request = stub_request 'PATH_INFO' => "/path/of/some/uri", - 'SCRIPT_NAME' => nil, - 'REQUEST_URI' => nil - assert_equal "/path/of/some/uri", request.request_uri - assert_equal "/path/of/some/uri", request.path - - request = stub_request 'PATH_INFO' => '/', 'REQUEST_URI' => nil - assert_equal "/", request.request_uri - assert_equal "/", request.path - - request = stub_request 'PATH_INFO' => '/?m=b', 'REQUEST_URI' => nil - assert_equal "/?m=b", request.request_uri - assert_equal "/", request.path - - request = stub_request 'PATH_INFO' => "/", - 'SCRIPT_NAME' => "/dispatch.cgi", - 'REQUEST_URI' => nil - assert_equal "/", request.request_uri - assert_equal "/", request.path - - ActionController::Base.relative_url_root = '/hieraki' - request = stub_request 'PATH_INFO' => "/hieraki/", - 'SCRIPT_NAME' => "/hieraki/dispatch.cgi", - 'REQUEST_URI' => nil - assert_equal "/hieraki/", request.request_uri - assert_equal "/", request.path - ActionController::Base.relative_url_root = nil - - request = stub_request 'REQUEST_URI' => '/hieraki/dispatch.cgi' - ActionController::Base.relative_url_root = '/hieraki' - assert_equal "/dispatch.cgi", request.path - ActionController::Base.relative_url_root = nil - - request = stub_request 'REQUEST_URI' => '/hieraki/dispatch.cgi' - ActionController::Base.relative_url_root = '/foo' - assert_equal "/hieraki/dispatch.cgi", request.path - ActionController::Base.relative_url_root = nil - - # This test ensures that Rails uses REQUEST_URI over PATH_INFO - ActionController::Base.relative_url_root = nil - request = stub_request 'REQUEST_URI' => "/some/path", - 'PATH_INFO' => "/another/path", - 'SCRIPT_NAME' => "/dispatch.cgi" - assert_equal "/some/path", request.request_uri - assert_equal "/some/path", request.path + test "full path" do + request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/path/of/some/uri', 'QUERY_STRING' => 'mapped=1' + assert_equal "/path/of/some/uri?mapped=1", request.fullpath + assert_equal "/path/of/some/uri", request.path_info + + request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/path/of/some/uri' + assert_equal "/path/of/some/uri", request.fullpath + assert_equal "/path/of/some/uri", request.path_info + + request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/' + assert_equal "/", request.fullpath + assert_equal "/", request.path_info + + request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/', 'QUERY_STRING' => 'm=b' + assert_equal "/?m=b", request.fullpath + assert_equal "/", request.path_info + + request = stub_request 'SCRIPT_NAME' => '/hieraki', 'PATH_INFO' => '/' + assert_equal "/hieraki/", request.fullpath + assert_equal "/", request.path_info + + request = stub_request 'SCRIPT_NAME' => '/collaboration/hieraki', 'PATH_INFO' => '/books/edit/2' + assert_equal "/collaboration/hieraki/books/edit/2", request.fullpath + assert_equal "/books/edit/2", request.path_info + + request = stub_request 'SCRIPT_NAME' => '/path', 'PATH_INFO' => '/of/some/uri', 'QUERY_STRING' => 'mapped=1' + assert_equal "/path/of/some/uri?mapped=1", request.fullpath + assert_equal "/of/some/uri", request.path_info end @@ -462,7 +381,7 @@ class RequestTest < ActiveSupport::TestCase [{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'], [{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'], [{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana'], - [{'baz'=>[{'foo'=>'baz'}]}, {'baz'=>[{'foo'=>'[FILTERED]'}]}, [/foo/]]] + [{'baz'=>[{'foo'=>'baz'}, "1"]}, {'baz'=>[{'foo'=>'[FILTERED]'}, "1"]}, [/foo/]]] test_hashes.each do |before_filter, after_filter, filter_words| request = stub_request('action_dispatch.parameter_filter' => filter_words) @@ -506,18 +425,14 @@ class RequestTest < ActiveSupport::TestCase protected - def stub_request(env={}) + def stub_request(env = {}) + ip_spoofing_check = env.key?(:ip_spoofing_check) ? env.delete(:ip_spoofing_check) : true + ip_app = ActionDispatch::RemoteIp.new(Proc.new { }, ip_spoofing_check, @trusted_proxies) + ip_app.call(env) ActionDispatch::Request.new(env) end def with_set(*args) args end - - def with_accept_header(value) - ActionController::Base.use_accept_header, old = value, ActionController::Base.use_accept_header - yield - ensure - ActionController::Base.use_accept_header = old - end end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 4697fa3e2b..c7f7f3102d 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -165,10 +165,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest assert_equal('public', @response.headers['Cache-Control']) assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag']) - pending do - assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag) - assert_equal({:public => true}, @response.cache_control) - end + assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag) + assert_equal({:public => true}, @response.cache_control) end test "response cache control from rackish app" do @@ -184,10 +182,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest assert_equal('public', @response.headers['Cache-Control']) assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag']) - pending do - assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag) - assert_equal({:public => true}, @response.cache_control) - end + assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag) + assert_equal({:public => true}, @response.cache_control) end test "response charset and content type from railsish app" do @@ -202,10 +198,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest get '/' assert_response :success - pending do - assert_equal('utf-16', @response.charset) - assert_equal(Mime::XML, @response.content_type) - end + assert_equal('utf-16', @response.charset) + assert_equal(Mime::XML, @response.content_type) assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type']) end @@ -220,10 +214,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest get '/' assert_response :success - pending do - assert_equal('utf-16', @response.charset) - assert_equal(Mime::XML, @response.content_type) - end + assert_equal('utf-16', @response.charset) + assert_equal(Mime::XML, @response.content_type) assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type']) end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index bcb97e4ae0..f5fcf9b0df 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -15,6 +15,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest stub_controllers do |routes| Routes = routes Routes.draw do + default_url_options :host => "rubyonrails.org" + controller :sessions do get 'login' => :new, :as => :login post 'login' => :create @@ -24,6 +26,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resource :session do get :create + + resource :info end match 'account/logout' => redirect("/logout"), :as => :logout_redirect @@ -104,7 +108,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - resources :posts, :only => [:index, :show] + resources :posts, :only => [:index, :show] do + resources :comments, :except => :destroy + end match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp @@ -162,6 +168,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest Routes end + include Routes.url_helpers + def test_logout with_test_routes do delete '/logout' @@ -183,6 +191,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true) assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true) + + assert_equal 'http://rubyonrails.org/login', Routes.url_for(:controller => 'sessions', :action => 'create') end end @@ -230,6 +240,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_session_info_nested_singleton_resource + with_test_routes do + get '/session/info' + assert_equal 'infos#show', @response.body + assert_equal '/session/info', session_info_path + end + end + def test_redirect_modulo with_test_routes do get '/account/modulo/name' @@ -471,7 +489,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - def test_posts + def test_resource_routes_with_only_and_except with_test_routes do get '/posts' assert_equal 'posts#index', @response.body @@ -481,9 +499,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'posts#show', @response.body assert_equal '/posts/1', post_path(:id => 1) + get '/posts/1/comments' + assert_equal 'comments#index', @response.body + assert_equal '/posts/1/comments', post_comments_path(:post_id => 1) + assert_raise(ActionController::RoutingError) { post '/posts' } assert_raise(ActionController::RoutingError) { put '/posts/1' } assert_raise(ActionController::RoutingError) { delete '/posts/1' } + assert_raise(ActionController::RoutingError) { delete '/posts/1/comments' } end end @@ -721,14 +744,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest private def with_test_routes - real_routes, temp_routes = ActionController::Routing::Routes, Routes - - ActionController::Routing.module_eval { remove_const :Routes } - ActionController::Routing.module_eval { const_set :Routes, temp_routes } - yield - ensure - ActionController::Routing.module_eval { remove_const :Routes } - ActionController::Routing.const_set(:Routes, real_routes) end end diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb new file mode 100644 index 0000000000..18b5b7ee00 --- /dev/null +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -0,0 +1,38 @@ +require 'abstract_unit' + +module TestUrlGeneration + class WithMountPoint < ActionDispatch::IntegrationTest + Router = ActionDispatch::Routing::RouteSet.new + Router.draw { match "/foo", :to => "my_route_generating#index", :as => :foo } + + class ::MyRouteGeneratingController < ActionController::Base + include Router.url_helpers + def index + render :text => foo_path + end + end + + include Router.url_helpers + + def _router + Router + end + + def app + Router + end + + test "generating URLS normally" do + assert_equal "/foo", foo_path + end + + test "accepting a :script_name option" do + assert_equal "/bar/foo", foo_path(:script_name => "/bar") + end + + test "the request's SCRIPT_NAME takes precedence over the router's" do + get "/foo", {}, 'SCRIPT_NAME' => "/new" + assert_equal "/new/foo", response.body + end + end +end
\ No newline at end of file diff --git a/actionpack/test/fixtures/hello.html b/actionpack/test/fixtures/hello.html new file mode 100644 index 0000000000..6769dd60bd --- /dev/null +++ b/actionpack/test/fixtures/hello.html @@ -0,0 +1 @@ +Hello world!
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/layout_render_file.erb b/actionpack/test/fixtures/test/layout_render_file.erb new file mode 100644 index 0000000000..2f8e921c5f --- /dev/null +++ b/actionpack/test/fixtures/test/layout_render_file.erb @@ -0,0 +1,2 @@ +<% content_for :title do %>title<% end -%> +<%= render :file => 'layouts/yield' -%>
\ No newline at end of file diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index 7346cb22bc..bf3e175f1f 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -6,10 +6,6 @@ class Customer < Struct.new(:name, :id) undef_method :to_json - def to_param - id.to_s - end - def to_xml(options={}) if options[:builder] options[:builder].name name @@ -21,13 +17,14 @@ class Customer < Struct.new(:name, :id) def to_js(options={}) "name: #{name.inspect}" end + alias :to_text :to_js def errors [] end - def destroyed? - false + def persisted? + id.present? end end @@ -42,8 +39,8 @@ module Quiz extend ActiveModel::Naming include ActiveModel::Conversion - def to_param - id.to_s + def persisted? + id.present? end end @@ -58,12 +55,12 @@ class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost alias_method :secret?, :secret - def new_record=(boolean) - @new_record = boolean + def persisted=(boolean) + @persisted = boolean end - def new_record? - @new_record + def persisted? + @persisted end attr_accessor :author @@ -83,8 +80,9 @@ class Comment attr_reader :id attr_reader :post_id def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end + def to_key; id ? [id] : nil end def save; @id = 1; @post_id = 1 end - def new_record?; @id.nil? end + def persisted?; @id.present? end def to_param; @id; end def name @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" @@ -102,8 +100,9 @@ class Tag attr_reader :id attr_reader :post_id def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end + def to_key; id ? [id] : nil end def save; @id = 1; @post_id = 1 end - def new_record?; @id.nil? end + def persisted?; @id.present? end def to_param; @id; end def value @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" @@ -121,8 +120,9 @@ class CommentRelevance attr_reader :id attr_reader :comment_id def initialize(id = nil, comment_id = nil); @id, @comment_id = id, comment_id end + def to_key; id ? [id] : nil end def save; @id = 1; @comment_id = 1 end - def new_record?; @id.nil? end + def persisted?; @id.present? end def to_param; @id; end def value @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" @@ -136,8 +136,9 @@ class TagRelevance attr_reader :id attr_reader :tag_id def initialize(id = nil, tag_id = nil); @id, @tag_id = id, tag_id end + def to_key; id ? [id] : nil end def save; @id = 1; @tag_id = 1 end - def new_record?; @id.nil? end + def persisted?; @id.present? end def to_param; @id; end def value @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" diff --git a/actionpack/test/lib/fixture_template.rb b/actionpack/test/lib/fixture_template.rb index 6b9e7c5270..02248d84ad 100644 --- a/actionpack/test/lib/fixture_template.rb +++ b/actionpack/test/lib/fixture_template.rb @@ -1,23 +1,28 @@ module ActionView #:nodoc: class FixtureResolver < PathResolver - def initialize(hash = {}, options = {}) - super(options) + attr_reader :hash + + def initialize(hash = {}) + super() @hash = hash end private - def query(path, exts) + def query(partial, path, exts) query = Regexp.escape(path) exts.each do |ext| - query << '(?:' << ext.map {|e| e && Regexp.escape(".#{e}") }.join('|') << ')' + query << '(' << ext.map {|e| e && Regexp.escape(".#{e}") }.join('|') << ')' end templates = [] @hash.select { |k,v| k =~ /^#{query}$/ }.each do |path, source| - templates << Template.new(source, path, *path_to_details(path)) + handler, format = extract_handler_and_format(path) + templates << Template.new(source, path, handler, + :partial => partial, :virtual_path => path, :format => format) end - templates.sort_by {|t| -t.details.values.compact.size } + + templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } end end diff --git a/actionpack/test/template/active_model_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb index 3e01ae78c3..371aa53c68 100644 --- a/actionpack/test/template/active_model_helper_test.rb +++ b/actionpack/test/template/active_model_helper_test.rb @@ -64,7 +64,7 @@ class ActiveModelHelperTest < ActionView::TestCase }.new end - def @post.new_record?() true end + def @post.persisted?() false end def @post.to_param() nil end def @post.column_for_attribute(attr_name) @@ -155,7 +155,7 @@ class ActiveModelHelperTest < ActionView::TestCase silence_warnings do class << @post - def new_record?() false end + def persisted?() true end def to_param() id end def id() 1 end end diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 586de66714..d9a89959da 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -1,14 +1,16 @@ require 'abstract_unit' +require 'active_support/ordered_options' -class AssetTagHelperTest < ActionView::TestCase - tests ActionView::Helpers::AssetTagHelper +class FakeController + attr_accessor :request - DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG.merge( - :assets_dir => File.dirname(__FILE__) + "/../fixtures/public", - :javascripts_dir => File.dirname(__FILE__) + "/../fixtures/public/javascripts", - :stylesheets_dir => File.dirname(__FILE__) + "/../fixtures/public/stylesheets") + def config + @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config) + end +end - include ActiveSupport::Configurable +class AssetTagHelperTest < ActionView::TestCase + tests ActionView::Helpers::AssetTagHelper def setup super @@ -32,8 +34,7 @@ class AssetTagHelperTest < ActionView::TestCase ) end - @controller = Class.new do - attr_accessor :request + @controller = Class.new(BasicController) do def url_for(*args) "http://www.example.com" end end.new @@ -50,7 +51,6 @@ class AssetTagHelperTest < ActionView::TestCase def teardown ActionController::Base.perform_caching = false - ActionController::Base.asset_host = nil ENV.delete('RAILS_ASSET_ID') end @@ -141,13 +141,16 @@ class AssetTagHelperTest < ActionView::TestCase ImageLinkToTag = { %(image_tag("xml.png")) => %(<img alt="Xml" src="/images/xml.png" />), - %(image_tag("..jpg")) => %(<img alt="" src="/images/..jpg" />), + %(image_tag("..jpg")) => %(<img alt="..jpg" src="/images/..jpg" />), %(image_tag("rss.gif", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.gif" />), %(image_tag("gold.png", :size => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />), %(image_tag("gold.png", "size" => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />), %(image_tag("error.png", "size" => "45")) => %(<img alt="Error" src="/images/error.png" />), %(image_tag("error.png", "size" => "45 x 70")) => %(<img alt="Error" src="/images/error.png" />), %(image_tag("error.png", "size" => "x")) => %(<img alt="Error" src="/images/error.png" />), + %(image_tag("google.com.png")) => %(<img alt="Google.com" src="/images/google.com.png" />), + %(image_tag("slash..png")) => %(<img alt="Slash." src="/images/slash..png" />), + %(image_tag(".pdf.png")) => %(<img alt=".pdf" src="/images/.pdf.png" />), %(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="http://www.rubyonrails.com/images/rails.png" />), %(image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />), %(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />) @@ -372,11 +375,9 @@ class AssetTagHelperTest < ActionView::TestCase end def test_timebased_asset_id_with_relative_url_root - ActionController::Base.relative_url_root = "/collaboration/hieraki" - expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s - assert_equal %(<img alt="Rails" src="#{ActionController::Base.relative_url_root}/images/rails.png?#{expected_time}" />), image_tag("rails.png") - ensure - ActionController::Base.relative_url_root = "" + @controller.config.relative_url_root = "/collaboration/hieraki" + expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s + assert_equal %(<img alt="Rails" src="#{@controller.config.relative_url_root}/images/rails.png?#{expected_time}" />), image_tag("rails.png") end def test_should_skip_asset_id_on_complete_url @@ -402,7 +403,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_image_path_with_caching_and_proc_asset_host_using_request ENV['RAILS_ASSET_ID'] = '' - ActionController::Base.asset_host = Proc.new do |source, request| + @controller.config.asset_host = Proc.new do |source, request| if request.ssl? "#{request.protocol}#{request.host_with_port}" else @@ -410,8 +411,6 @@ class AssetTagHelperTest < ActionView::TestCase end end - ActionController::Base.perform_caching = true - @controller.request.stubs(:ssl?).returns(false) assert_equal "http://assets15.example.com/images/xml.png", image_path("xml.png") @@ -422,7 +421,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_when_caching_on ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.asset_host = 'http://a0.example.com' + @controller.config.asset_host = 'http://a0.example.com' ActionController::Base.perform_caching = true assert_dom_equal( @@ -454,7 +453,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_when_caching_on_with_proc_asset_host ENV['RAILS_ASSET_ID'] = '' - ActionController::Base.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } + @controller.config.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } ActionController::Base.perform_caching = true assert_equal '/javascripts/scripts.js'.length, 23 @@ -471,7 +470,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_when_caching_on_with_2_argument_proc_asset_host ENV['RAILS_ASSET_ID'] = '' - ActionController::Base.asset_host = Proc.new { |source, request| + @controller.config.asset_host = Proc.new { |source, request| if request.ssl? "#{request.protocol}#{request.host_with_port}" else @@ -508,7 +507,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_when_caching_on_with_2_argument_object_asset_host ENV['RAILS_ASSET_ID'] = '' - ActionController::Base.asset_host = Class.new do + @controller.config.asset_host = Class.new do def call(source, request) if request.ssl? "#{request.protocol}#{request.host_with_port}" @@ -548,7 +547,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_when_caching_on_and_using_subdirectory ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.asset_host = 'http://a%d.example.com' + @controller.config.asset_host = 'http://a%d.example.com' ActionController::Base.perform_caching = true hash = '/javascripts/cache/money.js'.hash % 4 @@ -564,7 +563,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_with_all_and_recursive_puts_defaults_at_the_start_of_the_file ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.asset_host = 'http://a0.example.com' + @controller.config.asset_host = 'http://a0.example.com' ActionController::Base.perform_caching = true assert_dom_equal( @@ -585,7 +584,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_with_all_puts_defaults_at_the_start_of_the_file ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.asset_host = 'http://a0.example.com' + @controller.config.asset_host = 'http://a0.example.com' ActionController::Base.perform_caching = true assert_dom_equal( @@ -606,7 +605,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_with_relative_url_root ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.relative_url_root = "/collaboration/hieraki" + @controller.config.relative_url_root = "/collaboration/hieraki" ActionController::Base.perform_caching = true assert_dom_equal( @@ -624,7 +623,6 @@ class AssetTagHelperTest < ActionView::TestCase assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) ensure - ActionController::Base.relative_url_root = nil FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) end @@ -694,7 +692,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_stylesheet_link_tag_when_caching_on ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.asset_host = 'http://a0.example.com' + @controller.config.asset_host = 'http://a0.example.com' ActionController::Base.perform_caching = true assert_dom_equal( @@ -804,7 +802,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_stylesheet_link_tag_when_caching_on_with_proc_asset_host ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } + @controller.config.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } ActionController::Base.perform_caching = true assert_equal '/stylesheets/styles.css'.length, 23 @@ -821,7 +819,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_stylesheet_link_tag_with_relative_url_root ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.relative_url_root = "/collaboration/hieraki" + @controller.config.relative_url_root = "/collaboration/hieraki" ActionController::Base.perform_caching = true assert_dom_equal( @@ -841,7 +839,6 @@ class AssetTagHelperTest < ActionView::TestCase assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) ensure - ActionController::Base.relative_url_root = nil FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) end @@ -879,21 +876,16 @@ end class AssetTagHelperNonVhostTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper - DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG - include ActiveSupport::Configurable - def setup super - ActionController::Base.relative_url_root = "/collaboration/hieraki" - - @controller = Class.new do - attr_accessor :request - + @controller = Class.new(BasicController) do def url_for(options) "http://www.example.com/collaboration/hieraki" end end.new + @controller.config.relative_url_root = "/collaboration/hieraki" + @request = Class.new do def protocol 'gopher://' @@ -905,10 +897,6 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase ActionView::Helpers::AssetTagHelper::reset_javascript_include_default end - def teardown - ActionController::Base.relative_url_root = nil - end - def test_should_compute_proper_path assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag) assert_dom_equal(%(/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) @@ -923,43 +911,33 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase end def test_should_compute_proper_path_with_asset_host - ActionController::Base.asset_host = "http://assets.example.com" + @controller.config.asset_host = "http://assets.example.com" assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag) assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='http://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='http://assets.example.com/collaboration/hieraki/images/mouse.png'" src="http://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='http://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='http://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="http://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) - ensure - ActionController::Base.asset_host = "" end def test_should_ignore_asset_host_on_complete_url - ActionController::Base.asset_host = "http://assets.example.com" + @controller.config.asset_host = "http://assets.example.com" assert_dom_equal(%(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag("http://bar.example.com/stylesheets/style.css")) - ensure - ActionController::Base.asset_host = "" end def test_should_wildcard_asset_host_between_zero_and_four - ActionController::Base.asset_host = 'http://a%d.example.com' + @controller.config.asset_host = 'http://a%d.example.com' assert_match %r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png') - ensure - ActionController::Base.asset_host = nil end def test_asset_host_without_protocol_should_use_request_protocol - ActionController::Base.asset_host = 'a.example.com' + @controller.config.asset_host = 'a.example.com' assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_path('xml.png') - ensure - ActionController::Base.asset_host = nil end def test_asset_host_without_protocol_should_use_request_protocol_even_if_path_present - ActionController::Base.asset_host = 'a.example.com/files/go/here' + @controller.config.asset_host = 'a.example.com/files/go/here' assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_path('xml.png') - ensure - ActionController::Base.asset_host = nil end def test_assert_css_and_js_of_the_same_name_return_correct_extension diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb index 347cb98303..869ea22469 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionpack/test/template/atom_feed_helper_test.rb @@ -4,8 +4,8 @@ class Scroll < Struct.new(:id, :to_param, :title, :body, :updated_at, :created_a extend ActiveModel::Naming include ActiveModel::Conversion - def new_record? - true + def persisted? + false end end @@ -34,7 +34,7 @@ class ScrollsController < ActionController::Base feed.updated((@scrolls.first.created_at)) for scroll in @scrolls - feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param, :updated => Time.utc(2007, 1, scroll.id)) do |entry| + feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param.to_s, :updated => Time.utc(2007, 1, scroll.id)) do |entry| entry.title(scroll.title) entry.content(scroll.body, :type => 'html') @@ -55,7 +55,7 @@ class ScrollsController < ActionController::Base end for scroll in @scrolls - feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param, :updated => Time.utc(2007, 1, scroll.id)) do |entry| + feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param.to_s, :updated => Time.utc(2007, 1, scroll.id)) do |entry| entry.title(scroll.title) entry.content(scroll.body, :type => 'html') end diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb index 632988bb2e..2c719757e4 100644 --- a/actionpack/test/template/compiled_templates_test.rb +++ b/actionpack/test/template/compiled_templates_test.rb @@ -14,9 +14,6 @@ class CompiledTemplatesTest < Test::Unit::TestCase assert_equal "two", render(:file => "test/render_file_with_locals_and_default.erb", :locals => { :secret => "two" }) end - # This is broken in 1.8.6 (not supported in Rails 3.0) because the cache uses a Hash - # key. Since Ruby 1.8.6 implements Hash#hash using the hash's object_id, it will never - # successfully get a cache hit here. def test_template_changes_are_not_reflected_with_cached_templates assert_equal "Hello world!", render(:file => "test/hello_world.erb") modify_template "test/hello_world.erb", "Goodbye world!" do @@ -44,7 +41,7 @@ class CompiledTemplatesTest < Test::Unit::TestCase end def render_without_cache(*args) - path = ActionView::FileSystemResolverWithFallback.new(FIXTURE_LOAD_PATH) + path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH) view_paths = ActionView::Base.process_view_paths(path) ActionView::Base.new(view_paths, {}).render(*args) end diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index fb51b67185..da2477b6f8 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -1229,11 +1229,28 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, date_select("post", "written_on", :order => [ :month, :year ]) end + def test_date_select_without_day_and_with_disabled_html_option + @post = Post.new + @post.written_on = Date.new(2004, 6, 15) + + expected = "<input type=\"hidden\" id=\"post_written_on_3i\" disabled=\"disabled\" name=\"post[written_on(3i)]\" value=\"1\" />\n" + + expected << %{<select id="post_written_on_2i" disabled="disabled" name="post[written_on(2i)]">\n} + expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} + expected << "</select>\n" + + expected << %{<select id="post_written_on_1i" disabled="disabled" name="post[written_on(1i)]">\n} + expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} + expected << "</select>\n" + + assert_dom_equal expected, date_select("post", "written_on", { :order => [ :month, :year ] }, :disabled => true) + end + def test_date_select_within_fields_for @post = Post.new @post.written_on = Date.new(2004, 6, 15) - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.date_select(:written_on) end @@ -1249,7 +1266,7 @@ class DateHelperTest < ActionView::TestCase @post.written_on = Date.new(2004, 6, 15) id = 27 - fields_for :post, @post, :index => id do |f| + output_buffer = fields_for :post, @post, :index => id do |f| concat f.date_select(:written_on) end @@ -1265,7 +1282,7 @@ class DateHelperTest < ActionView::TestCase @post.written_on = Date.new(2004, 6, 15) id = nil - fields_for :post, @post, :index => id do |f| + output_buffer = fields_for :post, @post, :index => id do |f| concat f.date_select(:written_on) end @@ -1461,7 +1478,7 @@ class DateHelperTest < ActionView::TestCase @post = Post.new @post.written_on = Date.new(2004, 6, 15) - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.date_select(:written_on, {}, :class => 'selector') end @@ -1625,7 +1642,7 @@ class DateHelperTest < ActionView::TestCase @post = Post.new @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.time_select(:written_on, {}, :class => 'selector') end @@ -1713,6 +1730,25 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, time_select("post", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute'}) end + def test_time_select_with_disabled_html_option + @post = Post.new + @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<input type="hidden" id="post_written_on_1i" disabled="disabled" name="post[written_on(1i)]" value="2004" />\n} + expected << %{<input type="hidden" id="post_written_on_2i" disabled="disabled" name="post[written_on(2i)]" value="6" />\n} + expected << %{<input type="hidden" id="post_written_on_3i" disabled="disabled" name="post[written_on(3i)]" value="15" />\n} + + expected << %(<select id="post_written_on_4i" disabled="disabled" name="post[written_on(4i)]">\n) + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + expected << " : " + expected << %(<select id="post_written_on_5i" disabled="disabled" name="post[written_on(5i)]">\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, time_select("post", "written_on", {}, :disabled => true) + end + def test_datetime_select @post = Post.new @post.updated_at = Time.local(2004, 6, 15, 16, 35) @@ -1780,7 +1816,7 @@ class DateHelperTest < ActionView::TestCase @post = Post.new @post.updated_at = Time.local(2004, 6, 15, 16, 35) - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.datetime_select(:updated_at, {}, :class => 'selector') end @@ -2016,7 +2052,7 @@ class DateHelperTest < ActionView::TestCase @post.updated_at = Time.local(2004, 6, 15, 16, 35) id = 456 - fields_for :post, @post, :index => id do |f| + output_buffer = fields_for :post, @post, :index => id do |f| concat f.datetime_select(:updated_at) end @@ -2173,6 +2209,66 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, datetime_select("post", "updated_at", :discard_year => true, :discard_month => true) end + def test_datetime_select_discard_year_and_month_with_disabled_html_option + @post = Post.new + @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<input type="hidden" id="post_updated_at_1i" disabled="disabled" name="post[updated_at(1i)]" value="2004" />\n} + expected << %{<input type="hidden" id="post_updated_at_2i" disabled="disabled" name="post[updated_at(2i)]" value="6" />\n} + expected << %{<input type="hidden" id="post_updated_at_3i" disabled="disabled" name="post[updated_at(3i)]" value="15" />\n} + + expected << %{<select id="post_updated_at_4i" disabled="disabled" name="post[updated_at(4i)]">\n} + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + expected << " : " + expected << %{<select id="post_updated_at_5i" disabled="disabled" name="post[updated_at(5i)]">\n} + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, datetime_select("post", "updated_at", { :discard_year => true, :discard_month => true }, :disabled => true) + end + + def test_datetime_select_discard_hour + @post = Post.new + @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} + 1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } + expected << "</select>\n" + expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} + 1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) } + expected << "</select>\n" + expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} + 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, datetime_select("post", "updated_at", :discard_hour => true) + end + + def test_datetime_select_discard_minute + @post = Post.new + @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} + 1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } + expected << "</select>\n" + expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} + 1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) } + expected << "</select>\n" + expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} + 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } + expected << "</select>\n" + + expected << " — " + + expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + expected << %{<input type="hidden" id="post_updated_at_5i" name="post[updated_at(5i)]" value="16" />\n} + + assert_dom_equal expected, datetime_select("post", "updated_at", :discard_minute => true) + end + def test_datetime_select_invalid_order @post = Post.new @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) diff --git a/actionpack/test/template/erb/tag_helper_test.rb b/actionpack/test/template/erb/tag_helper_test.rb new file mode 100644 index 0000000000..cc96a43901 --- /dev/null +++ b/actionpack/test/template/erb/tag_helper_test.rb @@ -0,0 +1,82 @@ +require "abstract_unit" + +module ERBTest + class ViewContext + mock_controller = Class.new do + include SharedTestRoutes.url_helpers + end + + include ActionView::Helpers::TagHelper + include ActionView::Helpers::JavaScriptHelper + include ActionView::Helpers::FormHelper + + attr_accessor :output_buffer + + def protect_against_forgery?() false end + + define_method(:controller) do + mock_controller.new + end + end + + class DeprecatedViewContext < ViewContext + include ActionView::Helpers::DeprecatedBlockHelpers + end + + module SharedTagHelpers + extend ActiveSupport::Testing::Declarative + + def render_content(start, inside) + template = block_helper(start, inside) + ActionView::Template::Handlers::Erubis.new(template).evaluate(context.new) + end + + test "percent equals works for content_tag and does not require parenthesis on method call" do + assert_equal "<div>Hello world</div>", render_content("content_tag :div", "Hello world") + end + + test "percent equals works for javascript_tag" do + expected_output = "<script type=\"text/javascript\">\n//<![CDATA[\nalert('Hello')\n//]]>\n</script>" + assert_equal expected_output, render_content("javascript_tag", "alert('Hello')") + end + + test "percent equals works for javascript_tag with options" do + expected_output = "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('Hello')\n//]]>\n</script>" + assert_equal expected_output, render_content("javascript_tag(:id => 'the_js_tag')", "alert('Hello')") + end + + test "percent equals works with form tags" do + expected_output = "<form action=\"foo\" method=\"post\">hello</form>" + assert_equal expected_output, render_content("form_tag('foo')", "<%= 'hello' %>") + end + + test "percent equals works with fieldset tags" do + expected_output = "<fieldset><legend>foo</legend>hello</fieldset>" + assert_equal expected_output, render_content("field_set_tag('foo')", "<%= 'hello' %>") + end + end + + class TagHelperTest < ActiveSupport::TestCase + def context + ViewContext + end + + def block_helper(str, rest) + "<%= #{str} do %>#{rest}<% end %>" + end + + include SharedTagHelpers + end + + class DeprecatedTagHelperTest < ActiveSupport::TestCase + def context + DeprecatedViewContext + end + + def block_helper(str, rest) + "<% __in_erb_template=true %><% #{str} do %>#{rest}<% end %>" + end + + include SharedTagHelpers + end +end
\ No newline at end of file diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 7b909fff82..7c5ccfd6ed 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -4,6 +4,10 @@ require 'controller/fake_models' class FormHelperTest < ActionView::TestCase tests ActionView::Helpers::FormHelper + def form_for(*) + @output_buffer = super + end + def setup super @@ -53,6 +57,7 @@ class FormHelperTest < ActionView::TestCase def @post.id_before_type_cast; 123; end def @post.to_param; '123'; end + @post.persisted = true @post.title = "Hello World" @post.author_name = "" @post.body = "Back to the hill and over it again!" @@ -529,7 +534,7 @@ class FormHelperTest < ActionView::TestCase def test_submit_with_object_as_new_record_and_locale_strings old_locale, I18n.locale = I18n.locale, :submit - def @post.new_record?() true; end + @post.persisted = false form_for(:post, @post) do |f| concat f.submit end @@ -589,9 +594,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for form_for(:post, @post) do |f| - f.fields_for(:comment, @post) do |c| + concat f.fields_for(:comment, @post) { |c| concat c.text_field(:title) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -604,9 +609,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_nested_collections form_for('post[]', @post) do |f| concat f.text_field(:title) - f.fields_for('comment[]', @comment) do |c| + concat f.fields_for('comment[]', @comment) { |c| concat c.text_field(:name) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -620,9 +625,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_index_and_parent_fields form_for('post', @post, :index => 1) do |c| concat c.text_field(:title) - c.fields_for('comment', @comment, :index => 1) do |r| + concat c.fields_for('comment', @comment, :index => 1) { |r| concat r.text_field(:name) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -634,10 +639,10 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_index_and_nested_fields_for - form_for(:post, @post, :index => 1) do |f| - f.fields_for(:comment, @post) do |c| + output_buffer = form_for(:post, @post, :index => 1) do |f| + concat f.fields_for(:comment, @post) { |c| concat c.text_field(:title) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -649,9 +654,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_index_on_both form_for(:post, @post, :index => 1) do |f| - f.fields_for(:comment, @post, :index => 5) do |c| + concat f.fields_for(:comment, @post, :index => 5) { |c| concat c.text_field(:title) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -663,9 +668,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_auto_index form_for("post[]", @post) do |f| - f.fields_for(:comment, @post) do |c| + concat f.fields_for(:comment, @post) { |c| concat c.text_field(:title) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -677,9 +682,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_index_radio_button form_for(:post, @post) do |f| - f.fields_for(:comment, @post, :index => 5) do |c| + concat f.fields_for(:comment, @post, :index => 5) { |c| concat c.radio_button(:title, "hello") - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -691,9 +696,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_auto_index_on_both form_for("post[]", @post) do |f| - f.fields_for("comment[]", @post) do |c| + concat f.fields_for("comment[]", @post) { |c| concat c.text_field(:title) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -704,16 +709,16 @@ class FormHelperTest < ActionView::TestCase end def test_nested_fields_for_with_index_and_auto_index - form_for("post[]", @post) do |f| - f.fields_for(:comment, @post, :index => 5) do |c| + output_buffer = form_for("post[]", @post) do |f| + concat f.fields_for(:comment, @post, :index => 5) { |c| concat c.text_field(:title) - end + } end - form_for(:post, @post, :index => 1) do |f| - f.fields_for("comment[]", @post) do |c| + output_buffer << form_for(:post, @post, :index => 1) do |f| + concat f.fields_for("comment[]", @post) { |c| concat c.text_field(:title) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -731,9 +736,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) - f.fields_for(:author) do |af| + concat f.fields_for(:author) { |af| concat af.text_field(:name) - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -758,9 +763,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) - f.fields_for(:author) do |af| + concat f.fields_for(:author) { |af| concat af.text_field(:name) - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -777,10 +782,10 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) - f.fields_for(:author) do |af| + concat f.fields_for(:author) { |af| concat af.hidden_field(:id) concat af.text_field(:name) - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -798,9 +803,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) @post.comments.each do |comment| - f.fields_for(:comments, comment) do |cf| + concat f.fields_for(:comments, comment) { |cf| concat cf.text_field(:name) - end + } end end @@ -821,10 +826,10 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) @post.comments.each do |comment| - f.fields_for(:comments, comment) do |cf| + concat f.fields_for(:comments, comment) { |cf| concat cf.hidden_field(:id) concat cf.text_field(:name) - end + } end end @@ -845,9 +850,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) @post.comments.each do |comment| - f.fields_for(:comments, comment) do |cf| + concat f.fields_for(:comments, comment) { |cf| concat cf.text_field(:name) - end + } end end @@ -866,9 +871,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) @post.comments.each do |comment| - f.fields_for(:comments, comment) do |cf| + concat f.fields_for(:comments, comment) { |cf| concat cf.text_field(:name) - end + } end end @@ -902,9 +907,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) - f.fields_for(:comments, @post.comments) do |cf| + concat f.fields_for(:comments, @post.comments) { |cf| concat cf.text_field(:name) - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -924,9 +929,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) - f.fields_for(:comments, comments) do |cf| + concat f.fields_for(:comments, comments) { |cf| concat cf.text_field(:name) - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -946,10 +951,10 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) - f.fields_for(:comments) do |cf| + concat f.fields_for(:comments) { |cf| concat cf.text_field(:name) yielded_comments << cf.object - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -967,9 +972,9 @@ class FormHelperTest < ActionView::TestCase @post.comments = [] form_for(:post, @post) do |f| - f.fields_for(:comments, Comment.new(321), :child_index => 'abc') do |cf| + concat f.fields_for(:comments, Comment.new(321), :child_index => 'abc') { |cf| concat cf.text_field(:name) - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -987,24 +992,24 @@ class FormHelperTest < ActionView::TestCase @post.tags[0].relevances = [] @post.tags[1].relevances = [] form_for(:post, @post) do |f| - f.fields_for(:comments, @post.comments[0]) do |cf| + concat f.fields_for(:comments, @post.comments[0]) { |cf| concat cf.text_field(:name) - cf.fields_for(:relevances, CommentRelevance.new(314)) do |crf| + concat cf.fields_for(:relevances, CommentRelevance.new(314)) { |crf| concat crf.text_field(:value) - end - end - f.fields_for(:tags, @post.tags[0]) do |tf| + } + } + concat f.fields_for(:tags, @post.tags[0]) { |tf| concat tf.text_field(:value) - tf.fields_for(:relevances, TagRelevance.new(3141)) do |trf| + concat tf.fields_for(:relevances, TagRelevance.new(3141)) { |trf| concat trf.text_field(:value) - end - end - f.fields_for('tags', @post.tags[1]) do |tf| + } + } + concat f.fields_for('tags', @post.tags[1]) { |tf| concat tf.text_field(:value) - tf.fields_for(:relevances, TagRelevance.new(31415)) do |trf| + concat tf.fields_for(:relevances, TagRelevance.new(31415)) { |trf| concat trf.text_field(:value) - end - end + } + } end expected = '<form action="http://www.example.com" method="post">' + @@ -1026,7 +1031,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for - fields_for(:post, @post) do |f| + output_buffer = fields_for(:post, @post) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1042,7 +1047,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_index - fields_for("post[]", @post) do |f| + output_buffer = fields_for("post[]", @post) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1058,7 +1063,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_nil_index_option_override - fields_for("post[]", @post, :index => nil) do |f| + output_buffer = fields_for("post[]", @post, :index => nil) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1074,7 +1079,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_index_option_override - fields_for("post[]", @post, :index => "abc") do |f| + output_buffer = fields_for("post[]", @post, :index => "abc") do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1090,7 +1095,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_without_object - fields_for(:post) do |f| + output_buffer = fields_for(:post) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1106,7 +1111,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_only_object - fields_for(@post) do |f| + output_buffer = fields_for(@post) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1122,7 +1127,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_object_with_bracketed_name - fields_for("author[post]", @post) do |f| + output_buffer = fields_for("author[post]", @post) do |f| concat f.label(:title) concat f.text_field(:title) end @@ -1133,7 +1138,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_object_with_bracketed_name_and_index - fields_for("author[post]", @post, :index => 1) do |f| + output_buffer = fields_for("author[post]", @post, :index => 1) do |f| concat f.label(:title) concat f.text_field(:title) end @@ -1152,9 +1157,9 @@ class FormHelperTest < ActionView::TestCase concat post_form.text_field(:title) concat post_form.text_area(:body) - fields_for(:parent_post, @post) do |parent_fields| + concat fields_for(:parent_post, @post) { |parent_fields| concat parent_fields.check_box(:secret) - end + } end expected = @@ -1173,9 +1178,9 @@ class FormHelperTest < ActionView::TestCase concat post_form.text_field(:title) concat post_form.text_area(:body) - post_form.fields_for(@comment) do |comment_fields| + concat post_form.fields_for(@comment) { |comment_fields| concat comment_fields.text_field(:name) - end + } end expected = @@ -1272,7 +1277,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_labelled_builder - fields_for(:post, @post, :builder => LabelledFormBuilder) do |f| + output_buffer = fields_for(:post, @post, :builder => LabelledFormBuilder) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1363,7 +1368,7 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_new_object post = Post.new - post.new_record = true + post.persisted = false def post.id() nil end form_for(post) do |f| end @@ -1373,9 +1378,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_existing_object_in_list - @post.new_record = false @comment.save - form_for([@post, @comment]) {} expected = %(<form action="#{comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="_method" type="hidden" value="put" /></div></form>) @@ -1383,8 +1386,6 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_new_object_in_list - @post.new_record = false - form_for([@post, @comment]) {} expected = %(<form action="#{comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>) @@ -1392,9 +1393,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_existing_object_and_namespace_in_list - @post.new_record = false @comment.save - form_for([:admin, @post, @comment]) {} expected = %(<form action="#{admin_comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="_method" type="hidden" value="put" /></div></form>) @@ -1402,8 +1401,6 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_new_object_and_namespace_in_list - @post.new_record = false - form_for([:admin, @post, @comment]) {} expected = %(<form action="#{admin_comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>) diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index aa40e46aa8..5799e3db53 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -293,7 +293,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.category = "<mus>" - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.select(:category, %w( abe <mus> hest)) end @@ -307,7 +307,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.category = "<mus>" - fields_for :post, @post, :index => 108 do |f| + output_buffer = fields_for :post, @post, :index => 108 do |f| concat f.select(:category, %w( abe <mus> hest)) end @@ -322,7 +322,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "<mus>" def @post.to_param; 108; end - fields_for "post[]", @post do |f| + output_buffer = fields_for "post[]", @post do |f| concat f.select(:category, %w( abe <mus> hest)) end @@ -336,7 +336,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new options = "<option value=\"abe\">abe</option><option value=\"mus\">mus</option><option value=\"hest\">hest</option>" - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.select(:category, options, :prompt => 'The prompt') end @@ -462,7 +462,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.author_name = "Babe" - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.collection_select(:author_name, dummy_posts, :author_name, :author_name) end @@ -476,7 +476,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.author_name = "Babe" - fields_for :post, @post, :index => 815 do |f| + output_buffer = fields_for :post, @post, :index => 815 do |f| concat f.collection_select(:author_name, dummy_posts, :author_name, :author_name) end @@ -491,7 +491,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.author_name = "Babe" def @post.to_param; 815; end - fields_for "post[]", @post do |f| + output_buffer = fields_for "post[]", @post do |f| concat f.collection_select(:author_name, dummy_posts, :author_name, :author_name) end @@ -570,7 +570,7 @@ class FormOptionsHelperTest < ActionView::TestCase def test_time_zone_select_under_fields_for @firm = Firm.new("D") - fields_for :firm, @firm do |f| + output_buffer = fields_for :firm, @firm do |f| concat f.time_zone_select(:time_zone) end @@ -589,7 +589,7 @@ class FormOptionsHelperTest < ActionView::TestCase def test_time_zone_select_under_fields_for_with_index @firm = Firm.new("D") - fields_for :firm, @firm, :index => 305 do |f| + output_buffer = fields_for :firm, @firm, :index => 305 do |f| concat f.time_zone_select(:time_zone) end @@ -609,7 +609,7 @@ class FormOptionsHelperTest < ActionView::TestCase @firm = Firm.new("D") def @firm.to_param; 305; end - fields_for "firm[]", @firm do |f| + output_buffer = fields_for "firm[]", @firm do |f| concat f.time_zone_select(:time_zone) end @@ -787,7 +787,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.origin = 'dk' - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.grouped_collection_select("origin", @continents, :countries, :continent_name, :country_id, :country_name) end diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 6ac5df1ea9..868a35c476 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -3,17 +3,16 @@ require 'abstract_unit' class FormTagHelperTest < ActionView::TestCase tests ActionView::Helpers::FormTagHelper - include ActiveSupport::Configurable - DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG + # include ActiveSupport::Configurable + # DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG def setup super - @controller = Class.new do + @controller = Class.new(BasicController) do def url_for(options) "http://www.example.com" end - end - @controller = @controller.new + end.new end VALID_HTML_ID = /^[A-Za-z][-_:.A-Za-z0-9]*$/ # see http://www.w3.org/TR/html4/types.html#type-name @@ -60,16 +59,14 @@ class FormTagHelperTest < ActionView::TestCase end def test_form_tag_with_block_in_erb - __in_erb_template = '' - form_tag("http://example.com") { concat "Hello world!" } + output_buffer = form_tag("http://example.com") { concat "Hello world!" } expected = %(<form action="http://example.com" method="post">Hello world!</form>) assert_dom_equal expected, output_buffer end def test_form_tag_with_block_and_method_in_erb - __in_erb_template = '' - form_tag("http://example.com", :method => :put) { concat "Hello world!" } + output_buffer = form_tag("http://example.com", :method => :put) { concat "Hello world!" } expected = %(<form action="http://example.com" method="post"><div style='margin:0;padding:0;display:inline'><input type="hidden" name="_method" value="put" /></div>Hello world!</form>) assert_dom_equal expected, output_buffer @@ -127,19 +124,19 @@ class FormTagHelperTest < ActionView::TestCase end def test_select_tag - actual = select_tag "people", "<option>david</option>" + actual = select_tag "people", "<option>david</option>".html_safe expected = %(<select id="people" name="people"><option>david</option></select>) assert_dom_equal expected, actual end def test_select_tag_with_multiple - actual = select_tag "colors", "<option>Red</option><option>Blue</option><option>Green</option>", :multiple => :true + actual = select_tag "colors", "<option>Red</option><option>Blue</option><option>Green</option>".html_safe, :multiple => :true expected = %(<select id="colors" multiple="multiple" name="colors"><option>Red</option><option>Blue</option><option>Green</option></select>) assert_dom_equal expected, actual end def test_select_tag_disabled - actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>", :disabled => :true + actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :disabled => :true expected = %(<select id="places" disabled="disabled" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>) assert_dom_equal expected, actual end @@ -150,13 +147,13 @@ class FormTagHelperTest < ActionView::TestCase end def test_select_tag_with_include_blank - actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>", :include_blank => true + actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :include_blank => true expected = %(<select id="places" name="places"><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>) assert_dom_equal expected, actual end def test_select_tag_with_include_blank_with_string - actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>", :include_blank => "string" + actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :include_blank => "string" expected = %(<select id="places" name="places"><option value="">string</option><option>Home</option><option>Work</option><option>Pub</option></select>) assert_dom_equal expected, actual end @@ -282,9 +279,9 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes") assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil) assert_dom_equal %(<input type="checkbox" />), tag(:input, :type => "checkbox", :checked => false) - assert_dom_equal %(<select id="people" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => true) - assert_dom_equal %(<select id="people_" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people[]", "<option>david</option>", :multiple => true) - assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => nil) + assert_dom_equal %(<select id="people" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people", "<option>david</option>".html_safe, :multiple => true) + assert_dom_equal %(<select id="people_" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people[]", "<option>david</option>".html_safe, :multiple => true) + assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>".html_safe, :multiple => nil) end def test_stringify_symbol_keys @@ -333,26 +330,22 @@ class FormTagHelperTest < ActionView::TestCase end def test_field_set_tag_in_erb - __in_erb_template = '' - field_set_tag("Your details") { concat "Hello world!" } + output_buffer = field_set_tag("Your details") { concat "Hello world!" } expected = %(<fieldset><legend>Your details</legend>Hello world!</fieldset>) assert_dom_equal expected, output_buffer - self.output_buffer = ''.html_safe - field_set_tag { concat "Hello world!" } + output_buffer = field_set_tag { concat "Hello world!" } expected = %(<fieldset>Hello world!</fieldset>) assert_dom_equal expected, output_buffer - self.output_buffer = ''.html_safe - field_set_tag('') { concat "Hello world!" } + output_buffer = field_set_tag('') { concat "Hello world!" } expected = %(<fieldset>Hello world!</fieldset>) assert_dom_equal expected, output_buffer - self.output_buffer = ''.html_safe - field_set_tag('', :class => 'format') { concat "Hello world!" } + output_buffer = field_set_tag('', :class => 'format') { concat "Hello world!" } expected = %(<fieldset class="format">Hello world!</fieldset>) assert_dom_equal expected, output_buffer diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index b3e7abc387..f49b763881 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -7,8 +7,9 @@ class JavaScriptHelperTest < ActionView::TestCase attr_accessor :formats, :output_buffer - def reset_formats(format) - @format = format + def update_details(details) + @details = details + yield if block_given? end def setup @@ -73,18 +74,6 @@ class JavaScriptHelperTest < ActionView::TestCase javascript_tag("alert('hello')", :id => "the_js_tag") end - def test_javascript_tag_with_block_in_erb - __in_erb_template = '' - javascript_tag { concat "alert('hello')" } - assert_dom_equal "<script type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", output_buffer - end - - def test_javascript_tag_with_block_and_options_in_erb - __in_erb_template = '' - javascript_tag(:id => "the_js_tag") { concat "alert('hello')" } - assert_dom_equal "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", output_buffer - end - def test_javascript_cdata_section assert_dom_equal "\n//<![CDATA[\nalert('hello')\n//]]>\n", javascript_cdata_section("alert('hello')") end diff --git a/actionpack/test/template/subscriber_test.rb b/actionpack/test/template/log_subscriber_test.rb index 8bacab7088..5076dfa70f 100644 --- a/actionpack/test/template/subscriber_test.rb +++ b/actionpack/test/template/log_subscriber_test.rb @@ -1,22 +1,22 @@ require "abstract_unit" -require "rails/subscriber/test_helper" -require "action_view/railties/subscriber" +require "rails/log_subscriber/test_helper" +require "action_view/railties/log_subscriber" require "controller/fake_models" -class AVSubscriberTest < ActiveSupport::TestCase - include Rails::Subscriber::TestHelper +class AVLogSubscriberTest < ActiveSupport::TestCase + include Rails::LogSubscriber::TestHelper def setup + super @old_logger = ActionController::Base.logger @view = ActionView::Base.new(ActionController::Base.view_paths, {}) Rails.stubs(:root).returns(File.expand_path(FIXTURE_LOAD_PATH)) - Rails::Subscriber.add(:action_view, ActionView::Railties::Subscriber.new) - super + Rails::LogSubscriber.add(:action_view, ActionView::Railties::LogSubscriber.new) end def teardown super - Rails::Subscriber.subscribers.clear + Rails::LogSubscriber.log_subscribers.clear ActionController::Base.logger = @old_logger end diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb new file mode 100644 index 0000000000..697ebc694a --- /dev/null +++ b/actionpack/test/template/lookup_context_test.rb @@ -0,0 +1,165 @@ +require "abstract_unit" +require "abstract_controller/rendering" + +ActionView::LookupContext::DetailsKey.class_eval do + def self.details_keys + @details_keys + end +end + +class LookupContextTest < ActiveSupport::TestCase + def setup + @lookup_context = ActionView::LookupContext.new(FIXTURE_LOAD_PATH, {}) + end + + def teardown + I18n.locale = :en + ActionView::LookupContext::DetailsKey.details_keys.clear + end + + test "process view paths on initialization" do + assert_kind_of ActionView::PathSet, @lookup_context.view_paths + end + + test "normalizes details on initialization" do + formats = Mime::SET + [nil] + locale = [I18n.locale, nil] + assert_equal Hash[:formats => formats, :locale => locale], @lookup_context.details + end + + test "allows me to set details" do + @lookup_context.details = { :formats => [:html], :locale => :pt } + assert_equal Hash[:formats => [:html, nil], :locale => [:pt, nil]], @lookup_context.details + end + + test "does not allow details to be modified in place" do + assert @lookup_context.details.frozen? + end + + test "allows me to update an specific detail" do + @lookup_context.update_details(:locale => :pt) + assert_equal :pt, I18n.locale + formats = Mime::SET + [nil] + locale = [I18n.locale, nil] + assert_equal Hash[:formats => formats, :locale => locale], @lookup_context.details + end + + test "allows me to change some details to execute an specific block of code" do + formats = Mime::SET + [nil] + @lookup_context.update_details(:locale => :pt) do + assert_equal Hash[:formats => formats, :locale => [:pt, nil]], @lookup_context.details + end + assert_equal Hash[:formats => formats, :locale => [:en, nil]], @lookup_context.details + end + + test "provides getters and setters for formats" do + @lookup_context.formats = :html + assert_equal [:html], @lookup_context.formats + end + + test "handles */* formats" do + @lookup_context.formats = [:"*/*"] + assert_equal Mime::SET, @lookup_context.formats + end + + test "provides getters and setters for locale" do + @lookup_context.locale = :pt + assert_equal :pt, @lookup_context.locale + end + + test "changing lookup_context locale, changes I18n.locale" do + @lookup_context.locale = :pt + assert_equal :pt, I18n.locale + end + + test "delegates changing the locale to the I18n configuration object if it contains a lookup_context object" do + begin + I18n.config = AbstractController::I18nProxy.new(I18n.config, @lookup_context) + @lookup_context.locale = :pt + assert_equal :pt, I18n.locale + assert_equal :pt, @lookup_context.locale + ensure + I18n.config = I18n.config.i18n_config + end + + assert_equal :pt, I18n.locale + end + + test "find templates using the given view paths and configured details" do + template = @lookup_context.find("hello_world", "test") + assert_equal "Hello world!", template.source + + @lookup_context.locale = :da + template = @lookup_context.find("hello_world", "test") + assert_equal "Hey verden", template.source + end + + test "adds fallbacks to view paths when required" do + assert_equal 1, @lookup_context.view_paths.size + + @lookup_context.with_fallbacks do + assert_equal 3, @lookup_context.view_paths.size + assert @lookup_context.view_paths.include?(ActionView::FileSystemResolver.new("")) + assert @lookup_context.view_paths.include?(ActionView::FileSystemResolver.new("/")) + end + end + + test "add fallbacks just once in nested fallbacks calls" do + @lookup_context.with_fallbacks do + @lookup_context.with_fallbacks do + assert_equal 3, @lookup_context.view_paths.size + end + end + end + + test "generates a new details key for each details hash" do + keys = [] + keys << @lookup_context.details_key + assert_equal 1, keys.uniq.size + + @lookup_context.locale = :da + keys << @lookup_context.details_key + assert_equal 2, keys.uniq.size + + @lookup_context.locale = :en + keys << @lookup_context.details_key + assert_equal 2, keys.uniq.size + + @lookup_context.formats = :html + keys << @lookup_context.details_key + assert_equal 3, keys.uniq.size + + @lookup_context.formats = nil + keys << @lookup_context.details_key + assert_equal 3, keys.uniq.size + end + + test "gives the key forward to the resolver, so it can be used as cache key" do + @lookup_context.view_paths = ActionView::FixtureResolver.new("test/_foo.erb" => "Foo") + template = @lookup_context.find("foo", "test", true) + assert_equal "Foo", template.source + + # Now we are going to change the template, but it won't change the returned template + # since we will hit the cache. + @lookup_context.view_paths.first.hash["test/_foo.erb"] = "Bar" + template = @lookup_context.find("foo", "test", true) + assert_equal "Foo", template.source + + # This time we will change the locale. The updated template should be picked since + # lookup_context generated a new key after we changed the locale. + @lookup_context.locale = :da + template = @lookup_context.find("foo", "test", true) + assert_equal "Bar", template.source + + # Now we will change back the locale and it will still pick the old template. + # This is expected because lookup_context will reuse the previous key for :en locale. + @lookup_context.locale = :en + template = @lookup_context.find("foo", "test", true) + assert_equal "Foo", template.source + + # Finally, we can expire the cache. And the expected template will be used. + @lookup_context.view_paths.first.clear_cache + template = @lookup_context.find("foo", "test", true) + assert_equal "Bar", template.source + end +end
\ No newline at end of file diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb index d95bdc2b90..619293dc43 100644 --- a/actionpack/test/template/prototype_helper_test.rb +++ b/actionpack/test/template/prototype_helper_test.rb @@ -4,6 +4,7 @@ require 'active_model' class Bunny < Struct.new(:Bunny, :id) extend ActiveModel::Naming include ActiveModel::Conversion + def to_key() id ? [id] : nil end end class Author @@ -11,6 +12,7 @@ class Author include ActiveModel::Conversion attr_reader :id + def to_key() id ? [id] : nil end def save; @id = 1 end def new_record?; @id.nil? end def name @@ -23,6 +25,7 @@ class Article include ActiveModel::Conversion attr_reader :id attr_reader :author_id + def to_key() id ? [id] : nil end def save; @id = 1; @author_id = 1 end def new_record?; @id.nil? end def name @@ -36,8 +39,9 @@ class Author::Nested < Author; end class PrototypeHelperBaseTest < ActionView::TestCase attr_accessor :formats, :output_buffer - def reset_formats(format) - @format = format + def update_details(details) + @details = details + yield if block_given? end def setup diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb index 1cd18c0692..74d7bba4fe 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionpack/test/template/record_tag_helper_test.rb @@ -18,6 +18,7 @@ class RecordTagHelperTest < ActionView::TestCase def setup super @post = Post.new + @post.persisted = true end def test_content_tag_for diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index fdf3db1cdb..cea8ab1bce 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -33,17 +33,17 @@ module RenderTestCases end def test_render_file_with_localization - old_locale, I18n.locale = I18n.locale, :da + old_locale, @view.locale = @view.locale, :da assert_equal "Hey verden", @view.render(:file => "test/hello_world") ensure - I18n.locale = old_locale + @view.locale = old_locale end def test_render_file_with_dashed_locale - old_locale, I18n.locale = I18n.locale, :"pt-BR" + old_locale, @view.locale = @view.locale, :"pt-BR" assert_equal "Ola mundo", @view.render(:file => "test/hello_world") ensure - I18n.locale = old_locale + @view.locale = old_locale end def test_render_file_at_top_level @@ -108,7 +108,7 @@ module RenderTestCases @view.render(:partial => "test/raise") flunk "Render did not raise Template::Error" rescue ActionView::Template::Error => e - assert_match "undefined local variable or method `doesnt_exist'", e.message + assert_match %r!method.*doesnt_exist!, e.message assert_equal "", e.sub_template_message assert_equal "1", e.line_number assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name @@ -118,7 +118,7 @@ module RenderTestCases @view.render(:file => "test/sub_template_raise") flunk "Render did not raise Template::Error" rescue ActionView::Template::Error => e - assert_match "undefined local variable or method `doesnt_exist'", e.message + assert_match %r!method.*doesnt_exist!, e.message assert_equal "Trace of template inclusion: #{File.expand_path("#{FIXTURE_LOAD_PATH}/test/sub_template_raise.html.erb")}", e.sub_template_message assert_equal "1", e.line_number assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name @@ -233,6 +233,11 @@ module RenderTestCases @view.render(:file => "test/nested_layout.erb", :layout => "layouts/yield") end + def test_render_with_file_in_layout + assert_equal %(\n<title>title</title>\n\n), + @view.render(:file => "test/layout_render_file.erb") + end + if '1.9'.respond_to?(:force_encoding) def test_render_utf8_template_with_magic_comment with_external_encoding Encoding::ASCII_8BIT do @@ -265,7 +270,7 @@ class CachedViewRenderTest < ActiveSupport::TestCase # Ensure view path cache is primed def setup view_paths = ActionController::Base.view_paths - assert_equal ActionView::FileSystemResolverWithFallback, view_paths.first.class + assert_equal ActionView::FileSystemResolver, view_paths.first.class setup_view(view_paths) end end @@ -276,9 +281,9 @@ class LazyViewRenderTest < ActiveSupport::TestCase # Test the same thing as above, but make sure the view path # is not eager loaded def setup - path = ActionView::FileSystemResolverWithFallback.new(FIXTURE_LOAD_PATH) + path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH) view_paths = ActionView::Base.process_view_paths(path) - assert_equal ActionView::FileSystemResolverWithFallback, view_paths.first.class + assert_equal ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH), view_paths.first setup_view(view_paths) end end diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb index 433f6514cf..256d9bdcde 100644 --- a/actionpack/test/template/tag_helper_test.rb +++ b/actionpack/test/template/tag_helper_test.rb @@ -37,18 +37,18 @@ class TagHelperTest < ActionView::TestCase assert content_tag("a", "Create", "href" => "create").html_safe? assert_equal content_tag("a", "Create", "href" => "create"), content_tag("a", "Create", :href => "create") + assert_equal "<p><script>evil_js</script></p>", + content_tag(:p, '<script>evil_js</script>') end def test_content_tag_with_block_in_erb - __in_erb_template = '' - content_tag(:div) { concat "Hello world!" } - assert_dom_equal "<div>Hello world!</div>", output_buffer + buffer = content_tag(:div) { concat "Hello world!" } + assert_dom_equal "<div>Hello world!</div>", buffer end def test_content_tag_with_block_and_options_in_erb - __in_erb_template = '' - content_tag(:div, :class => "green") { concat "Hello world!" } - assert_dom_equal %(<div class="green">Hello world!</div>), output_buffer + buffer = content_tag(:div, :class => "green") { concat "Hello world!" } + assert_dom_equal %(<div class="green">Hello world!</div>), buffer end def test_content_tag_with_block_and_options_out_of_erb @@ -66,10 +66,10 @@ class TagHelperTest < ActionView::TestCase output_buffer end + # TAG TODO: Move this into a real template def test_content_tag_nested_in_content_tag_in_erb - __in_erb_template = true - content_tag("p") { concat content_tag("b", "Hello") } - assert_equal '<p><b>Hello</b></p>', output_buffer + buffer = content_tag("p") { concat content_tag("b", "Hello") } + assert_equal '<p><b>Hello</b></p>', buffer end def test_content_tag_with_escaped_array_class diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index be2c6b3108..195a6ea3ae 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -89,16 +89,23 @@ module ActionView end test "helper class that is being tested is always included in view instance" do - self.class.helper_class.module_eval do - def render_from_helper - render :partial => 'customer', :collection => @customers + # This ensure is a hidious hack to deal with these tests bleeding + # methods between eachother + begin + self.class.helper_class.module_eval do + def render_from_helper + render :partial => 'customer', :collection => @customers + end end - end - TestController.stubs(:controller_path).returns('test') + TestController.stubs(:controller_path).returns('test') - @customers = [stub(:name => 'Eloy'), stub(:name => 'Manfred')] - assert_match /Hello: EloyHello: Manfred/, render(:partial => 'test/from_helper') + @customers = [stub(:name => 'Eloy'), stub(:name => 'Manfred')] + assert_match /Hello: EloyHello: Manfred/, render(:partial => 'test/from_helper') + + ensure + self.class.helper_class.send(:remove_method, :render_from_helper) + end end test "no additional helpers should shared across test cases" do @@ -107,7 +114,7 @@ module ActionView end test "is able to use routes" do - controller.request.assign_parameters('foo', 'index') + controller.request.assign_parameters(@router, 'foo', 'index') assert_equal '/foo', url_for assert_equal '/bar', url_for(:controller => 'bar') end @@ -147,10 +154,16 @@ module ActionView end test "is able to make methods available to the view" do - _helpers.module_eval do - def render_from_helper; from_test_case end + # This ensure is a hidious hack to deal with these tests bleeding + # methods between eachother + begin + _helpers.module_eval do + def render_from_helper; from_test_case end + end + assert_equal 'Word!', render(:partial => 'test/from_helper') + ensure + _helpers.send(:remove_method, :render_from_helper) end - assert_equal 'Word!', render(:partial => 'test/from_helper') end def from_test_case; 'Word!'; end diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb index 4b73c44f7e..699fb2f5bc 100644 --- a/actionpack/test/template/translation_helper_test.rb +++ b/actionpack/test/template/translation_helper_test.rb @@ -18,6 +18,11 @@ class TranslationHelperTest < ActiveSupport::TestCase assert_equal expected, translate(:foo) end + def test_translation_of_an_array + I18n.expects(:translate).with(["foo", "bar"], :raise => true).returns(["foo", "bar"]) + assert_equal ["foo", "bar"], translate(["foo", "bar"]) + end + def test_delegates_localize_to_i18n @time = Time.utc(2008, 7, 8, 12, 18, 38) I18n.expects(:localize).with(@time) diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index e904e88f49..165cb655da 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -1,22 +1,21 @@ # encoding: utf-8 require 'abstract_unit' +require 'active_support/ordered_options' require 'controller/fake_controllers' -RequestMock = Struct.new("Request", :request_uri, :protocol, :host_with_port, :env) - class UrlHelperTest < ActionView::TestCase - include ActiveSupport::Configurable - DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG def setup super - @controller = Class.new do - attr_accessor :url, :request + @controller = Class.new(BasicController) do + attr_accessor :url def url_for(options) url end end + @controller = @controller.new + @request = @controller.request = ActionDispatch::TestRequest.new @controller.url = "http://www.example.com" end @@ -38,12 +37,13 @@ class UrlHelperTest < ActionView::TestCase end def test_url_for_with_back - @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'}) + @request.env['HTTP_REFERER'] = 'http://www.example.com/referer' assert_equal 'http://www.example.com/referer', url_for(:back) end def test_url_for_with_back_and_no_referer - @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {}) + @request.env['HOST_NAME'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' assert_equal 'javascript:history.back()', url_for(:back) end @@ -122,7 +122,6 @@ class UrlHelperTest < ActionView::TestCase url = {:controller => 'weblog', :action => 'show'} @controller = ActionController::Base.new @controller.request = ActionController::TestRequest.new - @controller.url = ActionController::UrlRewriter.new(@controller.request, url) assert_dom_equal(%q{<a href="/weblog/show">Test Link</a>}, link_to('Test Link', url)) end @@ -131,7 +130,6 @@ class UrlHelperTest < ActionView::TestCase url = {:controller => 'weblog', :action => 'show', :host => 'www.example.com'} @controller = ActionController::Base.new @controller.request = ActionController::TestRequest.new - @controller.url = ActionController::UrlRewriter.new(@controller.request, url) assert_dom_equal(%q{<a href="http://www.example.com/weblog/show">Test Link</a>}, link_to('Test Link', url)) end @@ -144,22 +142,28 @@ class UrlHelperTest < ActionView::TestCase end def test_link_tag_with_back - @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'}) + @request.env['HOST_NAME'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' + @request.env['HTTP_REFERER'] = 'http://www.example.com/referer' assert_dom_equal "<a href=\"http://www.example.com/referer\">go back</a>", link_to('go back', :back) end def test_link_tag_with_back_and_no_referer - @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {}) + @request.env['HOST_NAME'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' assert_dom_equal "<a href=\"javascript:history.back()\">go back</a>", link_to('go back', :back) end def test_link_tag_with_back - @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'}) + @request.env['HOST_NAME'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' + @request.env['HTTP_REFERER'] = 'http://www.example.com/referer' assert_dom_equal "<a href=\"http://www.example.com/referer\">go back</a>", link_to('go back', :back) end def test_link_tag_with_back_and_no_referer - @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {}) + @request.env['HOST_NAME'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' assert_dom_equal "<a href=\"javascript:history.back()\">go back</a>", link_to('go back', :back) end @@ -263,55 +267,60 @@ class UrlHelperTest < ActionView::TestCase end def test_current_page_with_simple_url - @controller.request = RequestMock.new("http://www.example.com/weblog/show") + @request.env['HTTP_HOST'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' @controller.url = "http://www.example.com/weblog/show" assert current_page?({ :action => "show", :controller => "weblog" }) assert current_page?("http://www.example.com/weblog/show") end def test_current_page_ignoring_params - @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") + @request.env['HTTP_HOST'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' + @request.env['QUERY_STRING'] = 'order=desc&page=1' @controller.url = "http://www.example.com/weblog/show?order=desc&page=1" assert current_page?({ :action => "show", :controller => "weblog" }) assert current_page?("http://www.example.com/weblog/show") end def test_current_page_with_params_that_match - @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") + @request.env['HTTP_HOST'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' + @request.env['QUERY_STRING'] = 'order=desc&page=1' @controller.url = "http://www.example.com/weblog/show?order=desc&page=1" assert current_page?({ :action => "show", :controller => "weblog", :order => "desc", :page => "1" }) assert current_page?("http://www.example.com/weblog/show?order=desc&page=1") end def test_link_unless_current - @controller.request = RequestMock.new("http://www.example.com/weblog/show") + @request.env['HTTP_HOST'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' @controller.url = "http://www.example.com/weblog/show" assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show") - @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc") + @request.env['QUERY_STRING'] = 'order=desc' @controller.url = "http://www.example.com/weblog/show" assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show") - @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") + @request.env['QUERY_STRING'] = 'order=desc&page=1' @controller.url = "http://www.example.com/weblog/show?order=desc&page=1" assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog", :order=>'desc', :page=>'1' }) assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=1") assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=1") - @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc") + @request.env['QUERY_STRING'] = 'order=desc' @controller.url = "http://www.example.com/weblog/show?order=asc" assert_equal "<a href=\"http://www.example.com/weblog/show?order=asc\">Showing</a>", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) assert_equal "<a href=\"http://www.example.com/weblog/show?order=asc\">Showing</a>", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=asc") - @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") + @request.env['QUERY_STRING'] = 'order=desc&page=1' @controller.url = "http://www.example.com/weblog/show?order=desc&page=2" assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&page=2\">Showing</a>", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&page=2\">Showing</a>", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=2") - - @controller.request = RequestMock.new("http://www.example.com/weblog/show") + @request.env['QUERY_STRING'] = '' @controller.url = "http://www.example.com/weblog/list" assert_equal "<a href=\"http://www.example.com/weblog/list\">Listing</a>", link_to_unless_current("Listing", :action => "list", :controller => "weblog") @@ -346,7 +355,7 @@ class UrlHelperTest < ActionView::TestCase end def test_mail_to_with_img - assert_dom_equal %(<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>), mail_to('feedback@example.com', '<img src="/feedback.png" />') + assert_dom_equal %(<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>), mail_to('feedback@example.com', '<img src="/feedback.png" />'.html_safe) end def test_mail_to_with_hex @@ -464,8 +473,6 @@ end class LinkToUnlessCurrentWithControllerTest < ActionController::TestCase def setup super - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new @controller = TasksController.new end @@ -499,14 +506,14 @@ end class Workshop extend ActiveModel::Naming include ActiveModel::Conversion - attr_accessor :id, :new_record + attr_accessor :id - def initialize(id, new_record) - @id, @new_record = id, new_record + def initialize(id) + @id = id end - def new_record? - @new_record + def persisted? + id.present? end def to_s @@ -517,14 +524,14 @@ end class Session extend ActiveModel::Naming include ActiveModel::Conversion - attr_accessor :id, :workshop_id, :new_record + attr_accessor :id, :workshop_id - def initialize(id, new_record) - @id, @new_record = id, new_record + def initialize(id) + @id = id end - def new_record? - @new_record + def persisted? + id.present? end def to_s @@ -534,12 +541,12 @@ end class WorkshopsController < ActionController::Base def index - @workshop = Workshop.new(1, true) + @workshop = Workshop.new(nil) render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" end def show - @workshop = Workshop.new(params[:id], false) + @workshop = Workshop.new(params[:id]) render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" end @@ -548,14 +555,14 @@ end class SessionsController < ActionController::Base def index - @workshop = Workshop.new(params[:workshop_id], false) - @session = Session.new(1, true) + @workshop = Workshop.new(params[:workshop_id]) + @session = Session.new(nil) render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>" end def show - @workshop = Workshop.new(params[:workshop_id], false) - @session = Session.new(params[:id], false) + @workshop = Workshop.new(params[:workshop_id]) + @session = Session.new(params[:id]) render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>" end @@ -565,8 +572,7 @@ end class PolymorphicControllerTest < ActionController::TestCase def setup super - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new + @response = ActionController::TestResponse.new end def test_new_resource |