diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2010-01-17 03:20:30 +0530 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2010-01-17 03:20:30 +0530 |
commit | b04230e3bbf912d60601e9e7b797c4cd43581d51 (patch) | |
tree | 97a2f784a2ec2bfae4f960af56a9280dad6f7774 /actionpack | |
parent | 867829b187969607aa12f2b0457f25da9c204db0 (diff) | |
parent | 6e3bee6cf1f0d2684152292db0a8b757249824fd (diff) | |
download | rails-b04230e3bbf912d60601e9e7b797c4cd43581d51.tar.gz rails-b04230e3bbf912d60601e9e7b797c4cd43581d51.tar.bz2 rails-b04230e3bbf912d60601e9e7b797c4cd43581d51.zip |
Merge remote branch 'mainstream/master'
Conflicts:
actionpack/lib/action_controller/metal/flash.rb
Diffstat (limited to 'actionpack')
96 files changed, 2976 insertions, 2019 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 782b4229fb..014a501080 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,13 @@ *Edge* +* Fixed that PrototypeHelper#update_page should return html_safe [DHH] + +* Fixed that much of DateHelper wouldn't return html_safe? strings [DHH] + +* Fixed that fragment caching should return a cache hit as html_safe (or it would all just get escaped) [DHH] + +* Added that ActionController::Base now does helper :all instead of relying on the default ApplicationController in Rails to do it [DHH] + * Added ActionDispatch::Request#authorization to access the http authentication header regardless of its proxy hiding [DHH] * Added :alert, :notice, and :flash as options to ActionController::Base#redirect_to that'll automatically set the proper flash before the redirection [DHH]. Examples: diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 237ab577ba..efc35a7e56 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -15,4 +15,5 @@ module AbstractController autoload :LocalizedCache autoload :Logger autoload :Rendering + autoload :UrlFor end diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index a6889d5d01..48725ad82a 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -86,6 +86,11 @@ module AbstractController abstract! + # Initialize controller with nil formats. + def initialize #:nodoc: + @_formats = nil + end + # Calls the action going through the entire action dispatch stack. # # The actual method that is called is determined by calling diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 332d86b089..d57136dbb8 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -17,12 +17,6 @@ module AbstractController self._view_paths ||= ActionView::PathSet.new end - # Initialize controller with nil formats. - def initialize(*) #:nodoc: - @_formats = nil - super - end - # An instance of a view class. The default view class is ActionView::Base # # The view class must have the following methods: diff --git a/actionpack/lib/abstract_controller/url_for.rb b/actionpack/lib/abstract_controller/url_for.rb new file mode 100644 index 0000000000..6b7d2b1f34 --- /dev/null +++ b/actionpack/lib/abstract_controller/url_for.rb @@ -0,0 +1,156 @@ +module AbstractController + # 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 AbstractController::Routing and AbstractController::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 AbstractController::UrlFor is what you're looking for. Read on for + # an introduction. + # + # == URL generation from parameters + # + # As you may know, some functions - such as AbstractController::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 AbstractController::UrlFor under the hood. And in particular, + # they use the AbstractController::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 AbstractController::UrlFor#url_for. + # So within mailers, you only have to type 'url_for' instead of 'AbstractController::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 AbstractController::UrlFor in your class: + # + # class User < ActiveRecord::Base + # include AbstractController::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) + extlib_inheritable_accessor :default_url_options, + :instance_writer => false, :instance_reader => false + self.default_url_options ||= {} + 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 AbstractController::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 + end + end +end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index d66fc3fcc9..8bc2cc79d2 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -23,31 +23,32 @@ module ActionController autoload :Helpers autoload :HideActions autoload :HttpAuthentication - autoload :Logger + autoload :Instrumentation autoload :MimeResponds autoload :RackDelegation autoload :Redirecting - autoload :Rendering autoload :Renderers + autoload :Rendering autoload :RequestForgeryProtection autoload :Rescue autoload :Responder autoload :SessionManagement autoload :Streaming + autoload :Testing autoload :UrlFor autoload :Verification end - autoload :Dispatcher, 'action_controller/dispatch/dispatcher' - autoload :PerformanceTest, 'action_controller/deprecated/performance_test' - autoload :Routing, 'action_controller/deprecated' + autoload :Dispatcher, 'action_controller/deprecated/dispatcher' autoload :Integration, 'action_controller/deprecated/integration_test' autoload :IntegrationTest, 'action_controller/deprecated/integration_test' + autoload :PerformanceTest, 'action_controller/deprecated/performance_test' + autoload :Routing, 'action_controller/deprecated' + autoload :TestCase, 'action_controller/test_case' eager_autoload do autoload :RecordIdentifier autoload :UrlRewriter - autoload :UrlWriter, 'action_controller/url_rewriter' # 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 b23be66910..260e5da336 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -6,6 +6,8 @@ module ActionController include AbstractController::Layouts include ActionController::Helpers + helper :all # By default, all helpers should be included + include ActionController::HideActions include ActionController::UrlFor include ActionController::Redirecting @@ -13,7 +15,6 @@ module ActionController include ActionController::Renderers::All include ActionController::ConditionalGet include ActionController::RackDelegation - include ActionController::Logger include ActionController::Configuration # Legacy modules @@ -31,9 +32,13 @@ module ActionController include ActionController::Streaming include ActionController::HttpAuthentication::Basic::ControllerMethods include ActionController::HttpAuthentication::Digest::ControllerMethods - include ActionController::FilterParameterLogging include ActionController::Translation + # Add instrumentations hooks at the bottom, to ensure they instrument + # all the methods properly. + include ActionController::Instrumentation + include ActionController::FilterParameterLogging + # TODO: Extract into its own module # This should be moved together with other normalizing behavior module ImplicitRender diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 69ed84da95..d784138ebe 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -60,17 +60,6 @@ module ActionController #:nodoc: def cache_configured? perform_caching && cache_store end - - def log_event(name, before, after, instrumenter_id, payload) - if name.to_s =~ /(read|write|cache|expire|exist)_(fragment|page)\??/ - key_or_path = payload[:key] || payload[:path] - human_name = name.to_s.humanize - duration = (after - before) * 1000 - logger.info("#{human_name} #{key_or_path.inspect} (%.1fms)" % duration) - else - super - end - end end def caching_allowed? diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index f569d0dd8b..00a7f034d3 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -36,8 +36,8 @@ module ActionController #:nodoc: def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc: if perform_caching - if fragment_exist?(name,options) - buffer.concat(read_fragment(name, options)) + if fragment_exist?(name, options) + buffer.safe_concat(read_fragment(name, options)) else pos = buffer.length block.call @@ -53,7 +53,7 @@ module ActionController #:nodoc: return content unless cache_configured? key = fragment_cache_key(key) - ActiveSupport::Notifications.instrument(:write_fragment, :key => key) do + instrument_fragment_cache :write_fragment, key do cache_store.write(key, content, options) end content @@ -64,7 +64,7 @@ module ActionController #:nodoc: return unless cache_configured? key = fragment_cache_key(key) - ActiveSupport::Notifications.instrument(:read_fragment, :key => key) do + instrument_fragment_cache :read_fragment, key do cache_store.read(key, options) end end @@ -74,7 +74,7 @@ module ActionController #:nodoc: return unless cache_configured? key = fragment_cache_key(key) - ActiveSupport::Notifications.instrument(:exist_fragment?, :key => key) do + instrument_fragment_cache :exist_fragment?, key do cache_store.exist?(key, options) end end @@ -101,16 +101,18 @@ module ActionController #:nodoc: key = fragment_cache_key(key) unless key.is_a?(Regexp) message = nil - ActiveSupport::Notifications.instrument(:expire_fragment, :key => key) do + instrument_fragment_cache :expire_fragment, key do if key.is_a?(Regexp) - message = "Expired fragments matching: #{key.source}" cache_store.delete_matched(key, options) else - message = "Expired fragment: #{key}" cache_store.delete(key, options) end end end + + def instrument_fragment_cache(name, key) + ActiveSupport::Notifications.instrument("action_controller.#{name}", :key => key){ yield } + end end end end diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index d46f528c7e..5797eeebd6 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -64,7 +64,7 @@ module ActionController #:nodoc: return unless perform_caching path = page_cache_path(path) - ActiveSupport::Notifications.instrument(:expire_page, :path => path) do + instrument_page_cache :expire_page, path do File.delete(path) if File.exist?(path) end end @@ -75,7 +75,7 @@ module ActionController #:nodoc: return unless perform_caching path = page_cache_path(path) - ActiveSupport::Notifications.instrument(:cache_page, :path => path) do + instrument_page_cache :write_page, path do FileUtils.makedirs(File.dirname(path)) File.open(path, "wb+") { |f| f.write(content) } end @@ -107,6 +107,10 @@ module ActionController #:nodoc: def page_cache_path(path) page_cache_directory + page_cache_file(path) end + + def instrument_page_cache(name, path) + ActiveSupport::Notifications.instrument("action_controller.#{name}", :path => path){ yield } + end end # Expires the page that was cached with the +options+ as a key. Example: diff --git a/actionpack/lib/action_controller/deprecated/dispatcher.rb b/actionpack/lib/action_controller/deprecated/dispatcher.rb new file mode 100644 index 0000000000..3da3c8ce7d --- /dev/null +++ b/actionpack/lib/action_controller/deprecated/dispatcher.rb @@ -0,0 +1,31 @@ +module ActionController + class Dispatcher + cattr_accessor :prepare_each_request + self.prepare_each_request = false + + class << self + def before_dispatch(*args, &block) + ActiveSupport::Deprecation.warn "ActionController::Dispatcher.before_dispatch is deprecated. " << + "Please use ActionDispatch::Callbacks.before instead.", caller + ActionDispatch::Callbacks.before(*args, &block) + end + + def after_dispatch(*args, &block) + ActiveSupport::Deprecation.warn "ActionController::Dispatcher.after_dispatch is deprecated. " << + "Please use ActionDispatch::Callbacks.after instead.", caller + ActionDispatch::Callbacks.after(*args, &block) + end + + def to_prepare(*args, &block) + ActiveSupport::Deprecation.warn "ActionController::Dispatcher.to_prepare is deprecated. " << + "Please use ActionDispatch::Callbacks.to_prepare instead.", caller + ActionDispatch::Callbacks.after(*args, &block) + end + + def new + ActiveSupport::Deprecation.warn "ActionController::Dispatcher.new is deprecated, use Rails.application instead." + Rails.application + end + end + end +end diff --git a/actionpack/lib/action_controller/dispatch/dispatcher.rb b/actionpack/lib/action_controller/dispatch/dispatcher.rb deleted file mode 100644 index cf02757cf6..0000000000 --- a/actionpack/lib/action_controller/dispatch/dispatcher.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'active_support/core_ext/module/delegation' - -module ActionController - # Dispatches requests to the appropriate controller and takes care of - # reloading the app after each request when Dependencies.load? is true. - class Dispatcher - cattr_accessor :prepare_each_request - self.prepare_each_request = false - - class << self - def define_dispatcher_callbacks(cache_classes) - unless cache_classes - # Run prepare callbacks before every request in development mode - self.prepare_each_request = true - - ActionDispatch::Callbacks.after_dispatch do - # Cleanup the application before processing the current request. - ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord) - ActiveSupport::Dependencies.clear - ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord) - end - - ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false - end - - if defined?(ActiveRecord) - to_prepare(:activerecord_instantiate_observers) do - ActiveRecord::Base.instantiate_observers - end - end - - if Base.logger && Base.logger.respond_to?(:flush) - after_dispatch do - Base.logger.flush - end - end - - to_prepare do - I18n.reload! - end - end - - delegate :to_prepare, :before_dispatch, :around_dispatch, :after_dispatch, - :to => ActionDispatch::Callbacks - - def new - # DEPRECATE Rails application fallback - Rails.application - end - end - end -end diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index a90f798cd5..0e869e4e87 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -21,6 +21,8 @@ module ActionController 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 @@ -29,15 +31,7 @@ module ActionController @variables_added @request_origin @url @parent_controller @action_name @before_filter_chain_aborted @_headers @_params - @_flash @_response) - - # Indicates whether or not optimise the generated named - # route helper methods - cattr_accessor :optimise_named_routes - self.optimise_named_routes = true - - cattr_accessor :resources_path_names - self.resources_path_names = { :new => 'new', :edit => 'edit' } + @_response) # Controls the resource action separator cattr_accessor :resource_action_separator diff --git a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb index 59e200396a..0b1e1ee6ab 100644 --- a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb +++ b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb @@ -2,6 +2,8 @@ module ActionController module FilterParameterLogging extend ActiveSupport::Concern + INTERNAL_PARAMS = %w(controller action format _method only_path) + module ClassMethods # Replace sensitive parameter data from the request log. # Filters parameters that have any of the arguments as a substring. @@ -48,27 +50,19 @@ module ActionController filtered_params[key] = value end - filtered_params + filtered_params.except!(*INTERNAL_PARAMS) end protected :filter_parameters end - - protected - - # Overwrite log_process_action to include parameters information. - # If this method is invoked, it means logger is defined, so don't - # worry with such scenario here. - def log_process_action(controller) #:nodoc: - params = controller.send(:filter_parameters, controller.request.params) - logger.info " Parameters: #{params.inspect}" unless params.empty? - super - end end - INTERNAL_PARAMS = [:controller, :action, :format, :_method, :only_path] - protected + def append_info_to_payload(payload) + super + payload[:params] = filter_parameters(request.params) + end + def filter_parameters(params) params.dup.except!(*INTERNAL_PARAMS) end diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index 25e25940a7..bd768b634e 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -1,187 +1,14 @@ module ActionController #:nodoc: - # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed - # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create - # action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can - # then expose the flash to its template. Actually, that exposure is automatically done. Example: - # - # class PostsController < ActionController::Base - # def create - # # save post - # flash[:notice] = "Successfully created post" - # redirect_to @post - # end - # - # def show - # # doesn't need to assign the flash notice to the template, that's done automatically - # end - # end - # - # show.html.erb - # <% if flash[:notice] %> - # <div class="notice"><%= flash[:notice] %></div> - # <% end %> - # - # This example just places a string in the flash, but you can put any object in there. And of course, you can put as - # many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed. - # - # See docs on the FlashHash class for more details about the flash. module Flash extend ActiveSupport::Concern included do + delegate :flash, :to => :request + delegate :alert, :notice, :to => "request.flash" helper_method :alert, :notice end - class FlashNow #:nodoc: - def initialize(flash) - @flash = flash - end - - def []=(k, v) - @flash[k] = v - @flash.discard(k) - v - end - - def [](k) - @flash[k] - end - end - - class FlashHash < Hash - def initialize #:nodoc: - super - @used = Set.new - end - - def []=(k, v) #:nodoc: - keep(k) - super - end - - def update(h) #:nodoc: - h.keys.each { |k| keep(k) } - super - end - - alias :merge! :update - - def replace(h) #:nodoc: - @used = Set.new - super - end - - # Sets a flash that will not be available to the next action, only to the current. - # - # flash.now[:message] = "Hello current action" - # - # This method enables you to use the flash as a central messaging system in your app. - # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>). - # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will - # vanish when the current action is done. - # - # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>. - def now - FlashNow.new(self) - end - - # Keeps either the entire current flash or a specific flash entry available for the next action: - # - # flash.keep # keeps the entire flash - # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded - def keep(k = nil) - use(k, false) - end - - # Marks the entire flash or a single flash entry to be discarded by the end of the current action: - # - # flash.discard # discard the entire flash at the end of the current action - # flash.discard(:warning) # discard only the "warning" entry at the end of the current action - def discard(k = nil) - use(k) - end - - # Mark for removal entries that were kept, and delete unkept ones. - # - # This method is called automatically by filters, so you generally don't need to care about it. - def sweep #:nodoc: - keys.each do |k| - unless @used.include?(k) - @used << k - else - delete(k) - @used.delete(k) - end - end - - # clean up after keys that could have been left over by calling reject! or shift on the flash - (@used - keys).each{ |k| @used.delete(k) } - end - - def store(session) - return if self.empty? - session["flash"] = self - end - - private - # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods - # use() # marks the entire flash as used - # use('msg') # marks the "msg" entry as used - # use(nil, false) # marks the entire flash as unused (keeps it around for one more action) - # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action) - # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself - # if no key is passed. - def use(key = nil, used = true) - Array(key || keys).each { |k| used ? @used << k : @used.delete(k) } - return key ? self[key] : self - end - end - - # Access the contents of the flash. Use <tt>flash["notice"]</tt> to - # read a notice you put there or <tt>flash["notice"] = "hello"</tt> - # to put a new one. - def flash #:doc: - unless @_flash - @_flash = session["flash"] || FlashHash.new - @_flash.sweep - end - - @_flash - end - - # Convenience accessor for flash[:alert] - def alert - flash[:alert] - end - - # Convenience accessor for flash[:alert]= - def alert=(message) - flash[:alert] = message - end - - # Convenience accessor for flash[:notice] - def notice - flash[:notice] - end - - # Convenience accessor for flash[:notice]= - def notice=(message) - flash[:notice] = message - end - protected - def process_action(method_name) - @_flash = nil - super - @_flash.store(session) if @_flash - @_flash = nil - end - - def reset_session - super - @_flash = nil - end - def redirect_to(options = {}, response_status_and_flash = {}) #:doc: if alert = response_status_and_flash.delete(:alert) flash[:alert] = alert diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index c82d9cf369..37be8b3999 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 - include UrlFor + extend ActiveSupport::Concern + include ActionController::UrlFor # Return a response that has no content (merely headers). The options # argument is interpreted to be a hash of header names and values. diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb new file mode 100644 index 0000000000..7222b7b2fa --- /dev/null +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -0,0 +1,102 @@ +require 'abstract_controller/logger' + +module ActionController + # Adds instrumentation to several ends in ActionController::Base. It also provides + # some hooks related with process_action, this allows an ORM like ActiveRecord + # and/or DataMapper to plug in ActionController and show related information. + # + # Check ActiveRecord::Railties::ControllerRuntime for an example. + module Instrumentation + extend ActiveSupport::Concern + + included do + include AbstractController::Logger + end + + attr_internal :view_runtime + + def process_action(action, *args) + ActiveSupport::Notifications.instrument("action_controller.process_action") do |payload| + result = super + payload[:controller] = self.class.name + payload[:action] = self.action_name + payload[:formats] = request.formats.map(&:to_s) + payload[:remote_ip] = request.remote_ip + payload[:method] = request.method + payload[:status] = response.status + payload[:request_uri] = request.request_uri rescue "unknown" + append_info_to_payload(payload) + result + end + end + + def render(*args, &block) + if logger + render_output = nil + + self.view_runtime = cleanup_view_runtime do + Benchmark.ms { render_output = super } + end + + render_output + else + super + end + end + + def send_file(path, options={}) + ActiveSupport::Notifications.instrument("action_controller.send_file", + options.merge(:path => path)) do + super + end + end + + def send_data(data, options = {}) + ActiveSupport::Notifications.instrument("action_controller.send_data", options) do + super + end + end + + def redirect_to(*args) + ActiveSupport::Notifications.instrument("action_controller.redirect_to") do |payload| + result = super + payload[:status] = self.status + payload[:location] = self.location + result + end + end + + protected + + # A hook which allows you to clean up any time taken into account in + # views wrongly, like database querying time. + # + # def cleanup_view_runtime + # super - time_taken_in_something_expensive + # end + # + # :api: plugin + def cleanup_view_runtime #:nodoc: + yield + end + + # Everytime after an action is processed, this method is invoked + # with the payload, so you can add more information. + # :api: plugin + def append_info_to_payload(payload) #:nodoc: + payload[:view_runtime] = view_runtime + end + + module ClassMethods + # A hook which allows other frameworks to log what happened during + # controller process action. This method should return an array + # with the messages to be added. + # :api: plugin + def log_process_action(payload) #:nodoc: + messages, view_runtime = [], payload[:view_runtime] + messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime + messages + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal/logger.rb b/actionpack/lib/action_controller/metal/logger.rb deleted file mode 100644 index 4f4370e5f0..0000000000 --- a/actionpack/lib/action_controller/metal/logger.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'abstract_controller/logger' - -module ActionController - # Adds instrumentation to <tt>process_action</tt> and a <tt>log_event</tt> method - # responsible to log events from ActiveSupport::Notifications. This module handles - # :process_action and :render_template events but allows any other module to hook - # into log_event and provide its own logging facilities (as in ActionController::Caching). - module Logger - extend ActiveSupport::Concern - - included do - include AbstractController::Logger - end - - attr_internal :view_runtime - - def process_action(action) - ActiveSupport::Notifications.instrument(:process_action, :controller => self, :action => action) do - super - end - end - - def render(*args, &block) - if logger - render_output = nil - - self.view_runtime = cleanup_view_runtime do - Benchmark.ms { render_output = super } - end - - render_output - else - super - end - end - - # If you want to remove any time taken into account in :view_runtime - # wrongly, you can do it here: - # - # def cleanup_view_runtime - # super - time_taken_in_something_expensive - # end - # - # :api: plugin - def cleanup_view_runtime #:nodoc: - yield - end - - module ClassMethods - # This is the hook invoked by ActiveSupport::Notifications.subscribe. - # If you need to log any event, overwrite the method and do it here. - def log_event(name, before, after, instrumenter_id, payload) #:nodoc: - if name == :process_action - duration = [(after - before) * 1000, 0.01].max - controller = payload[:controller] - request = controller.request - - logger.info "\n\nProcessed #{controller.class.name}##{payload[:action]} " \ - "to #{request.formats} (for #{request.remote_ip} at #{before.to_s(:db)}) " \ - "[#{request.method.to_s.upcase}]" - - log_process_action(controller) - - message = "Completed in %.0fms" % duration - message << " | #{controller.response.status}" - message << " [#{request.request_uri rescue "unknown"}]" - - logger.info(message) - elsif name == :render_template - # TODO Make render_template logging work if you are using just ActionView - duration = (after - before) * 1000 - message = "Rendered #{payload[:identifier]}" - message << " within #{payload[:layout]}" if payload[:layout] - message << (" (%.1fms)" % duration) - logger.info(message) - end - end - - protected - - # A hook which allows logging what happened during controller process action. - # :api: plugin - def log_process_action(controller) #:nodoc: - view_runtime = controller.send :view_runtime - logger.info(" View runtime: %.1fms" % view_runtime.to_f) if view_runtime - end - end - end -end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 468c5f4fae..4c02677729 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -215,7 +215,10 @@ module ActionController #:nodoc: # a proc to it. # def respond_with(*resources, &block) - if response = retrieve_response_from_mimes([], &block) + raise "In order to use respond_with, first you need to declare the formats your " << + "controller responds to in the class level" if mimes_for_respond_to.empty? + + if response = retrieve_response_from_mimes(&block) options = resources.extract_options! options.merge!(:default_response => response) (options.delete(:responder) || responder).call(self, resources, options) @@ -246,9 +249,9 @@ module ActionController #:nodoc: # Collects mimes and return the response for the negotiated format. Returns # nil if :not_acceptable was sent to the client. # - def retrieve_response_from_mimes(mimes, &block) + def retrieve_response_from_mimes(mimes=nil, &block) collector = Collector.new { default_render } - mimes = collect_mimes_from_class_level if mimes.empty? + mimes ||= collect_mimes_from_class_level mimes.each { |mime| collector.send(mime) } block.call(collector) if block_given? diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 7a2f9a6fc5..faf0589fd2 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -9,7 +9,9 @@ module ActionController module Redirecting extend ActiveSupport::Concern + include AbstractController::Logger + include ActionController::UrlFor # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: # @@ -55,8 +57,6 @@ module ActionController self.status = _extract_redirect_to_status(options, response_status) self.location = _compute_redirect_to_location(options) self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>" - - logger.info("Redirected to #{location}") if logger && logger.info? end private diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 288b5d7c99..8f03b8bb17 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -88,13 +88,11 @@ module ActionController #:nodoc: @performed_render = false if options[:x_sendfile] - logger.info "Sending #{X_SENDFILE_HEADER} header #{path}" if logger 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| - logger.info "Streaming file #{path}" unless logger.nil? len = options[:buffer_size] || 4096 File.open(path, 'rb') do |file| while buf = file.read(len) @@ -103,7 +101,6 @@ module ActionController #:nodoc: end } else - logger.info "Sending file #{path}" unless logger.nil? File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read } end end @@ -141,7 +138,6 @@ 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: - logger.info "Sending data #{options[:filename]}" if logger send_file_headers! options.merge(:length => data.bytesize) render :status => options[:status], :text => data end diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 8c3810ebcb..73feacb872 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -2,40 +2,14 @@ module ActionController module UrlFor extend ActiveSupport::Concern - include RackDelegation + include AbstractController::UrlFor + include ActionController::RackDelegation - # 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) - end - - def rewrite_options(options) #:nodoc: - if defaults = default_url_options(options) - defaults.merge(options) - else - options - end - end + protected - def url_for(options = {}) - options ||= {} - case options - when String - options - when Hash - @url ||= UrlRewriter.new(request, params) - @url.rewrite(rewrite_options(options)) - else - polymorphic_url(options) - end + def _url_rewriter + return ActionController::UrlRewriter unless request + @_url_rewriter ||= ActionController::UrlRewriter.new(request, params) end end end diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index f861d12905..741101a210 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -5,6 +5,9 @@ module ActionController class Railtie < Rails::Railtie plugin_name :action_controller + require "action_controller/railties/subscriber" + subscriber ActionController::Railties::Subscriber.new + initializer "action_controller.set_configs" do |app| app.config.action_controller.each do |k,v| ActionController::Base.send "#{k}=", v @@ -23,26 +26,6 @@ module ActionController app.reload_routes! end - # Include middleware to serve up static assets - initializer "action_controller.initialize_static_server" do |app| - if app.config.serve_static_assets - app.config.middleware.use(ActionDispatch::Static, Rails.public_path) - end - end - - initializer "action_controller.initialize_middleware_stack" do |app| - middleware = app.config.middleware - middleware.use(::Rack::Lock, :if => lambda { ActionController::Base.allow_concurrency }) - middleware.use(::Rack::Runtime) - middleware.use(ActionDispatch::ShowExceptions, lambda { ActionController::Base.consider_all_requests_local }) - middleware.use(ActionDispatch::Callbacks, lambda { ActionController::Dispatcher.prepare_each_request }) - middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options }) - middleware.use(ActionDispatch::ParamsParser) - middleware.use(::Rack::MethodOverride) - middleware.use(::Rack::Head) - middleware.use(ActionDispatch::StringCoercion) - end - initializer "action_controller.initialize_framework_caches" do ActionController::Base.cache_store ||= RAILS_CACHE end @@ -57,19 +40,41 @@ module ActionController ActionController::Base.view_paths = view_path if ActionController::Base.view_paths.blank? end + class MetalMiddlewareBuilder + def initialize(metals) + @metals = metals + end + + def new(app) + ActionDispatch::Cascade.new(@metals, app) + end + + def name + ActionDispatch::Cascade.name + end + alias_method :to_s, :name + end + initializer "action_controller.initialize_metal" do |app| - Rails::Rack::Metal.requested_metals = app.config.metals + metal_root = "#{Rails.root}/app/metal" + load_list = app.config.metals || Dir["#{metal_root}/**/*.rb"] - app.config.middleware.insert_before(:"ActionDispatch::ParamsParser", - Rails::Rack::Metal, :if => Rails::Rack::Metal.metals.any?) + metals = load_list.map { |metal| + metal = File.basename(metal.gsub("#{metal_root}/", ''), '.rb') + require_dependency metal + metal.camelize.constantize + }.compact + + middleware = MetalMiddlewareBuilder.new(metals) + app.config.middleware.insert_before(:"ActionDispatch::ParamsParser", middleware) end - # # Prepare dispatcher callbacks and run 'prepare' callbacks + # Prepare dispatcher callbacks and run 'prepare' callbacks initializer "action_controller.prepare_dispatcher" do |app| # TODO: This used to say unless defined?(Dispatcher). Find out why and fix. + # Notice that at this point, ActionDispatch::Callbacks were already loaded. require 'rails/dispatcher' - - Dispatcher.define_dispatcher_callbacks(app.config.cache_classes) + ActionController::Dispatcher.prepare_each_request = true unless app.config.cache_classes unless app.config.cache_classes # Setup dev mode route reloading @@ -80,15 +85,7 @@ module ActionController app.reload_routes! end end - ActionDispatch::Callbacks.before_dispatch { |callbacks| reload_routes.call } - end - end - - initializer "action_controller.notifications" do |app| - require 'active_support/notifications' - - ActiveSupport::Notifications.subscribe do |*args| - ActionController::Base.log_event(*args) if ActionController::Base.logger + ActionDispatch::Callbacks.before { |callbacks| reload_routes.call } end end diff --git a/actionpack/lib/action_controller/railties/subscriber.rb b/actionpack/lib/action_controller/railties/subscriber.rb new file mode 100644 index 0000000000..a9f5d16c58 --- /dev/null +++ b/actionpack/lib/action_controller/railties/subscriber.rb @@ -0,0 +1,60 @@ +module ActionController + module Railties + class Subscriber < Rails::Subscriber + def process_action(event) + payload = event.payload + + info "\nProcessed #{payload[:controller]}##{payload[:action]} " \ + "to #{payload[:formats].join(', ')} (for #{payload[:remote_ip]} at #{event.time.to_s(:db)}) " \ + "[#{payload[:method].to_s.upcase}]" + + info " Parameters: #{payload[:params].inspect}" unless payload[:params].blank? + + additions = ActionController::Base.log_process_action(payload) + + message = "Completed in %.0fms" % event.duration + message << " (#{additions.join(" | ")})" unless additions.blank? + message << " | #{payload[:status]} [#{payload[:request_uri]}]\n\n" + + info(message) + 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 << " (%.1fms)" + info(message % [event.payload[:path], event.duration]) + end + + def redirect_to(event) + info "Redirected to #{event.payload[:location]}" + end + + def send_data(event) + info("Sent data %s (%.1fms)" % [event.payload[:filename], event.duration]) + end + + %w(write_fragment read_fragment exist_fragment? + expire_fragment expire_page write_page).each do |method| + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{method}(event) + key_or_path = event.payload[:key] || event.payload[:path] + human_name = #{method.to_s.humanize.inspect} + info("\#{human_name} \#{key_or_path} (%.1fms)" % event.duration) + end + METHOD + end + + def logger + ActionController::Base.logger + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 398ea52495..14557ca782 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -1,6 +1,5 @@ -require 'active_support/test_case' require 'rack/session/abstract/id' -require 'action_controller/metal/testing' +require 'action_view/test_case' module ActionController class TestRequest < ActionDispatch::TestRequest #:nodoc: @@ -240,13 +239,15 @@ module ActionController @request.assign_parameters(@controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters) @request.session = ActionController::TestSession.new(session) unless session.nil? - @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash + @request.session["flash"] = @request.flash.update(flash || {}) + @request.session["flash"].sweep @controller.request = @request @controller.params.merge!(parameters) build_request_uri(action, parameters) Base.class_eval { include Testing } @controller.process_with_new_base_test(@request, @response) + @request.session.delete('flash') if @request.session['flash'].blank? @response end diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb index 52b66c9303..933a1fa8f9 100644 --- a/actionpack/lib/action_controller/url_rewriter.rb +++ b/actionpack/lib/action_controller/url_rewriter.rb @@ -1,153 +1,21 @@ -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::UrlWriter 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::UrlWriter under the hood. And in particular, - # they use the ActionController::UrlWriter#url_for method. One can generate - # the same path as the above example by using the following code: - # - # include UrlWriter - # 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 UrlWriter 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 UrlWriter - # 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::UrlWriter#url_for. - # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlWriter#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 - # - # UrlWriter 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::UrlWriter in your class: - # - # class User < ActiveRecord::Base - # include ActionController::UrlWriter - # - # def base_uri - # user_path(self) - # end - # end - # - # User.find(1).base_uri # => "/users/1" - module UrlWriter - def self.included(base) #:nodoc: - ActionController::Routing::Routes.install_helpers(base) - base.mattr_accessor :default_url_options - - # The default options for urls written by this writer. Typically a <tt>:host</tt> pair is provided. - base.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>: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 = self.class.default_url_options.merge(options) - - url = '' - - unless options.delete(:only_path) - url << (options.delete(:protocol) || 'http') - url << '://' unless url.match("://") - - raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host] - - url << options.delete(:host) - url << ":#{options.delete(:port)}" if options.key?(:port) - else - # Delete the unused options to prevent their appearance in the query string. - [:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) } - end - trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash) - url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root] - anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor] - generated = Routing::Routes.generate(options, {}) - url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated) - url << anchor if anchor - - url - end - end +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 = {}) - rewrite_url(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 @@ -156,49 +24,53 @@ module ActionController alias_method :to_s, :to_str - private - # Given a path and options, returns a rewritten URL string - def rewrite_url(options) - rewritten_url = "" - - unless options[:only_path] - rewritten_url << (options[:protocol] || @request.protocol) - rewritten_url << "://" unless rewritten_url.match("://") - rewritten_url << rewrite_authentication(options) - rewritten_url << (options[:host] || @request.host_with_port) - rewritten_url << ":#{options.delete(:port)}" if options.key?(:port) - end - - path = rewrite_path(options) - 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 << "##{CGI.escape(options[:anchor].to_param.to_s)}" if options[:anchor] - - rewritten_url + 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 - # Given a Hash of options, generates a route - def rewrite_path(options) - options = options.symbolize_keys - options.update(options[:params].symbolize_keys) if options[:params] + 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] - if (overwrite = options.delete(:overwrite_params)) - options.update(@parameters.symbolize_keys) - options.update(overwrite.symbolize_keys) - end + rewritten_url + end - RESERVED_OPTIONS.each { |k| options.delete(k) } + protected - # Generates the query string, too - Routing::Routes.generate(options, @request.symbolized_path_parameters) + 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] - def rewrite_authentication(options) - if options[:user] && options[:password] - "#{CGI.escape(options.delete(:user))}:#{CGI.escape(options.delete(:password))}@" - else - "" - end + 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 1e87a016f9..7b44212310 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -43,18 +43,27 @@ module ActionDispatch autoload_under 'middleware' do autoload :Callbacks autoload :Cascade + autoload :Flash + autoload :Head autoload :ParamsParser autoload :Rescue autoload :ShowExceptions autoload :Static - autoload :StringCoercion end autoload :MiddlewareStack, 'action_dispatch/middleware/stack' autoload :Routing module Http - autoload :Headers, 'action_dispatch/http/headers' + extend ActiveSupport::Autoload + + autoload :Cache + autoload :Headers + autoload :MimeNegotiation + autoload :Parameters + autoload :Upload + autoload :UploadedFile, 'action_dispatch/http/upload' + autoload :URL end module Session diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb new file mode 100644 index 0000000000..428e62dc6b --- /dev/null +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -0,0 +1,123 @@ +module ActionDispatch + module Http + module Cache + module Request + def if_modified_since + if since = env['HTTP_IF_MODIFIED_SINCE'] + Time.rfc2822(since) rescue nil + end + end + + def if_none_match + env['HTTP_IF_NONE_MATCH'] + end + + def not_modified?(modified_at) + if_modified_since && modified_at && if_modified_since >= modified_at + end + + def etag_matches?(etag) + if_none_match && if_none_match == etag + end + + # Check response freshness (Last-Modified and ETag) against request + # If-Modified-Since and If-None-Match conditions. If both headers are + # supplied, both must match, or the request is not considered fresh. + def fresh?(response) + last_modified = if_modified_since + etag = if_none_match + + return false unless last_modified || etag + + success = true + success &&= not_modified?(response.last_modified) if last_modified + success &&= etag_matches?(response.etag) if etag + success + end + end + + module Response + def cache_control + @cache_control ||= {} + end + + def last_modified + if last = headers['Last-Modified'] + Time.httpdate(last) + end + end + + def last_modified? + headers.include?('Last-Modified') + end + + def last_modified=(utc_time) + headers['Last-Modified'] = utc_time.httpdate + end + + def etag + @etag + end + + def etag? + @etag + end + + def etag=(etag) + key = ActiveSupport::Cache.expand_cache_key(etag) + @etag = %("#{Digest::MD5.hexdigest(key)}") + end + + private + + def handle_conditional_get! + if etag? || last_modified? || !@cache_control.empty? + set_conditional_cache_control! + elsif nonempty_ok_response? + self.etag = @body + + if request && request.etag_matches?(etag) + self.status = 304 + self.body = [] + end + + set_conditional_cache_control! + else + headers["Cache-Control"] = "no-cache" + end + end + + def nonempty_ok_response? + @status == 200 && string_body? + end + + def string_body? + !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) } + end + + DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" + + def set_conditional_cache_control! + control = @cache_control + + if control.empty? + headers["Cache-Control"] = DEFAULT_CACHE_CONTROL + elsif @cache_control[:no_cache] + headers["Cache-Control"] = "no-cache" + else + extras = control[:extras] + max_age = control[:max_age] + + options = [] + options << "max-age=#{max_age.to_i}" if max_age + options << (control[:public] ? "public" : "private") + options << "must-revalidate" if control[:must_revalidate] + options.concat(extras) if extras + + headers["Cache-Control"] = options.join(", ") + end + end + 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 new file mode 100644 index 0000000000..40617e239a --- /dev/null +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -0,0 +1,101 @@ +module ActionDispatch + module Http + module MimeNegotiation + # The MIME type of the HTTP request, such as Mime::XML. + # + # For backward compatibility, the post \format is extracted from the + # X-Post-Data-Format HTTP header if present. + def content_type + @env["action_dispatch.request.content_type"] ||= begin + if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/ + Mime::Type.lookup($1.strip.downcase) + else + nil + end + end + end + + # Returns the accepted MIME type for the request. + def accepts + @env["action_dispatch.request.accepts"] ||= begin + header = @env['HTTP_ACCEPT'].to_s.strip + + if header.empty? + [content_type] + else + Mime::Type.parse(header) + end + end + end + + # Returns the Mime type for the \format used in the request. + # + # GET /posts/5.xml | request.format => Mime::XML + # GET /posts/5.xhtml | request.format => Mime::HTML + # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt> + # + def format(view_path = []) + formats.first + end + + def formats + accept = @env['HTTP_ACCEPT'] + + @env["action_dispatch.request.formats"] ||= + if parameters[:format] + Array(Mime[parameters[:format]]) + elsif xhr? || (accept && !accept.include?(?,)) + accepts + else + [Mime::HTML] + end + end + + # Sets the \format by string extension, which can be used to force custom formats + # that are not controlled by the extension. + # + # class ApplicationController < ActionController::Base + # before_filter :adjust_format_for_iphone + # + # private + # def adjust_format_for_iphone + # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] + # end + # end + def format=(extension) + parameters[:format] = extension.to_s + @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. + # + def negotiate_mime(order) + formats.each do |priority| + if priority == Mime::ALL + return order.first + elsif order.include?(priority) + return priority + end + end + + order.include?(Mime::ALL) ? formats.first : nil + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb new file mode 100644 index 0000000000..97546d5f93 --- /dev/null +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -0,0 +1,50 @@ +require 'active_support/core_ext/hash/keys' + +module ActionDispatch + module Http + module Parameters + # Returns both GET and POST \parameters in a single hash. + def parameters + @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access + end + alias :params :parameters + + def path_parameters=(parameters) #:nodoc: + @env.delete("action_dispatch.request.symbolized_path_parameters") + @env.delete("action_dispatch.request.parameters") + @env["action_dispatch.request.path_parameters"] = parameters + end + + # The same as <tt>path_parameters</tt> with explicitly symbolized keys. + def symbolized_path_parameters + @env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys + end + + # Returns a hash with the \parameters used to form the \path of the request. + # Returned hash keys are strings: + # + # {'action' => 'my_action', 'controller' => 'my_controller'} + # + # See <tt>symbolized_path_parameters</tt> for symbolized keys. + def path_parameters + @env["action_dispatch.request.path_parameters"] ||= {} + end + + private + + # Convert nested Hashs to HashWithIndifferentAccess + def normalize_parameters(value) + case value + when Hash + h = {} + value.each { |k, v| h[k] = normalize_parameters(v) } + h.with_indifferent_access + when Array + value.map { |e| normalize_parameters(e) } + else + value + end + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 6e8a5dcb8a..187ce7c15d 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -2,14 +2,17 @@ require 'tempfile' require 'stringio' require 'strscan' -require 'active_support/memoizable' -require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/string/access' require 'action_dispatch/http/headers' module ActionDispatch class Request < Rack::Request + include ActionDispatch::Http::Cache::Request + include ActionDispatch::Http::MimeNegotiation + include ActionDispatch::Http::Parameters + include ActionDispatch::Http::Upload + include ActionDispatch::Http::URL %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_TRANSLATED REMOTE_HOST @@ -19,9 +22,11 @@ module ActionDispatch HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_NEGOTIATE HTTP_PRAGMA ].each do |env| - define_method(env.sub(/^HTTP_/n, '').downcase) do - @env[env] - end + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{env.sub(/^HTTP_/n, '').downcase} + @env["#{env}"] + end + METHOD end def key?(key) @@ -35,7 +40,8 @@ module ActionDispatch # <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS # constant above, an UnknownHttpMethod exception is raised. def request_method - HTTP_METHOD_LOOKUP[super] || raise(ActionController::UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}") + method = env["rack.methodoverride.original_method"] || env["REQUEST_METHOD"] + HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}") end # Returns the HTTP request \method used for action processing as a @@ -43,7 +49,8 @@ module ActionDispatch # method returns <tt>:get</tt> for a HEAD request because the two are # functionally equivalent from the application's perspective.) def method - request_method == :head ? :get : request_method + method = env["REQUEST_METHOD"] + HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}") end # Is this a GET (or HEAD) request? Equivalent to <tt>request.method == :get</tt>. @@ -53,17 +60,17 @@ module ActionDispatch # Is this a POST request? Equivalent to <tt>request.method == :post</tt>. def post? - request_method == :post + method == :post end # Is this a PUT request? Equivalent to <tt>request.method == :put</tt>. def put? - request_method == :put + method == :put end # Is this a DELETE request? Equivalent to <tt>request.method == :delete</tt>. def delete? - request_method == :delete + method == :delete end # Is this a HEAD request? Since <tt>request.method</tt> sees HEAD as <tt>:get</tt>, @@ -79,25 +86,6 @@ module ActionDispatch Http::Headers.new(@env) end - # Returns the content length of the request as an integer. - def content_length - super.to_i - end - - # The MIME type of the HTTP request, such as Mime::XML. - # - # For backward compatibility, the post \format is extracted from the - # X-Post-Data-Format HTTP header if present. - def content_type - @env["action_dispatch.request.content_type"] ||= begin - if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/ - Mime::Type.lookup($1.strip.downcase) - else - nil - end - end - end - def forgery_whitelisted? method == :get || xhr? || content_type.nil? || !content_type.verify_request? end @@ -106,104 +94,9 @@ module ActionDispatch content_type.to_s end - # Returns the accepted MIME type for the request. - def accepts - @env["action_dispatch.request.accepts"] ||= begin - header = @env['HTTP_ACCEPT'].to_s.strip - - if header.empty? - [content_type] - else - Mime::Type.parse(header) - end - end - end - - def if_modified_since - if since = env['HTTP_IF_MODIFIED_SINCE'] - Time.rfc2822(since) rescue nil - end - end - - def if_none_match - env['HTTP_IF_NONE_MATCH'] - end - - def not_modified?(modified_at) - if_modified_since && modified_at && if_modified_since >= modified_at - end - - def etag_matches?(etag) - if_none_match && if_none_match == etag - end - - # Check response freshness (Last-Modified and ETag) against request - # If-Modified-Since and If-None-Match conditions. If both headers are - # supplied, both must match, or the request is not considered fresh. - def fresh?(response) - last_modified = if_modified_since - etag = if_none_match - - return false unless last_modified || etag - - success = true - success &&= not_modified?(response.last_modified) if last_modified - success &&= etag_matches?(response.etag) if etag - success - end - - # Returns the Mime type for the \format used in the request. - # - # GET /posts/5.xml | request.format => Mime::XML - # GET /posts/5.xhtml | request.format => Mime::HTML - # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt> - # - def format(view_path = []) - formats.first - end - - def formats - accept = @env['HTTP_ACCEPT'] - - @env["action_dispatch.request.formats"] ||= - if parameters[:format] - Array.wrap(Mime[parameters[:format]]) - elsif xhr? || (accept && !accept.include?(?,)) - accepts - else - [Mime::HTML] - end - end - - # Sets the \format by string extension, which can be used to force custom formats - # that are not controlled by the extension. - # - # class ApplicationController < ActionController::Base - # before_filter :adjust_format_for_iphone - # - # private - # def adjust_format_for_iphone - # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] - # end - # end - def format=(extension) - parameters[:format] = extension.to_s - @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 + # Returns the content length of the request as an integer. + def content_length + super.to_i end # Returns true if the request's "X-Requested-With" header contains @@ -236,7 +129,7 @@ module ActionDispatch 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) + 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} @@ -262,124 +155,6 @@ EOM (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil end - # Returns the complete URL used for this request. - def url - protocol + host_with_port + request_uri - end - - # Returns 'https://' if this is an SSL request and 'http://' otherwise. - def protocol - ssl? ? 'https://' : 'http://' - end - - # Is this an SSL request? - def ssl? - @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https' - end - - # Returns the \host for this request, such as "example.com". - def raw_host_with_port - if forwarded = env["HTTP_X_FORWARDED_HOST"] - forwarded.split(/,\s?/).last - else - env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}" - end - end - - # Returns the host for this request, such as example.com. - def host - raw_host_with_port.sub(/:\d+$/, '') - end - - # Returns a \host:\port string for this request, such as "example.com" or - # "example.com:8080". - def host_with_port - "#{host}#{port_string}" - end - - # Returns the port number of this request as an integer. - def port - if raw_host_with_port =~ /:(\d+)$/ - $1.to_i - else - standard_port - end - end - - # Returns the standard \port number for this request's protocol. - def standard_port - case protocol - when 'https://' then 443 - else 80 - end - end - - # Returns a \port suffix like ":8080" if the \port number of this request - # is not the default HTTP \port 80 or HTTPS \port 443. - def port_string - port == standard_port ? '' : ":#{port}" - end - - def server_port - @env['SERVER_PORT'].to_i - end - - # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify - # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk". - def domain(tld_length = 1) - return nil unless named_host?(host) - - host.split('.').last(1 + tld_length).join('.') - end - - # Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be - # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>, - # such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt> - # in "www.rubyonrails.co.uk". - def subdomains(tld_length = 1) - return [] unless named_host?(host) - parts = host.split('.') - 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] || '') - 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 - end - # Read the request \body. This is useful for web services that need to # work with raw requests directly. def raw_post @@ -390,33 +165,6 @@ EOM @env['RAW_POST_DATA'] end - # Returns both GET and POST \parameters in a single hash. - def parameters - @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access - end - alias_method :params, :parameters - - def path_parameters=(parameters) #:nodoc: - @env.delete("action_dispatch.request.symbolized_path_parameters") - @env.delete("action_dispatch.request.parameters") - @env["action_dispatch.request.path_parameters"] = parameters - end - - # The same as <tt>path_parameters</tt> with explicitly symbolized keys. - def symbolized_path_parameters - @env["action_dispatch.request.symbolized_path_parameters"] ||= path_parameters.symbolize_keys - end - - # Returns a hash with the \parameters used to form the \path of the request. - # Returned hash keys are strings: - # - # {'action' => 'my_action', 'controller' => 'my_controller'} - # - # See <tt>symbolized_path_parameters</tt> for symbolized keys. - def path_parameters - @env["action_dispatch.request.path_parameters"] ||= {} - end - # The request body is an IO input stream. If the RAW_POST_DATA environment # variable is already set, wrap it in a StringIO. def body @@ -432,18 +180,6 @@ EOM FORM_DATA_MEDIA_TYPES.include?(content_type.to_s) end - # Override Rack's GET method to support indifferent access - def GET - @env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super) - end - alias_method :query_parameters, :GET - - # Override Rack's POST method to support indifferent access - def POST - @env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super) - end - alias_method :request_parameters, :POST - def body_stream #:nodoc: @env['rack.input'] end @@ -461,9 +197,18 @@ EOM @env['rack.session.options'] = options end - def flash - session['flash'] || {} + # Override Rack's GET method to support indifferent access + def GET + @env["action_dispatch.request.query_parameters"] ||= normalize_parameters(super) + end + alias :query_parameters :GET + + # Override Rack's POST method to support indifferent access + def POST + @env["action_dispatch.request.request_parameters"] ||= normalize_parameters(super) end + alias :request_parameters :POST + # Returns the authorization header regardless of whether it was specified directly or through one of the # proxy alternatives. @@ -473,77 +218,5 @@ EOM @env['X_HTTP_AUTHORIZATION'] || @env['REDIRECT_X_HTTP_AUTHORIZATION'] end - - # Receives an array of mimes and return the first user sent mime that - # matches the order array. - # - def negotiate_mime(order) - formats.each do |priority| - if priority == Mime::ALL - return order.first - elsif order.include?(priority) - return priority - end - end - - order.include?(Mime::ALL) ? formats.first : nil - end - - private - - def named_host?(host) - !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) - end - - module UploadedFile - def self.extended(object) - object.class_eval do - attr_accessor :original_path, :content_type - alias_method :local_path, :path if method_defined?(:path) - end - end - - # Take the basename of the upload's original filename. - # This handles the full Windows paths given by Internet Explorer - # (and perhaps other broken user agents) without affecting - # those which give the lone filename. - # The Windows regexp is adapted from Perl's File::Basename. - def original_filename - unless defined? @original_filename - @original_filename = - unless original_path.blank? - if original_path =~ /^(?:.*[:\\\/])?(.*)/m - $1 - else - File.basename original_path - end - end - end - @original_filename - end - end - - # Convert nested Hashs to HashWithIndifferentAccess and replace - # file upload hashs with UploadedFile objects - def normalize_parameters(value) - case value - when Hash - if value.has_key?(:tempfile) - upload = value[:tempfile] - upload.extend(UploadedFile) - upload.original_path = value[:filename] - upload.content_type = value[:type] - upload - else - h = {} - value.each { |k, v| h[k] = normalize_parameters(v) } - h.with_indifferent_access - end - when Array - value.map { |e| normalize_parameters(e) } - else - value - end - end end end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 8524bbd993..65df9b1f03 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -32,6 +32,8 @@ module ActionDispatch # :nodoc: # end # end class Response < Rack::Response + include ActionDispatch::Http::Cache::Response + attr_accessor :request, :blank attr_writer :header, :sending_file @@ -55,10 +57,6 @@ module ActionDispatch # :nodoc: yield self if block_given? end - def cache_control - @cache_control ||= {} - end - def status=(status) @status = Rack::Utils.status_code(status) end @@ -114,33 +112,6 @@ module ActionDispatch # :nodoc: # information. attr_accessor :charset, :content_type - def last_modified - if last = headers['Last-Modified'] - Time.httpdate(last) - end - end - - def last_modified? - headers.include?('Last-Modified') - end - - def last_modified=(utc_time) - headers['Last-Modified'] = utc_time.httpdate - end - - def etag - @etag - end - - def etag? - @etag - end - - def etag=(etag) - key = ActiveSupport::Cache.expand_cache_key(etag) - @etag = %("#{Digest::MD5.hexdigest(key)}") - end - CONTENT_TYPE = "Content-Type" cattr_accessor(:default_charset) { "utf-8" } @@ -222,31 +193,6 @@ module ActionDispatch # :nodoc: end private - def handle_conditional_get! - if etag? || last_modified? || !@cache_control.empty? - set_conditional_cache_control! - elsif nonempty_ok_response? - self.etag = @body - - if request && request.etag_matches?(etag) - self.status = 304 - self.body = [] - end - - set_conditional_cache_control! - else - headers["Cache-Control"] = "no-cache" - end - end - - def nonempty_ok_response? - @status == 200 && string_body? - end - - def string_body? - !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) } - end - def assign_default_content_type_and_charset! return if headers[CONTENT_TYPE].present? @@ -259,27 +205,5 @@ module ActionDispatch # :nodoc: headers[CONTENT_TYPE] = type end - DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" - - def set_conditional_cache_control! - control = @cache_control - - if control.empty? - headers["Cache-Control"] = DEFAULT_CACHE_CONTROL - elsif @cache_control[:no_cache] - headers["Cache-Control"] = "no-cache" - else - extras = control[:extras] - max_age = control[:max_age] - - options = [] - options << "max-age=#{max_age.to_i}" if max_age - options << (control[:public] ? "public" : "private") - options << "must-revalidate" if control[:must_revalidate] - options.concat(extras) if extras - - headers["Cache-Control"] = options.join(", ") - end - end end end diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb new file mode 100644 index 0000000000..dc6121b911 --- /dev/null +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -0,0 +1,48 @@ +module ActionDispatch + module Http + module UploadedFile + def self.extended(object) + object.class_eval do + attr_accessor :original_path, :content_type + alias_method :local_path, :path if method_defined?(:path) + end + end + + # Take the basename of the upload's original filename. + # This handles the full Windows paths given by Internet Explorer + # (and perhaps other broken user agents) without affecting + # those which give the lone filename. + # The Windows regexp is adapted from Perl's File::Basename. + def original_filename + unless defined? @original_filename + @original_filename = + unless original_path.blank? + if original_path =~ /^(?:.*[:\\\/])?(.*)/m + $1 + else + File.basename original_path + end + end + end + @original_filename + end + end + + module Upload + # Convert nested Hashs to HashWithIndifferentAccess and replace + # file upload hashs with UploadedFile objects + def normalize_parameters(value) + if Hash === value && value.has_key?(:tempfile) + upload = value[:tempfile] + upload.extend(UploadedFile) + upload.original_path = value[:filename] + upload.content_type = value[:type] + upload + else + super + end + end + private :normalize_parameters + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb new file mode 100644 index 0000000000..40ceb5a9b6 --- /dev/null +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -0,0 +1,129 @@ +module ActionDispatch + module Http + module URL + # Returns the complete URL used for this request. + def url + protocol + host_with_port + request_uri + end + + # Returns 'https://' if this is an SSL request and 'http://' otherwise. + def protocol + ssl? ? 'https://' : 'http://' + end + + # Is this an SSL request? + def ssl? + @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https' + end + + # Returns the \host for this request, such as "example.com". + def raw_host_with_port + if forwarded = env["HTTP_X_FORWARDED_HOST"] + forwarded.split(/,\s?/).last + else + env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}" + end + end + + # Returns the host for this request, such as example.com. + def host + raw_host_with_port.sub(/:\d+$/, '') + end + + # Returns a \host:\port string for this request, such as "example.com" or + # "example.com:8080". + def host_with_port + "#{host}#{port_string}" + end + + # Returns the port number of this request as an integer. + def port + if raw_host_with_port =~ /:(\d+)$/ + $1.to_i + else + standard_port + end + end + + # Returns the standard \port number for this request's protocol. + def standard_port + case protocol + when 'https://' then 443 + else 80 + end + end + + # Returns a \port suffix like ":8080" if the \port number of this request + # is not the default HTTP \port 80 or HTTPS \port 443. + def port_string + port == standard_port ? '' : ":#{port}" + end + + def server_port + @env['SERVER_PORT'].to_i + end + + # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify + # a different <tt>tld_length</tt>, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk". + def domain(tld_length = 1) + return nil unless named_host?(host) + + host.split('.').last(1 + tld_length).join('.') + end + + # Returns all the \subdomains as an array, so <tt>["dev", "www"]</tt> would be + # returned for "dev.www.rubyonrails.org". You can specify a different <tt>tld_length</tt>, + # such as 2 to catch <tt>["www"]</tt> instead of <tt>["www", "rubyonrails"]</tt> + # in "www.rubyonrails.co.uk". + def subdomains(tld_length = 1) + return [] unless named_host?(host) + parts = host.split('.') + 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] || '') + 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 + end + + private + + def named_host?(host) + !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 49bc20f11f..5ec406e134 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -1,4 +1,10 @@ module ActionDispatch + # Provide callbacks to be executed before and after the request dispatch. + # + # It also provides a to_prepare callback, which is performed in all requests + # in development by only once in production and notification callback for async + # operations. + # class Callbacks include ActiveSupport::Callbacks @@ -29,12 +35,6 @@ module ActionDispatch set_callback(:call, :after, *args, &block) end - class << self - # DEPRECATED - alias_method :before_dispatch, :before - alias_method :after_dispatch, :after - end - def initialize(app, prepare_each_request = false) @app, @prepare_each_request = app, prepare_each_request run_callbacks(:prepare) @@ -45,6 +45,8 @@ module ActionDispatch run_callbacks(:prepare) if @prepare_each_request @app.call(env) end + ensure + ActiveSupport::Notifications.instrument "action_dispatch.callback" end end end diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb new file mode 100644 index 0000000000..99b36366d6 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -0,0 +1,174 @@ +module ActionDispatch + class Request + # Access the contents of the flash. Use <tt>flash["notice"]</tt> to + # read a notice you put there or <tt>flash["notice"] = "hello"</tt> + # to put a new one. + def flash + session['flash'] ||= Flash::FlashHash.new + end + end + + # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed + # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create + # action that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can + # then expose the flash to its template. Actually, that exposure is automatically done. Example: + # + # class PostsController < ActionController::Base + # def create + # # save post + # flash[:notice] = "Successfully created post" + # redirect_to posts_path(@post) + # end + # + # def show + # # doesn't need to assign the flash notice to the template, that's done automatically + # end + # end + # + # show.html.erb + # <% if flash[:notice] %> + # <div class="notice"><%= flash[:notice] %></div> + # <% end %> + # + # This example just places a string in the flash, but you can put any object in there. And of course, you can put as + # many as you like at a time too. Just remember: They'll be gone by the time the next action has been performed. + # + # See docs on the FlashHash class for more details about the flash. + class Flash + class FlashNow #:nodoc: + def initialize(flash) + @flash = flash + end + + def []=(k, v) + @flash[k] = v + @flash.discard(k) + v + end + + def [](k) + @flash[k] + end + end + + class FlashHash < Hash + def initialize #:nodoc: + super + @used = Set.new + end + + def []=(k, v) #:nodoc: + keep(k) + super + end + + def update(h) #:nodoc: + h.keys.each { |k| keep(k) } + super + end + + alias :merge! :update + + def replace(h) #:nodoc: + @used = Set.new + super + end + + # Sets a flash that will not be available to the next action, only to the current. + # + # flash.now[:message] = "Hello current action" + # + # This method enables you to use the flash as a central messaging system in your app. + # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>). + # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will + # vanish when the current action is done. + # + # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>. + def now + FlashNow.new(self) + end + + # Keeps either the entire current flash or a specific flash entry available for the next action: + # + # flash.keep # keeps the entire flash + # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded + def keep(k = nil) + use(k, false) + end + + # Marks the entire flash or a single flash entry to be discarded by the end of the current action: + # + # flash.discard # discard the entire flash at the end of the current action + # flash.discard(:warning) # discard only the "warning" entry at the end of the current action + def discard(k = nil) + use(k) + end + + # Mark for removal entries that were kept, and delete unkept ones. + # + # This method is called automatically by filters, so you generally don't need to care about it. + def sweep #:nodoc: + keys.each do |k| + unless @used.include?(k) + @used << k + else + delete(k) + @used.delete(k) + end + end + + # clean up after keys that could have been left over by calling reject! or shift on the flash + (@used - keys).each{ |k| @used.delete(k) } + end + + # Convenience accessor for flash[:alert] + def alert + self[:alert] + end + + # Convenience accessor for flash[:alert]= + def alert=(message) + self[:alert] = message + end + + # Convenience accessor for flash[:notice] + def notice + self[:notice] + end + + # Convenience accessor for flash[:notice]= + def notice=(message) + self[:notice] = message + end + + private + # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods + # use() # marks the entire flash as used + # use('msg') # marks the "msg" entry as used + # use(nil, false) # marks the entire flash as unused (keeps it around for one more action) + # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action) + # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself + # if no key is passed. + def use(key = nil, used = true) + Array(key || keys).each { |k| used ? @used << k : @used.delete(k) } + return key ? self[key] : self + end + end + + def initialize(app) + @app = app + end + + def call(env) + if (session = env['rack.session']) && (flash = session['flash']) + flash.sweep + end + + @app.call(env) + ensure + if (session = env['rack.session']) && (flash = session['flash']) && flash.empty? + session.delete('flash') + end + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/head.rb b/actionpack/lib/action_dispatch/middleware/head.rb new file mode 100644 index 0000000000..56e2d2f2a8 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/head.rb @@ -0,0 +1,18 @@ +module ActionDispatch + class Head + def initialize(app) + @app = app + end + + def call(env) + if env["REQUEST_METHOD"] == "HEAD" + env["REQUEST_METHOD"] = "GET" + env["rack.methodoverride.original_method"] = "HEAD" + status, headers, body = @app.call(env) + [status, headers, []] + else + @app.call(env) + end + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 7d4f0998ce..311880cabc 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -102,7 +102,7 @@ module ActionDispatch # Note that the regexp does not allow $1 to end with a ':' $1.constantize rescue LoadError, NameError => const_error - raise ActionDispatch::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n" + raise ActionDispatch::Session::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n" end retry diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 4ebc8a2ab9..10f04dcdf6 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -1,7 +1,24 @@ require 'active_support/core_ext/exception' +require 'active_support/notifications' require 'action_dispatch/http/request' module ActionDispatch + # This middleware rescues any exception returned by the application and renders + # nice exception pages if it's being rescued locally. + # + # Every time an exception is caught, a notification is published, becoming a good API + # to deal with exceptions. So, if you want send an e-mail through ActionMailer + # everytime this notification is published, you just need to do the following: + # + # ActiveSupport::Notifications.subscribe "action_dispatch.show_exception" do |name, start, end, instrumentation_id, payload| + # ExceptionNotifier.deliver_exception(start, payload) + # end + # + # The payload is a hash which has two pairs: + # + # * :env - Contains the rack env for the given request; + # * :exception - The exception raised; + # class ShowExceptions LOCALHOST = '127.0.0.1'.freeze @@ -44,8 +61,11 @@ module ActionDispatch def call(env) @app.call(env) rescue Exception => exception - raise exception if env['action_dispatch.show_exceptions'] == false - render_exception(env, exception) + ActiveSupport::Notifications.instrument 'action_dispatch.show_exception', + :env => env, :exception => exception do + raise exception if env['action_dispatch.show_exceptions'] == false + render_exception(env, exception) + end end private diff --git a/actionpack/lib/action_dispatch/middleware/string_coercion.rb b/actionpack/lib/action_dispatch/middleware/string_coercion.rb deleted file mode 100644 index 232e947835..0000000000 --- a/actionpack/lib/action_dispatch/middleware/string_coercion.rb +++ /dev/null @@ -1,29 +0,0 @@ -module ActionDispatch - class StringCoercion - class UglyBody < ActiveSupport::BasicObject - def initialize(body) - @body = body - end - - def each - @body.each do |part| - yield part.to_s - end - end - - private - def method_missing(*args, &block) - @body.__send__(*args, &block) - end - end - - def initialize(app) - @app = app - end - - def call(env) - status, headers, body = @app.call(env) - [status, headers, UglyBody.new(body)] - end - end -end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 8f33346a4f..9aaa4355f2 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -68,16 +68,11 @@ module ActionDispatch end def normalize_path(path) - path = nil if path == "" - path = "#{@scope[:path]}#{path}" if @scope[:path] - path = Rack::Mount::Utils.normalize_path(path) if path - - raise ArgumentError, "path is required" unless path - - path + path = "#{@scope[:path]}/#{path}" + raise ArgumentError, "path is required" if path.empty? + Mapper.normalize_path(path) end - def app Constraints.new( to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults), @@ -123,7 +118,6 @@ module ActionDispatch end end - def blocks if @options[:constraints].present? && !@options[:constraints].is_a?(Hash) block = @options[:constraints] @@ -162,6 +156,14 @@ module ActionDispatch end end + # Invokes Rack::Mount::Utils.normalize path and ensure that + # (:locale) becomes (/:locale) instead of /(:locale). + def self.normalize_path(path) + path = Rack::Mount::Utils.normalize_path(path) + path.sub!(%r{/\(+/?:}, '(/:') + path + end + module Base def initialize(set) @set = set @@ -200,13 +202,22 @@ module ActionDispatch path = args.shift || block path_proc = path.is_a?(Proc) ? path : proc { |params| path % params } status = options[:status] || 301 + body = 'Moved Permanently' lambda do |env| - req = Rack::Request.new(env) - params = path_proc.call(env["action_dispatch.request.path_parameters"]) - url = req.scheme + '://' + req.host + params + req = Request.new(env) + + uri = URI.parse(path_proc.call(req.params.symbolize_keys)) + uri.scheme ||= req.scheme + uri.host ||= req.host + uri.port ||= req.port unless req.port == 80 - [ status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently'] ] + headers = { + 'Location' => uri.to_s, + 'Content-Type' => 'text/html', + 'Content-Length' => body.length.to_s + } + [ status, headers, [body] ] end end @@ -236,46 +247,35 @@ module ActionDispatch options[:controller] = args.first end - if path = options.delete(:path) - path_set = true - path, @scope[:path] = @scope[:path], Rack::Mount::Utils.normalize_path(@scope[:path].to_s + path.to_s) - else - path_set = false - end + recover = {} - if name_prefix = options.delete(:name_prefix) - name_prefix_set = true - name_prefix, @scope[:name_prefix] = @scope[:name_prefix], (@scope[:name_prefix] ? "#{@scope[:name_prefix]}_#{name_prefix}" : name_prefix) - else - name_prefix_set = false + options[:constraints] ||= {} + unless options[:constraints].is_a?(Hash) + block, options[:constraints] = options[:constraints], {} end - if controller = options.delete(:controller) - controller_set = true - controller, @scope[:controller] = @scope[:controller], controller - else - controller_set = false + scope_options.each do |option| + if value = options.delete(option) + recover[option] = @scope[option] + @scope[option] = send("merge_#{option}_scope", @scope[option], value) + end end - constraints = options.delete(:constraints) || {} - unless constraints.is_a?(Hash) - block, constraints = constraints, {} - end - constraints, @scope[:constraints] = @scope[:constraints], (@scope[:constraints] || {}).merge(constraints) - blocks, @scope[:blocks] = @scope[:blocks], (@scope[:blocks] || []) + [block] + recover[:block] = @scope[:blocks] + @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block) - options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options) + recover[:options] = @scope[:options] + @scope[:options] = merge_options_scope(@scope[:options], options) yield - self ensure - @scope[:path] = path if path_set - @scope[:name_prefix] = name_prefix if name_prefix_set - @scope[:controller] = controller if controller_set - @scope[:options] = options - @scope[:blocks] = blocks - @scope[:constraints] = constraints + scope_options.each do |option| + @scope[option] = recover[option] if recover.has_key?(option) + end + + @scope[:options] = recover[:options] + @scope[:blocks] = recover[:block] end def controller(controller) @@ -283,7 +283,7 @@ module ActionDispatch end def namespace(path) - scope("/#{path}") { yield } + scope(path.to_s, :name_prefix => path.to_s, :namespace => path.to_s) { yield } end def constraints(constraints = {}) @@ -304,25 +304,83 @@ module ActionDispatch args.push(options) super(*args) end + + private + def scope_options + @scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym } + end + + def merge_path_scope(parent, child) + Mapper.normalize_path("#{parent}/#{child}") + end + + def merge_name_prefix_scope(parent, child) + parent ? "#{parent}_#{child}" : child + end + + def merge_namespace_scope(parent, child) + parent ? "#{parent}/#{child}" : child + end + + def merge_controller_scope(parent, child) + @scope[:namespace] ? "#{@scope[:namespace]}/#{child}" : child + end + + def merge_resources_path_names_scope(parent, child) + merge_options_scope(parent, child) + end + + def merge_constraints_scope(parent, child) + merge_options_scope(parent, child) + end + + def merge_blocks_scope(parent, child) + (parent || []) + [child] + end + + def merge_options_scope(parent, child) + (parent || {}).merge(child) + end end module Resources + CRUD_ACTIONS = [:index, :show, :create, :update, :destroy] + class Resource #:nodoc: - attr_reader :plural, :singular + def self.default_actions + [:index, :create, :new, :show, :update, :destroy, :edit] + end + + attr_reader :plural, :singular, :options def initialize(entities, options = {}) entities = entities.to_s + @options = options @plural = entities.pluralize @singular = entities.singularize end + def default_actions + self.class.default_actions + end + + def actions + if only = options[:only] + only.map(&:to_sym) + elsif except = options[:except] + default_actions - except.map(&:to_sym) + else + default_actions + end + end + def name - plural + options[:as] || plural end def controller - plural + options[:controller] || plural end def member_name @@ -339,15 +397,24 @@ module ActionDispatch end class SingletonResource < Resource #:nodoc: + def self.default_actions + [:show, :create, :update, :destroy, :new, :edit] + end + def initialize(entity, options = {}) super end def name - singular + options[:as] || singular end end + def initialize(*args) + super + @scope[:resources_path_names] = @set.resources_path_names + end + def resource(*resources, &block) options = resources.extract_options! @@ -357,7 +424,14 @@ module ActionDispatch return self end - resource = SingletonResource.new(resources.pop) + if path_names = options.delete(:path_names) + scope(:resources_path_names => path_names) do + resource(resources, options) + end + return self + end + + resource = SingletonResource.new(resources.pop, options) if @scope[:scope_level] == :resources nested do @@ -366,16 +440,16 @@ module ActionDispatch return self end - scope(:path => "/#{resource.name}", :controller => resource.controller) do + scope(:path => resource.name.to_s, :controller => resource.controller) do with_scope_level(:resource, resource) do yield if block_given? - get "(.:format)", :to => :show, :as => resource.member_name - post "(.:format)", :to => :create - put "(.:format)", :to => :update - delete "(.:format)", :to => :destroy - get "/new(.:format)", :to => :new, :as => "new_#{resource.singular}" - get "/edit(.:format)", :to => :edit, :as => "edit_#{resource.singular}" + get :show, :as => resource.member_name if resource.actions.include?(:show) + post :create if resource.actions.include?(:create) + put :update if resource.actions.include?(:update) + delete :destroy if resource.actions.include?(:destroy) + get :new, :as => resource.singular if resource.actions.include?(:new) + get :edit, :as => resource.singular if resource.actions.include?(:edit) end end @@ -391,7 +465,14 @@ module ActionDispatch return self end - resource = Resource.new(resources.pop) + if path_names = options.delete(:path_names) + scope(:resources_path_names => path_names) do + resources(resources, options) + end + return self + end + + resource = Resource.new(resources.pop, options) if @scope[:scope_level] == :resources nested do @@ -400,28 +481,22 @@ module ActionDispatch return self end - scope(:path => "/#{resource.name}", :controller => resource.controller) do + scope(:path => resource.name.to_s, :controller => resource.controller) do with_scope_level(:resources, resource) do yield if block_given? with_scope_level(:collection) do - get "(.:format)", :to => :index, :as => resource.collection_name - post "(.:format)", :to => :create - - with_exclusive_name_prefix :new do - get "/new(.:format)", :to => :new, :as => resource.singular - end + get :index, :as => resource.collection_name if resource.actions.include?(:index) + post :create if resource.actions.include?(:create) + get :new, :as => resource.singular if resource.actions.include?(:new) end with_scope_level(:member) do - scope("/:id") do - get "(.:format)", :to => :show, :as => resource.member_name - put "(.:format)", :to => :update - delete "(.:format)", :to => :destroy - - with_exclusive_name_prefix :edit do - get "/edit(.:format)", :to => :edit, :as => resource.singular - end + scope(':id') do + get :show, :as => resource.member_name if resource.actions.include?(:show) + put :update if resource.actions.include?(:update) + delete :destroy if resource.actions.include?(:destroy) + get :edit, :as => resource.singular if resource.actions.include?(:edit) end end end @@ -448,7 +523,7 @@ module ActionDispatch end with_scope_level(:member) do - scope("/:id", :name_prefix => parent_resource.member_name, :as => "") do + scope(':id', :name_prefix => parent_resource.member_name, :as => "") do yield end end @@ -460,7 +535,7 @@ module ActionDispatch end with_scope_level(:nested) do - scope("/#{parent_resource.id_segment}", :name_prefix => parent_resource.member_name) do + scope(parent_resource.id_segment, :name_prefix => parent_resource.member_name) do yield end end @@ -474,9 +549,22 @@ module ActionDispatch return self end + resources_path_names = options.delete(:path_names) + if args.first.is_a?(Symbol) - with_exclusive_name_prefix(args.first) do - return match("/#{args.first}(.:format)", options.merge(:to => args.first.to_sym)) + action = args.first + if CRUD_ACTIONS.include?(action) + begin + old_path = @scope[:path] + @scope[:path] = "#{@scope[:path]}(.:format)" + return match(options.reverse_merge(:to => action)) + ensure + @scope[:path] = old_path + end + else + with_exclusive_name_prefix(action) do + return match("#{action_path(action, resources_path_names)}(.:format)", options.reverse_merge(:to => action)) + end end end @@ -502,6 +590,11 @@ module ActionDispatch end private + def action_path(name, path_names = nil) + path_names ||= @scope[:resources_path_names] + path_names[name.to_sym] || name.to_s + end + def with_exclusive_name_prefix(prefix) begin old_name_prefix = @scope[:name_prefix] diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index bd397432ce..660d28dbec 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -74,9 +74,8 @@ module ActionDispatch @routes = {} @helpers = [] - @module ||= Module.new - @module.instance_methods.each do |selector| - @module.class_eval { remove_method selector } + @module ||= Module.new do + instance_methods.each { |selector| remove_method(selector) } end end @@ -138,67 +137,87 @@ module ActionDispatch end end - def named_helper_module_eval(code, *args) - @module.module_eval(code, *args) - end - def define_hash_access(route, name, kind, options) selector = hash_access_name(name, kind) - named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks + + # We use module_eval to avoid leaks + @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 def #{selector}(options = nil) # def hash_for_users_url(options = nil) options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false} end # end protected :#{selector} # protected :hash_for_users_url - end_eval + END_EVAL helpers << selector end + # Create a url helper allowing ordered parameters to be associated + # with corresponding dynamic segments, so you can do: + # + # foo_url(bar, baz, bang) + # + # Instead of: + # + # foo_url(:bar => bar, :baz => baz, :bang => bang) + # + # Also allow options hash, so you can do: + # + # foo_url(bar, baz, bang, :sort_by => 'baz') + # def define_url_helper(route, name, kind, options) selector = url_helper_name(name, kind) - # The segment keys used for positional parameters - hash_access_method = hash_access_name(name, kind) - # allow ordered parameters to be associated with corresponding - # dynamic segments, so you can do + # We use module_eval to avoid leaks. # - # foo_url(bar, baz, bang) + # 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) # - # instead of + # keys = [] + # keys -= options.keys if args.size < keys.size - 1 # - # foo_url(:bar => bar, :baz => baz, :bang => bang) + # args = args.zip(keys).inject({}) do |h, (v, k)| + # h[k] = v + # h + # end # - # Also allow options hash, so you can do + # # Tell url_for to skip default_url_options + # options[:use_defaults] = false + # options.merge!(args) + # end # - # foo_url(bar, baz, bang, :sort_by => 'baz') - # - named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks - def #{selector}(*args) # def users_url(*args) - # - opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first - args.first || {} # args.first || {} - else # else - options = args.extract_options! # options = args.extract_options! - args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)| - h[k] = v # h[k] = v - h # h - end # end - options.merge(args) # options.merge(args) - end # end - # - url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts)) - # - end # end - #Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL. - def formatted_#{selector}(*args) # def formatted_users_url(*args) - ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn( - "formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " + - "Please pass format to the standard " + # "Please pass format to the standard " + - "#{selector} method instead.", caller) # "users_url method instead.", caller) - #{selector}(*args) # users_url(*args) - end # end - protected :#{selector} # protected :users_url - end_eval + # 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) + end + + url_for(options) + end + protected :#{selector} + END_EVAL helpers << selector end end @@ -206,9 +225,16 @@ module ActionDispatch attr_accessor :routes, :named_routes attr_accessor :disable_clear_and_finalize + def self.default_resources_path_names + { :new => 'new', :edit => 'edit' } + end + + attr_accessor :resources_path_names + def initialize self.routes = [] self.named_routes = NamedRouteCollection.new + self.resources_path_names = self.class.default_resources_path_names @disable_clear_and_finalize = false end diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 5686bbdbde..c2486d3730 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -2,6 +2,15 @@ module ActionDispatch module Assertions # A small suite of assertions that test responses from Rails applications. module ResponseAssertions + extend ActiveSupport::Concern + + included do + # TODO: Need to pull in AV::Template monkey patches that track which + # templates are rendered. assert_template should probably be part + # of AV instead of AD. + require 'action_view/test_case' + end + # Asserts that the response is one of the following types: # # * <tt>:success</tt> - Status code was 200 diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index c2dc591ff7..a6b1126e2b 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -524,7 +524,7 @@ module ActionDispatch fix_content = lambda do |node| # Gets around a bug in the Rails 1.1 HTML parser. - node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { CGI.escapeHTML($1) } + node.content.gsub(/<!\[CDATA\[(.*)(\]\]>)?/m) { Rack::Utils.escapeHTML($1) } end selected = elements.map do |element| diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 2a5f5dcd5c..4ec47d146c 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -1,6 +1,5 @@ require 'stringio' require 'uri' -require 'active_support/test_case' require 'active_support/core_ext/object/metaclass' require 'rack/test' diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 8ce6e82524..93aa69c060 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -52,6 +52,8 @@ module ActionView autoload :TemplateHandler, 'action_view/template' autoload :TemplateHandlers, 'action_view/template' end + + autoload :TestCase, 'action_view/test_case' end require 'action_view/erb/util' diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index c70f29f098..87b7adf6c4 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -216,7 +216,7 @@ module ActionView end options[:object_name] ||= params.first - I18n.with_options :locale => options[:locale], :scope => [:activemodel, :errors, :template] do |locale| + I18n.with_options :locale => options[:locale], :scope => [:errors, :template] do |locale| header_message = if options.include?(:header_message) options[:header_message] else diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 4b51dc7856..34f38b0a8a 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -616,7 +616,7 @@ module ActionView build_selects_from_types(order) else - "#{select_date}#{@options[:datetime_separator]}#{select_time}" + "#{select_date}#{@options[:datetime_separator]}#{select_time}".html_safe! end end @@ -835,7 +835,7 @@ module ActionView select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt] select_html << select_options_as_html.to_s - content_tag(:select, select_html, select_options) + "\n" + (content_tag(:select, select_html, select_options) + "\n").html_safe! end # Builds a prompt option tag with supplied options or from default options @@ -860,12 +860,12 @@ module ActionView # build_hidden(:year, 2008) # => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />" def build_hidden(type, value) - tag(:input, { + (tag(:input, { :type => "hidden", :id => input_id_from_type(type), :name => input_name_from_type(type), :value => value - }) + "\n" + }) + "\n").html_safe! end # Returns the name attribute for the input tag @@ -896,7 +896,7 @@ module ActionView separator = separator(type) unless type == order.first # don't add on last field select.insert(0, separator.to_s + send("select_#{type}").to_s) end - select + select.html_safe! end # Returns the separator for a given datetime component diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 81c9c88820..20e9916d62 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -505,7 +505,7 @@ module ActionView # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation - # is found in the current I18n locale (through views.labels.<modelname>.<attribute>) or you specify it explicitly. + # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly. # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to # target labels for radio_button tags (where the value is used in the ID of the input tag). @@ -517,8 +517,8 @@ module ActionView # You can localize your labels based on model and attribute names. # For example you can define the following in your locale (e.g. en.yml) # - # views: - # labels: + # helpers: + # label: # post: # body: "Write your entire text here" # @@ -777,7 +777,7 @@ module ActionView options["for"] ||= name_and_id["id"] content = if text.blank? - I18n.t("views.labels.#{object_name}.#{method_name}", :default => "").presence + I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence else text.to_s end @@ -798,7 +798,7 @@ module ActionView if field_type == "hidden" options.delete("size") end - options["type"] = field_type + options["type"] ||= field_type options["value"] ||= value_before_type_cast(object) unless field_type == "file" options["value"] &&= html_escape(options["value"]) add_default_name_and_id(options) @@ -842,7 +842,12 @@ module ActionView checked = self.class.check_box_checked?(value(object), checked_value) end options["checked"] = "checked" if checked - add_default_name_and_id(options) + if options["multiple"] + add_default_name_and_id_for_value(checked_value, options) + options.delete("multiple") + else + add_default_name_and_id(options) + end hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value) checkbox = tag("input", options) (hidden + checkbox).html_safe! @@ -1058,7 +1063,7 @@ module ActionView def radio_button(method, tag_value, options = {}) @template.radio_button(@object_name, method, tag_value, objectify_options(options)) end - + def hidden_field(method, options = {}) @emitted_hidden_id = true if method == :id @template.hidden_field(@object_name, method, objectify_options(options)) @@ -1072,7 +1077,36 @@ module ActionView @template.error_messages_for(@object_name, objectify_options(options)) end - def submit(value = "Save changes", options = {}) + # Add the submit button for the given form. When no value is given, it checks + # if the object is a new resource or not to create the proper label: + # + # <% form_for @post do |f| %> + # <%= f.submit %> + # <% end %> + # + # In the example above, if @post is a new record, it will use "Create Post" as + # submit button label, otherwise, it uses "Update Post". + # + # Those labels can be customized using I18n, under the helpers.submit key and accept + # the {{model}} as translation interpolation: + # + # en: + # helpers: + # submit: + # create: "Create a {{model}}" + # update: "Confirm changes to {{model}}" + # + # It also searches for a key specific for the given object: + # + # en: + # helpers: + # submit: + # post: + # create: "Add {{model}}" + # + def submit(value=nil, options={}) + value, options = nil, value if value.is_a?(Hash) + value ||= submit_default_value @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit")) end @@ -1085,6 +1119,24 @@ module ActionView @default_options.merge(options.merge(:object => @object)) end + def submit_default_value + object = @object.respond_to?(:to_model) ? @object.to_model : @object + key = object ? (object.new_record? ? :create : :update) : :submit + + model = if object.class.respond_to?(:model_name) + object.class.model_name.human + else + @object_name.to_s.humanize + end + + defaults = [] + defaults << :"helpers.submit.#{object_name}.#{key}" + defaults << :"helpers.submit.#{key}" + defaults << "#{key.to_s.humanize} #{model}" + + I18n.t(defaults.shift, :model => model, :default => defaults) + end + def nested_attributes_association?(association_name) @object.respond_to?("#{association_name}_attributes=") end diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 935ab5f3e8..02ad637509 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -571,7 +571,7 @@ module ActionView option_tags = "<option value=\"\">#{options[:include_blank] if options[:include_blank].kind_of?(String)}</option>\n" + option_tags end if value.blank? && options[:prompt] - prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('support.select.prompt', :default => 'Please select') + 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 diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 397871b85e..64b71663c3 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -92,7 +92,7 @@ module ActionView :precision => precision, :delimiter => delimiter, :separator => separator) - ).gsub(/%u/, unit) + ).gsub(/%u/, unit).html_safe! rescue number end diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 8c1f0ad81f..ff7bc3b34e 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -1030,7 +1030,7 @@ module ActionView # page.hide 'spinner' # end def update_page(&block) - JavaScriptGenerator.new(@template, &block).to_s + JavaScriptGenerator.new(@template, &block).to_s.html_safe! end # Works like update_page but wraps the generated JavaScript in a <script> diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index a3bee3e8c2..814d86812d 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -565,7 +565,7 @@ module ActionView end link_text = block_given?? yield(href) : href - href = 'http://' + href unless href.index('http') == 0 + href = 'http://' + href unless href =~ %r{^[a-z]+://}i content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation.reverse.join('') end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 5b136d4f54..14628c5404 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -11,6 +11,11 @@ module ActionView module UrlHelper include JavaScriptHelper + # Need to map default url options to controller one. + def default_url_options(*args) #:nodoc: + @controller.send(:default_url_options, *args) + end + # Returns the URL for the set of +options+ provided. This takes the # same options as +url_for+ in Action Controller (see the # documentation for <tt>ActionController::Base#url_for</tt>). Note that by default @@ -461,10 +466,10 @@ module ActionView string = '' extras = '' - extras << "cc=#{CGI.escape(cc).gsub("+", "%20")}&" unless cc.nil? - extras << "bcc=#{CGI.escape(bcc).gsub("+", "%20")}&" unless bcc.nil? - extras << "body=#{CGI.escape(body).gsub("+", "%20")}&" unless body.nil? - extras << "subject=#{CGI.escape(subject).gsub("+", "%20")}&" unless subject.nil? + extras << "cc=#{Rack::Utils.escape(cc).gsub("+", "%20")}&" unless cc.nil? + extras << "bcc=#{Rack::Utils.escape(bcc).gsub("+", "%20")}&" unless bcc.nil? + extras << "body=#{Rack::Utils.escape(body).gsub("+", "%20")}&" unless body.nil? + extras << "subject=#{Rack::Utils.escape(subject).gsub("+", "%20")}&" unless subject.nil? extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty? email_address = email_address.to_s diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml index 5e2a92b89a..a3548051c1 100644 --- a/actionpack/lib/action_view/locale/en.yml +++ b/actionpack/lib/action_view/locale/en.yml @@ -9,7 +9,7 @@ delimiter: "," # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) precision: 3 - + # Used in number_to_currency() currency: format: @@ -20,15 +20,15 @@ separator: "." delimiter: "," precision: 2 - + # Used in number_to_percentage() percentage: format: # These three are to override number.format and are optional - # separator: + # separator: delimiter: "" - # precision: - + # precision: + # Used in number_to_precision() precision: format: @@ -36,12 +36,12 @@ # separator: delimiter: "" # precision: - + # Used in number_to_human_size() human: format: # These three are to override number.format and are optional - # separator: + # separator: delimiter: "" precision: 1 storage_units: @@ -102,16 +102,22 @@ minute: "Minute" second: "Seconds" - activemodel: - errors: - template: - header: - one: "1 error prohibited this {{model}} from being saved" - other: "{{count}} errors prohibited this {{model}} from being saved" - # The variable :count is also available - body: "There were problems with the following fields:" + errors: + template: + header: + one: "1 error prohibited this {{model}} from being saved" + other: "{{count}} errors prohibited this {{model}} from being saved" + # The variable :count is also available + body: "There were problems with the following fields:" - support: + helpers: select: - # default value for :prompt => true in FormOptionsHelper + # Default value for :prompt => true in FormOptionsHelper prompt: "Please select" + + # Default translation keys for submit FormHelper + submit: + create: 'Create {{model}}' + update: 'Update {{model}}' + submit: 'Save {{model}}' + diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb index a90e0636b9..968dc7b25e 100644 --- a/actionpack/lib/action_view/railtie.rb +++ b/actionpack/lib/action_view/railtie.rb @@ -1,2 +1,17 @@ require "action_view" -require "rails"
\ No newline at end of file +require "rails" + +module ActionView + class Railtie < Rails::Railtie + plugin_name :action_view + + require "action_view/railties/subscriber" + subscriber ActionView::Railties::Subscriber.new + + initializer "action_view.cache_asset_timestamps" do |app| + unless app.config.cache_classes + ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/railties/subscriber.rb b/actionpack/lib/action_view/railties/subscriber.rb new file mode 100644 index 0000000000..803f19379c --- /dev/null +++ b/actionpack/lib/action_view/railties/subscriber.rb @@ -0,0 +1,24 @@ +module ActionView + module Railties + class Subscriber < Rails::Subscriber + def render_template(event) + message = "Rendered #{from_rails_root(event.payload[:identifier])}" + message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] + message << (" (%.1fms)" % event.duration) + info(message) + end + alias :render_partial :render_template + alias :render_collection :render_template + + def logger + ActionController::Base.logger + end + + protected + + def from_rails_root(string) + string.sub("#{Rails.root}/", "").sub(/^app\/views\//, "") + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 5158415c20..8c936ae09e 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -212,34 +212,34 @@ module ActionView end def render - options = @options + identifier = ((@template = find_template) ? @template.identifier : @path) if @collection - ActiveSupport::Notifications.instrument(:render_collection, :path => @path, - :count => @collection.size) do + ActiveSupport::Notifications.instrument("action_view.render_collection", + :identifier => identifier || "collection", :count => @collection.size) do render_collection end else - content = ActiveSupport::Notifications.instrument(:render_partial, :path => @path) do + content = ActiveSupport::Notifications.instrument("action_view.render_partial", + :identifier => identifier) do render_partial end - if !@block && options[:layout] - content = @view._render_layout(find_template(options[:layout]), @locals){ content } + if !@block && (layout = @options[:layout]) + content = @view._render_layout(find_template(layout), @locals){ content } end content end end def render_collection - @template = template = find_template return nil if @collection.blank? if @options.key?(:spacer_template) spacer = find_template(@options[:spacer_template]).render(@view, @locals) end - result = template ? collection_with_template : collection_without_template + result = @template ? collection_with_template : collection_without_template result.join(spacer).html_safe! end @@ -277,7 +277,6 @@ module ActionView end def render_partial(object = @object) - @template = template = find_template locals, view = @locals, @view object ||= locals[template.variable_name] diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index 48316cac53..ec278ca783 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -93,25 +93,23 @@ module ActionView def _render_template(template, layout = nil, options = {}) locals = options[:locals] || {} - content = ActiveSupport::Notifications.instrument(:render_template, - :identifier => template.identifier, :layout => (layout ? layout.identifier : nil)) do - template.render(self, locals) - end + ActiveSupport::Notifications.instrument("action_view.render_template", + :identifier => template.identifier, :layout => layout.try(:identifier)) do - @_content_for[:layout] = content + content = template.render(self, locals) + @_content_for[:layout] = content - if layout - @_layout = layout.identifier - content = _render_layout(layout, locals) - end + if layout + @_layout = layout.identifier + content = _render_layout(layout, locals) + end - content + content + end end def _render_layout(layout, locals, &block) - ActiveSupport::Notifications.instrument(:render_layout, :identifier => layout.identifier) do - layout.render(self, locals){ |*name| _layout_for(*name, &block) } - end + layout.render(self, locals){ |*name| _layout_for(*name, &block) } end end end diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index 67e086d8bd..2abb352d4e 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -13,7 +13,7 @@ module ActionView #:nodoc: end def identifier - self + 'text template' end def inspect diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index be9a2ed50d..16d66b6eca 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -1,6 +1,3 @@ -require 'active_support/test_case' -require 'action_controller/test_case' - module ActionView class Base alias_method :initialize_without_template_tracking, :initialize diff --git a/actionpack/test/abstract/url_for_test.rb b/actionpack/test/abstract/url_for_test.rb new file mode 100644 index 0000000000..e5570349b8 --- /dev/null +++ b/actionpack/test/abstract/url_for_test.rb @@ -0,0 +1,272 @@ +require 'abstract_unit' + +module AbstractController + module Testing + + class UrlForTests < ActionController::TestCase + class W + include AbstractController::UrlFor + end + + def teardown + W.default_url_options.clear + end + + def add_host! + W.default_url_options[:host] = 'www.basecamphq.com' + end + + def test_exception_is_thrown_without_host + assert_raise RuntimeError do + W.new.url_for :controller => 'c', :action => 'a', :id => 'i' + end + end + + def test_anchor + assert_equal('/c/a#anchor', + W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor') + ) + end + + def test_anchor_should_call_to_param + assert_equal('/c/a#anchor', + W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor')) + ) + end + + def test_anchor_should_be_cgi_escaped + assert_equal('/c/a#anc%2Fhor', + W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor')) + ) + end + + def test_default_host + add_host! + assert_equal('http://www.basecamphq.com/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i') + ) + end + + def test_host_may_be_overridden + add_host! + assert_equal('http://37signals.basecamphq.com/c/a/i', + W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i') + ) + end + + def test_port + add_host! + assert_equal('http://www.basecamphq.com:3000/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000) + ) + end + + def test_protocol + add_host! + assert_equal('https://www.basecamphq.com/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') + ) + end + + def test_protocol_with_and_without_separator + add_host! + assert_equal('https://www.basecamphq.com/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') + ) + assert_equal('https://www.basecamphq.com/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https://') + ) + end + + def test_trailing_slash + add_host! + options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'} + assert_equal('http://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) ) + end + + def test_trailing_slash_with_protocol + add_host! + options = { :trailing_slash => true,:protocol => 'https', :controller => 'foo', :action => 'bar', :id => '33'} + assert_equal('https://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) ) + assert_equal 'https://www.basecamphq.com/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string'})) + end + + def test_trailing_slash_with_only_path + options = {:controller => 'foo', :trailing_slash => true} + assert_equal '/foo/', W.new.url_for(options.merge({:only_path => true})) + options.update({:action => 'bar', :id => '33'}) + assert_equal '/foo/bar/33/', W.new.url_for(options.merge({:only_path => true})) + assert_equal '/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string',:only_path => true})) + end + + def test_trailing_slash_with_anchor + options = {:trailing_slash => true, :controller => 'foo', :action => 'bar', :id => '33', :only_path => true, :anchor=> 'chapter7'} + assert_equal '/foo/bar/33/#chapter7', W.new.url_for(options) + assert_equal '/foo/bar/33/?query=string#chapter7', W.new.url_for(options.merge({:query => 'string'})) + end + + def test_trailing_slash_with_params + url = W.new.url_for(:trailing_slash => true, :only_path => true, :controller => 'cont', :action => 'act', :p1 => 'cafe', :p2 => 'link') + params = extract_params(url) + assert_equal params[0], { :p1 => 'cafe' }.to_query + assert_equal params[1], { :p2 => 'link' }.to_query + end + + def test_relative_url_root_is_respected + orig_relative_url_root = ActionController::Base.relative_url_root + ActionController::Base.relative_url_root = '/subdir' + + add_host! + assert_equal('https://www.basecamphq.com/subdir/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') + ) + ensure + ActionController::Base.relative_url_root = orig_relative_url_root + end + + def test_named_routes + with_routing do |set| + set.draw do |map| + match 'this/is/verbose', :to => 'home#index', :as => :no_args + match 'home/sweet/home/:user', :to => 'home#index', :as => :home + end + + # We need to create a new class in order to install the new named route. + kls = Class.new { include AbstractController::UrlFor } + controller = kls.new + assert controller.respond_to?(:home_url) + assert_equal 'http://www.basecamphq.com/home/sweet/home/again', + controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again') + + assert_equal("/home/sweet/home/alabama", controller.send(:home_path, :user => 'alabama', :host => 'unused')) + assert_equal("http://www.basecamphq.com/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'www.basecamphq.com')) + assert_equal("http://www.basecamphq.com/this/is/verbose", controller.send(:no_args_url, :host=>'www.basecamphq.com')) + end + 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 AbstractController::UrlFor } + controller = kls.new + + assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again', + controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again') + end + ensure + ActionController::Base.relative_url_root = orig_relative_url_root + end + + def test_only_path + with_routing do |set| + set.draw do |map| + match 'home/sweet/home/:user', :to => 'home#index', :as => :home + match ':controller/:action/:id' + end + + # We need to create a new class in order to install the new named route. + kls = Class.new { include AbstractController::UrlFor } + controller = kls.new + assert controller.respond_to?(:home_url) + assert_equal '/brave/new/world', + controller.send(:url_for, :controller => 'brave', :action => 'new', :id => 'world', :only_path => true) + + assert_equal("/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'unused', :only_path => true)) + assert_equal("/home/sweet/home/alabama", controller.send(:home_path, 'alabama')) + end + end + + def test_one_parameter + assert_equal('/c/a?param=val', + W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val') + ) + end + + def test_two_parameters + url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2') + params = extract_params(url) + assert_equal params[0], { :p1 => 'X1' }.to_query + assert_equal params[1], { :p2 => 'Y2' }.to_query + end + + def test_hash_parameter + url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'}) + params = extract_params(url) + assert_equal params[0], { 'query[category]' => 'prof' }.to_query + assert_equal params[1], { 'query[name]' => 'Bob' }.to_query + end + + def test_array_parameter + url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof']) + params = extract_params(url) + assert_equal params[0], { 'query[]' => 'Bob' }.to_query + assert_equal params[1], { 'query[]' => 'prof' }.to_query + end + + def test_hash_recursive_parameters + url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'}) + params = extract_params(url) + assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query + assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query + assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query + end + + def test_hash_recursive_and_array_parameters + url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'}) + assert_match %r(^/c/a/101), url + params = extract_params(url) + assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query + assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query + assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query + assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query + end + + def test_path_generation_for_symbol_parameter_keys + assert_generates("/image", :controller=> :image) + end + + def test_named_routes_with_nil_keys + with_routing do |set| + set.draw do |map| + match 'posts.:format', :to => 'posts#index', :as => :posts + match '/', :to => 'posts#index', :as => :main + end + + # We need to create a new class in order to install the new named route. + kls = Class.new { include AbstractController::UrlFor } + kls.default_url_options[:host] = 'www.basecamphq.com' + + controller = kls.new + params = {:action => :index, :controller => :posts, :format => :xml} + assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params)) + params[:format] = nil + assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params)) + end + end + + def test_multiple_includes_maintain_distinct_options + first_class = Class.new { include AbstractController::UrlFor } + second_class = Class.new { include AbstractController::UrlFor } + + first_host, second_host = 'firsthost.com', 'secondhost.com' + + first_class.default_url_options[:host] = first_host + second_class.default_url_options[:host] = second_host + + assert_equal first_class.default_url_options[:host], first_host + assert_equal second_class.default_url_options[:host], second_host + end + + private + def extract_params(url) + url.split('?', 2).last.split('&').sort + end + end + end +end
\ No newline at end of file diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 8c65087898..10913c0fdb 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -19,8 +19,6 @@ require 'action_view' require 'action_view/base' require 'action_dispatch' require 'fixture_template' -require 'active_support/test_case' -require 'action_view/test_case' require 'active_support/dependencies' activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) @@ -50,14 +48,6 @@ ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') FIXTURES = Pathname.new(FIXTURE_LOAD_PATH) -# Turn on notifications -require 'active_support/notifications' -Thread.abort_on_exception = true - -ActiveSupport::Notifications.subscribe do |*args| - ActionController::Base.log_event(*args) if ActionController::Base.logger -end - module SetupOnce extend ActiveSupport::Concern @@ -95,29 +85,15 @@ class ActiveSupport::TestCase end end -class MockLogger - attr_reader :logged - attr_accessor :level - - def initialize - @level = Logger::DEBUG - @logged = [] - end - - def method_missing(method, *args, &blk) - @logged << args.first - @logged << blk.call if block_given? - end -end - class ActionController::IntegrationTest < ActiveSupport::TestCase def self.build_app(routes = nil) + ActionDispatch::Flash ActionDispatch::MiddlewareStack.new { |middleware| - middleware.use "ActionDispatch::StringCoercion" middleware.use "ActionDispatch::ShowExceptions" middleware.use "ActionDispatch::Callbacks" middleware.use "ActionDispatch::ParamsParser" - middleware.use "Rack::Head" + middleware.use "ActionDispatch::Flash" + middleware.use "ActionDispatch::Head" }.build(routes || ActionController::Routing::Routes) end diff --git a/actionpack/test/active_record_unit.rb b/actionpack/test/active_record_unit.rb index 9a094cf66b..4f2b052720 100644 --- a/actionpack/test/active_record_unit.rb +++ b/actionpack/test/active_record_unit.rb @@ -17,7 +17,6 @@ unless defined?(ActiveRecord) && defined?(Fixtures) raise LoadError, "#{PATH_TO_AR} doesn't exist" unless File.directory?(PATH_TO_AR) $LOAD_PATH.unshift PATH_TO_AR require 'active_record' - require 'active_record/fixtures' rescue LoadError => e $stderr.print "Failed to load Active Record. Skipping Active Record assertion tests: #{e}" ActiveRecordTestConnector.able_to_connect = false diff --git a/actionpack/test/activerecord/controller_runtime_test.rb b/actionpack/test/activerecord/controller_runtime_test.rb index 0f534da14b..37c7738301 100644 --- a/actionpack/test/activerecord/controller_runtime_test.rb +++ b/actionpack/test/activerecord/controller_runtime_test.rb @@ -1,39 +1,53 @@ require 'active_record_unit' require 'active_record/railties/controller_runtime' require 'fixtures/project' +require 'rails/subscriber/test_helper' +require 'action_controller/railties/subscriber' ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime -class ARLoggingController < ActionController::Base - def show - render :inline => "<%= Project.all %>" +module ControllerRuntimeSubscriberTest + class SubscriberController < ActionController::Base + def show + render :inline => "<%= Project.all %>" + end end -end -class ARLoggingTest < ActionController::TestCase - tests ARLoggingController + def self.included(base) + base.tests SubscriberController + end def setup + @old_logger = ActionController::Base.logger + Rails::Subscriber.add(:action_controller, ActionController::Railties::Subscriber.new) super - set_logger end - def wait - ActiveSupport::Notifications.notifier.wait + def teardown + super + Rails::Subscriber.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 - assert_match /ActiveRecord runtime/, logs[3] + + assert_equal 2, @logger.logged(:info).size + assert_match /\(Views: [\d\.]+ms | ActiveRecord: [\d\.]+ms\)/, @logger.logged(:info)[1] end - private - def set_logger - @controller.logger = MockLogger.new - end + class SyncSubscriberTest < ActionController::TestCase + include Rails::Subscriber::SyncTestHelper + include ControllerRuntimeSubscriberTest + end - def logs - @logs ||= @controller.logger.logged.compact.map {|l| l.to_s.strip} - end -end + class AsyncSubscriberTest < ActionController::TestCase + include Rails::Subscriber::AsyncTestHelper + include ControllerRuntimeSubscriberTest + end +end
\ No newline at end of file diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index ad744421db..ea82758cf5 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::UrlWriter + include ActionController::UrlFor self.default_url_options[:host] = 'example.com' def setup diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 65118f9bc9..1510a6a7e0 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -6,6 +6,7 @@ require 'pp' # require 'pp' early to prevent hidden_methods from not picking up module Submodule class ContainedEmptyController < ActionController::Base end + class ContainedNonEmptyController < ActionController::Base def public_action render :nothing => true @@ -20,12 +21,15 @@ module Submodule end hide_action :another_hidden_action end + class SubclassedController < ContainedNonEmptyController hide_action :public_action # Hiding it here should not affect the superclass. end end + class EmptyController < ActionController::Base end + class NonEmptyController < ActionController::Base def public_action render :nothing => true @@ -37,7 +41,6 @@ class NonEmptyController < ActionController::Base end class MethodMissingController < ActionController::Base - hide_action :shouldnt_be_called def shouldnt_be_called raise "NO WAY!" @@ -48,16 +51,15 @@ protected def method_missing(selector) render :text => selector.to_s end - end class DefaultUrlOptionsController < ActionController::Base - def default_url_options_action - render :nothing => true + def from_view + render :inline => "<%= #{params[:route]} %>" end def default_url_options(options = nil) - { :host => 'www.override.com', :action => 'new', :bacon => 'chunky' } + { :host => 'www.override.com', :action => 'new', :locale => 'en' } end end @@ -68,6 +70,7 @@ class ControllerClassTests < Test::Unit::TestCase assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path end + def test_controller_name assert_equal 'empty', EmptyController.controller_name assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name @@ -86,41 +89,16 @@ class ControllerInstanceTests < Test::Unit::TestCase def test_action_methods @empty_controllers.each do |c| - hide_mocha_methods_from_controller(c) assert_equal Set.new, c.class.__send__(:action_methods), "#{c.controller_path} should be empty!" end + @non_empty_controllers.each do |c| - hide_mocha_methods_from_controller(c) assert_equal Set.new(%w(public_action)), c.class.__send__(:action_methods), "#{c.controller_path} should not be empty!" end end - - protected - # Mocha adds some public instance methods to Object that would be - # considered actions, so explicitly hide_action them. - def hide_mocha_methods_from_controller(controller) - mocha_methods = [ - :expects, :mocha, :mocha_inspect, :reset_mocha, :stubba_object, - :stubba_method, :stubs, :verify, :__metaclass__, :__is_a__, :to_matcher, - ] - controller.class.__send__(:hide_action, *mocha_methods) - end end - class PerformActionTest < ActionController::TestCase - class MockLogger - attr_reader :logged - - def initialize - @logged = [] - end - - def method_missing(method, *args) - @logged << args.first.to_s - end - end - def use_controller(controller_class) @controller = controller_class.new @@ -128,9 +106,8 @@ class PerformActionTest < ActionController::TestCase # a more accurate simulation of what happens in "real life". @controller.logger = Logger.new(nil) - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new - + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new @request.host = "www.nextangle.com" rescue_action_in_public! @@ -145,8 +122,7 @@ class PerformActionTest < ActionController::TestCase def test_method_missing_is_not_an_action_name use_controller MethodMissingController - - assert ! @controller.__send__(:action_method?, 'method_missing') + assert !@controller.__send__(:action_method?, 'method_missing') get :method_missing assert_response :success @@ -172,16 +148,43 @@ class DefaultUrlOptionsTest < ActionController::TestCase def test_default_url_options_are_used_if_set with_routing do |set| set.draw do |map| - match 'default_url_options', :to => 'default_url_options#default_url_options_action', :as => :default_url_options + match 'from_view', :to => 'default_url_options#from_view', :as => :from_view match ':controller/:action' end - get :default_url_options_action # Make a dummy request so that the controller is initialized properly. + get :from_view, :route => "from_view_url" - assert_equal 'http://www.override.com/default_url_options/new?bacon=chunky', @controller.url_for(:controller => 'default_url_options') - assert_equal 'http://www.override.com/default_url_options?bacon=chunky', @controller.send(:default_url_options_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 + + def test_default_url_options_are_used_in_non_positional_parameters + with_routing do |set| + set.draw do |map| + scope("/:locale") do + resources :descriptions + end + match ':controller/:action' + end + + get :from_view, :route => "description_path(1)" + + assert_equal '/en/descriptions/1', @response.body + assert_equal '/en/descriptions', @controller.send(:descriptions_path) + assert_equal '/pl/descriptions', @controller.send(:descriptions_path, "pl") + assert_equal '/pl/descriptions', @controller.send(:descriptions_path, :locale => "pl") + assert_equal '/pl/descriptions.xml', @controller.send(:descriptions_path, "pl", "xml") + assert_equal '/en/descriptions.xml', @controller.send(:descriptions_path, :format => "xml") + assert_equal '/en/descriptions/1', @controller.send(:description_path, 1) + assert_equal '/pl/descriptions/1', @controller.send(:description_path, "pl", 1) + assert_equal '/pl/descriptions/1', @controller.send(:description_path, 1, :locale => "pl") + assert_equal '/pl/descriptions/1.xml', @controller.send(:description_path, "pl", 1, "xml") + assert_equal '/en/descriptions/1.xml', @controller.send(:description_path, 1, :format => "xml") + end + end + end class EmptyUrlOptionsTest < ActionController::TestCase @@ -197,15 +200,12 @@ class EmptyUrlOptionsTest < ActionController::TestCase get :public_action assert_equal "http://www.example.com/non_empty/public_action", @controller.url_for end -end -class EnsureNamedRoutesWorksTicket22BugTest < ActionController::TestCase - def test_named_routes_still_work + def test_named_routes_with_path_without_doing_a_request_first with_routing do |set| set.draw do |map| resources :things end - EmptyController.send :include, ActionController::UrlWriter assert_equal '/things', EmptyController.new.send(:things_path) end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 679eaf7b38..8a13d1e5f1 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -629,20 +629,6 @@ class FragmentCachingTest < ActionController::TestCase assert_equal 'generated till now -> fragment content', buffer end - def test_fragment_for_logging - fragment_computed = false - events = [] - ActiveSupport::Notifications.subscribe { |*args| events << args } - - buffer = 'generated till now -> ' - @controller.fragment_for(buffer, 'expensive') { fragment_computed = true } - - assert fragment_computed - assert_equal 'generated till now -> ', buffer - ActiveSupport::Notifications.notifier.wait - assert_equal [:exist_fragment?, :write_fragment], events.map(&:first) - end - end class FunctionalCachingController < ActionController::Base diff --git a/actionpack/test/controller/dispatcher_test.rb b/actionpack/test/controller/dispatcher_test.rb index 64f1ad7610..7e19bce3b7 100644 --- a/actionpack/test/controller/dispatcher_test.rb +++ b/actionpack/test/controller/dispatcher_test.rb @@ -1,73 +1,59 @@ require 'abstract_unit' -class DispatcherTest < Test::Unit::TestCase - Dispatcher = ActionController::Dispatcher - - class Foo - cattr_accessor :a, :b - end +# Ensure deprecated dispatcher works +class DeprecatedDispatcherTest < ActiveSupport::TestCase + class DummyApp + def call(env) + [200, {}, 'response'] + end + end def setup - ENV['REQUEST_METHOD'] = 'GET' - - # Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks ActionDispatch::Callbacks.reset_callbacks(:prepare) ActionDispatch::Callbacks.reset_callbacks(:call) - - ActionController::Routing::Routes.stubs(:call).returns([200, {}, 'response']) - Dispatcher.stubs(:require_dependency) end - def teardown - ENV.delete 'REQUEST_METHOD' - end + def test_assert_deprecated_to_prepare + a = nil + + assert_deprecated do + ActionController::Dispatcher.to_prepare { a = 1 } + end - def test_clears_dependencies_after_dispatch_if_in_loading_mode - ActiveSupport::Dependencies.expects(:clear).once - dispatch(false) + assert_nil a + dispatch + assert_equal 1, a end - def test_prepare_callbacks - a = b = c = nil - ActionDispatch::Callbacks.to_prepare { |*args| a = b = c = 1 } - ActionDispatch::Callbacks.to_prepare { |*args| b = c = 2 } - ActionDispatch::Callbacks.to_prepare { |*args| c = 3 } + def test_assert_deprecated_before_dispatch + a = nil - # Ensure to_prepare callbacks are not run when defined - assert_nil a || b || c + assert_deprecated do + ActionController::Dispatcher.before_dispatch { a = 1 } + end - # Run callbacks + assert_nil a dispatch - assert_equal 1, a - assert_equal 2, b - assert_equal 3, c - - # Make sure they are only run once - a = b = c = nil - dispatch - assert_nil a || b || c end - def test_to_prepare_with_identifier_replaces - ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a, Foo.b = 1, 1 } - ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a = 2 } + def test_assert_deprecated_after_dispatch + a = nil + + assert_deprecated do + ActionController::Dispatcher.after_dispatch { a = 1 } + end + assert_nil a dispatch - assert_equal 2, Foo.a - assert_equal nil, Foo.b + assert_equal 1, a end private - def dispatch(cache_classes = true) - ActionController::Dispatcher.prepare_each_request = false - Dispatcher.define_dispatcher_callbacks(cache_classes) - @dispatcher ||= ActionDispatch::Callbacks.new(ActionController::Routing::Routes) - @dispatcher.call({'rack.input' => StringIO.new(''), 'action_dispatch.show_exceptions' => false}) + def dispatch(cache_classes = true) + @dispatcher ||= ActionDispatch::Callbacks.new(DummyApp.new, !cache_classes) + @dispatcher.call({'rack.input' => StringIO.new('')}) end - def assert_subclasses(howmany, klass, message = klass.subclasses.inspect) - assert_equal howmany, klass.subclasses.size, message - end end diff --git a/actionpack/test/controller/filter_params_test.rb b/actionpack/test/controller/filter_params_test.rb index 420ebeacf4..d0635669c2 100644 --- a/actionpack/test/controller/filter_params_test.rb +++ b/actionpack/test/controller/filter_params_test.rb @@ -66,18 +66,6 @@ class FilterParamTest < ActionController::TestCase assert_raise(NoMethodError) { @controller.filter_parameters([{'password' => '[FILTERED]'}]) } end - def test_filter_parameters_inside_logs - FilterParamController.filter_parameter_logging(:lifo, :amount) - - get :payment, :lifo => 'Pratik', :amount => '420', :step => '1' - ActiveSupport::Notifications.notifier.wait - - filtered_params_logs = logs.detect {|l| l =~ /\AParameters/ } - assert filtered_params_logs.index('"amount"=>"[FILTERED]"') - assert filtered_params_logs.index('"lifo"=>"[FILTERED]"') - assert filtered_params_logs.index('"step"=>"1"') - end - private def set_logger diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index a9b60386f1..85a2e7f44b 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -159,7 +159,7 @@ class FlashTest < ActionController::TestCase end def test_keep_and_discard_return_values - flash = ActionController::Flash::FlashHash.new + flash = ActionDispatch::Flash::FlashHash.new flash.update(:foo => :foo_indeed, :bar => :bar_indeed) assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed @@ -187,4 +187,42 @@ class FlashTest < ActionController::TestCase get :redirect_with_other_flashes assert_equal "Horses!", @controller.send(:flash)[:joyride] end -end
\ No newline at end of file +end + +class FlashIntegrationTest < ActionController::IntegrationTest + SessionKey = '_myapp_session' + SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33' + + class TestController < ActionController::Base + def set_flash + flash["that"] = "hello" + head :ok + end + + def use_flash + render :inline => "flash: #{flash["that"]}" + end + end + + def test_flash + with_test_route_set do + get '/set_flash' + assert_response :success + assert_equal "hello", @request.flash["that"] + + get '/use_flash' + assert_response :success + assert_equal "flash: hello", @response.body + end + end + + private + def with_test_route_set + with_routing do |set| + set.draw do |map| + match ':action', :to => ActionDispatch::Session::CookieStore.new(TestController, :key => SessionKey, :secret => SessionSecret) + end + yield + end + end +end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 624b14e69b..683ab5236c 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -254,6 +254,10 @@ class IntegrationProcessTest < ActionController::IntegrationTest render :text => "Created", :status => 201 end + def method + render :text => "method: #{request.method}" + end + def cookie_monster cookies["cookie_1"] = nil cookies["cookie_3"] = "chocolate" @@ -379,6 +383,14 @@ class IntegrationProcessTest < ActionController::IntegrationTest head '/post' assert_equal 201, status assert_equal "", body + + get '/get/method' + assert_equal 200, status + assert_equal "method: get", body + + head '/get/method' + assert_equal 200, status + assert_equal "", body end end @@ -391,6 +403,7 @@ class IntegrationProcessTest < ActionController::IntegrationTest with_routing do |set| set.draw do |map| match ':action', :to => ::IntegrationProcessTest::IntegrationController + get 'get/:action', :to => ::IntegrationProcessTest::IntegrationController end yield end diff --git a/actionpack/test/controller/logging_test.rb b/actionpack/test/controller/logging_test.rb deleted file mode 100644 index 4206dffa7e..0000000000 --- a/actionpack/test/controller/logging_test.rb +++ /dev/null @@ -1,80 +0,0 @@ -require 'abstract_unit' - -module Another - class LoggingController < ActionController::Base - layout "layouts/standard" - - def show - render :nothing => true - end - - def with_layout - render :template => "test/hello_world", :layout => true - end - end -end - -class LoggingTest < ActionController::TestCase - tests Another::LoggingController - - def setup - super - set_logger - end - - def get(*args) - super - wait - end - - def wait - ActiveSupport::Notifications.notifier.wait - end - - def test_logging_without_parameters - get :show - assert_equal 4, logs.size - assert_nil logs.detect {|l| l =~ /Parameters/ } - end - - def test_logging_with_parameters - get :show, :id => '10' - assert_equal 5, logs.size - - params = logs.detect {|l| l =~ /Parameters/ } - assert_equal 'Parameters: {"id"=>"10"}', params - end - - def test_log_controller_with_namespace_and_action - get :show - assert_match /Processed\sAnother::LoggingController#show/, logs[1] - end - - def test_log_view_runtime - get :show - assert_match /View runtime/, logs[2] - end - - def test_log_completed_status_and_request_uri - get :show - last = logs.last - assert_match /Completed/, last - assert_match /200/, last - assert_match /another\/logging\/show/, last - end - - def test_logger_prints_layout_and_template_rendering_info - get :with_layout - logged = logs.find {|l| l =~ /render/i } - assert_match /Rendered (.*)test\/hello_world.erb within (.*)layouts\/standard.html.erb/, logged - end - - private - def set_logger - @controller.logger = MockLogger.new - end - - def logs - @logs ||= @controller.logger.logged.compact.map {|l| l.to_s.strip} - end -end diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 6b9cace9cd..ba2347e4e2 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -461,31 +461,27 @@ end class RespondWithController < ActionController::Base respond_to :html, :json - respond_to :xml, :except => :using_defaults - respond_to :js, :only => [ :using_defaults, :using_resource ] + respond_to :xml, :except => :using_resource_with_block + respond_to :js, :only => [ :using_resource_with_block, :using_resource ] - def using_defaults - respond_to do |format| - format.csv { render :text => "CSV" } - end + def using_resource + respond_with(resource) end - def using_defaults_with_type_list - respond_to(:js, :xml) + def using_resource_with_block + respond_with(resource) do |format| + format.csv { render :text => "CSV" } + end end - def default_overwritten - respond_to do |format| + def using_resource_with_overwrite_block + respond_with(resource) do |format| format.html { render :text => "HTML" } end end - def using_resource - respond_with(Customer.new("david", 13)) - end - def using_resource_with_collection - respond_with([Customer.new("david", 13), Customer.new("jamis", 9)]) + respond_with([resource, Customer.new("jamis", 9)]) end def using_resource_with_parent @@ -493,16 +489,16 @@ class RespondWithController < ActionController::Base end def using_resource_with_status_and_location - respond_with(Customer.new("david", 13), :location => "http://test.host/", :status => :created) + respond_with(resource, :location => "http://test.host/", :status => :created) end def using_resource_with_responder responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" } - respond_with(Customer.new("david", 13), :responder => responder) + respond_with(resource, :responder => responder) end def using_resource_with_action - respond_with(Customer.new("david", 13), :action => :foo) do |format| + respond_with(resource, :action => :foo) do |format| format.html { raise ActionView::MissingTemplate.new([], "method") } end end @@ -511,11 +507,15 @@ class RespondWithController < ActionController::Base responder = Class.new(ActionController::Responder) do def respond; @controller.render :text => "respond #{format}"; end end - respond_with(Customer.new("david", 13), :responder => responder) + respond_with(resource, :responder => responder) end protected + def resource + Customer.new("david", 13) + end + def _render_js(js, options) self.content_type ||= Mime::JS self.response_body = js.respond_to?(:to_js) ? js.to_js : js @@ -527,12 +527,18 @@ class InheritedRespondWithController < RespondWithController respond_to :xml, :json def index - respond_with(Customer.new("david", 13)) do |format| + respond_with(resource) do |format| format.json { render :text => "JSON" } end end end +class EmptyRespondWithController < ActionController::Base + def index + respond_with(Customer.new("david", 13)) + end +end + class RespondWithControllerTest < ActionController::TestCase tests RespondWithController @@ -547,56 +553,54 @@ class RespondWithControllerTest < ActionController::TestCase ActionController::Base.use_accept_header = false end - def test_using_defaults + def test_using_resource + @request.accept = "text/javascript" + get :using_resource + assert_equal "text/javascript", @response.content_type + assert_equal '$("body").visualEffect("highlight");', @response.body + + @request.accept = "application/xml" + get :using_resource + assert_equal "application/xml", @response.content_type + assert_equal "<name>david</name>", @response.body + + @request.accept = "application/json" + assert_raise ActionView::MissingTemplate do + get :using_resource + end + end + + def test_using_resource_with_block @request.accept = "*/*" - get :using_defaults + get :using_resource_with_block assert_equal "text/html", @response.content_type assert_equal 'Hello world!', @response.body @request.accept = "text/csv" - get :using_defaults + get :using_resource_with_block assert_equal "text/csv", @response.content_type assert_equal "CSV", @response.body - @request.accept = "text/javascript" - get :using_defaults - assert_equal "text/javascript", @response.content_type - assert_equal '$("body").visualEffect("highlight");', @response.body - end - - def test_using_defaults_with_type_list - @request.accept = "*/*" - get :using_defaults_with_type_list - assert_equal "text/javascript", @response.content_type - assert_equal '$("body").visualEffect("highlight");', @response.body - @request.accept = "application/xml" - get :using_defaults_with_type_list + get :using_resource assert_equal "application/xml", @response.content_type - assert_equal "<p>Hello world!</p>\n", @response.body + assert_equal "<name>david</name>", @response.body end - def test_default_overwritten - get :default_overwritten + def test_using_resource_with_overwrite_block + get :using_resource_with_overwrite_block assert_equal "text/html", @response.content_type assert_equal "HTML", @response.body end - def test_using_resource - @request.accept = "text/javascript" - get :using_resource - assert_equal "text/javascript", @response.content_type - assert_equal '$("body").visualEffect("highlight");', @response.body - + def test_not_acceptable @request.accept = "application/xml" - get :using_resource - assert_equal "application/xml", @response.content_type - assert_equal "<name>david</name>", @response.body + get :using_resource_with_block + assert_equal 406, @response.status - @request.accept = "application/json" - assert_raise ActionView::MissingTemplate do - get :using_resource - end + @request.accept = "text/javascript" + get :using_resource_with_overwrite_block + assert_equal 406, @response.status end def test_using_resource_for_post_with_html_redirects_on_success @@ -831,22 +835,12 @@ class RespondWithControllerTest < ActionController::TestCase RespondWithController.responder = ActionController::Responder end - def test_not_acceptable - @request.accept = "application/xml" - get :using_defaults - assert_equal 406, @response.status - - @request.accept = "text/html" - get :using_defaults_with_type_list - assert_equal 406, @response.status - - @request.accept = "application/json" - get :using_defaults_with_type_list - assert_equal 406, @response.status - - @request.accept = "text/javascript" - get :default_overwritten - assert_equal 406, @response.status + def test_error_is_raised_if_no_respond_to_is_declared_and_respond_with_is_called + @controller = EmptyRespondWithController.new + @request.accept = "*/*" + assert_raise RuntimeError do + get :index + end end private diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 1a03396ae9..01ed491732 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -30,16 +30,6 @@ module Backoffice end class ResourcesTest < ActionController::TestCase - # The assertions in these tests are incompatible with the hash method - # optimisation. This could indicate user level problems - def setup - ActionController::Base.optimise_named_routes = false - end - - def teardown - ActionController::Base.optimise_named_routes = true - end - def test_should_arrange_actions resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, :collection => { :rss => :get, :reorder => :post, :csv => :post }, @@ -304,27 +294,27 @@ class ResourcesTest < ActionController::TestCase end end - def test_member_when_changed_default_restful_actions_and_path_names_not_specified - default_path_names = ActionController::Base.resources_path_names - ActionController::Base.resources_path_names = {:new => 'nuevo', :edit => 'editar'} - - with_restful_routing :messages do - new_options = { :action => 'new', :controller => 'messages' } - new_path = "/messages/nuevo" - edit_options = { :action => 'edit', :id => '1', :controller => 'messages' } - edit_path = "/messages/1/editar" - - assert_restful_routes_for :messages do |options| - assert_recognizes(options.merge(new_options), :path => new_path, :method => :get) - end - - assert_restful_routes_for :messages do |options| - assert_recognizes(options.merge(edit_options), :path => edit_path, :method => :get) - end - end - ensure - ActionController::Base.resources_path_names = default_path_names - end + # def test_member_when_changed_default_restful_actions_and_path_names_not_specified + # default_path_names = ActionController::Base.resources_path_names + # ActionController::Base.resources_path_names = {:new => 'nuevo', :edit => 'editar'} + # + # with_restful_routing :messages do + # new_options = { :action => 'new', :controller => 'messages' } + # new_path = "/messages/nuevo" + # edit_options = { :action => 'edit', :id => '1', :controller => 'messages' } + # edit_path = "/messages/1/editar" + # + # assert_restful_routes_for :messages do |options| + # assert_recognizes(options.merge(new_options), :path => new_path, :method => :get) + # end + # + # assert_restful_routes_for :messages do |options| + # assert_recognizes(options.merge(edit_options), :path => edit_path, :method => :get) + # end + # end + # ensure + # ActionController::Base.resources_path_names = default_path_names + # end def test_with_two_member_actions_with_same_method [:put, :post].each do |method| diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index c15eaade58..f390bbdc89 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -82,9 +82,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase attr_reader :rs def setup - # These tests assume optimisation is on, so re-enable it. - ActionController::Base.optimise_named_routes = true - @rs = ::ActionController::Routing::RouteSet.new end @@ -632,7 +629,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase end def test_routes_changed_correctly_after_clear - ActionController::Base.optimise_named_routes = true rs = ::ActionController::Routing::RouteSet.new rs.draw do |r| r.connect 'ca', :controller => 'ca', :action => "aa" diff --git a/actionpack/test/controller/subscriber_test.rb b/actionpack/test/controller/subscriber_test.rb new file mode 100644 index 0000000000..ef1a325799 --- /dev/null +++ b/actionpack/test/controller/subscriber_test.rb @@ -0,0 +1,192 @@ +require "abstract_unit" +require "rails/subscriber/test_helper" +require "action_controller/railties/subscriber" + +module Another + class SubscribersController < ActionController::Base + def show + render :nothing => true + end + + def redirector + redirect_to "http://foo.bar/" + end + + def data_sender + send_data "cool data", :filename => "omg.txt" + end + + def xfile_sender + send_file File.expand_path("company.rb", FIXTURE_LOAD_PATH), :x_sendfile => true + end + + def file_sender + send_file File.expand_path("company.rb", FIXTURE_LOAD_PATH) + end + + def with_fragment_cache + render :inline => "<%= cache('foo'){ 'bar' } %>" + end + + def with_page_cache + cache_page("Super soaker", "/index.html") + render :nothing => true + end + end +end + +module ActionControllerSubscriberTest + + def self.included(base) + base.tests Another::SubscribersController + end + + def setup + @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 + end + + def teardown + super + Rails::Subscriber.subscribers.clear + FileUtils.rm_rf(@cache_path) + ActionController::Base.logger = @old_logger + end + + def set_logger(logger) + ActionController::Base.logger = logger + end + + def test_process_action + get :show + wait + assert_equal 2, logs.size + assert_match /Processed\sAnother::SubscribersController#show/, logs[0] + end + + def test_process_action_formats + get :show + wait + assert_equal 2, logs.size + assert_match /text\/html/, logs[0] + end + + def test_process_action_without_parameters + get :show + wait + assert_nil logs.detect {|l| l =~ /Parameters/ } + end + + def test_process_action_with_parameters + get :show, :id => '10' + wait + + assert_equal 3, logs.size + assert_equal 'Parameters: {"id"=>"10"}', logs[1] + end + + def test_process_action_with_view_runtime + get :show + wait + assert_match /\(Views: [\d\.]+ms\)/, logs[1] + end + + def test_process_action_with_status_and_request_uri + get :show + wait + last = logs.last + assert_match /Completed/, last + assert_match /200/, last + assert_match /another\/subscribers\/show/, last + end + + def test_process_action_with_filter_parameters + Another::SubscribersController.filter_parameter_logging(:lifo, :amount) + + get :show, :lifo => 'Pratik', :amount => '420', :step => '1' + wait + + params = logs[1] + assert_match /"amount"=>"\[FILTERED\]"/, params + assert_match /"lifo"=>"\[FILTERED\]"/, params + assert_match /"step"=>"1"/, params + end + + def test_redirect_to + get :redirector + wait + + assert_equal 3, logs.size + assert_equal "Redirected to http://foo.bar/", logs[0] + end + + def test_send_data + get :data_sender + wait + + assert_equal 3, logs.size + assert_match /Sent data omg\.txt/, logs[0] + end + + def test_send_file + get :file_sender + wait + + assert_equal 3, logs.size + assert_match /Sent file/, logs[0] + assert_match /test\/fixtures\/company\.rb/, logs[0] + end + + def test_send_xfile + get :xfile_sender + wait + + assert_equal 3, logs.size + assert_match /Sent X\-Sendfile header/, logs[0] + assert_match /test\/fixtures\/company\.rb/, logs[0] + end + + def test_with_fragment_cache + ActionController::Base.perform_caching = true + get :with_fragment_cache + wait + + assert_equal 4, logs.size + assert_match /Exist fragment\? views\/foo/, logs[0] + assert_match /Write fragment views\/foo/, logs[1] + ensure + ActionController::Base.perform_caching = true + end + + def test_with_page_cache + ActionController::Base.perform_caching = true + get :with_page_cache + wait + + assert_equal 3, logs.size + assert_match /Write page/, logs[0] + assert_match /\/index\.html/, logs[0] + ensure + ActionController::Base.perform_caching = true + end + + def logs + @logs ||= @logger.logged(:info) + end + + class SyncSubscriberTest < ActionController::TestCase + include Rails::Subscriber::SyncTestHelper + include ActionControllerSubscriberTest + end + + class AsyncSubscriberTest < ActionController::TestCase + include Rails::Subscriber::AsyncTestHelper + include ActionControllerSubscriberTest + end +end diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index 428f40b9f8..c2b8cd85d8 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -100,286 +100,3 @@ class UrlRewriterTests < ActionController::TestCase end end -class UrlWriterTests < ActionController::TestCase - class W - include ActionController::UrlWriter - end - - def teardown - W.default_url_options.clear - end - - def add_host! - W.default_url_options[:host] = 'www.basecamphq.com' - end - - def test_exception_is_thrown_without_host - assert_raise RuntimeError do - W.new.url_for :controller => 'c', :action => 'a', :id => 'i' - end - end - - def test_anchor - assert_equal('/c/a#anchor', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor') - ) - end - - def test_anchor_should_call_to_param - assert_equal('/c/a#anchor', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor')) - ) - end - - def test_anchor_should_be_cgi_escaped - assert_equal('/c/a#anc%2Fhor', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor')) - ) - end - - def test_default_host - add_host! - assert_equal('http://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i') - ) - end - - def test_host_may_be_overridden - add_host! - assert_equal('http://37signals.basecamphq.com/c/a/i', - W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i') - ) - end - - def test_port - add_host! - assert_equal('http://www.basecamphq.com:3000/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000) - ) - end - - def test_protocol - add_host! - assert_equal('https://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') - ) - end - - def test_protocol_with_and_without_separator - add_host! - assert_equal('https://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') - ) - assert_equal('https://www.basecamphq.com/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https://') - ) - end - - def test_trailing_slash - add_host! - options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'} - assert_equal('http://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) ) - end - - def test_trailing_slash_with_protocol - add_host! - options = { :trailing_slash => true,:protocol => 'https', :controller => 'foo', :action => 'bar', :id => '33'} - assert_equal('https://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) ) - assert_equal 'https://www.basecamphq.com/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string'})) - end - - def test_trailing_slash_with_only_path - options = {:controller => 'foo', :trailing_slash => true} - assert_equal '/foo/', W.new.url_for(options.merge({:only_path => true})) - options.update({:action => 'bar', :id => '33'}) - assert_equal '/foo/bar/33/', W.new.url_for(options.merge({:only_path => true})) - assert_equal '/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string',:only_path => true})) - end - - def test_trailing_slash_with_anchor - options = {:trailing_slash => true, :controller => 'foo', :action => 'bar', :id => '33', :only_path => true, :anchor=> 'chapter7'} - assert_equal '/foo/bar/33/#chapter7', W.new.url_for(options) - assert_equal '/foo/bar/33/?query=string#chapter7', W.new.url_for(options.merge({:query => 'string'})) - end - - def test_trailing_slash_with_params - url = W.new.url_for(:trailing_slash => true, :only_path => true, :controller => 'cont', :action => 'act', :p1 => 'cafe', :p2 => 'link') - params = extract_params(url) - assert_equal params[0], { :p1 => 'cafe' }.to_query - assert_equal params[1], { :p2 => 'link' }.to_query - end - - def test_relative_url_root_is_respected - orig_relative_url_root = ActionController::Base.relative_url_root - ActionController::Base.relative_url_root = '/subdir' - - add_host! - assert_equal('https://www.basecamphq.com/subdir/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') - ) - ensure - ActionController::Base.relative_url_root = orig_relative_url_root - end - - def test_named_routes - with_routing do |set| - set.draw do |map| - match 'this/is/verbose', :to => 'home#index', :as => :no_args - match 'home/sweet/home/:user', :to => 'home#index', :as => :home - end - - # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlWriter } - controller = kls.new - assert controller.respond_to?(:home_url) - assert_equal 'http://www.basecamphq.com/home/sweet/home/again', - controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again') - - assert_equal("/home/sweet/home/alabama", controller.send(:home_path, :user => 'alabama', :host => 'unused')) - assert_equal("http://www.basecamphq.com/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'www.basecamphq.com')) - assert_equal("http://www.basecamphq.com/this/is/verbose", controller.send(:no_args_url, :host=>'www.basecamphq.com')) - end - 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::UrlWriter } - controller = kls.new - - assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again', - controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again') - end - ensure - ActionController::Base.relative_url_root = orig_relative_url_root - end - - def test_only_path - with_routing do |set| - set.draw do |map| - match 'home/sweet/home/:user', :to => 'home#index', :as => :home - match ':controller/:action/:id' - end - - # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlWriter } - controller = kls.new - assert controller.respond_to?(:home_url) - assert_equal '/brave/new/world', - controller.send(:url_for, :controller => 'brave', :action => 'new', :id => 'world', :only_path => true) - - assert_equal("/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'unused', :only_path => true)) - assert_equal("/home/sweet/home/alabama", controller.send(:home_path, 'alabama')) - end - end - - def test_one_parameter - assert_equal('/c/a?param=val', - W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val') - ) - end - - def test_two_parameters - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2') - params = extract_params(url) - assert_equal params[0], { :p1 => 'X1' }.to_query - assert_equal params[1], { :p2 => 'Y2' }.to_query - end - - def test_hash_parameter - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'}) - params = extract_params(url) - assert_equal params[0], { 'query[category]' => 'prof' }.to_query - assert_equal params[1], { 'query[name]' => 'Bob' }.to_query - end - - def test_array_parameter - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof']) - params = extract_params(url) - assert_equal params[0], { 'query[]' => 'Bob' }.to_query - assert_equal params[1], { 'query[]' => 'prof' }.to_query - end - - def test_hash_recursive_parameters - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'}) - params = extract_params(url) - assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query - assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query - assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query - end - - def test_hash_recursive_and_array_parameters - url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'}) - assert_match %r(^/c/a/101), url - params = extract_params(url) - assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query - assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query - assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query - assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query - end - - def test_path_generation_for_symbol_parameter_keys - assert_generates("/image", :controller=> :image) - end - - def test_named_routes_with_nil_keys - with_routing do |set| - set.draw do |map| - match 'posts.:format', :to => 'posts#index', :as => :posts - match '/', :to => 'posts#index', :as => :main - end - - # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlWriter } - kls.default_url_options[:host] = 'www.basecamphq.com' - - controller = kls.new - params = {:action => :index, :controller => :posts, :format => :xml} - assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params)) - params[:format] = nil - assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params)) - end - end - - def test_formatted_url_methods_are_deprecated - with_routing do |set| - set.draw do |map| - resources :posts - end - # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlWriter } - controller = kls.new - params = {:id => 1, :format => :xml} - assert_deprecated do - assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params)) - end - assert_deprecated do - assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml)) - end - end - end - - def test_multiple_includes_maintain_distinct_options - first_class = Class.new { include ActionController::UrlWriter } - second_class = Class.new { include ActionController::UrlWriter } - - first_host, second_host = 'firsthost.com', 'secondhost.com' - - first_class.default_url_options[:host] = first_host - second_class.default_url_options[:host] = second_host - - assert_equal first_class.default_url_options[:host], first_host - assert_equal second_class.default_url_options[:host], second_host - end - - private - def extract_params(url) - url.split('?', 2).last.split('&').sort - end -end diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb new file mode 100644 index 0000000000..f3ea5209f4 --- /dev/null +++ b/actionpack/test/dispatch/callbacks_test.rb @@ -0,0 +1,107 @@ +require 'abstract_unit' + +class DispatcherTest < Test::Unit::TestCase + class Foo + cattr_accessor :a, :b + end + + class DummyApp + def call(env) + [200, {}, 'response'] + end + end + + def setup + Foo.a, Foo.b = 0, 0 + ActionDispatch::Callbacks.reset_callbacks(:prepare) + ActionDispatch::Callbacks.reset_callbacks(:call) + end + + def test_prepare_callbacks_with_cache_classes + a = b = c = nil + ActionDispatch::Callbacks.to_prepare { |*args| a = b = c = 1 } + ActionDispatch::Callbacks.to_prepare { |*args| b = c = 2 } + ActionDispatch::Callbacks.to_prepare { |*args| c = 3 } + + # Ensure to_prepare callbacks are not run when defined + assert_nil a || b || c + + # Run callbacks + dispatch + + assert_equal 1, a + assert_equal 2, b + assert_equal 3, c + + # Make sure they are only run once + a = b = c = nil + dispatch + assert_nil a || b || c + end + + def test_prepare_callbacks_without_cache_classes + a = b = c = nil + ActionDispatch::Callbacks.to_prepare { |*args| a = b = c = 1 } + ActionDispatch::Callbacks.to_prepare { |*args| b = c = 2 } + ActionDispatch::Callbacks.to_prepare { |*args| c = 3 } + + # Ensure to_prepare callbacks are not run when defined + assert_nil a || b || c + + # Run callbacks + dispatch(false) + + assert_equal 1, a + assert_equal 2, b + assert_equal 3, c + + # Make sure they are run again + a = b = c = nil + dispatch(false) + assert_equal 1, a + assert_equal 2, b + assert_equal 3, c + end + + def test_to_prepare_with_identifier_replaces + ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a, Foo.b = 1, 1 } + ActionDispatch::Callbacks.to_prepare(:unique_id) { |*args| Foo.a = 2 } + + dispatch + assert_equal 2, Foo.a + assert_equal 0, Foo.b + end + + def test_before_and_after_callbacks + ActionDispatch::Callbacks.before { |*args| Foo.a += 1; Foo.b += 1 } + ActionDispatch::Callbacks.after { |*args| Foo.a += 1; Foo.b += 1 } + + dispatch + assert_equal 2, Foo.a + assert_equal 2, Foo.b + + dispatch + assert_equal 4, Foo.a + assert_equal 4, Foo.b + end + + def test_should_send_an_instrumentation_callback_for_async_processing + ActiveSupport::Notifications.expects(:instrument).with("action_dispatch.callback") + dispatch + end + + def test_should_send_an_instrumentation_callback_for_async_processing_even_on_failure + ActiveSupport::Notifications.notifier.expects(:publish) + assert_raise RuntimeError do + dispatch { |env| raise "OMG" } + end + end + + private + + def dispatch(cache_classes = true, &block) + @dispatcher ||= ActionDispatch::Callbacks.new(block || DummyApp.new, !cache_classes) + @dispatcher.call({'rack.input' => StringIO.new('')}) + end + +end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index b62df9a6b2..cb95ecea50 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -319,7 +319,7 @@ class RequestTest < ActiveSupport::TestCase end test "allow method hacking on post" do - [:get, :head, :options, :put, :post, :delete].each do |method| + [:get, :options, :put, :post, :delete].each do |method| request = stub_request "REQUEST_METHOD" => method.to_s.upcase assert_equal(method == :head ? :get : method, request.method) end @@ -341,7 +341,7 @@ class RequestTest < ActiveSupport::TestCase end test "head masquerading as get" do - request = stub_request 'REQUEST_METHOD' => 'HEAD' + request = stub_request 'REQUEST_METHOD' => 'GET', "rack.methodoverride.original_method" => "HEAD" assert_equal :get, request.method assert request.get? assert request.head? diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index c4b0b9cdbf..6dccabdb3f 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -22,6 +22,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest delete 'logout' => :destroy, :as => :logout end + resource :session do + get :create + end + match 'account/logout' => redirect("/logout"), :as => :logout_redirect match 'account/login', :to => redirect("/login") @@ -29,6 +33,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest match 'account/modulo/:name', :to => redirect("/%{name}s") match 'account/proc/:name', :to => redirect {|params| "/#{params[:name].pluralize}" } + match 'account/google' => redirect('http://www.google.com/') match 'openid/login', :via => [:get, :post], :to => "openid#login" @@ -47,6 +52,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get 'admin/accounts' => "queenbee#accounts" end + scope 'es' do + resources :projects, :path_names => { :edit => 'cambiar' }, :as => 'projeto' + end + resources :projects, :controller => :project do resources :involvements, :attachments @@ -56,7 +65,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resources :companies do resources :people - resource :avatar + resource :avatar, :controller => :avatar end resources :images do @@ -65,7 +74,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resources :people do nested do - namespace ":access_token" do + scope "/:access_token" do resource :avatar end end @@ -88,6 +97,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + resources :replies do + member do + put :answer, :to => :mark_as_answer + delete :answer, :to => :unmark_as_answer + end + end + + resources :posts, :only => [:index, :show] + match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp match 'people/:id/update', :to => 'people#update', :as => :update_person @@ -97,7 +115,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest match 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article namespace :account do + match 'description', :to => "account#description", :as => "description" resource :subscription, :credit, :credit_card + + namespace :admin do + resource :subscription + end + end + + namespace :forum do + resources :products, :as => '' do + resources :questions + end end controller :articles do @@ -112,6 +141,16 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resources :rooms end + scope '(:locale)', :locale => /en|pl/ do + resources :descriptions + end + + namespace :admin do + scope '(/:locale)', :locale => /en|pl/ do + resources :descriptions + end + end + match '/info' => 'projects#info', :as => 'info' root :to => 'projects#index' @@ -165,6 +204,31 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_session_singleton_resource + with_test_routes do + get '/session' + assert_equal 'sessions#create', @response.body + assert_equal '/session', session_path + + post '/session' + assert_equal 'sessions#create', @response.body + + put '/session' + assert_equal 'sessions#update', @response.body + + delete '/session' + assert_equal 'sessions#destroy', @response.body + + get '/session/new' + assert_equal 'sessions#new', @response.body + assert_equal '/session/new', new_session_path + + get '/session/edit' + assert_equal 'sessions#edit', @response.body + assert_equal '/session/edit', edit_session_path + end + end + def test_redirect_modulo with_test_routes do get '/account/modulo/name' @@ -193,20 +257,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - # TODO: rackmount is broken - # def test_admin - # with_test_routes do - # get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'} - # assert_equal 'queenbee#index', @response.body - # - # assert_raise(ActionController::RoutingError) { get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} } - # - # get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'} - # assert_equal 'queenbee#accounts', @response.body - # - # assert_raise(ActionController::RoutingError) { get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} } - # end - # end + def test_admin + with_test_routes do + get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'} + assert_equal 'queenbee#index', @response.body + + assert_raise(ActionController::RoutingError) { get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} } + + get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'} + assert_equal 'queenbee#accounts', @response.body + + assert_raise(ActionController::RoutingError) { get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} } + end + end def test_global with_test_routes do @@ -231,31 +294,34 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_projects with_test_routes do get '/projects' - assert_equal 'projects#index', @response.body + assert_equal 'project#index', @response.body assert_equal '/projects', projects_path + post '/projects' + assert_equal 'project#create', @response.body + get '/projects.xml' - assert_equal 'projects#index', @response.body + assert_equal 'project#index', @response.body assert_equal '/projects.xml', projects_path(:format => 'xml') get '/projects/new' - assert_equal 'projects#new', @response.body + assert_equal 'project#new', @response.body assert_equal '/projects/new', new_project_path get '/projects/new.xml' - assert_equal 'projects#new', @response.body + assert_equal 'project#new', @response.body assert_equal '/projects/new.xml', new_project_path(:format => 'xml') get '/projects/1' - assert_equal 'projects#show', @response.body + assert_equal 'project#show', @response.body assert_equal '/projects/1', project_path(:id => '1') get '/projects/1.xml' - assert_equal 'projects#show', @response.body + assert_equal 'project#show', @response.body assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml') get '/projects/1/edit' - assert_equal 'projects#edit', @response.body + assert_equal 'project#edit', @response.body assert_equal '/projects/1/edit', edit_project_path(:id => '1') end end @@ -317,7 +383,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1') get '/projects/1/companies/1/avatar' - assert_equal 'avatars#show', @response.body + assert_equal 'avatar#show', @response.body assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1') end end @@ -394,6 +460,42 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_replies + with_test_routes do + put '/replies/1/answer' + assert_equal 'replies#mark_as_answer', @response.body + + delete '/replies/1/answer' + assert_equal 'replies#unmark_as_answer', @response.body + end + end + + def test_posts + with_test_routes do + get '/posts' + assert_equal 'posts#index', @response.body + assert_equal '/posts', posts_path + + get '/posts/1' + assert_equal 'posts#show', @response.body + assert_equal '/posts/1', post_path(:id => 1) + + assert_raise(ActionController::RoutingError) { post '/posts' } + assert_raise(ActionController::RoutingError) { put '/posts/1' } + assert_raise(ActionController::RoutingError) { delete '/posts/1' } + end + end + + def test_path_names + with_test_routes do + get '/es/projeto' + assert_equal 'projects#index', @response.body + + get '/es/projeto/1/cambiar' + assert_equal 'projects#edit', @response.body + end + end + def test_sprockets with_test_routes do get '/sprockets.js' @@ -419,6 +521,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_forum_products + with_test_routes do + get '/forum' + assert_equal 'forum/products#index', @response.body + assert_equal '/forum', forum_products_path + + get '/forum/basecamp' + assert_equal 'forum/products#show', @response.body + assert_equal '/forum/basecamp', forum_product_path(:id => 'basecamp') + + get '/forum/basecamp/questions' + assert_equal 'forum/questions#index', @response.body + assert_equal '/forum/basecamp/questions', forum_product_questions_path(:product_id => 'basecamp') + + get '/forum/basecamp/questions/1' + assert_equal 'forum/questions#show', @response.body + assert_equal '/forum/basecamp/questions/1', forum_product_question_path(:product_id => 'basecamp', :id => 1) + end + end + def test_articles_perma with_test_routes do get '/articles/2009/08/18/rails-3' @@ -431,13 +553,24 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_account_namespace with_test_routes do get '/account/subscription' - assert_equal 'subscriptions#show', @response.body + assert_equal 'account/subscriptions#show', @response.body + assert_equal '/account/subscription', account_subscription_path get '/account/credit' - assert_equal 'credits#show', @response.body + assert_equal 'account/credits#show', @response.body + assert_equal '/account/credit', account_credit_path get '/account/credit_card' - assert_equal 'credit_cards#show', @response.body + assert_equal 'account/credit_cards#show', @response.body + assert_equal '/account/credit_card', account_credit_card_path + end + end + + def test_nested_namespace + with_test_routes do + get '/account/admin/subscription' + assert_equal 'account/admin/subscriptions#show', @response.body + assert_equal '/account/admin/subscription', account_admin_subscription_path end end @@ -472,7 +605,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'projects#index', @response.body end end - + def test_index with_test_routes do assert_equal '/info', info_path @@ -488,7 +621,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'projects#info', @response.body end end - + def test_convention_match_with_no_scope with_test_routes do assert_equal '/account/overview', account_overview_path @@ -497,6 +630,78 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_redirect_with_complete_url + with_test_routes do + get '/account/google' + assert_equal 301, @response.status + assert_equal 'http://www.google.com/', @response.headers['Location'] + assert_equal 'Moved Permanently', @response.body + end + end + + def test_redirect_with_port + previous_host, self.host = self.host, 'www.example.com:3000' + with_test_routes do + get '/account/login' + assert_equal 301, @response.status + assert_equal 'http://www.example.com:3000/login', @response.headers['Location'] + assert_equal 'Moved Permanently', @response.body + end + ensure + self.host = previous_host + end + + def test_normalize_namespaced_matches + with_test_routes do + assert_equal '/account/description', account_description_path + + get '/account/description' + assert_equal 'account#description', @response.body + end + end + + def test_optional_scoped_path + with_test_routes do + assert_equal '/en/descriptions', descriptions_path("en") + assert_equal '/descriptions', descriptions_path(nil) + assert_equal '/en/descriptions/1', description_path("en", 1) + assert_equal '/descriptions/1', description_path(nil, 1) + + get '/en/descriptions' + assert_equal 'descriptions#index', @response.body + + get '/descriptions' + assert_equal 'descriptions#index', @response.body + + get '/en/descriptions/1' + assert_equal 'descriptions#show', @response.body + + get '/descriptions/1' + assert_equal 'descriptions#show', @response.body + end + end + + def test_nested_optional_scoped_path + with_test_routes do + assert_equal '/admin/en/descriptions', admin_descriptions_path("en") + assert_equal '/admin/descriptions', admin_descriptions_path(nil) + assert_equal '/admin/en/descriptions/1', admin_description_path("en", 1) + assert_equal '/admin/descriptions/1', admin_description_path(nil, 1) + + get '/admin/en/descriptions' + assert_equal 'admin/descriptions#index', @response.body + + get '/admin/descriptions' + assert_equal 'admin/descriptions#index', @response.body + + get '/admin/en/descriptions/1' + assert_equal 'admin/descriptions#show', @response.body + + get '/admin/descriptions/1' + assert_equal 'admin/descriptions#show', @response.body + end + end + private def with_test_routes real_routes, temp_routes = ActionController::Routing::Routes, Routes diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index 9f6a93756c..951fb4a22e 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -104,4 +104,27 @@ class ShowExceptionsTest < ActionController::IntegrationTest assert_response 405 assert_match /ActionController::MethodNotAllowed/, body end + + test "publishes notifications" do + # Wait pending notifications to be published + ActiveSupport::Notifications.notifier.wait + + @app, event = ProductionApp, nil + self.remote_addr = '127.0.0.1' + + ActiveSupport::Notifications.subscribe('action_dispatch.show_exception') do |*args| + event = args + end + + get "/" + assert_response 500 + assert_match /puke/, body + + ActiveSupport::Notifications.notifier.wait + + assert_equal 'action_dispatch.show_exception', event.first + assert_kind_of Hash, event.last[:env] + assert_equal 'GET', event.last[:env]["REQUEST_METHOD"] + assert_kind_of RuntimeError, event.last[:exception] + end end diff --git a/actionpack/test/dispatch/string_coercion_test.rb b/actionpack/test/dispatch/string_coercion_test.rb deleted file mode 100644 index d79b17b932..0000000000 --- a/actionpack/test/dispatch/string_coercion_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'abstract_unit' - -class StringCoercionTest < ActiveSupport::TestCase - test "body responds to each" do - original_body = [] - body = ActionDispatch::StringCoercion::UglyBody.new(original_body) - - assert original_body.respond_to?(:each) - assert body.respond_to?(:each) - end - - test "body responds to to_path" do - original_body = [] - def original_body.to_path; end - body = ActionDispatch::StringCoercion::UglyBody.new(original_body) - - assert original_body.respond_to?(:to_path) - assert body.respond_to?(:to_path) - end - - test "body does not responds to to_path" do - original_body = [] - body = ActionDispatch::StringCoercion::UglyBody.new(original_body) - - assert !original_body.respond_to?(:to_path) - assert !body.respond_to?(:to_path) - end - - test "calls to_s on body parts" do - app = lambda { |env| - [200, {'Content-Type' => 'html'}, [1, 2, 3]] - } - app = ActionDispatch::StringCoercion.new(app) - parts = [] - status, headers, body = app.call({}) - body.each { |part| parts << part } - - assert_equal %w( 1 2 3 ), parts - end -end diff --git a/actionpack/test/fixtures/respond_with/using_defaults.js.rjs b/actionpack/test/fixtures/respond_with/using_defaults.js.rjs deleted file mode 100644 index 469fcd8e15..0000000000 --- a/actionpack/test/fixtures/respond_with/using_defaults.js.rjs +++ /dev/null @@ -1 +0,0 @@ -page[:body].visual_effect :highlight
\ No newline at end of file diff --git a/actionpack/test/fixtures/respond_with/using_defaults_with_type_list.js.rjs b/actionpack/test/fixtures/respond_with/using_defaults_with_type_list.js.rjs deleted file mode 100644 index 469fcd8e15..0000000000 --- a/actionpack/test/fixtures/respond_with/using_defaults_with_type_list.js.rjs +++ /dev/null @@ -1 +0,0 @@ -page[:body].visual_effect :highlight
\ No newline at end of file diff --git a/actionpack/test/fixtures/respond_with/using_defaults_with_type_list.xml.builder b/actionpack/test/fixtures/respond_with/using_defaults_with_type_list.xml.builder deleted file mode 100644 index 598d62e2fc..0000000000 --- a/actionpack/test/fixtures/respond_with/using_defaults_with_type_list.xml.builder +++ /dev/null @@ -1 +0,0 @@ -xml.p "Hello world!"
\ No newline at end of file diff --git a/actionpack/test/fixtures/respond_with/using_defaults.html.erb b/actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb index 6769dd60bd..6769dd60bd 100644 --- a/actionpack/test/fixtures/respond_with/using_defaults.html.erb +++ b/actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index b0e5d7a94c..7346cb22bc 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -69,7 +69,7 @@ class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost attr_accessor :author def author_attributes=(attributes); end - attr_accessor :comments + attr_accessor :comments, :comment_ids def comments_attributes=(attributes); end attr_accessor :tags diff --git a/actionpack/test/template/active_model_helper_i18n_test.rb b/actionpack/test/template/active_model_helper_i18n_test.rb index 2465444fc5..4eb2f262bd 100644 --- a/actionpack/test/template/active_model_helper_i18n_test.rb +++ b/actionpack/test/template/active_model_helper_i18n_test.rb @@ -16,27 +16,27 @@ class ActiveModelHelperI18nTest < Test::Unit::TestCase stubs(:content_tag).returns 'content_tag' - I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved" - I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:' + I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved" + I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:' end def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message - I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').never + I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').never error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en') end def test_error_messages_for_given_no_header_option_it_translates_header_message - I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns 'header message' + I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:errors, :template], :count => 1, :model => '').returns 'header message' error_messages_for(:object => @object, :locale => 'en') end def test_error_messages_for_given_a_message_option_it_does_not_translate_message - I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).never + I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).never error_messages_for(:object => @object, :message => 'message', :locale => 'en') end def test_error_messages_for_given_no_message_option_it_translates_message - I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:' + I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:errors, :template]).returns 'There were problems with the following fields:' error_messages_for(:object => @object, :locale => 'en') end end diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 9fb2080f77..fb51b67185 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -2475,6 +2475,28 @@ class DateHelperTest < ActionView::TestCase }, options) end + def test_select_html_safety + assert select_day(16).html_safe? + assert select_month(8).html_safe? + assert select_year(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe? + assert select_minute(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe? + assert select_second(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe? + + assert select_minute(8, :use_hidden => true).html_safe? + assert select_month(8, :prompt => 'Choose month').html_safe? + + assert select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector').html_safe? + assert select_date(Time.mktime(2003, 8, 16), :date_separator => " / ", :start_year => 2003, :end_year => 2005, :prefix => "date[first]").html_safe? + end + + def test_object_select_html_safety + @post = Post.new + @post.written_on = Date.new(2004, 6, 15) + + assert date_select("post", "written_on", :default => Time.local(2006, 9, 19, 15, 16, 35), :include_blank => true).html_safe? + assert time_select("post", "written_on", :ignore_date => true).html_safe? + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index b1e9fe99a2..0c5c5d17ee 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -16,8 +16,8 @@ class FormHelperTest < ActionView::TestCase } } }, - :views => { - :labels => { + :helpers => { + :label => { :post => { :body => "Write entire text here" } @@ -25,6 +25,20 @@ class FormHelperTest < ActionView::TestCase } } + # Create "submit" locale for testing I18n submit helpers + I18n.backend.store_translations 'submit', { + :helpers => { + :submit => { + :create => 'Create {{model}}', + :update => 'Confirm {{model}} changes', + :submit => 'Save changes', + :another_post => { + :update => 'Update your {{model}}' + } + } + } + } + @post = Post.new @comment = Comment.new def @post.errors() @@ -190,6 +204,11 @@ class FormHelperTest < ActionView::TestCase hidden_field("post", "title", :value => "Something Else") end + def test_text_field_with_custom_type + assert_dom_equal '<input id="user_email" size="30" name="user[email]" type="email" />', + text_field("user", "email", :type => "email") + end + def test_check_box assert_dom_equal( '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />', @@ -234,6 +253,19 @@ class FormHelperTest < ActionView::TestCase ) end + def test_check_box_with_multiple_behavior + @post.comment_ids = [2,3] + assert_dom_equal( + '<input name="post[comment_ids][]" type="hidden" value="0" /><input id="post_comment_ids_1" name="post[comment_ids][]" type="checkbox" value="1" />', + check_box("post", "comment_ids", { :multiple => true }, 1) + ) + assert_dom_equal( + '<input name="post[comment_ids][]" type="hidden" value="0" /><input checked="checked" id="post_comment_ids_3" name="post[comment_ids][]" type="checkbox" value="3" />', + check_box("post", "comment_ids", { :multiple => true }, 3) + ) + end + + def test_checkbox_disabled_still_submits_checked_value assert_dom_equal( '<input name="post[secret]" type="hidden" value="1" /><input checked="checked" disabled="disabled" id="post_secret" name="post[secret]" type="checkbox" value="1" />', @@ -475,6 +507,67 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_submit_with_object_as_new_record_and_locale_strings + old_locale, I18n.locale = I18n.locale, :submit + + def @post.new_record?() true; end + form_for(:post, @post) do |f| + concat f.submit + end + + expected = "<form action='http://www.example.com' method='post'>" + + "<input name='commit' id='post_submit' type='submit' value='Create Post' />" + + "</form>" + assert_dom_equal expected, output_buffer + ensure + I18n.locale = old_locale + end + + def test_submit_with_object_as_existing_record_and_locale_strings + old_locale, I18n.locale = I18n.locale, :submit + + form_for(:post, @post) do |f| + concat f.submit + end + + expected = "<form action='http://www.example.com' method='post'>" + + "<input name='commit' id='post_submit' type='submit' value='Confirm Post changes' />" + + "</form>" + assert_dom_equal expected, output_buffer + ensure + I18n.locale = old_locale + end + + def test_submit_without_object_and_locale_strings + old_locale, I18n.locale = I18n.locale, :submit + + form_for(:post) do |f| + concat f.submit :class => "extra" + end + + expected = "<form action='http://www.example.com' method='post'>" + + "<input name='commit' class='extra' id='post_submit' type='submit' value='Save changes' />" + + "</form>" + assert_dom_equal expected, output_buffer + ensure + I18n.locale = old_locale + end + + def test_submit_with_object_and_nested_lookup + old_locale, I18n.locale = I18n.locale, :submit + + form_for(:another_post, @post) do |f| + concat f.submit + end + + expected = "<form action='http://www.example.com' method='post'>" + + "<input name='commit' id='another_post_submit' type='submit' value='Update your Post' />" + + "</form>" + assert_dom_equal expected, output_buffer + ensure + I18n.locale = old_locale + end + def test_nested_fields_for form_for(:post, @post) do |f| f.fields_for(:comment, @post) do |c| @@ -659,7 +752,7 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - + def test_nested_fields_for_with_existing_records_on_a_nested_attributes_one_to_one_association_with_explicit_hidden_field_placement @post.author = Author.new(321) @@ -670,7 +763,7 @@ class FormHelperTest < ActionView::TestCase concat af.text_field(:name) end end - + expected = '<form action="http://www.example.com" method="post">' + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + @@ -715,7 +808,7 @@ class FormHelperTest < ActionView::TestCase end end end - + expected = '<form action="http://www.example.com" method="post">' + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + diff --git a/actionpack/test/template/form_options_helper_i18n_test.rb b/actionpack/test/template/form_options_helper_i18n_test.rb index 91e370efa7..4972ea6511 100644 --- a/actionpack/test/template/form_options_helper_i18n_test.rb +++ b/actionpack/test/template/form_options_helper_i18n_test.rb @@ -6,7 +6,7 @@ class FormOptionsHelperI18nTests < ActionView::TestCase def setup @prompt_message = 'Select!' I18n.backend.send(:init_translations) - I18n.backend.store_translations :en, :support => { :select => { :prompt => @prompt_message } } + I18n.backend.store_translations :en, :helpers => { :select => { :prompt => @prompt_message } } end def teardown @@ -14,7 +14,7 @@ class FormOptionsHelperI18nTests < ActionView::TestCase end def test_select_with_prompt_true_translates_prompt_message - I18n.expects(:translate).with('support.select.prompt', { :default => 'Please select' }) + I18n.expects(:translate).with('helpers.select.prompt', { :default => 'Please select' }) select('post', 'category', [], :prompt => true) end @@ -24,4 +24,4 @@ class FormOptionsHelperI18nTests < ActionView::TestCase select('post', 'category', [], :prompt => true) ) end -end
\ No newline at end of file +end diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index f0f686f6e2..03caad3d46 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -13,8 +13,13 @@ class JavaScriptHelperTest < ActionView::TestCase def setup super + ActiveSupport.escape_html_entities_in_json = true @template = self end + + def teardown + ActiveSupport.escape_html_entities_in_json = false + end def _evaluate_assigns_and_ivars() end diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb index 313a769088..9225153798 100644 --- a/actionpack/test/template/prototype_helper_test.rb +++ b/actionpack/test/template/prototype_helper_test.rb @@ -317,6 +317,11 @@ class JavaScriptGeneratorTest < PrototypeHelperBaseTest def setup super @generator = create_generator + ActiveSupport.escape_html_entities_in_json = true + end + + def teardown + ActiveSupport.escape_html_entities_in_json = false end def _evaluate_assigns_and_ivars() end diff --git a/actionpack/test/template/subscriber_test.rb b/actionpack/test/template/subscriber_test.rb new file mode 100644 index 0000000000..af0b3102cf --- /dev/null +++ b/actionpack/test/template/subscriber_test.rb @@ -0,0 +1,102 @@ +require "abstract_unit" +require "rails/subscriber/test_helper" +require "action_view/railties/subscriber" +require "controller/fake_models" + +module ActionViewSubscriberTest + + def setup + @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 + end + + def teardown + super + Rails::Subscriber.subscribers.clear + ActionController::Base.logger = @old_logger + end + + def set_logger(logger) + ActionController::Base.logger = logger + end + + def test_render_file_template + @view.render(:file => "test/hello_world.erb") + wait + + assert_equal 1, @logger.logged(:info).size + assert_match /Rendered test\/hello_world\.erb/, @logger.logged(:info).last + end + + def test_render_text_template + @view.render(:text => "OMG") + wait + + assert_equal 1, @logger.logged(:info).size + assert_match /Rendered text template/, @logger.logged(:info).last + end + + def test_render_inline_template + @view.render(:inline => "<%= 'OMG' %>") + wait + + assert_equal 1, @logger.logged(:info).size + assert_match /Rendered inline template/, @logger.logged(:info).last + end + + def test_render_partial_template + @view.render(:partial => "test/customer") + wait + + assert_equal 1, @logger.logged(:info).size + assert_match /Rendered test\/_customer.erb/, @logger.logged(:info).last + end + + def test_render_partial_with_implicit_path + @view.stubs(:controller_path).returns("test") + @view.render(Customer.new("david"), :greeting => "hi") + wait + + assert_equal 1, @logger.logged(:info).size + assert_match /Rendered customers\/_customer\.html\.erb/, @logger.logged(:info).last + end + + def test_render_collection_template + @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), Customer.new("mary") ]) + wait + + assert_equal 1, @logger.logged(:info).size + assert_match /Rendered test\/_customer.erb/, @logger.logged(:info).last + end + + def test_render_collection_with_implicit_path + @view.stubs(:controller_path).returns("test") + @view.render([ Customer.new("david"), Customer.new("mary") ], :greeting => "hi") + wait + + assert_equal 1, @logger.logged(:info).size + assert_match /Rendered customers\/_customer\.html\.erb/, @logger.logged(:info).last + end + + def test_render_collection_template_without_path + @view.stubs(:controller_path).returns("test") + @view.render([ GoodCustomer.new("david"), Customer.new("mary") ], :greeting => "hi") + wait + + assert_equal 1, @logger.logged(:info).size + assert_match /Rendered collection/, @logger.logged(:info).last + end + + class SyncSubscriberTest < ActiveSupport::TestCase + include Rails::Subscriber::SyncTestHelper + include ActionViewSubscriberTest + end + + class AsyncSubscriberTest < ActiveSupport::TestCase + include Rails::Subscriber::AsyncTestHelper + include ActionViewSubscriberTest + end +end
\ No newline at end of file diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 08143ba680..088c07b8bb 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -360,6 +360,20 @@ class TextHelperTest < ActionView::TestCase assert_equal %(<p>#{link10_result} Link</p>), auto_link("<p>#{link10_raw} Link</p>") end + def test_auto_link_other_protocols + silence_warnings do + begin + old_re_value = ActionView::Helpers::TextHelper::AUTO_LINK_RE + ActionView::Helpers::TextHelper.const_set :AUTO_LINK_RE, %r{(ftp://)[^\s<]+} + link_raw = 'ftp://example.com/file.txt' + link_result = generate_result(link_raw) + assert_equal %(Download #{link_result}), auto_link("Download #{link_raw}") + ensure + ActionView::Helpers::TextHelper.const_set :AUTO_LINK_RE, old_re_value + end + end + end + def test_auto_link_already_linked linked1 = generate_result('Ruby On Rails', 'http://www.rubyonrails.com') linked2 = generate_result('www.rubyonrails.com', 'http://www.rubyonrails.com') |