diff options
Diffstat (limited to 'actionpack')
145 files changed, 6074 insertions, 1266 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index a00f63dcd4..32aba2091a 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,22 @@ *Rails 3.0.0 [Release Candidate] (unreleased)* +* link_to, button_to, and tag/tag_options now rely on html_escape instead of escape_once. [fxn] + +* url_for returns always unescaped strings, and the :escape option is gone. [fxn] + +* Added accept-charset parameter and _snowman hidden field to force the contents + of Rails POSTed forms to be in UTF-8 [Yehuda Katz] + +* Upgrade to Rack 1.2.1 [Jeremy Kemper] + +* Allow :path to be given to match/get/post/put/delete instead of :path_names in the new router [Carlos Antônio da Silva] + +* Added resources_path_names to the new router DSL [José Valim] + +* Allow options to be given to the namespace method in the new router [Carlos Antônio da Silva] + +* Deprecate :name_prefix in the new router DSL [José Valim] + * Add shallow routes back to the new router [Diego Carrion, Andrew White] resources :posts do @@ -14,7 +31,6 @@ * Removed textilize, textilize_without_paragraph and markdown helpers. [Santiago Pastorino] - *Rails 3.0.0 [beta 4] (June 8th, 2010)* * Remove middleware laziness [José Valim] diff --git a/actionpack/README b/actionpack/README index 8bdcb9120a..1a59f728cc 100644 --- a/actionpack/README +++ b/actionpack/README @@ -183,20 +183,20 @@ A short rundown of the major features: * Automated benchmarking and integrated logging - Processing WeblogController#index (for 127.0.0.1 at Fri May 28 00:41:55) - Parameters: {"action"=>"index", "controller"=>"weblog"} - Rendering weblog/index (200 OK) - Completed in 0.029281 (34 reqs/sec) + Started GET "/weblog" for 127.0.0.1 at Fri May 28 00:41:55 + Processing by WeblogController#index as HTML + Rendered weblog/index.html.erb within layouts/application (25.7ms) + Completed 200 OK in 29.3ms If Active Record is used as the model, you'll have the database debugging as well: - Processing PostsController#create (for 127.0.0.1 at Sat Jun 19 14:04:23) - Params: {"controller"=>"posts", "action"=>"create", - "post"=>{"title"=>"this is good"} } - SQL (0.000627) INSERT INTO posts (title) VALUES('this is good') + Started POST "/posts" for 127.0.0.1 at Sat Jun 19 14:04:23 + Processing by PostsController#create as HTML + Parameters: {"post"=>{"title"=>"this is good"}} + SQL (0.6ms) INSERT INTO posts (title) VALUES('this is good') Redirected to http://example.com/posts/5 - Completed in 0.221764 (4 reqs/sec) | DB: 0.059920 (27%) + Completed 302 Found in 221ms (Views: 215ms | ActiveRecord: 0.6ms) You specify a logger through a class method, such as: diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 8e95315252..b4311599a9 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -23,9 +23,9 @@ Gem::Specification.new do |s| s.add_dependency('activemodel', version) s.add_dependency('builder', '~> 2.1.2') s.add_dependency('i18n', '~> 0.4.1') - s.add_dependency('rack', '~> 1.1.0') + s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.4') - s.add_dependency('rack-mount', '~> 0.6.4') + #s.add_dependency('rack-mount', '~> 0.6.6') s.add_dependency('tzinfo', '~> 0.3.16') s.add_dependency('erubis', '~> 2.6.5') end diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index e1027840ef..8a8337858b 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -1,4 +1,5 @@ require 'active_support/configurable' +require 'active_support/descendants_tracker' require 'active_support/core_ext/module/anonymous' module AbstractController @@ -10,6 +11,7 @@ module AbstractController attr_internal :action_name include ActiveSupport::Configurable + extend ActiveSupport::DescendantsTracker class << self attr_reader :abstract @@ -21,17 +23,6 @@ module AbstractController @abstract = true end - def inherited(klass) - ::AbstractController::Base.descendants << klass.to_s - super - end - - # A list of all descendents of AbstractController::Base. This is - # useful for initializers which need to add behavior to all controllers. - def descendants - @descendants ||= [] - end - # A list of all internal methods for a controller. This finds the first # abstract superclass of a controller, and gets a list of all public # instance methods on that abstract class. Public instance methods of diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 8611d0d3c3..a70ba0d2e3 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,3 +1,5 @@ +require "action_controller/log_subscriber" + module ActionController class Base < Metal abstract! diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb new file mode 100644 index 0000000000..ece270b3ce --- /dev/null +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -0,0 +1,56 @@ +require 'active_support/core_ext/object/blank' + +module ActionController + class LogSubscriber < ActiveSupport::LogSubscriber + INTERNAL_PARAMS = %w(controller action format _method only_path) + + def start_processing(event) + payload = event.payload + params = payload[:params].except(*INTERNAL_PARAMS) + + info " Processing by #{payload[:controller]}##{payload[:action]} as #{payload[:formats].first.to_s.upcase}" + info " Parameters: #{params.inspect}" unless params.empty? + end + + def process_action(event) + payload = event.payload + additions = ActionController::Base.log_process_action(payload) + + message = "Completed #{payload[:status]} #{Rack::Utils::HTTP_STATUS_CODES[payload[:status]]} in %.0fms" % event.duration + message << " (#{additions.join(" | ")})" unless additions.blank? + + info(message) + end + + def send_file(event) + message = "Sent file %s" + 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 + +ActionController::LogSubscriber.attach_to :action_controller
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 775a5002e2..2281c500c5 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -52,8 +52,7 @@ module ActionController class Metal < AbstractController::Base abstract! - # :api: public - attr_internal :params, :env + attr_internal :env # Returns the last part of the controller's name, underscored, without the ending # "Controller". For instance, MyApp::MyPostsController would return "my_posts" for @@ -62,7 +61,7 @@ module ActionController # ==== Returns # String def self.controller_name - @controller_name ||= controller_path.split("/").last + @controller_name ||= self.name.demodulize.sub(/Controller$/, '').underscore end # Delegates to the class' #controller_name @@ -85,6 +84,14 @@ module ActionController super end + def params + @_params ||= request.parameters + end + + def params=(val) + @_params = val + end + # Basic implementations for content_type=, location=, and headers are # provided to reduce the dependency on the RackDelegation module # in Renderer and Redirector. diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index b6d4fb1284..89201fb5ee 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -58,12 +58,12 @@ module ActionController module ClassMethods def helpers_dir - ActiveSupport::Deprecation.warn "helpers_dir is deprecated, use helpers_path instead" + ActiveSupport::Deprecation.warn "helpers_dir is deprecated, use helpers_path instead", caller self.helpers_path end def helpers_dir=(value) - ActiveSupport::Deprecation.warn "helpers_dir= is deprecated, use helpers_path= instead" + ActiveSupport::Deprecation.warn "helpers_dir= is deprecated, use helpers_path= instead", caller self.helpers_path = Array(value) end diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index 508ea6e2b7..544b4989c7 100644 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -14,10 +14,6 @@ module ActionController super(action, request) end - def params - @_params ||= @_request.parameters - end - def response_body=(body) response.body = body if response super diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 8c25b147ef..b632e7aab6 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -55,7 +55,7 @@ module ActionController #:nodoc: config_accessor :request_forgery_protection_token self.request_forgery_protection_token ||= :authenticity_token - # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode. + # Controls whether request forgery protection is turned on or not. Turned off by default only in test mode. config_accessor :allow_forgery_protection self.allow_forgery_protection = true if allow_forgery_protection.nil? diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 22bdcd0f3c..cb644dfd16 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -89,9 +89,7 @@ module ActionController #:nodoc: def initialize(controller, resources, options={}) @controller = controller - @request = controller.request - @format = controller.formats.first - @resource = resources.is_a?(Array) ? resources.last : resources + @resource = resources.last @resources = resources @options = options @action = options.delete(:action) @@ -101,6 +99,14 @@ module ActionController #:nodoc: delegate :head, :render, :redirect_to, :to => :controller delegate :get?, :post?, :put?, :delete?, :to => :request + def request + @request ||= @controller.request + end + + def format + @format ||= @controller.formats.first + end + # Undefine :to_json and :to_yaml since it's defined on Object undef_method(:to_json) if method_defined?(:to_json) undef_method(:to_yaml) if method_defined?(:to_yaml) @@ -147,7 +153,7 @@ module ActionController #:nodoc: elsif has_errors? && default_action render :action => default_action else - redirect_to resource_location + redirect_to navigation_location end end @@ -160,7 +166,7 @@ module ActionController #:nodoc: elsif has_errors? display resource.errors, :status => :unprocessable_entity elsif post? - display resource, :status => :created, :location => resource_location + display resource, :status => :created, :location => api_location else head :ok end @@ -178,6 +184,8 @@ module ActionController #:nodoc: def resource_location options[:location] || resources end + alias :navigation_location :resource_location + alias :api_location :resource_location # If a given response block was given, use it, otherwise call render on # controller. diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index 7f2eb4306b..bee50a7a3b 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -11,7 +11,7 @@ module ActionController # polymorphic_url([:admin, @article, @comment]) # # results in: - # + # # admin_article_comment_url(@article, @comment) # # == Usage within the framework @@ -166,6 +166,7 @@ module ActionController route << RecordIdentifier.__send__("plural_class_name", record) route = route.singularize if inflection == :singular route << "_" + route << "index_" if RecordIdentifier.uncountable?(record) && inflection == :plural end action_prefix(options) + route + routing_type(options).to_s diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 0e3cdffadc..86395c4d93 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -6,7 +6,6 @@ require "active_support/core_ext/class/subclasses" require "active_support/deprecation/proxy_wrappers" require "active_support/deprecation" -require "action_controller/railties/log_subscriber" require "action_controller/railties/url_helpers" module ActionController @@ -35,8 +34,6 @@ module ActionController end end - log_subscriber :action_controller, ActionController::Railties::LogSubscriber.new - initializer "action_controller.set_configs" do |app| paths = app.config.paths ac = app.config.action_controller diff --git a/actionpack/lib/action_controller/railties/log_subscriber.rb b/actionpack/lib/action_controller/railties/log_subscriber.rb deleted file mode 100644 index 00ac3bdf67..0000000000 --- a/actionpack/lib/action_controller/railties/log_subscriber.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'active_support/core_ext/object/blank' - -module ActionController - module Railties - class LogSubscriber < Rails::LogSubscriber - INTERNAL_PARAMS = %w(controller action format _method only_path) - - def start_processing(event) - payload = event.payload - params = payload[:params].except(*INTERNAL_PARAMS) - - info " Processing by #{payload[:controller]}##{payload[:action]} as #{payload[:formats].first.to_s.upcase}" - info " Parameters: #{params.inspect}" unless params.empty? - end - - def process_action(event) - payload = event.payload - additions = ActionController::Base.log_process_action(payload) - - message = "Completed #{payload[:status]} #{Rack::Utils::HTTP_STATUS_CODES[payload[:status]]} in %.0fms" % event.duration - message << " (#{additions.join(" | ")})" unless additions.blank? - - info(message) - end - - def send_file(event) - message = "Sent file %s" - 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/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 907c369218..d20c3b64c5 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -1,7 +1,7 @@ require 'active_support/core_ext/module' module ActionController - # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or + # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or # Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate # the view actions to a higher logical level. Example: # @@ -28,7 +28,7 @@ module ActionController # end # # As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know - # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming + # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming # convention and allows you to write less code if you follow it. module RecordIdentifier extend self @@ -59,7 +59,7 @@ module ActionController # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id: # # dom_id(Post.find(45), :edit) # => "edit_post_45" - def dom_id(record, prefix = nil) + def dom_id(record, prefix = nil) if record_id = record_key_for_dom_id(record) "#{dom_class(record, prefix)}#{JOIN}#{record_id}" else @@ -102,6 +102,14 @@ module ActionController model_name_from_record_or_class(record_or_class).singular end + # Identifies whether the class name of a record or class is uncountable. Examples: + # + # uncountable?(Sheep) # => true + # uncountable?(Post) => false + def uncountable?(record_or_class) + plural_class_name(record_or_class) == singular_class_name(record_or_class) + end + private def model_name_from_record_or_class(record_or_class) (record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 21281b606e..650eb16ac0 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -40,7 +40,7 @@ module ActionController ActiveSupport::Notifications.unsubscribe("!render_template.action_view") end - # Asserts that the request was rendered with the appropriate template file or partials + # Asserts that the request was rendered with the appropriate template file or partials. # # ==== Examples # @@ -53,6 +53,12 @@ module ActionController # # assert that no partials were rendered # assert_template :partial => false # + # In a view test case, you can also assert that specific locals are passed + # to partials: + # + # # assert that the "_customer" partial was rendered with a specific object + # assert_template :partial => '_customer', :locals => { :customer => @customer } + # def assert_template(options = {}, message = nil) validate_request! @@ -72,9 +78,13 @@ module ActionController end when Hash if expected_partial = options[:partial] - if expected_count = options[:count] + if expected_locals = options[:locals] + actual_locals = @locals[expected_partial.to_s.sub(/^_/,'')] + expected_locals.each_pair do |k,v| + assert_equal(v, actual_locals[k]) + end + elsif expected_count = options[:count] actual_count = @partials[expected_partial] - # actual_count = found.nil? ? 0 : found[1] msg = build_message(message, "expecting ? to be rendered ? time(s) but rendered ? time(s)", expected_partial, expected_count, actual_count) @@ -183,12 +193,14 @@ module ActionController replace(session.stringify_keys) @loaded = true end + + def exists?; true; end end # Superclass for ActionController functional tests. Functional tests allow you to # test a single controller action per test method. This should not be confused with # integration tests (see ActionController::IntegrationTest), which are more like - # "stories" that can involve multiple controllers and mutliple actions (i.e. multiple + # "stories" that can involve multiple controllers and multiple actions (i.e. multiple # different HTTP requests). # # == Basic example @@ -442,7 +454,7 @@ module ActionController end # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline - # (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular + # (skipping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular # rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else # than 0.0.0.0. # diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb index 6c0331636c..a874519978 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb @@ -177,6 +177,7 @@ module HTML #:nodoc: case text when "\\" then value << text + break if scanner.eos? value << scanner.getch when delim break diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb index 064ff3724d..240dc1890f 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb @@ -96,6 +96,7 @@ module HTML #:nodoc: while match = @scanner.scan_until(/[\\#{delim}]/) text << match break if @scanner.matched == delim + break if @scanner.eos? text << @scanner.getch # skip the escaped character end end diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 9b9e81440b..e9fdf75cc8 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -89,7 +89,7 @@ module ActionDispatch if etag? || last_modified? || !@cache_control.empty? set_conditional_cache_control! elsif nonempty_ok_response? - self.etag = @body + self.etag = body if request && request.etag_matches?(etag) self.status = 304 @@ -137,4 +137,4 @@ module ActionDispatch end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index d6a805bf3b..c6fc582851 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -60,7 +60,7 @@ module Mime def initialize(order, name, q=nil) @order = order @name = name.strip - q ||= 0.0 if @name == Mime::ALL # default wilcard match to end of list + q ||= 0.0 if @name == Mime::ALL # default wildcard match to end of list @q = ((q || 1.0).to_f * 100).to_i end diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index bc43414e75..add8cab2ab 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -6,7 +6,11 @@ module ActionDispatch 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 + @env["action_dispatch.request.parameters"] ||= begin + params = request_parameters.merge(query_parameters) + params.merge!(path_parameters) + encode_params(params).with_indifferent_access + end end alias :params :parameters @@ -32,7 +36,32 @@ module ActionDispatch end private - # Convert nested Hashs to HashWithIndifferentAccess + + # TODO: Validate that the characters are UTF-8. If they aren't, + # you'll get a weird error down the road, but our form handling + # should really prevent that from happening + def encode_params(params) + return params unless "ruby".encoding_aware? + + if params.is_a?(String) + return params.force_encoding("UTF-8").encode! + elsif !params.is_a?(Hash) + return params + end + + params.each do |k, v| + case v + when Hash + encode_params(v) + when Array + v.map! {|el| encode_params(el) } + else + encode_params(v) + end + end + end + + # Convert nested Hash to HashWithIndifferentAccess def normalize_parameters(value) case value when Hash diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 98f4f5ae18..fd23b1df79 100755..100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -194,9 +194,12 @@ module ActionDispatch @env['rack.input'] end + # TODO This should be broken apart into AD::Request::Session and probably + # be included by the session middleware. def reset_session - self.session_options.delete(:id) + session.destroy if session self.session = {} + @env['action_dispatch.request.flash_hash'] = nil end def session=(session) #:nodoc: diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 81d2517304..8ee4b81cdd 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -31,8 +31,8 @@ module ActionDispatch end module Upload - # Convert nested Hashs to HashWithIndifferentAccess and replace - # file upload hashs with UploadedFile objects + # Convert nested Hash to HashWithIndifferentAccess and replace + # file upload hash with UploadedFile objects def normalize_parameters(value) if Hash === value && value.has_key?(:tempfile) upload = value[:tempfile] diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index d07841218a..e4ae480bfb 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -8,7 +8,7 @@ module ActionDispatch class Callbacks include ActiveSupport::Callbacks - define_callbacks :call, :terminator => "result == false", :rescuable => true + define_callbacks :call, :rescuable => true define_callbacks :prepare, :scope => :name # Add a preparation callback. Preparation callbacks are run before every @@ -37,12 +37,12 @@ module ActionDispatch def initialize(app, prepare_each_request = false) @app, @prepare_each_request = app, prepare_each_request - run_callbacks(:prepare) + _run_prepare_callbacks end def call(env) - run_callbacks(:call) do - run_callbacks(:prepare) if @prepare_each_request + _run_call_callbacks do + _run_prepare_callbacks if @prepare_each_request @app.call(env) end end diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 18771fe782..bfa30cf5af 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -4,7 +4,7 @@ module ActionDispatch # read a notice you put there or <tt>flash["notice"] = "hello"</tt> # to put a new one. def flash - session['flash'] ||= Flash::FlashHash.new + @env['action_dispatch.request.flash_hash'] ||= (session["flash"] || Flash::FlashHash.new) end end @@ -176,7 +176,14 @@ module ActionDispatch @app.call(env) ensure - if (session = env['rack.session']) && session.key?('flash') && session['flash'].empty? + session = env['rack.session'] || {} + flash_hash = env['action_dispatch.request.flash_hash'] + + if flash_hash && (!flash_hash.empty? || session.key?('flash')) + session["flash"] = flash_hash + end + + if session.key?('flash') && session['flash'].empty? session.delete('flash') 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 3e8d64b0c6..08bc80dbc2 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -12,6 +12,35 @@ module ActionDispatch ENV_SESSION_KEY = 'rack.session'.freeze ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze + # thin wrapper around Hash that allows us to lazily + # load session id into session_options + class OptionsHash < Hash + def initialize(by, env, default_options) + @by = by + @env = env + @session_id_loaded = false + merge!(default_options) + end + + def [](key) + if key == :id + load_session_id! unless super(:id) || has_session_id? + end + super(key) + end + + private + + def has_session_id? + @session_id_loaded + end + + def load_session_id! + self[:id] = @by.send(:extract_session_id, @env) + @session_id_loaded = true + end + end + class SessionHash < Hash def initialize(by, env) super() @@ -21,66 +50,75 @@ module ActionDispatch end def [](key) - load! unless @loaded + load_for_read! + super(key.to_s) + end + + def has_key?(key) + load_for_read! super(key.to_s) end def []=(key, value) - load! unless @loaded + load_for_write! super(key.to_s, value) end def to_hash + load_for_read! h = {}.replace(self) h.delete_if { |k,v| v.nil? } h end def update(hash) - load! unless @loaded + load_for_write! super(hash.stringify_keys) end def delete(key) - load! unless @loaded + load_for_write! super(key.to_s) end def inspect - load! unless @loaded + load_for_read! super end + def exists? + return @exists if instance_variable_defined?(:@exists) + @exists = @by.send(:exists?, @env) + end + def loaded? @loaded end + def destroy + clear + @by.send(:destroy, @env) if @by + @env[ENV_SESSION_OPTIONS_KEY][:id] = nil if @env && @env[ENV_SESSION_OPTIONS_KEY] + @loaded = false + end + private - def load! - stale_session_check! do - id, session = @by.send(:load_session, @env) - (@env[ENV_SESSION_OPTIONS_KEY] ||= {})[:id] = id - replace(session.stringify_keys) - @loaded = true - end + + def load_for_read! + load! if !loaded? && exists? end - def stale_session_check! - yield - rescue ArgumentError => argument_error - if argument_error.message =~ %r{undefined class/module ([\w:]*\w)} - begin - # Note that the regexp does not allow $1 to end with a ':' - $1.constantize - rescue LoadError, NameError => const_error - 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 - else - raise - end + def load_for_write! + load! unless loaded? + end + + def load! + id, session = @by.send(:load_session, @env) + @env[ENV_SESSION_OPTIONS_KEY][:id] = id + replace(session.stringify_keys) + @loaded = true end + end DEFAULT_OPTIONS = { @@ -108,8 +146,8 @@ module ActionDispatch session_data = env[ENV_SESSION_KEY] options = env[ENV_SESSION_OPTIONS_KEY] - if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after] - session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?) + if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after] + session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded? sid = options[:id] || generate_sid session_data = session_data.to_hash @@ -133,7 +171,7 @@ module ActionDispatch def prepare!(env) env[ENV_SESSION_KEY] = SessionHash.new(self, env) - env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup + env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options) end def generate_sid @@ -141,15 +179,30 @@ module ActionDispatch end def set_cookie(request, options) - request.cookie_jar[@key] = options + if request.cookie_jar[@key] != options[:value] || !options[:expires].nil? + request.cookie_jar[@key] = options + end end def load_session(env) - request = Rack::Request.new(env) - sid = request.cookies[@key] - sid ||= request.params[@key] unless @cookie_only - sid, session = get_session(env, sid) - [sid, session] + stale_session_check! do + sid = current_session_id(env) + sid, session = get_session(env, sid) + [sid, session] + end + end + + def extract_session_id(env) + stale_session_check! do + request = ActionDispatch::Request.new(env) + sid = request.cookies[@key] + sid ||= request.params[@key] unless @cookie_only + sid + end + end + + def current_session_id(env) + env[ENV_SESSION_OPTIONS_KEY][:id] end def ensure_session_key! @@ -161,6 +214,26 @@ module ActionDispatch end end + def stale_session_check! + yield + rescue ArgumentError => argument_error + if argument_error.message =~ %r{undefined class/module ([\w:]*\w)} + begin + # Note that the regexp does not allow $1 to end with a ':' + $1.constantize + rescue LoadError, NameError => const_error + 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 + else + raise + end + end + + def exists?(env) + current_session_id(env).present? + end + def get_session(env, sid) raise '#get_session needs to be implemented.' end @@ -169,6 +242,10 @@ module ActionDispatch raise '#set_session needs to be implemented and should return ' << 'the value to be stored in the cookie (usually the sid)' end + + def destroy(env) + raise '#destroy needs to be implemented.' + end end end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 92a86ee229..dce47c63bc 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -39,16 +39,6 @@ module ActionDispatch # # Note that changing digest or secret invalidates all existing sessions! class CookieStore < AbstractStore - class OptionsHash < Hash - def initialize(by, env, default_options) - @session_data = env[AbstractStore::ENV_SESSION_KEY] - merge!(default_options) - end - - def [](key) - key == :id ? @session_data[:session_id] : super(key) - end - end def initialize(app, options = {}) super(app, options.merge!(:cookie_only => true)) @@ -57,19 +47,32 @@ module ActionDispatch private - def prepare!(env) - env[ENV_SESSION_KEY] = SessionHash.new(self, env) - env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options) - end - def load_session(env) - request = ActionDispatch::Request.new(env) - data = request.cookie_jar.signed[@key] + data = unpacked_cookie_data(env) data = persistent_session_id!(data) - data.stringify_keys! [data["session_id"], data] end + def extract_session_id(env) + if data = unpacked_cookie_data(env) + data["session_id"] + else + nil + end + end + + def unpacked_cookie_data(env) + env["action_dispatch.request.unsigned_session_cookie"] ||= begin + stale_session_check! do + request = ActionDispatch::Request.new(env) + if data = request.cookie_jar.signed[@key] + data.stringify_keys! + end + data || {} + end + end + end + def set_cookie(request, options) request.cookie_jar.signed[@key] = options end @@ -78,6 +81,10 @@ module ActionDispatch persistent_session_id!(session_data, sid) end + def destroy(env) + # session data is stored on client; nothing to do here + end + def persistent_session_id!(data, sid=nil) data ||= {} data["session_id"] ||= sid || generate_sid diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb index 8df8f977e8..28e3dbd732 100644 --- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb @@ -42,6 +42,15 @@ module ActionDispatch rescue MemCache::MemCacheError, Errno::ECONNREFUSED false end + + def destroy(env) + if sid = current_session_id(env) + @pool.delete(sid) + end + rescue MemCache::MemCacheError, Errno::ECONNREFUSED + false + end + end end end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 0a6d2bfc8a..e095b51342 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -122,7 +122,7 @@ module ActionDispatch end def render(status, body) - [status, {'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s}, [body]] + [status, {'Content-Type' => 'text/html', 'Content-Length' => body.bytesize.to_s}, [body]] end def public_path diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 4240e7a5d5..4618f3befc 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -70,7 +70,7 @@ module ActionDispatch end def active - ActiveSupport::Deprecation.warn "All middlewares in the chaing are active since the laziness " << + ActiveSupport::Deprecation.warn "All middlewares in the chain are active since the laziness " << "was removed from the middleware stack", caller end diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 38da44d7e7..ed93211255 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -10,7 +10,7 @@ module ActionDispatch # Prepare dispatcher callbacks and run 'prepare' callbacks initializer "action_dispatch.prepare_dispatcher" do |app| - ActionDispatch::Callbacks.to_prepare { app.routes_reloader.reload_if_changed } + ActionDispatch::Callbacks.to_prepare { app.routes_reloader.execute_if_updated } end end end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 3d9084a27e..401d98b663 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/regexp' require 'action_controller/polymorphic_routes' module ActionDispatch - # == Routing + # = Routing # # The routing module provides URL rewriting in native Ruby. It's a way to # redirect incoming requests to controllers and actions. This replaces diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb index 1b676669e2..bc3f62fb48 100644 --- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb +++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb @@ -28,61 +28,13 @@ module ActionDispatch end end - # Mapper instances are used to build routes. The object passed to the draw - # block in config/routes.rb is a Mapper instance. - # - # Mapper instances have relatively few instance methods, in order to avoid - # clashes with named routes. - # - # == Overview - # - # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms, - # is something that can be pointed at and it will respond with a representation of the data requested. - # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application - # requests XML data. - # - # RESTful design is based on the assumption that there are four generic verbs that a user of an - # application can request from a \resource (the noun). - # - # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used - # denotes the type of action that should take place. - # - # === The Different Methods and their Usage - # - # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request. - # * POST - Creation of \resources. - # * PUT - Editing of attributes on a \resource. - # * DELETE - Deletion of a \resource. - # - # === Examples - # - # # A GET request on the Posts resource is asking for all Posts - # GET /posts - # - # # A GET request on a single Post resource is asking for that particular Post - # GET /posts/1 - # - # # A POST request on the Posts resource is asking for a Post to be created with the supplied details - # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } } - # - # # A PUT request on a single Post resource is asking for a Post to be updated - # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } } - # - # # A DELETE request on a single Post resource is asking for it to be deleted - # DELETE /posts # with => { :id => 1 } - # - # By using the REST convention, users of our application can assume certain things about how the data - # is requested and how it is returned. Rails simplifies the routing part of RESTful design by - # supplying you with methods to create them in your routes.rb file. - # - # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer class DeprecatedMapper #:nodoc: def initialize(set) #:nodoc: + ActiveSupport::Deprecation.warn "You are using the old router DSL which will be removed in Rails 3.1. " << + "Please check how to update your router file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/" @set = set end - # Create an unnamed route with the provided +path+ and +options+. See - # ActionDispatch::Routing for an introduction to routes. def connect(path, options = {}) options = options.dup @@ -240,17 +192,6 @@ module ActionDispatch connect(path, options) end - # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model. - # Example: - # - # map.namespace(:admin) do |admin| - # admin.resources :products, - # :has_many => [ :tags, :images, :variants ] - # end - # - # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController. - # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for - # Admin::TagsController. def namespace(name, options = {}, &block) if options[:namespace] with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block) @@ -411,334 +352,11 @@ module ActionDispatch alias_method :nesting_path_prefix, :path end - # Creates named routes for implementing verb-oriented controllers - # for a collection \resource. - # - # For example: - # - # map.resources :messages - # - # will map the following actions in the corresponding controller: - # - # class MessagesController < ActionController::Base - # # GET messages_url - # def index - # # return all messages - # end - # - # # GET new_message_url - # def new - # # return an HTML form for describing a new message - # end - # - # # POST messages_url - # def create - # # create a new message - # end - # - # # GET message_url(:id => 1) - # def show - # # find and return a specific message - # end - # - # # GET edit_message_url(:id => 1) - # def edit - # # return an HTML form for editing a specific message - # end - # - # # PUT message_url(:id => 1) - # def update - # # find and update a specific message - # end - # - # # DELETE message_url(:id => 1) - # def destroy - # # delete a specific message - # end - # end - # - # Along with the routes themselves, +resources+ generates named routes for use in - # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers: - # - # Named Route Helpers - # ============ ===================================================== - # messages messages_url, hash_for_messages_url, - # messages_path, hash_for_messages_path - # - # message message_url(id), hash_for_message_url(id), - # message_path(id), hash_for_message_path(id) - # - # new_message new_message_url, hash_for_new_message_url, - # new_message_path, hash_for_new_message_path - # - # edit_message edit_message_url(id), hash_for_edit_message_url(id), - # edit_message_path(id), hash_for_edit_message_path(id) - # - # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example: - # - # redirect_to :controller => 'messages', :action => 'index' - # # and - # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %> - # - # now become: - # - # redirect_to messages_url - # # and - # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically - # - # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your - # form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object: - # - # <%= form_tag message_path(@message), :method => :put %> - # - # or - # - # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %> - # - # or - # - # <% form_for @message do |f| %> - # - # which takes into account whether <tt>@message</tt> is a new record or not and generates the - # path and method accordingly. - # - # The +resources+ method accepts the following options to customize the resulting routes: - # * <tt>:collection</tt> - Add named routes for other actions that operate on the collection. - # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>, - # an array of any of the previous, or <tt>:any</tt> if the method does not matter. - # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+. - # * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member. - # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action. - # * <tt>:controller</tt> - Specify the controller name for the routes. - # * <tt>:singular</tt> - Specify the singular name used in the member routes. - # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either - # regular expressions (which must match for the route to match) or extra parameters. For example: - # - # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' } - # - # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller. - # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes. - # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example: - # # products_path == '/productos' - # map.resources :products, :as => 'productos' do |product| - # # product_reviews_path(product) == '/productos/1234/comentarios' - # product.resources :product_reviews, :as => 'comentarios' - # end - # - # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current. - # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources. - # - # You may directly specify the routing association with +has_one+ and +has_many+ like: - # - # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments] - # - # This is the same as: - # - # map.resources :notes do |notes| - # notes.resource :author - # notes.resources :comments - # notes.resources :attachments - # end - # - # * <tt>:path_names</tt> - Specify different path names for the actions. For example: - # # new_products_path == '/productos/nuevo' - # # bids_product_path(1) == '/productos/1/licitacoes' - # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' } - # - # You can also set default action names from an environment, like this: - # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' } - # - # * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables. - # - # Weblog comments usually belong to a post, so you might use +resources+ like: - # - # map.resources :articles - # map.resources :comments, :path_prefix => '/articles/:article_id' - # - # You can nest +resources+ calls to set this automatically: - # - # map.resources :articles do |article| - # article.resources :comments - # end - # - # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>. - # - # article_comments_url(@article) - # article_comment_url(@article, @comment) - # - # article_comments_url(:article_id => @article) - # article_comment_url(:article_id => @article, :id => @comment) - # - # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly: - # - # articles_comments_url(@comment.article_id, @comment) - # - # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore. - # Use this if you have named routes that may clash. - # - # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_' - # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_' - # - # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource: - # - # map.resources :articles do |article| - # article.resources :comments, :name_prefix => nil - # end - # - # This will yield named \resources like so: - # - # comments_url(@article) - # comment_url(@article, @comment) - # - # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member - # (ie. those with an :id parameter) will not use the parent path prefix or name prefix. - # - # The <tt>:shallow</tt> option is inherited by any nested resource(s). - # - # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources: - # - # map.resources :users, :shallow => true do |user| - # user.resources :posts do |post| - # post.resources :comments - # end - # end - # # --> GET /users/1/posts (maps to the PostsController#index action as usual) - # # also adds the usual named route called "user_posts" - # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested) - # # also adds the named route called "post" - # # --> GET /posts/2/comments (maps to the CommentsController#index action) - # # also adds the named route called "post_comments" - # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested) - # # also adds the named route called "comment" - # - # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like: - # - # map.resources :users, :has_many => { :posts => :comments }, :shallow => true - # - # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to. - # - # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a - # list of action names. By default, routes are generated for all seven actions. - # - # For example: - # - # map.resources :posts, :only => [:index, :show] do |post| - # post.resources :comments, :except => [:update, :destroy] - # end - # # --> GET /posts (maps to the PostsController#index action) - # # --> POST /posts (fails) - # # --> GET /posts/1 (maps to the PostsController#show action) - # # --> DELETE /posts/1 (fails) - # # --> POST /posts/1/comments (maps to the CommentsController#create action) - # # --> PUT /posts/1/comments/1 (fails) - # - # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied. - # - # Examples: - # - # map.resources :messages, :path_prefix => "/thread/:thread_id" - # # --> GET /thread/7/messages/1 - # - # map.resources :messages, :collection => { :rss => :get } - # # --> GET /messages/rss (maps to the #rss action) - # # also adds a named route called "rss_messages" - # - # map.resources :messages, :member => { :mark => :post } - # # --> POST /messages/1/mark (maps to the #mark action) - # # also adds a named route called "mark_message" - # - # map.resources :messages, :new => { :preview => :post } - # # --> POST /messages/new/preview (maps to the #preview action) - # # also adds a named route called "preview_new_message" - # - # map.resources :messages, :new => { :new => :any, :preview => :post } - # # --> POST /messages/new/preview (maps to the #preview action) - # # also adds a named route called "preview_new_message" - # # --> /messages/new can be invoked via any request method - # - # map.resources :messages, :controller => "categories", - # :path_prefix => "/category/:category_id", - # :name_prefix => "category_" - # # --> GET /categories/7/messages/1 - # # has named route "category_message" - # - # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an - # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in - # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes. def resources(*entities, &block) options = entities.extract_options! entities.each { |entity| map_resource(entity, options.dup, &block) } end - # Creates named routes for implementing verb-oriented controllers for a singleton \resource. - # A singleton \resource is global to its current context. For unnested singleton \resources, - # the \resource is global to the current user visiting the application, such as a user's - # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent - # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>. - # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>: - # - # map.resources :projects do |project| - # project.resource :project_manager - # end - # - # See +resources+ for general conventions. These are the main differences: - # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name. - # * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option. - # * No default index route is created for the singleton \resource controller. - # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1') - # - # For example: - # - # map.resource :account - # - # maps these actions in the Accounts controller: - # - # class AccountsController < ActionController::Base - # # GET new_account_url - # def new - # # return an HTML form for describing the new account - # end - # - # # POST account_url - # def create - # # create an account - # end - # - # # GET account_url - # def show - # # find and return the account - # end - # - # # GET edit_account_url - # def edit - # # return an HTML form for editing the account - # end - # - # # PUT account_url - # def update - # # find and update the account - # end - # - # # DELETE account_url - # def destroy - # # delete the account - # end - # end - # - # Along with the routes themselves, +resource+ generates named routes for - # use in controllers and views. <tt>map.resource :account</tt> produces - # these named routes and helpers: - # - # Named Route Helpers - # ============ ============================================= - # account account_url, hash_for_account_url, - # account_path, hash_for_account_path - # - # new_account new_account_url, hash_for_new_account_url, - # new_account_path, hash_for_new_account_path - # - # edit_account edit_account_url, hash_for_edit_account_url, - # edit_account_path, hash_for_edit_account_path def resource(*entities, &block) options = entities.extract_options! entities.each { |entity| map_singleton_resource(entity, options.dup, &block) } diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 46304b0336..0b4ba8c9f5 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -33,7 +33,7 @@ module ActionDispatch end class Mapping #:nodoc: - IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow] + IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix] def initialize(set, scope, args) @set, @scope = set, scope @@ -55,7 +55,7 @@ module ActionDispatch path = args.first end - if @scope[:module] && options[:to] + if @scope[:module] && options[:to] && !options[:to].is_a?(Proc) if options[:to].to_s.include?("#") options[:to] = "#{@scope[:module]}/#{options[:to]}" elsif @scope[:controller].nil? @@ -102,7 +102,7 @@ module ActionDispatch end def requirements - @requirements ||= (@options[:constraints] || {}).tap do |requirements| + @requirements ||= (@options[:constraints].is_a?(Hash) ? @options[:constraints] : {}).tap do |requirements| requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints] @options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) } end @@ -131,6 +131,7 @@ module ActionDispatch end defaults[:controller] ||= default_controller + defaults[:action] ||= default_action defaults.delete(:controller) if defaults[:controller].blank? defaults.delete(:action) if defaults[:action].blank? @@ -187,6 +188,12 @@ module ActionDispatch @scope[:controller].to_s end end + + def default_action + if @options[:action] + @options[:action].to_s + end + end end # Invokes Rack::Mount::Utils.normalize path and ensure that @@ -194,7 +201,7 @@ module ActionDispatch # for root cases, where the latter is the correct one. def self.normalize_path(path) path = Rack::Mount::Utils.normalize_path(path) - path.sub!(%r{/(\(+)/?:}, '\1/:') unless path =~ %r{^/\(+:.*\)$} + path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^/]+\)$} path end @@ -299,6 +306,11 @@ module ActionDispatch options = args.extract_options! options = options.dup + if name_prefix = options.delete(:name_prefix) + options[:as] ||= name_prefix + ActiveSupport::Deprecation.warn ":name_prefix was deprecated in the new router syntax. Use :as instead.", caller + end + case args.first when String options[:path] = args.first @@ -341,9 +353,11 @@ module ActionDispatch scope(controller.to_sym) { yield } end - def namespace(path) + def namespace(path, options = {}) path = path.to_s - scope(:path => path, :name_prefix => path, :module => path) { yield } + options = { :path => path, :as => path, :module => path, + :shallow_path => path, :shallow_prefix => path }.merge!(options) + scope(options) { yield } end def constraints(constraints = {}) @@ -359,10 +373,10 @@ module ActionDispatch options = (@scope[:options] || {}).merge(options) - if @scope[:name_prefix] && !options[:as].blank? - options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}" - elsif @scope[:name_prefix] && options[:as] == "" - options[:as] = @scope[:name_prefix].to_s + if @scope[:as] && !options[:as].blank? + options[:as] = "#{@scope[:as]}_#{options[:as]}" + elsif @scope[:as] && options[:as] == "" + options[:as] = @scope[:as].to_s end args.push(options) @@ -378,7 +392,15 @@ module ActionDispatch Mapper.normalize_path("#{parent}/#{child}") end - def merge_name_prefix_scope(parent, child) + def merge_shallow_path_scope(parent, child) + Mapper.normalize_path("#{parent}/#{child}") + end + + def merge_as_scope(parent, child) + parent ? "#{parent}_#{child}" : child + end + + def merge_shallow_prefix_scope(parent, child) parent ? "#{parent}_#{child}" : child end @@ -403,7 +425,9 @@ module ActionDispatch end def merge_blocks_scope(parent, child) - (parent || []) + [child] + merged = parent ? parent.dup : [] + merged << child if child + merged end def merge_options_scope(parent, child) @@ -416,22 +440,27 @@ module ActionDispatch end module Resources + # CANONICAL_ACTIONS holds all actions that does not need a prefix or + # a path appended since they fit properly in their scope level. + VALID_ON_OPTIONS = [:new, :collection, :member] + CANONICAL_ACTIONS = [:index, :create, :new, :show, :update, :destroy] + MERGE_FROM_SCOPE_OPTIONS = [:shallow, :constraints] + class Resource #:nodoc: - def self.default_actions - [:index, :create, :new, :show, :update, :destroy, :edit] - end + DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit] attr_reader :controller, :path, :options def initialize(entities, options = {}) @name = entities.to_s @path = options.delete(:path) || @name - @controller = options.delete(:controller) || @name.to_s.pluralize + @controller = (options.delete(:controller) || @name).to_s + @as = options.delete(:as) @options = options end def default_actions - self.class.default_actions + self.class::DEFAULT_ACTIONS end def actions @@ -445,7 +474,7 @@ module ActionDispatch end def name - options[:as] || @name + @as || @name end def plural @@ -497,7 +526,7 @@ module ActionDispatch def nested_options {}.tap do |opts| - opts[:name_prefix] = member_name + opts[:as] = member_name opts["#{singular}_id".to_sym] = id_constraint if id_constraint? opts[:options] = { :shallow => shallow? } unless options[:shallow].nil? end @@ -515,8 +544,8 @@ module ActionDispatch ["#{path}/:id", options] end - def new_scope - [path] + def new_scope(new_path) + ["#{path}/#{new_path}"] end def nested_scope @@ -525,17 +554,20 @@ module ActionDispatch end class SingletonResource < Resource #:nodoc: - def self.default_actions - [:show, :create, :update, :destroy, :new, :edit] - end + DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit] - def initialize(entity, options = {}) - super + def initialize(entities, options) + @name = entities.to_s + @path = options.delete(:path) || @name + @controller = (options.delete(:controller) || @name.to_s.pluralize).to_s + @as = options.delete(:as) + @options = options end def member_name name end + alias :collection_name :member_name def nested_path path @@ -543,7 +575,7 @@ module ActionDispatch def nested_options {}.tap do |opts| - opts[:name_prefix] = member_name + opts[:as] = member_name opts[:options] = { :shallow => shallow? } unless @options[:shallow].nil? end end @@ -562,10 +594,12 @@ module ActionDispatch @scope[:path_names] = @set.resources_path_names end + def resources_path_names(options) + @scope[:path_names].merge!(options) + end + def resource(*resources, &block) options = resources.extract_options! - options = (@scope[:options] || {}).merge(options) - options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow) if apply_common_behavior_for(:resource, resources, options, &block) return self @@ -575,9 +609,12 @@ module ActionDispatch yield if block_given? collection_scope do - post :create if parent_resource.actions.include?(:create) - get :new if parent_resource.actions.include?(:new) - end + post :create + end if parent_resource.actions.include?(:create) + + new_scope do + get :new + end if parent_resource.actions.include?(:new) member_scope do get :show if parent_resource.actions.include?(:show) @@ -592,8 +629,6 @@ module ActionDispatch def resources(*resources, &block) options = resources.extract_options! - options = (@scope[:options] || {}).merge(options) - options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow) if apply_common_behavior_for(:resources, resources, options, &block) return self @@ -605,9 +640,12 @@ module ActionDispatch collection_scope do get :index if parent_resource.actions.include?(:index) post :create if parent_resource.actions.include?(:create) - get :new if parent_resource.actions.include?(:new) end + new_scope do + get :new + end if parent_resource.actions.include?(:new) + member_scope do get :show if parent_resource.actions.include?(:show) put :update if parent_resource.actions.include?(:update) @@ -644,10 +682,8 @@ module ActionDispatch raise ArgumentError, "can't use new outside resource(s) scope" end - with_scope_level(:new) do - scope(*parent_resource.new_scope) do - yield - end + new_scope do + yield end end @@ -659,10 +695,10 @@ module ActionDispatch with_scope_level(:nested) do if parent_resource.shallow? with_exclusive_scope do - if @scope[:module].blank? + if @scope[:shallow_path].blank? scope(*parent_resource.nested_scope) { yield } else - scope(@scope[:module], :name_prefix => @scope[:module].tr('/', '_')) do + scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do scope(*parent_resource.nested_scope) { yield } end end @@ -673,7 +709,7 @@ module ActionDispatch end end - def namespace(path) + def namespace(path, options = {}) if resource_scope? nested { super } else @@ -688,8 +724,7 @@ module ActionDispatch end def match(*args) - options = args.extract_options! - + options = args.extract_options!.dup options[:anchor] = true unless options.key?(:anchor) if args.length > 1 @@ -697,17 +732,12 @@ module ActionDispatch return self end - if [:collection, :member, :new].include?(options[:on]) + on = options.delete(:on) + if VALID_ON_OPTIONS.include?(on) args.push(options) - - case options.delete(:on) - when :collection - return collection { match(*args) } - when :member - return member { match(*args) } - when :new - return new { match(*args) } - end + return send(on){ match(*args) } + elsif on + raise ArgumentError, "Unknown scope #{on.inspect} given to :on" end if @scope[:scope_level] == :resource @@ -715,14 +745,26 @@ module ActionDispatch return member { match(*args) } end - path_names = options.delete(:path_names) + path = options.delete(:path) + action = args.first - if args.first.is_a?(Symbol) - path = path_for_action(args.first, path_names) - options = options_for_action(args.first, options) + if action.is_a?(Symbol) + path = path_for_action(action, path) + options[:to] ||= action + options[:as] = name_for_action(action, options[:as]) + + with_exclusive_scope do + return super(path, options) + end + elsif resource_method_scope? + path = path_for_custom_action + options[:as] = name_for_action(options[:as]) if options[:as] + args.push(options) with_exclusive_scope do - return match(path, options) + scope(path) do + return super + end end end @@ -736,8 +778,8 @@ module ActionDispatch def root(options={}) if @scope[:scope_level] == :resources - with_scope_level(:collection) do - scope(parent_resource.path, :name_prefix => parent_resource.collection_name) do + with_scope_level(:nested) do + scope(parent_resource.path, :as => parent_resource.collection_name) do super(options) end end @@ -747,11 +789,11 @@ module ActionDispatch end protected + def parent_resource #:nodoc: @scope[:scope_level_resource] end - private def apply_common_behavior_for(method, resources, options, &block) if resources.length > 1 resources.each { |r| send(method, r, options, &block) } @@ -765,6 +807,10 @@ module ActionDispatch return true end + scope_options = @scope.slice(*MERGE_FROM_SCOPE_OPTIONS).delete_if{ |k,v| v.blank? } + options.reverse_merge!(scope_options) unless scope_options.empty? + options.reverse_merge!(@scope[:options]) unless @scope[:options].blank? + if resource_scope? nested do send(method, resources.pop, options, &block) @@ -779,14 +825,20 @@ module ActionDispatch [:resource, :resources].include?(@scope[:scope_level]) end + def resource_method_scope? + [:collection, :member, :new].include?(@scope[:scope_level]) + end + def with_exclusive_scope begin - old_name_prefix, old_path = @scope[:name_prefix], @scope[:path] - @scope[:name_prefix], @scope[:path] = nil, nil + old_name_prefix, old_path = @scope[:as], @scope[:path] + @scope[:as], @scope[:path] = nil, nil - yield + with_scope_level(:exclusive) do + yield + end ensure - @scope[:name_prefix], @scope[:path] = old_name_prefix, old_path + @scope[:as], @scope[:path] = old_name_prefix, old_path end end @@ -807,6 +859,14 @@ module ActionDispatch end end + def new_scope + with_scope_level(:new) do + scope(*parent_resource.new_scope(action_path(:new))) do + yield + end + end + end + def collection_scope with_scope_level(:collection) do scope(*parent_resource.collection_scope) do @@ -823,91 +883,71 @@ module ActionDispatch end end - def path_for_action(action, path_names) - case action - when :index, :create - "#{@scope[:path]}(.:format)" - when :show, :update, :destroy - if parent_resource.shallow? - "#{@scope[:module]}/#{parent_resource.path}/:id(.:format)" - else - "#{@scope[:path]}(.:format)" - end - when :new - "#{@scope[:path]}/#{action_path(:new)}(.:format)" - when :edit - if parent_resource.shallow? - "#{@scope[:module]}/#{parent_resource.path}/:id/#{action_path(:edit)}(.:format)" - else - "#{@scope[:path]}/#{action_path(:edit)}(.:format)" - end + def canonical_action?(action, flag) + flag && CANONICAL_ACTIONS.include?(action) + end + + def shallow_scoping? + parent_resource && parent_resource.shallow? && @scope[:scope_level] == :member + end + + def path_for_action(action, path) + prefix = shallow_scoping? ? + "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path] + + if canonical_action?(action, path.blank?) + "#{prefix}(.:format)" else - case @scope[:scope_level] - when :collection - "#{@scope[:path]}/#{action_path(action)}(.:format)" - when :new - "#{@scope[:path]}/#{action_path(:new)}/#{action_path(action)}(.:format)" - else - if parent_resource.shallow? - "#{@scope[:module]}/#{parent_resource.path}/:id/#{action_path(action)}(.:format)" - else - "#{@scope[:path]}/#{action_path(action)}(.:format)" - end - end + "#{prefix}/#{action_path(action, path)}(.:format)" end end - def action_path(name, path_names = nil) - path_names ||= @scope[:path_names] - path_names[name.to_sym] || name.to_s + def path_for_custom_action + if shallow_scoping? + "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" + else + @scope[:path] + end end - def options_for_action(action, options) - options.reverse_merge( - :to => action, - :as => name_for_action(action) - ) + def action_path(name, path = nil) + path || @scope[:path_names][name.to_sym] || name.to_s + end + + def prefix_name_for_action(action, as) + if as.present? + "#{as}_" + elsif as + "" + elsif !canonical_action?(action, @scope[:scope_level]) + "#{action}_" + end end - def name_for_action(action) - name_prefix = @scope[:name_prefix].blank? ? "" : "#{@scope[:name_prefix]}_" - shallow_prefix = @scope[:module].blank? ? "" : "#{@scope[:module].tr('/', '_')}_" + def name_for_action(action, as=nil) + prefix = prefix_name_for_action(action, as) + name_prefix = @scope[:as] - case action - when :index - "#{name_prefix}#{parent_resource.collection_name}" - when :show - if parent_resource.shallow? - "#{shallow_prefix}#{parent_resource.member_name}" - else - "#{name_prefix}#{parent_resource.member_name}" - end - when :edit - if parent_resource.shallow? - "edit_#{shallow_prefix}#{parent_resource.member_name}" - else - "edit_#{name_prefix}#{parent_resource.member_name}" - end + if parent_resource + collection_name = parent_resource.collection_name + member_name = parent_resource.member_name + name_prefix = "#{name_prefix}_" if name_prefix.present? + end + + case @scope[:scope_level] + when :collection + "#{prefix}#{name_prefix}#{collection_name}" when :new - "new_#{name_prefix}#{parent_resource.member_name}" - when :update, :create, :destroy - nil + "#{prefix}new_#{name_prefix}#{member_name}" else - case @scope[:scope_level] - when :collection - "#{action}_#{name_prefix}#{parent_resource.collection_name}" - when :new - "#{action}_new_#{name_prefix}#{parent_resource.member_name}" + if shallow_scoping? + shallow_prefix = "#{@scope[:shallow_prefix]}_" if @scope[:shallow_prefix].present? + "#{prefix}#{shallow_prefix}#{member_name}" else - if parent_resource.shallow? - "#{action}_#{shallow_prefix}#{parent_resource.member_name}" - else - "#{action}_#{name_prefix}#{parent_resource.member_name}" - end + "#{prefix}#{name_prefix}#{member_name}" end end end - end include Base diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb index 6f37eadd6b..aefebf8f80 100644 --- a/actionpack/lib/action_dispatch/routing/route.rb +++ b/actionpack/lib/action_dispatch/routing/route.rb @@ -2,9 +2,10 @@ module ActionDispatch module Routing class Route #:nodoc: attr_reader :app, :conditions, :defaults, :name - attr_reader :path, :requirements + attr_reader :path, :requirements, :set - def initialize(app, conditions, requirements, defaults, name, anchor) + def initialize(set, app, conditions, requirements, defaults, name, anchor) + @set = set @app = app @defaults = defaults @name = name @@ -24,6 +25,9 @@ module ActionDispatch h[k] = Rack::Mount::RegexpWithNamedGroups.new(v) h } + + @conditions.delete_if{ |k,v| k != :path_info && !valid_condition?(k) } + @requirements.delete_if{ |k,v| !valid_condition?(k) } end def verb @@ -50,6 +54,11 @@ module ActionDispatch "%-6s %-40s %s" % [(verb || :any).to_s.upcase, path, requirements.inspect] end end + + private + def valid_condition?(method) + segment_keys.include?(method) || set.valid_conditions.include?(method) + end end end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 57a73dde75..5ecad6bc04 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,8 +1,10 @@ -require 'rack/mount' require 'forwardable' require 'active_support/core_ext/object/to_query' require 'action_dispatch/routing/deprecated_mapper' +$: << File.expand_path('../../vendor/rack-mount-0.6.6.pre', __FILE__) +require 'rack/mount' + module ActionDispatch module Routing class RouteSet #:nodoc: @@ -185,9 +187,9 @@ module ActionDispatch end end - attr_accessor :routes, :named_routes + attr_accessor :set, :routes, :named_routes attr_accessor :disable_clear_and_finalize, :resources_path_names - attr_accessor :default_url_options, :request_class + attr_accessor :default_url_options, :request_class, :valid_conditions def self.default_resources_path_names { :new => 'new', :edit => 'edit' } @@ -199,7 +201,11 @@ module ActionDispatch self.resources_path_names = self.class.default_resources_path_names.dup self.controller_namespaces = Set.new self.default_url_options = {} + self.request_class = request_class + self.valid_conditions = request_class.public_instance_methods.map { |m| m.to_sym } + self.valid_conditions.delete(:id) + self.valid_conditions.push(:controller, :action) @disable_clear_and_finalize = false clear! @@ -277,7 +283,7 @@ module ActionDispatch end def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true) - route = Route.new(app, conditions, requirements, defaults, name, anchor) + route = Route.new(self, app, conditions, requirements, defaults, name, anchor) @set.add_route(*route) named_routes[name] = route if name routes << route @@ -296,6 +302,7 @@ module ActionDispatch @extras = extras normalize_options! + normalize_recall! normalize_controller_action_id! use_relative_controller! controller.sub!(%r{^/}, '') if controller @@ -336,6 +343,15 @@ module ActionDispatch end end + def normalize_recall! + # If the target route is not a standard route then remove controller and action + # from the options otherwise they will appear in the url parameters + if block_or_proc_route_target? + recall.delete(:controller) unless segment_keys.include?(:controller) + recall.delete(:action) unless segment_keys.include?(:action) + end + end + # This pulls :controller, :action, and :id out of the recall. # The recall key is only used if there is no key in the options # or if the key in the options is identical. If any of @@ -371,7 +387,7 @@ module ActionDispatch def generate error = ActionController::RoutingError.new("No route matches #{options.inspect}") - path, params = @set.generate(:path_info, named_route, options, recall, opts) + path, params = @set.set.generate(:path_info, named_route, options, recall, opts) raise error unless path @@ -402,6 +418,19 @@ module ActionDispatch return false unless current_controller controller.to_param != current_controller.to_param end + + private + def named_route_exists? + named_route && set.named_routes[named_route] + end + + def block_or_proc_route_target? + named_route_exists? && !set.named_routes[named_route].app.is_a?(Dispatcher) + end + + def segment_keys + named_route_exists? ? set.named_routes[named_route].segment_keys : [] + end end # Generate the path indicated by the arguments, and return an array of @@ -415,7 +444,7 @@ module ActionDispatch end def generate(options, recall = {}, extras = false) - Generator.new(options, recall, @set, extras).generate + Generator.new(options, recall, self, extras).generate end RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash] @@ -447,7 +476,7 @@ module ActionDispatch # ROUTES TODO: This can be called directly, so script_name should probably be set in the router rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) - rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor] + rewritten_url << "##{Rack::Mount::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor] rewritten_url end diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 1499c03bdf..9338fa9e70 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -53,7 +53,6 @@ module ActionDispatch extras.each_key { |key| expected_options.delete key } unless extras.nil? expected_options.stringify_keys! - routing_diff = expected_options.diff(request.path_parameters) msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>", request.path_parameters, expected_options, expected_options.diff(request.path_parameters)) assert_block(msg) { request.path_parameters == expected_options } diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index 0e82b41590..2fc9e2b7d6 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -7,9 +7,7 @@ require 'action_controller/vendor/html-scanner' module ActionDispatch module Assertions - unless const_defined?(:NO_STRIP) - NO_STRIP = %w{pre script style textarea} - end + NO_STRIP = %w{pre script style textarea} # Adds the +assert_select+ method for use in Rails functional # test cases, which can be used to make assertions on the response HTML of a controller @@ -359,7 +357,7 @@ module ActionDispatch # position. Possible values are <tt>:top</tt>, <tt>:bottom</tt>, <tt>:before</tt> # and <tt>:after</tt>. # - # Use the argument <tt>:redirect</tt> follwed by a path to check that an statement + # Use the argument <tt>:redirect</tt> followed by a path to check that an statement # which redirects to the specified path is generated. # # Using the <tt>:remove</tt> statement, you will be able to pass a block, but it will @@ -581,27 +579,25 @@ module ActionDispatch end protected - unless const_defined?(:RJS_STATEMENTS) - RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\"" - RJS_ANY_ID = "\"([^\"])*\"" - RJS_STATEMENTS = { - :chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)", - :chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)", - :replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)", - :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)", - :redirect => "window.location.href = #{RJS_ANY_ID}" - } - [:remove, :show, :hide, :toggle].each do |action| - RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)" - end - RJS_INSERTIONS = ["top", "bottom", "before", "after"] - RJS_INSERTIONS.each do |insertion| - RJS_STATEMENTS["insert_#{insertion}".to_sym] = "Element.insert\\(#{RJS_ANY_ID}, \\{ #{insertion}: #{RJS_PATTERN_HTML} \\}\\)" - end - RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}): #{RJS_PATTERN_HTML} \\}\\)" - RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})") - RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/ + RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\"" + RJS_ANY_ID = "\"([^\"])*\"" + RJS_STATEMENTS = { + :chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)", + :chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)", + :replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)", + :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)", + :redirect => "window.location.href = #{RJS_ANY_ID}" + } + [:remove, :show, :hide, :toggle].each do |action| + RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)" + end + RJS_INSERTIONS = ["top", "bottom", "before", "after"] + RJS_INSERTIONS.each do |insertion| + RJS_STATEMENTS["insert_#{insertion}".to_sym] = "Element.insert\\(#{RJS_ANY_ID}, \\{ #{insertion}: #{RJS_PATTERN_HTML} \\}\\)" end + RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}): #{RJS_PATTERN_HTML} \\}\\)" + RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})") + RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/ # +assert_select+ and +css_select+ call this to obtain the content in the HTML # page, or from all the RJS statements, depending on the type of response. diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index 9a51a32899..44fb1bde99 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -53,7 +53,7 @@ module ActionDispatch # Returns the template of the file which was used to # render this response (or nil) def rendered - ActiveSupport::Deprecation.warn("response.rendered has been deprecated. Use tempate.rendered instead", caller) + ActiveSupport::Deprecation.warn("response.rendered has been deprecated. Use template.rendered instead", caller) @template.instance_variable_get(:@_rendered) end @@ -89,7 +89,7 @@ module ActionDispatch # A shortcut to the template.assigns def template_objects - ActiveSupport::Deprecation.warn("response.template_objects has been deprecated. Use tempate.assigns instead", caller) + ActiveSupport::Deprecation.warn("response.template_objects has been deprecated. Use template.assigns instead", caller) @template.assigns || {} end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb new file mode 100644 index 0000000000..9fbf707724 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb @@ -0,0 +1,32 @@ +require 'rack' + +module Rack #:nodoc: + # A stackable dynamic tree based Rack router. + # + # Rack::Mount supports Rack's Cascade style of trying several routes until + # it finds one that is not a 404. This allows multiple routes to be nested + # or stacked on top of each other. Since the application endpoint can + # trigger the router to continue matching, middleware can be used to add + # arbitrary conditions to any route. This allows you to route based on + # other request attributes, session information, or even data dynamically + # pulled from a database. + module Mount + autoload :CodeGeneration, 'rack/mount/code_generation' + autoload :GeneratableRegexp, 'rack/mount/generatable_regexp' + autoload :Multimap, 'rack/mount/multimap' + autoload :Prefix, 'rack/mount/prefix' + autoload :RegexpWithNamedGroups, 'rack/mount/regexp_with_named_groups' + autoload :Route, 'rack/mount/route' + autoload :RouteSet, 'rack/mount/route_set' + autoload :RoutingError, 'rack/mount/route_set' + autoload :Strexp, 'rack/mount/strexp' + autoload :Utils, 'rack/mount/utils' + autoload :Version, 'rack/mount/version' + + module Analysis #:nodoc: + autoload :Frequency, 'rack/mount/analysis/frequency' + autoload :Histogram, 'rack/mount/analysis/histogram' + autoload :Splitting, 'rack/mount/analysis/splitting' + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb new file mode 100644 index 0000000000..671258f807 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb @@ -0,0 +1,60 @@ +require 'rack/mount/utils' + +module Rack::Mount + module Analysis + class Frequency #:nodoc: + def initialize(*keys) + clear + keys.each { |key| self << key } + end + + def clear + @raw_keys = [] + @key_frequency = Analysis::Histogram.new + self + end + + def <<(key) + raise ArgumentError unless key.is_a?(Hash) + @raw_keys << key + nil + end + + def possible_keys + @possible_keys ||= begin + @raw_keys.map do |key| + key.inject({}) { |requirements, (method, requirement)| + process_key(requirements, method, requirement) + requirements + } + end + end + end + + def process_key(requirements, method, requirement) + if requirement.is_a?(Regexp) + expression = Utils.parse_regexp(requirement) + + if expression.is_a?(Regin::Expression) && expression.anchored_to_line? + expression = Regin::Expression.new(expression.reject { |e| e.is_a?(Regin::Anchor) }) + return requirements[method] = expression.to_s if expression.literal? + end + end + + requirements[method] = requirement + end + + def report + @report ||= begin + possible_keys.each { |keys| keys.each_pair { |key, _| @key_frequency << key } } + return [] if @key_frequency.count <= 1 + @key_frequency.keys_in_upper_quartile + end + end + + def expire! + @possible_keys = @report = nil + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb new file mode 100644 index 0000000000..20aaa132f9 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb @@ -0,0 +1,74 @@ +module Rack::Mount + module Analysis + class Histogram < Hash #:nodoc: + attr_reader :count + + def initialize + @count = 0 + super(0) + expire_caches! + end + + def <<(value) + @count += 1 + self[value] += 1 if value + expire_caches! + self + end + + def sorted_by_frequency + sort_by { |_, value| value }.reverse! + end + + def max + @max ||= values.max || 0 + end + + def min + @min ||= values.min || 0 + end + + def mean + @mean ||= calculate_mean + end + + def standard_deviation + @standard_deviation ||= calculate_standard_deviation + end + + def upper_quartile_limit + @upper_quartile_limit ||= calculate_upper_quartile_limit + end + + def keys_in_upper_quartile + @keys_in_upper_quartile ||= compute_keys_in_upper_quartile + end + + private + def calculate_mean + count / size + end + + def calculate_variance + values.inject(0) { |sum, e| sum + (e - mean) ** 2 } / count.to_f + end + + def calculate_standard_deviation + Math.sqrt(calculate_variance) + end + + def calculate_upper_quartile_limit + mean + standard_deviation + end + + def compute_keys_in_upper_quartile + sorted_by_frequency.select { |_, value| value >= upper_quartile_limit }.map! { |key, _| key } + end + + def expire_caches! + @max = @min = @mean = @standard_deviation = nil + @keys_in_upper_quartile = nil + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb new file mode 100644 index 0000000000..8a8c551302 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb @@ -0,0 +1,159 @@ +require 'rack/mount/utils' + +module Rack::Mount + module Analysis + class Splitting < Frequency + NULL = "\0".freeze + + class Key < Struct.new(:method, :index, :separators) + def self.split(value, separator_pattern) + keys = value.split(separator_pattern) + keys.shift if keys[0] == '' + keys << NULL + keys + end + + def call(cache, obj) + (cache[method] ||= self.class.split(obj.send(method), separators))[index] + end + + def call_source(cache, obj) + "(#{cache}[:#{method}] ||= Analysis::Splitting::Key.split(#{obj}.#{method}, #{separators.inspect}))[#{index}]" + end + + def inspect + "#{method}[#{index}]" + end + end + + def clear + @boundaries = {} + super + end + + def <<(key) + super + key.each_pair do |k, v| + analyze_capture_boundaries(v, @boundaries[k] ||= Histogram.new) + end + end + + def separators(key) + (@boundaries[key].keys_in_upper_quartile + ['/']).uniq + end + + def process_key(requirements, method, requirement) + separators = separators(method) + if requirement.is_a?(Regexp) && separators.any? + generate_split_keys(requirement, separators).each_with_index do |value, index| + requirements[Key.new(method, index, Regexp.union(*separators))] = value + end + else + super + end + end + + private + def analyze_capture_boundaries(regexp, boundaries) #:nodoc: + return boundaries unless regexp.is_a?(Regexp) + + parts = Utils.parse_regexp(regexp) + parts.each_with_index do |part, index| + if part.is_a?(Regin::Group) + if index > 0 + previous = parts[index-1] + if previous.is_a?(Regin::Character) && previous.literal? + boundaries << previous.to_s + end + end + + if inside = part.expression[0] + if inside.is_a?(Regin::Character) && inside.literal? + boundaries << inside.to_s + end + end + + if index < parts.length + following = parts[index+1] + if following.is_a?(Regin::Character) && following.literal? + boundaries << following.to_s + end + end + end + end + + boundaries + end + + def generate_split_keys(regexp, separators) #:nodoc: + segments = [] + buf = nil + parts = Utils.parse_regexp(regexp) + parts.each_with_index do |part, index| + case part + when Regin::Anchor + if part.value == '$' || part.value == '\Z' + segments << join_buffer(buf, regexp) if buf + segments << NULL + buf = nil + break + end + when Regin::CharacterClass + break if separators.any? { |s| part.include?(s) } + buf = nil + segments << part.to_regexp(true) + when Regin::Character + if separators.any? { |s| part.include?(s) } + segments << join_buffer(buf, regexp) if buf + peek = parts[index+1] + if peek.is_a?(Regin::Character) && separators.include?(peek.value) + segments << '' + end + buf = nil + else + buf ||= Regin::Expression.new([]) + buf += [part] + end + when Regin::Group + if part.quantifier == '?' + value = part.expression.first + if separators.any? { |s| value.include?(s) } + segments << join_buffer(buf, regexp) if buf + buf = nil + end + break + elsif part.quantifier == nil + break if separators.any? { |s| part.include?(s) } + buf = nil + segments << part.to_regexp(true) + else + break + end + else + break + end + + if index + 1 == parts.size + segments << join_buffer(buf, regexp) if buf + buf = nil + break + end + end + + while segments.length > 0 && (segments.last.nil? || segments.last == '') + segments.pop + end + + segments + end + + def join_buffer(parts, regexp) + if parts.literal? + parts.to_s + else + parts.to_regexp(true) + end + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb new file mode 100644 index 0000000000..903c79fdc6 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb @@ -0,0 +1,113 @@ +module Rack::Mount + module CodeGeneration #:nodoc: + def _expired_recognize(env) #:nodoc: + raise 'route set not finalized' + end + + def rehash + super + optimize_recognize! + end + + private + def expire! + if @optimized_recognize_defined + remove_metaclass_method :recognize + + class << self + alias_method :recognize, :_expired_recognize + end + + @optimized_recognize_defined = false + end + + super + end + + def optimize_container_iterator(container) + body = [] + + container.each_with_index { |route, i| + body << "route = self[#{i}]" + body << 'matches = {}' + body << 'params = route.defaults.dup' + + conditions = [] + route.conditions.each do |method, condition| + b = [] + if condition.is_a?(Regexp) + b << "if m = obj.#{method}.match(#{condition.inspect})" + b << "matches[:#{method}] = m" + if (named_captures = route.named_captures[method]) && named_captures.any? + b << 'captures = m.captures' + b << 'p = nil' + b << named_captures.map { |k, j| "params[#{k.inspect}] = p if p = captures[#{j}]" }.join('; ') + end + else + b << "if m = obj.#{method} == route.conditions[:#{method}]" + end + b << 'true' + b << 'end' + conditions << "(#{b.join('; ')})" + end + + body << <<-RUBY + if #{conditions.join(' && ')} + yield route, matches, params + end + RUBY + } + + container.instance_eval(<<-RUBY, __FILE__, __LINE__) + def optimized_each(obj) + #{body.join("\n")} + nil + end + RUBY + end + + def optimize_recognize! + keys = @recognition_keys.map { |key| + if key.respond_to?(:call_source) + key.call_source(:cache, :obj) + else + "obj.#{key}" + end + }.join(', ') + + @optimized_recognize_defined = true + + remove_metaclass_method :recognize + + instance_eval(<<-RUBY, __FILE__, __LINE__) + def recognize(obj) + cache = {} + container = @recognition_graph[#{keys}] + optimize_container_iterator(container) unless container.respond_to?(:optimized_each) + + if block_given? + container.optimized_each(obj) do |route, matches, params| + yield route, matches, params + end + else + container.optimized_each(obj) do |route, matches, params| + return route, matches, params + end + end + + nil + end + RUBY + end + + # method_defined? can't distinguish between instance + # and meta methods. So we have to rescue if the method + # has not been defined in the metaclass yet. + def remove_metaclass_method(symbol) + metaclass = class << self; self; end + metaclass.send(:remove_method, symbol) + rescue NameError => e + nil + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb new file mode 100644 index 0000000000..47bbab3784 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb @@ -0,0 +1,210 @@ +require 'rack/mount/utils' + +module Rack::Mount + class GeneratableRegexp < Regexp #:nodoc: + class DynamicSegment #:nodoc: + attr_reader :name, :requirement + + def initialize(name, requirement) + @name, @requirement = name.to_sym, requirement + freeze + end + + def ==(obj) + @name == obj.name && @requirement == obj.requirement + end + + def =~(str) + @requirement =~ str + end + + def to_hash + { @name => @requirement } + end + + def inspect + "/(?<#{@name}>#{@requirement.source})/" + end + end + + module InstanceMethods + def self.extended(obj) + obj.segments + end + + def defaults=(defaults) + @required_captures = nil + @required_params = nil + @required_defaults = nil + @defaults = defaults + end + + def defaults + @defaults ||= {} + end + + def generatable? + segments.any? + end + + def generate(params = {}, recall = {}, options = {}) + return nil unless generatable? + + merged = recall.merge(params) + return nil unless required_params.all? { |p| merged.include?(p) } + return nil unless required_defaults.all? { |k, v| merged[k] == v } + + generate_from_segments(segments, params, merged, options) + end + + def segments + @segments ||= begin + defaults + segments = [] + catch(:halt) do + expression = Utils.parse_regexp(self) + segments = parse_segments(expression) + end + segments + end + end + + def captures + segments.flatten.find_all { |s| s.is_a?(DynamicSegment) } + end + + def required_captures + @required_captures ||= segments.find_all { |s| + s.is_a?(DynamicSegment) && !@defaults.include?(s.name) + }.freeze + end + + def required_params + @required_params ||= required_captures.map { |s| s.name }.freeze + end + + def required_defaults + @required_defaults ||= begin + required_defaults = @defaults.dup + captures.inject({}) { |h, s| h.merge!(s.to_hash) }.keys.each { |name| + required_defaults.delete(name) + } + required_defaults + end + end + + def freeze + segments + captures + required_captures + required_params + required_defaults + super + end + + private + def parse_segments(segments) + s = [] + segments.each_with_index do |part, index| + case part + when Regin::Anchor + # ignore + when Regin::Character + throw :halt unless part.literal? + + if s.last.is_a?(String) + s.last << part.value.dup + else + s << part.value.dup + end + when Regin::Group + if part.name + s << DynamicSegment.new(part.name, part.expression.to_regexp(true)) + else + s << parse_segments(part.expression) + end + when Regin::Expression + return parse_segments(part) + else + throw :halt + end + end + + s + end + + EMPTY_STRING = ''.freeze + + def generate_from_segments(segments, params, merged, options, optional = false) + if optional + return EMPTY_STRING if segments.all? { |s| s.is_a?(String) } + return EMPTY_STRING unless segments.flatten.any? { |s| + params.has_key?(s.name) if s.is_a?(DynamicSegment) + } + return EMPTY_STRING if segments.any? { |segment| + if segment.is_a?(DynamicSegment) + value = merged[segment.name] || @defaults[segment.name] + value = parameterize(segment.name, value, options) + + merged_value = parameterize(segment.name, merged[segment.name], options) + default_value = parameterize(segment.name, @defaults[segment.name], options) + + if value.nil? || segment !~ value + true + elsif merged_value == default_value + # Nasty control flow + return :clear_remaining_segments + else + false + end + end + } + end + + generated = segments.map do |segment| + case segment + when String + segment + when DynamicSegment + value = params[segment.name] || merged[segment.name] || @defaults[segment.name] + value = parameterize(segment.name, value, options) + if value && segment =~ value.to_s + value + else + return + end + when Array + value = generate_from_segments(segment, params, merged, options, true) + if value == :clear_remaining_segments + segment.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) } + EMPTY_STRING + elsif value.nil? + EMPTY_STRING + else + value + end + end + end + + # Delete any used items from the params + segments.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) } + + generated.join + end + + def parameterize(name, value, options) + if block = options[:parameterize] + block.call(name, value) + else + value + end + end + end + include InstanceMethods + + def initialize(regexp) + super + segments + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb new file mode 100644 index 0000000000..0f8eaaec67 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb @@ -0,0 +1,53 @@ +begin + require 'nested_multimap' +rescue LoadError + $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/multimap')) + require 'nested_multimap' +end + +module Rack::Mount + class Multimap < NestedMultimap #:nodoc: + def store(*args) + keys = args.dup + value = keys.pop + key = keys.shift + + raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value + + unless key.respond_to?(:=~) + raise ArgumentError, "unsupported key: #{args.first.inspect}" + end + + if key.is_a?(Regexp) + if keys.empty? + @hash.each_pair { |k, l| l << value if k =~ key } + self.default << value + else + @hash.each_pair { |k, _| + if k =~ key + args[0] = k + super(*args) + end + } + + self.default = self.class.new(default) unless default.is_a?(self.class) + default[*keys.dup] = value + end + else + super(*args) + end + end + alias_method :[]=, :store + + undef :index, :invert + + def height + containers_with_default.max { |a, b| a.length <=> b.length }.length + end + + def average_height + lengths = containers_with_default.map { |e| e.length } + lengths.inject(0) { |sum, len| sum += len }.to_f / lengths.size + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb new file mode 100644 index 0000000000..58892e4c4f --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb @@ -0,0 +1,36 @@ +require 'rack/mount/utils' + +module Rack::Mount + class Prefix #:nodoc: + EMPTY_STRING = ''.freeze + PATH_INFO = 'PATH_INFO'.freeze + SCRIPT_NAME = 'SCRIPT_NAME'.freeze + SLASH = '/'.freeze + + KEY = 'rack.mount.prefix'.freeze + + def initialize(app, prefix = nil) + @app, @prefix = app, prefix.freeze + freeze + end + + def call(env) + if prefix = env[KEY] || @prefix + old_path_info = env[PATH_INFO].dup + old_script_name = env[SCRIPT_NAME].dup + + begin + env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO].sub(prefix, EMPTY_STRING)) + env[PATH_INFO] = EMPTY_STRING if env[PATH_INFO] == SLASH + env[SCRIPT_NAME] = Utils.normalize_path(env[SCRIPT_NAME].to_s + prefix) + @app.call(env) + ensure + env[PATH_INFO] = old_path_info + env[SCRIPT_NAME] = old_script_name + end + else + @app.call(env) + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb new file mode 100644 index 0000000000..c11292b2a2 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb @@ -0,0 +1,69 @@ +module Rack::Mount + if Regin.regexp_supports_named_captures? + RegexpWithNamedGroups = Regexp + else + require 'strscan' + + # A wrapper that adds shim named capture support to older + # versions of Ruby. + # + # Because the named capture syntax causes a parse error, an + # alternate syntax is used to indicate named captures. + # + # Ruby 1.9+ named capture syntax: + # + # /(?<foo>[a-z]+)/ + # + # Ruby 1.8 shim syntax: + # + # /(?:<foo>[a-z]+)/ + class RegexpWithNamedGroups < Regexp + def self.new(regexp) #:nodoc: + if regexp.is_a?(RegexpWithNamedGroups) + regexp + else + super + end + end + + # Wraps Regexp with named capture support. + def initialize(regexp) + regexp = Regexp.compile(regexp) unless regexp.is_a?(Regexp) + source, options = regexp.source, regexp.options + @names, scanner = [], StringScanner.new(source) + + while scanner.skip_until(/\(/) + if scanner.scan(/\?:<([^>]+)>/) + @names << scanner[1] + elsif scanner.scan(/\?(i?m?x?\-?i?m?x?)?:/) + # ignore noncapture + else + @names << nil + end + end + source.gsub!(/\?:<([^>]+)>/, '') + + @names = [] unless @names.any? + @names.freeze + + super(source, options) + end + + def names + @names.dup + end + + def named_captures + named_captures = {} + names.each_with_index { |n, i| + named_captures[n] = [i+1] if n + } + named_captures + end + + def eql?(other) + super && @names.eql?(other.names) + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb new file mode 100644 index 0000000000..680c40f147 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb @@ -0,0 +1,130 @@ +require 'rack/mount/generatable_regexp' +require 'rack/mount/regexp_with_named_groups' +require 'rack/mount/utils' + +module Rack::Mount + # Route is an internal class used to wrap a single route attributes. + # + # Plugins should not depend on any method on this class or instantiate + # new Route objects. Instead use the factory method, RouteSet#add_route + # to create new routes and add them to the set. + class Route + # Valid rack application to call if conditions are met + attr_reader :app + + # A hash of conditions to match against. Conditions may be expressed + # as strings or regexps to match against. + attr_reader :conditions + + # A hash of values that always gets merged into the parameters hash + attr_reader :defaults + + # Symbol identifier for the route used with named route generations + attr_reader :name + + attr_reader :named_captures + + def initialize(app, conditions, defaults, name) + unless app.respond_to?(:call) + raise ArgumentError, 'app must be a valid rack application' \ + ' and respond to call' + end + @app = app + + @name = name ? name.to_sym : nil + @defaults = (defaults || {}).freeze + + @conditions = {} + + conditions.each do |method, pattern| + next unless method && pattern + + pattern = Regexp.compile("\\A#{Regexp.escape(pattern)}\\Z") if pattern.is_a?(String) + + if pattern.is_a?(Regexp) + pattern = Utils.normalize_extended_expression(pattern) + pattern = RegexpWithNamedGroups.new(pattern) + pattern.extend(GeneratableRegexp::InstanceMethods) + pattern.defaults = @defaults + end + + @conditions[method] = pattern.freeze + end + + @named_captures = {} + @conditions.map { |method, condition| + next unless condition.respond_to?(:named_captures) + @named_captures[method] = condition.named_captures.inject({}) { |named_captures, (k, v)| + named_captures[k.to_sym] = v.last - 1 + named_captures + }.freeze + } + @named_captures.freeze + + @has_significant_params = @conditions.any? { |method, condition| + (condition.respond_to?(:required_params) && condition.required_params.any?) || + (condition.respond_to?(:required_defaults) && condition.required_defaults.any?) + } + + if @conditions.has_key?(:path_info) && + !Utils.regexp_anchored?(@conditions[:path_info]) + @prefix = true + @app = Prefix.new(@app) + else + @prefix = false + end + + @conditions.freeze + end + + def prefix? + @prefix + end + + + def generation_keys + @conditions.inject({}) { |keys, (method, condition)| + if condition.respond_to?(:required_defaults) + keys.merge!(condition.required_defaults) + else + keys + end + } + end + + def significant_params? + @has_significant_params + end + + def generate(method, params = {}, recall = {}, options = {}) + if method.nil? + result = @conditions.inject({}) { |h, (m, condition)| + if condition.respond_to?(:generate) + h[m] = condition.generate(params, recall, options) + end + h + } + return nil if result.values.compact.empty? + else + if condition = @conditions[method] + if condition.respond_to?(:generate) + result = condition.generate(params, recall, options) + end + end + end + + if result + @defaults.each do |key, value| + params.delete(key) if params[key] == value + end + end + + result + end + + + def inspect #:nodoc: + "#<#{self.class.name} @app=#{@app.inspect} @conditions=#{@conditions.inspect} @defaults=#{@defaults.inspect} @name=#{@name.inspect}>" + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb new file mode 100644 index 0000000000..0e5a65a640 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb @@ -0,0 +1,409 @@ +require 'rack/mount/multimap' +require 'rack/mount/route' +require 'rack/mount/utils' + +module Rack::Mount + class RoutingError < StandardError; end + + class RouteSet + # Initialize a new RouteSet without optimizations + def self.new_without_optimizations(options = {}, &block) + new(options.merge(:_optimize => false), &block) + end + + # Basic RouteSet initializer. + # + # If a block is given, the set is yielded and finalized. + # + # See other aspects for other valid options: + # - <tt>Generation::RouteSet.new</tt> + # - <tt>Recognition::RouteSet.new</tt> + def initialize(options = {}, &block) + @parameters_key = options.delete(:parameters_key) || 'rack.routing_args' + @parameters_key.freeze + + @named_routes = {} + + @recognition_key_analyzer = Analysis::Splitting.new + @generation_key_analyzer = Analysis::Frequency.new + + @request_class = options.delete(:request_class) || Rack::Request + @valid_conditions = @request_class.public_instance_methods.map! { |m| m.to_sym } + + extend CodeGeneration unless options[:_optimize] == false + @optimized_recognize_defined = false + + @routes = [] + expire! + + if block_given? + yield self + rehash + end + end + + # Builder method to add a route to the set + # + # <tt>app</tt>:: A valid Rack app to call if the conditions are met. + # <tt>conditions</tt>:: A hash of conditions to match against. + # Conditions may be expressed as strings or + # regexps to match against. + # <tt>defaults</tt>:: A hash of values that always gets merged in + # <tt>name</tt>:: Symbol identifier for the route used with named + # route generations + def add_route(app, conditions = {}, defaults = {}, name = nil) + unless conditions.is_a?(Hash) + raise ArgumentError, 'conditions must be a Hash' + end + + unless conditions.all? { |method, pattern| + @valid_conditions.include?(method) + } + raise ArgumentError, 'conditions may only include ' + + @valid_conditions.inspect + end + + route = Route.new(app, conditions, defaults, name) + @routes << route + + @recognition_key_analyzer << route.conditions + + @named_routes[route.name] = route if route.name + @generation_key_analyzer << route.generation_keys + + expire! + route + end + + def recognize(obj) + raise 'route set not finalized' unless @recognition_graph + + cache = {} + keys = @recognition_keys.map { |key| + if key.respond_to?(:call) + key.call(cache, obj) + else + obj.send(key) + end + } + + @recognition_graph[*keys].each do |route| + matches = {} + params = route.defaults.dup + + if route.conditions.all? { |method, condition| + value = obj.send(method) + if condition.is_a?(Regexp) && (m = value.match(condition)) + matches[method] = m + captures = m.captures + route.named_captures[method].each do |k, i| + if v = captures[i] + params[k] = v + end + end + true + elsif value == condition + true + else + false + end + } + if block_given? + yield route, matches, params + else + return route, matches, params + end + end + end + + nil + end + + X_CASCADE = 'X-Cascade'.freeze + PASS = 'pass'.freeze + PATH_INFO = 'PATH_INFO'.freeze + + # Rack compatible recognition and dispatching method. Routes are + # tried until one returns a non-catch status code. If no routes + # match, the catch status code is returned. + # + # This method can only be invoked after the RouteSet has been + # finalized. + def call(env) + raise 'route set not finalized' unless @recognition_graph + + env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO]) + + request = nil + req = @request_class.new(env) + recognize(req) do |route, matches, params| + # TODO: We only want to unescape params from uri related methods + params.each { |k, v| params[k] = Utils.unescape_uri(v) if v.is_a?(String) } + + if route.prefix? + env[Prefix::KEY] = matches[:path_info].to_s + end + + env[@parameters_key] = params + result = route.app.call(env) + return result unless result[1][X_CASCADE] == PASS + end + + request || [404, {'Content-Type' => 'text/html', 'X-Cascade' => 'pass'}, ['Not Found']] + end + + # Generates a url from Rack env and identifiers or significant keys. + # + # To generate a url by named route, pass the name in as a +Symbol+. + # url(env, :dashboard) # => "/dashboard" + # + # Additional parameters can be passed in as a hash + # url(env, :people, :id => "1") # => "/people/1" + # + # If no name route is given, it will fall back to a slower + # generation search. + # url(env, :controller => "people", :action => "show", :id => "1") + # # => "/people/1" + def url(env, *args) + named_route, params = nil, {} + + case args.length + when 2 + named_route, params = args[0], args[1].dup + when 1 + if args[0].is_a?(Hash) + params = args[0].dup + else + named_route = args[0] + end + else + raise ArgumentError + end + + only_path = params.delete(:only_path) + recall = env[@parameters_key] || {} + + unless result = generate(:all, named_route, params, recall, + :parameterize => lambda { |name, param| Utils.escape_uri(param) }) + return + end + + parts, params = result + return unless parts + + params.each do |k, v| + if v + params[k] = v + else + params.delete(k) + end + end + + req = stubbed_request_class.new(env) + req._stubbed_values = parts.merge(:query_string => Utils.build_nested_query(params)) + only_path ? req.fullpath : req.url + end + + def generate(method, *args) #:nodoc: + raise 'route set not finalized' unless @generation_graph + + method = nil if method == :all + named_route, params, recall, options = extract_params!(*args) + merged = recall.merge(params) + route = nil + + if named_route + if route = @named_routes[named_route.to_sym] + recall = route.defaults.merge(recall) + url = route.generate(method, params, recall, options) + [url, params] + else + raise RoutingError, "#{named_route} failed to generate from #{params.inspect}" + end + else + keys = @generation_keys.map { |key| + if k = merged[key] + k.to_s + else + nil + end + } + @generation_graph[*keys].each do |r| + next unless r.significant_params? + if url = r.generate(method, params, recall, options) + return [url, params] + end + end + + raise RoutingError, "No route matches #{params.inspect}" + end + end + + # Number of routes in the set + def length + @routes.length + end + + def rehash #:nodoc: + @recognition_keys = build_recognition_keys + @recognition_graph = build_recognition_graph + @generation_keys = build_generation_keys + @generation_graph = build_generation_graph + end + + # Finalizes the set and builds optimized data structures. You *must* + # freeze the set before you can use <tt>call</tt> and <tt>url</tt>. + # So remember to call freeze after you are done adding routes. + def freeze + unless frozen? + rehash + + @recognition_key_analyzer = nil + @generation_key_analyzer = nil + @valid_conditions = nil + + @routes.each { |route| route.freeze } + @routes.freeze + end + + super + end + + def marshal_dump #:nodoc: + hash = {} + + instance_variables_to_serialize.each do |ivar| + hash[ivar] = instance_variable_get(ivar) + end + + if graph = hash[:@recognition_graph] + hash[:@recognition_graph] = graph.dup + end + + hash + end + + def marshal_load(hash) #:nodoc: + hash.each do |ivar, value| + instance_variable_set(ivar, value) + end + end + + protected + def recognition_stats + { :keys => @recognition_keys, + :keys_size => @recognition_keys.size, + :graph_size => @recognition_graph.size, + :graph_height => @recognition_graph.height, + :graph_average_height => @recognition_graph.average_height } + end + + private + def expire! #:nodoc: + @recognition_keys = @recognition_graph = nil + @recognition_key_analyzer.expire! + + @generation_keys = @generation_graph = nil + @generation_key_analyzer.expire! + end + + def instance_variables_to_serialize + instance_variables.map { |ivar| ivar.to_sym } - [:@stubbed_request_class, :@optimized_recognize_defined] + end + + # An internal helper method for constructing a nested set from + # the linear route set. + # + # build_nested_route_set([:request_method, :path_info]) { |route, method| + # route.send(method) + # } + def build_nested_route_set(keys, &block) + graph = Multimap.new + @routes.each_with_index do |route, index| + catch(:skip) do + k = keys.map { |key| block.call(key, index) } + Utils.pop_trailing_nils!(k) + k.map! { |key| key || /.+/ } + graph[*k] = route + end + end + graph + end + + def build_recognition_graph + build_nested_route_set(@recognition_keys) { |k, i| + @recognition_key_analyzer.possible_keys[i][k] + } + end + + def build_recognition_keys + @recognition_key_analyzer.report + end + + def build_generation_graph + build_nested_route_set(@generation_keys) { |k, i| + throw :skip unless @routes[i].significant_params? + + if k = @generation_key_analyzer.possible_keys[i][k] + k.to_s + else + nil + end + } + end + + def build_generation_keys + @generation_key_analyzer.report + end + + def extract_params!(*args) + case args.length + when 4 + named_route, params, recall, options = args + when 3 + if args[0].is_a?(Hash) + params, recall, options = args + else + named_route, params, recall = args + end + when 2 + if args[0].is_a?(Hash) + params, recall = args + else + named_route, params = args + end + when 1 + if args[0].is_a?(Hash) + params = args[0] + else + named_route = args[0] + end + else + raise ArgumentError + end + + named_route ||= nil + params ||= {} + recall ||= {} + options ||= {} + + [named_route, params.dup, recall.dup, options.dup] + end + + def stubbed_request_class + @stubbed_request_class ||= begin + klass = Class.new(@request_class) + klass.public_instance_methods.each do |method| + next if method =~ /^__|object_id/ + klass.class_eval <<-RUBY + def #{method}(*args, &block) + @_stubbed_values[:#{method}] || super + end + RUBY + end + klass.class_eval { attr_accessor :_stubbed_values } + klass + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb new file mode 100644 index 0000000000..d0d8797008 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb @@ -0,0 +1,68 @@ +require 'rack/mount/strexp/parser' + +module Rack::Mount + class Strexp + class << self + # Parses segmented string expression and converts it into a Regexp + # + # Strexp.compile('foo') + # # => %r{\Afoo\Z} + # + # Strexp.compile('foo/:bar', {}, ['/']) + # # => %r{\Afoo/(?<bar>[^/]+)\Z} + # + # Strexp.compile(':foo.example.com') + # # => %r{\A(?<foo>.+)\.example\.com\Z} + # + # Strexp.compile('foo/:bar', {:bar => /[a-z]+/}, ['/']) + # # => %r{\Afoo/(?<bar>[a-z]+)\Z} + # + # Strexp.compile('foo(.:extension)') + # # => %r{\Afoo(\.(?<extension>.+))?\Z} + # + # Strexp.compile('src/*files') + # # => %r{\Asrc/(?<files>.+)\Z} + def compile(str, requirements = {}, separators = [], anchor = true) + return Regexp.compile(str) if str.is_a?(Regexp) + + requirements = requirements ? requirements.dup : {} + normalize_requirements!(requirements, separators) + + parser = StrexpParser.new + parser.anchor = anchor + parser.requirements = requirements + + begin + re = parser.scan_str(str) + rescue Racc::ParseError => e + raise RegexpError, e.message + end + + Regexp.compile(re) + end + alias_method :new, :compile + + private + def normalize_requirements!(requirements, separators) + requirements.each do |key, value| + if value.is_a?(Regexp) + if regexp_has_modifiers?(value) + requirements[key] = value + else + requirements[key] = value.source + end + else + requirements[key] = Regexp.escape(value) + end + end + requirements.default ||= separators.any? ? + "[^#{separators.join}]+" : '.+' + requirements + end + + def regexp_has_modifiers?(regexp) + regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0 + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb new file mode 100644 index 0000000000..cfe05afc61 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb @@ -0,0 +1,160 @@ +# +# DO NOT MODIFY!!!! +# This file is automatically generated by Racc 1.4.6 +# from Racc grammer file "". +# + +require 'racc/parser.rb' + +require 'rack/mount/utils' +require 'rack/mount/strexp/tokenizer' + +module Rack + module Mount + class StrexpParser < Racc::Parser + + +if Regin.regexp_supports_named_captures? + REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze +else + REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze +end + +attr_accessor :anchor, :requirements +##### State transition tables begin ### + +racc_action_table = [ + 1, 2, 3, 9, 4, 1, 2, 3, 12, 4, + 1, 2, 3, 11, 4, 1, 2, 3, nil, 4 ] + +racc_action_check = [ + 0, 0, 0, 5, 0, 3, 3, 3, 9, 3, + 8, 8, 8, 8, 8, 6, 6, 6, nil, 6 ] + +racc_action_pointer = [ + -2, nil, nil, 3, nil, 3, 13, nil, 8, 8, + nil, nil, nil ] + +racc_action_default = [ + -8, -4, -5, -8, -7, -8, -1, -3, -8, -8, + -2, -6, 13 ] + +racc_goto_table = [ + 6, 5, 10, 8, 10 ] + +racc_goto_check = [ + 2, 1, 3, 2, 3 ] + +racc_goto_pointer = [ + nil, 1, 0, -4 ] + +racc_goto_default = [ + nil, nil, nil, 7 ] + +racc_reduce_table = [ + 0, 0, :racc_error, + 1, 8, :_reduce_1, + 2, 9, :_reduce_2, + 1, 9, :_reduce_none, + 1, 10, :_reduce_4, + 1, 10, :_reduce_5, + 3, 10, :_reduce_6, + 1, 10, :_reduce_7 ] + +racc_reduce_n = 8 + +racc_shift_n = 13 + +racc_token_table = { + false => 0, + :error => 1, + :PARAM => 2, + :GLOB => 3, + :LPAREN => 4, + :RPAREN => 5, + :CHAR => 6 } + +racc_nt_base = 7 + +racc_use_result_var = true + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ + "$end", + "error", + "PARAM", + "GLOB", + "LPAREN", + "RPAREN", + "CHAR", + "$start", + "target", + "expr", + "token" ] + +Racc_debug_parser = false + +##### State transition tables end ##### + +# reduce 0 omitted + +def _reduce_1(val, _values, result) + result = anchor ? "\\A#{val.join}\\Z" : "\\A#{val.join}" + result +end + +def _reduce_2(val, _values, result) + result = val.join + result +end + +# reduce 3 omitted + +def _reduce_4(val, _values, result) + name = val[0].to_sym + requirement = requirements[name] + result = REGEXP_NAMED_CAPTURE % [name, requirement] + + result +end + +def _reduce_5(val, _values, result) + name = val[0].to_sym + requirement = requirements[name] + result = REGEXP_NAMED_CAPTURE % [name, '.+' || requirement] + + result +end + +def _reduce_6(val, _values, result) + result = "(?:#{val[1]})?" + result +end + +def _reduce_7(val, _values, result) + result = Regexp.escape(val[0]) + result +end + +def _reduce_none(val, _values, result) + val[0] +end + + end # class StrexpParser + end # module Mount +end # module Rack diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y new file mode 100644 index 0000000000..ffbd9fae11 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y @@ -0,0 +1,34 @@ +class Rack::Mount::StrexpParser +rule + target: expr { result = anchor ? "\\A#{val.join}\\Z" : "\\A#{val.join}" } + + expr: expr token { result = val.join } + | token + + token: PARAM { + name = val[0].to_sym + requirement = requirements[name] + result = REGEXP_NAMED_CAPTURE % [name, requirement] + } + | GLOB { + name = val[0].to_sym + requirement = requirements[name] + result = REGEXP_NAMED_CAPTURE % [name, '.+' || requirement] + } + | LPAREN expr RPAREN { result = "(?:#{val[1]})?" } + | CHAR { result = Regexp.escape(val[0]) } +end + +---- header ---- +require 'rack/mount/utils' +require 'rack/mount/strexp/tokenizer' + +---- inner + +if Regin.regexp_supports_named_captures? + REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze +else + REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze +end + +attr_accessor :anchor, :requirements diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb new file mode 100644 index 0000000000..0ff7f67661 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb @@ -0,0 +1,83 @@ +#-- +# DO NOT MODIFY!!!! +# This file is automatically generated by rex 1.0.5.beta1 +# from lexical definition file "lib/rack/mount/strexp/tokenizer.rex". +#++ + +require 'racc/parser' +class Rack::Mount::StrexpParser < Racc::Parser + require 'strscan' + + class ScanError < StandardError ; end + + attr_reader :lineno + attr_reader :filename + attr_accessor :state + + def scan_setup(str) + @ss = StringScanner.new(str) + @lineno = 1 + @state = nil + end + + def action + yield + end + + def scan_str(str) + scan_setup(str) + do_parse + end + alias :scan :scan_str + + def load_file( filename ) + @filename = filename + open(filename, "r") do |f| + scan_setup(f.read) + end + end + + def scan_file( filename ) + load_file(filename) + do_parse + end + + + def next_token + return if @ss.eos? + + text = @ss.peek(1) + @lineno += 1 if text == "\n" + token = case @state + when nil + case + when (text = @ss.scan(/\\(\(|\)|:|\*)/)) + action { [:CHAR, @ss[1]] } + + when (text = @ss.scan(/\:([a-zA-Z_]\w*)/)) + action { [:PARAM, @ss[1]] } + + when (text = @ss.scan(/\*([a-zA-Z_]\w*)/)) + action { [:GLOB, @ss[1]] } + + when (text = @ss.scan(/\(/)) + action { [:LPAREN, text] } + + when (text = @ss.scan(/\)/)) + action { [:RPAREN, text] } + + when (text = @ss.scan(/./)) + action { [:CHAR, text] } + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '" + text + "'" + end # if + + else + raise ScanError, "undefined state: '" + state.to_s + "'" + end # case state + token + end # def next_token + +end # class diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex new file mode 100644 index 0000000000..473bd096e1 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex @@ -0,0 +1,12 @@ +class Rack::Mount::StrexpParser +macro + RESERVED \(|\)|:|\* + ALPHA_U [a-zA-Z_] +rule + \\({RESERVED}) { [:CHAR, @ss[1]] } + \:({ALPHA_U}\w*) { [:PARAM, @ss[1]] } + \*({ALPHA_U}\w*) { [:GLOB, @ss[1]] } + \( { [:LPAREN, text] } + \) { [:RPAREN, text] } + . { [:CHAR, text] } +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb new file mode 100644 index 0000000000..aa23b1162f --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb @@ -0,0 +1,148 @@ +begin + require 'regin' +rescue LoadError + $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/regin')) + require 'regin' +end + +require 'uri' + +module Rack::Mount + # Private utility methods used throughout Rack::Mount. + #-- + # This module is a trash can. Try to move these functions into + # more appropriate contexts. + #++ + module Utils + # Normalizes URI path. + # + # Strips off trailing slash and ensures there is a leading slash. + # + # normalize_path("/foo") # => "/foo" + # normalize_path("/foo/") # => "/foo" + # normalize_path("foo") # => "/foo" + # normalize_path("") # => "/" + def normalize_path(path) + path = "/#{path}" + path.squeeze!('/') + path.sub!(%r{/+\Z}, '') + path = '/' if path == '' + path + end + module_function :normalize_path + + # Removes trailing nils from array. + # + # pop_trailing_nils!([1, 2, 3]) # => [1, 2, 3] + # pop_trailing_nils!([1, 2, 3, nil, nil]) # => [1, 2, 3] + # pop_trailing_nils!([nil]) # => [] + def pop_trailing_nils!(ary) + while ary.length > 0 && ary.last.nil? + ary.pop + end + ary + end + module_function :pop_trailing_nils! + + RESERVED_PCHAR = ':@&=+$,;%' + SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}" + if RUBY_VERSION >= '1.9' + UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze + else + UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze + end + + Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI + + def escape_uri(uri) + Parser.escape(uri.to_s, UNSAFE_PCHAR) + end + module_function :escape_uri + + if ''.respond_to?(:force_encoding) + def unescape_uri(uri) + Parser.unescape(uri).force_encoding('utf-8') + end + else + def unescape_uri(uri) + URI.unescape(uri) + end + end + module_function :unescape_uri + + # Taken from Rack 1.1.x to build nested query strings + def build_nested_query(value, prefix = nil) #:nodoc: + case value + when Array + value.map { |v| + build_nested_query(v, "#{prefix}[]") + }.join("&") + when Hash + value.map { |k, v| + build_nested_query(v, prefix ? "#{prefix}[#{Rack::Utils.escape(k)}]" : Rack::Utils.escape(k)) + }.join("&") + when String + raise ArgumentError, "value must be a Hash" if prefix.nil? + "#{prefix}=#{Rack::Utils.escape(value)}" + else + prefix + end + end + module_function :build_nested_query + + # Determines whether the regexp must match the entire string. + # + # regexp_anchored?(/^foo$/) # => true + # regexp_anchored?(/foo/) # => false + # regexp_anchored?(/^foo/) # => false + # regexp_anchored?(/foo$/) # => false + def regexp_anchored?(regexp) + regexp.source =~ /\A(\\A|\^).*(\\Z|\$)\Z/m ? true : false + end + module_function :regexp_anchored? + + def normalize_extended_expression(regexp) + return regexp unless regexp.options & Regexp::EXTENDED != 0 + source = regexp.source + source.gsub!(/#.+$/, '') + source.gsub!(/\s+/, '') + source.gsub!(/\\\//, '/') + Regexp.compile(source) + end + module_function :normalize_extended_expression + + def parse_regexp(regexp) + cache = @@_parse_regexp_cache ||= {} + + if expression = cache[regexp] + return expression + end + + unless regexp.is_a?(RegexpWithNamedGroups) + regexp = RegexpWithNamedGroups.new(regexp) + end + + expression = Regin.parse(regexp) + + unless Regin.regexp_supports_named_captures? + tag_captures = Proc.new do |group| + case group + when Regin::Group + # TODO: dup instead of mutating + group.instance_variable_set('@name', regexp.names[group.index]) if group.index + tag_captures.call(group.expression) + when Regin::Expression + group.each { |child| tag_captures.call(child) } + end + end + tag_captures.call(expression) + end + + cache[regexp] = expression.freeze + expression + rescue Racc::ParseError, Regin::Parser::ScanError + [] + end + module_function :parse_regexp + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb new file mode 100644 index 0000000000..0b49b49280 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb @@ -0,0 +1,569 @@ +require 'forwardable' +require 'multiset' + +# Multimap is a generalization of a map or associative array +# abstract data type in which more than one value may be associated +# with and returned for a given key. +# +# == Example +# +# require 'multimap' +# map = Multimap.new +# map["a"] = 100 +# map["b"] = 200 +# map["a"] = 300 +# map["a"] # -> [100, 300] +# map["b"] # -> [200] +# map.keys # -> #<Multiset: {a, a, b}> +class Multimap + extend Forwardable + + include Enumerable + + # call-seq: + # Multimap[ [key =>|, value]* ] => multimap + # + # Creates a new multimap populated with the given objects. + # + # Multimap["a", 100, "b", 200] #=> {"a"=>[100], "b"=>[200]} + # Multimap["a" => 100, "b" => 200] #=> {"a"=>[100], "b"=>[200]} + def self.[](*args) + default = [] + + if args.size == 2 && args.last.is_a?(Hash) + default = args.shift + elsif !args.first.is_a?(Hash) && args.size % 2 == 1 + default = args.shift + end + + if args.size == 1 && args.first.is_a?(Hash) + args[0] = args.first.inject({}) { |hash, (key, value)| + unless value.is_a?(default.class) + value = (default.dup << value) + end + hash[key] = value + hash + } + else + index = 0 + args.map! { |value| + unless index % 2 == 0 || value.is_a?(default.class) + value = (default.dup << value) + end + index += 1 + value + } + end + + map = new + map.instance_variable_set(:@hash, Hash[*args]) + map.default = default + map + end + + # call-seq: + # Multimap.new => multimap + # Multimap.new(default) => multimap + # + # Returns a new, empty multimap. + # + # map = Multimap.new(Set.new) + # h["a"] = 100 + # h["b"] = 200 + # h["a"] #=> [100].to_set + # h["c"] #=> [].to_set + def initialize(default = []) + @hash = Hash.new(default) + end + + def initialize_copy(original) #:nodoc: + @hash = Hash.new(original.default.dup) + original._internal_hash.each_pair do |key, container| + @hash[key] = container.dup + end + end + + def_delegators :@hash, :clear, :default, :default=, :empty?, + :fetch, :has_key?, :key? + + # Retrieves the <i>value</i> object corresponding to the + # <i>*keys</i> object. + def [](key) + @hash[key] + end + + # call-seq: + # map[key] = value => value + # map.store(key, value) => value + # + # Associates the value given by <i>value</i> with the key + # given by <i>key</i>. Unlike a regular hash, multiple can be + # assoicated with the same value. + # + # map = Multimap["a" => 100, "b" => 200] + # map["a"] = 9 + # map["c"] = 4 + # map #=> {"a" => [100, 9], "b" => [200], "c" => [4]} + def store(key, value) + update_container(key) do |container| + container << value + container + end + end + alias_method :[]=, :store + + # call-seq: + # map.delete(key, value) => value + # map.delete(key) => value + # + # Deletes and returns a key-value pair from <i>map</i>. If only + # <i>key</i> is given, all the values matching that key will be + # deleted. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.delete("b", 300) #=> 300 + # map.delete("a") #=> [100] + def delete(key, value = nil) + if value + @hash[key].delete(value) + else + @hash.delete(key) + end + end + + # call-seq: + # map.each { |key, value| block } => map + # + # Calls <i>block</i> for each key/value pair in <i>map</i>, passing + # the key and value to the block as a two-element array. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.each { |key, value| puts "#{key} is #{value}" } + # + # <em>produces:</em> + # + # a is 100 + # b is 200 + # b is 300 + def each + each_pair do |key, value| + yield [key, value] + end + end + + # call-seq: + # map.each_association { |key, container| block } => map + # + # Calls <i>block</i> once for each key/container in <i>map</i>, passing + # the key and container to the block as parameters. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.each_association { |key, container| puts "#{key} is #{container}" } + # + # <em>produces:</em> + # + # a is [100] + # b is [200, 300] + def each_association(&block) + @hash.each_pair(&block) + end + + # call-seq: + # map.each_container { |container| block } => map + # + # Calls <i>block</i> for each container in <i>map</i>, passing the + # container as a parameter. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.each_container { |container| puts container } + # + # <em>produces:</em> + # + # [100] + # [200, 300] + def each_container + each_association do |_, container| + yield container + end + end + + # call-seq: + # map.each_key { |key| block } => map + # + # Calls <i>block</i> for each key in <i>hsh</i>, passing the key + # as a parameter. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.each_key { |key| puts key } + # + # <em>produces:</em> + # + # a + # b + # b + def each_key + each_pair do |key, _| + yield key + end + end + + # call-seq: + # map.each_pair { |key_value_array| block } => map + # + # Calls <i>block</i> for each key/value pair in <i>map</i>, + # passing the key and value as parameters. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.each_pair { |key, value| puts "#{key} is #{value}" } + # + # <em>produces:</em> + # + # a is 100 + # b is 200 + # b is 300 + def each_pair + each_association do |key, values| + values.each do |value| + yield key, value + end + end + end + + # call-seq: + # map.each_value { |value| block } => map + # + # Calls <i>block</i> for each key in <i>map</i>, passing the + # value as a parameter. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.each_value { |value| puts value } + # + # <em>produces:</em> + # + # 100 + # 200 + # 300 + def each_value + each_pair do |_, value| + yield value + end + end + + def ==(other) #:nodoc: + case other + when Multimap + @hash == other._internal_hash + else + @hash == other + end + end + + def eql?(other) #:nodoc: + case other + when Multimap + @hash.eql?(other._internal_hash) + else + @hash.eql?(other) + end + end + + def freeze #:nodoc: + each_container { |container| container.freeze } + default.freeze + super + end + + # call-seq: + # map.has_value?(value) => true or false + # map.value?(value) => true or false + # + # Returns <tt>true</tt> if the given value is present for any key + # in <i>map</i>. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.has_value?(300) #=> true + # map.has_value?(999) #=> false + def has_value?(value) + values.include?(value) + end + alias_method :value?, :has_value? + + # call-seq: + # map.index(value) => key + # + # Returns the key for a given value. If not found, returns + # <tt>nil</tt>. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.index(100) #=> "a" + # map.index(200) #=> "b" + # map.index(999) #=> nil + def index(value) + invert[value] + end + + # call-seq: + # map.delete_if {| key, value | block } -> map + # + # Deletes every key-value pair from <i>map</i> for which <i>block</i> + # evaluates to <code>true</code>. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.delete_if {|key, value| value >= 300 } + # #=> Multimap["a" => 100, "b" => 200] + # + def delete_if + each_association do |key, container| + container.delete_if do |value| + yield [key, value] + end + end + self + end + + # call-seq: + # map.reject {| key, value | block } -> map + # + # Same as <code>Multimap#delete_if</code>, but works on (and returns) a + # copy of the <i>map</i>. Equivalent to + # <code><i>map</i>.dup.delete_if</code>. + # + def reject(&block) + dup.delete_if(&block) + end + + # call-seq: + # map.reject! {| key, value | block } -> map or nil + # + # Equivalent to <code>Multimap#delete_if</code>, but returns + # <code>nil</code> if no changes were made. + # + def reject!(&block) + old_size = size + delete_if(&block) + old_size == size ? nil : self + end + + # call-seq: + # map.replace(other_map) => map + # + # Replaces the contents of <i>map</i> with the contents of + # <i>other_map</i>. + # + # map = Multimap["a" => 100, "b" => 200] + # map.replace({ "c" => 300, "d" => 400 }) + # #=> Multimap["c" => 300, "d" => 400] + def replace(other) + case other + when Array + @hash.replace(self.class[self.default, *other]) + when Hash + @hash.replace(self.class[self.default, other]) + when self.class + @hash.replace(other) + else + raise ArgumentError + end + end + + # call-seq: + # map.invert => multimap + # + # Returns a new multimap created by using <i>map</i>'s values as keys, + # and the keys as values. + # + # map = Multimap["n" => 100, "m" => 100, "d" => [200, 300]] + # map.invert #=> Multimap[100 => ["n", "m"], 200 => "d", 300 => "d"] + def invert + h = self.class.new(default.dup) + each_pair { |key, value| h[value] = key } + h + end + + # call-seq: + # map.keys => multiset + # + # Returns a new +Multiset+ populated with the keys from this hash. See also + # <tt>Multimap#values</tt> and <tt>Multimap#containers</tt>. + # + # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400] + # map.keys #=> Multiset.new(["a", "b", "b", "c"]) + def keys + keys = Multiset.new + each_key { |key| keys << key } + keys + end + + # Returns true if the given key is present in Multimap. + def include?(key) + keys.include?(key) + end + alias_method :member?, :include? + + # call-seq: + # map.length => fixnum + # map.size => fixnum + # + # Returns the number of key-value pairs in the map. + # + # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400] + # map.length #=> 4 + # map.delete("a") #=> 100 + # map.length #=> 3 + def size + values.size + end + alias_method :length, :size + + # call-seq: + # map.merge(other_map) => multimap + # + # Returns a new multimap containing the contents of <i>other_map</i> and + # the contents of <i>map</i>. + # + # map1 = Multimap["a" => 100, "b" => 200] + # map2 = Multimap["a" => 254, "c" => 300] + # map2.merge(map2) #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300] + # map1 #=> Multimap["a" => 100, "b" => 200] + def merge(other) + dup.update(other) + end + + # call-seq: + # map.merge!(other_map) => multimap + # map.update(other_map) => multimap + # + # Adds each pair from <i>other_map</i> to <i>map</i>. + # + # map1 = Multimap["a" => 100, "b" => 200] + # map2 = Multimap["b" => 254, "c" => 300] + # + # map1.merge!(map2) + # #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300] + def update(other) + case other + when self.class + other.each_pair { |key, value| store(key, value) } + when Hash + update(self.class[self.default, other]) + else + raise ArgumentError + end + self + end + alias_method :merge!, :update + + # call-seq: + # map.select { |key, value| block } => multimap + # + # Returns a new Multimap consisting of the pairs for which the + # block returns true. + # + # map = Multimap["a" => 100, "b" => 200, "c" => 300] + # map.select { |k,v| k > "a" } #=> Multimap["b" => 200, "c" => 300] + # map.select { |k,v| v < 200 } #=> Multimap["a" => 100] + def select + inject(self.class.new) { |map, (key, value)| + map[key] = value if yield([key, value]) + map + } + end + + # call-seq: + # map.to_a => array + # + # Converts <i>map</i> to a nested array of [<i>key, + # value</i>] arrays. + # + # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400] + # map.to_a #=> [["a", 100], ["b", 200], ["b", 300], ["c", 400]] + def to_a + ary = [] + each_pair do |key, value| + ary << [key, value] + end + ary + end + + # call-seq: + # map.to_hash => hash + # + # Converts <i>map</i> to a basic hash. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.to_hash #=> { "a" => [100], "b" => [200, 300] } + def to_hash + @hash.dup + end + + # call-seq: + # map.containers => array + # + # Returns a new array populated with the containers from <i>map</i>. See + # also <tt>Multimap#keys</tt> and <tt>Multimap#values</tt>. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.containers #=> [[100], [200, 300]] + def containers + containers = [] + each_container { |container| containers << container } + containers + end + + # call-seq: + # map.values => array + # + # Returns a new array populated with the values from <i>map</i>. See + # also <tt>Multimap#keys</tt> and <tt>Multimap#containers</tt>. + # + # map = Multimap["a" => 100, "b" => [200, 300]] + # map.values #=> [100, 200, 300] + def values + values = [] + each_value { |value| values << value } + values + end + + # Return an array containing the values associated with the given keys. + def values_at(*keys) + @hash.values_at(*keys) + end + + def marshal_dump #:nodoc: + @hash + end + + def marshal_load(hash) #:nodoc: + @hash = hash + end + + def to_yaml(opts = {}) #:nodoc: + YAML::quick_emit(self, opts) do |out| + out.map(taguri, to_yaml_style) do |map| + @hash.each do |k, v| + map.add(k, v) + end + map.add('__default__', @hash.default) + end + end + end + + def yaml_initialize(tag, val) #:nodoc: + default = val.delete('__default__') + @hash = val + @hash.default = default + self + end + + protected + def _internal_hash #:nodoc: + @hash + end + + def update_container(key) #:nodoc: + container = @hash[key] + container = container.dup if container.equal?(default) + container = yield(container) + @hash[key] = container + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb new file mode 100644 index 0000000000..119bf12646 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb @@ -0,0 +1,185 @@ +require 'set' + +# Multiset implements a collection of unordered values and +# allows duplicates. +# +# == Example +# +# require 'multiset' +# s1 = Multiset.new [1, 2] # -> #<Multiset: {1, 2}> +# s1.add(2) # -> #<Multiset: {1, 2, 2}> +# s1.merge([2, 6]) # -> #<Multiset: {1, 2, 2, 2, 3}> +# s1.multiplicity(2) # -> 3 +# s1.multiplicity(3) # -> 1 +class Multiset < Set + def initialize(*args, &block) #:nodoc: + @hash = Hash.new(0) + super + end + + # Returns the number of times an element belongs to the multiset. + def multiplicity(e) + @hash[e] + end + + # Returns the total number of elements in a multiset, including + # repeated memberships + def cardinality + @hash.inject(0) { |s, (e, m)| s += m } + end + alias_method :size, :cardinality + alias_method :length, :cardinality + + # Converts the set to an array. The order of elements is uncertain. + def to_a + inject([]) { |ary, (key, _)| ary << key } + end + + # Returns true if the set is a superset of the given set. + def superset?(set) + set.is_a?(self.class) or raise ArgumentError, "value must be a set" + return false if cardinality < set.cardinality + set.all? { |o| set.multiplicity(o) <= multiplicity(o) } + end + + # Returns true if the set is a proper superset of the given set. + def proper_superset?(set) + set.is_a?(self.class) or raise ArgumentError, "value must be a set" + return false if cardinality <= set.cardinality + set.all? { |o| set.multiplicity(o) <= multiplicity(o) } + end + + # Returns true if the set is a subset of the given set. + def subset?(set) + set.is_a?(self.class) or raise ArgumentError, "value must be a set" + return false if set.cardinality < cardinality + all? { |o| multiplicity(o) <= set.multiplicity(o) } + end + + # Returns true if the set is a proper subset of the given set. + def proper_subset?(set) + set.is_a?(self.class) or raise ArgumentError, "value must be a set" + return false if set.cardinality <= cardinality + all? { |o| multiplicity(o) <= set.multiplicity(o) } + end + + # Calls the given block once for each element in the set, passing + # the element as parameter. Returns an enumerator if no block is + # given. + def each + @hash.each_pair do |key, multiplicity| + multiplicity.times do + yield(key) + end + end + self + end + + # Adds the given object to the set and returns self. Use +merge+ to + # add many elements at once. + def add(o) + @hash[o] ||= 0 + @hash[o] += 1 + self + end + alias << add + + undef :add? + + # Deletes all the identical object from the set and returns self. + # If +n+ is given, it will remove that amount of identical objects + # from the set. Use +subtract+ to delete many different items at + # once. + def delete(o, n = nil) + if n + @hash[o] ||= 0 + @hash[o] -= n if @hash[o] > 0 + @hash.delete(o) if @hash[o] == 0 + else + @hash.delete(o) + end + self + end + + undef :delete? + + # Deletes every element of the set for which block evaluates to + # true, and returns self. + def delete_if + each { |o| delete(o) if yield(o) } + self + end + + # Merges the elements of the given enumerable object to the set and + # returns self. + def merge(enum) + enum.each { |o| add(o) } + self + end + + # Deletes every element that appears in the given enumerable object + # and returns self. + def subtract(enum) + enum.each { |o| delete(o, 1) } + self + end + + # Returns a new set containing elements common to the set and the + # given enumerable object. + def &(enum) + s = dup + n = self.class.new + enum.each { |o| + if s.include?(o) + s.delete(o, 1) + n.add(o) + end + } + n + end + alias intersection & + + # Returns a new set containing elements exclusive between the set + # and the given enumerable object. (set ^ enum) is equivalent to + # ((set | enum) - (set & enum)). + def ^(enum) + n = self.class.new(enum) + each { |o| n.include?(o) ? n.delete(o, 1) : n.add(o) } + n + end + + # Returns true if two sets are equal. Two multisets are equal if + # they have the same cardinalities and each element has the same + # multiplicity in both sets. The equality of each element inside + # the multiset is defined according to Object#eql?. + def eql?(set) + return true if equal?(set) + set = self.class.new(set) unless set.is_a?(self.class) + return false unless cardinality == set.cardinality + superset?(set) && subset?(set) + end + alias_method :==, :eql? + + def marshal_dump #:nodoc: + @hash + end + + def marshal_load(hash) #:nodoc: + @hash = hash + end + + def to_yaml(opts = {}) #:nodoc: + YAML::quick_emit(self, opts) do |out| + out.map(taguri, to_yaml_style) do |map| + @hash.each do |k, v| + map.add(k, v) + end + end + end + end + + def yaml_initialize(tag, val) #:nodoc: + @hash = val + self + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb new file mode 100644 index 0000000000..4eb088b91a --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb @@ -0,0 +1,158 @@ +require 'multimap' + +# NestedMultimap allows values to be assoicated with a nested +# set of keys. +class NestedMultimap < Multimap + # call-seq: + # multimap[*keys] = value => value + # multimap.store(*keys, value) => value + # + # Associates the value given by <i>value</i> with multiple key + # given by <i>keys</i>. + # + # map = NestedMultimap.new + # map["a"] = 100 + # map["a", "b"] = 101 + # map["a"] = 102 + # map #=> {"a"=>{"b"=>[100, 101, 102], default => [100, 102]}} + def store(*args) + keys = args + value = args.pop + + raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value + + if keys.length > 1 + update_container(keys.shift) do |container| + container = self.class.new(container) unless container.is_a?(self.class) + container[*keys] = value + container + end + elsif keys.length == 1 + super(keys.first, value) + else + self << value + end + end + alias_method :[]=, :store + + # call-seq: + # multimap << obj => multimap + # + # Pushes the given object on to the end of all the containers. + # + # map = NestedMultimap["a" => [100], "b" => [200, 300]] + # map << 300 + # map["a"] #=> [100, 300] + # map["c"] #=> [300] + def <<(value) + @hash.each_value { |container| container << value } + self.default << value + self + end + + # call-seq: + # multimap[*keys] => value + # multimap[key1, key2, key3] => value + # + # Retrieves the <i>value</i> object corresponding to the + # <i>*keys</i> object. + def [](*keys) + i, l, r, k = 0, keys.length, self, self.class + while r.is_a?(k) + r = i < l ? r._internal_hash[keys[i]] : r.default + i += 1 + end + r + end + + # call-seq: + # multimap.each_association { |key, container| block } => multimap + # + # Calls <i>block</i> once for each key/container in <i>map</i>, passing + # the key and container to the block as parameters. + # + # map = NestedMultimap.new + # map["a"] = 100 + # map["a", "b"] = 101 + # map["a"] = 102 + # map["c"] = 200 + # map.each_association { |key, container| puts "#{key} is #{container}" } + # + # <em>produces:</em> + # + # ["a", "b"] is [100, 101, 102] + # "c" is [200] + def each_association + super() do |key, container| + if container.respond_to?(:each_association) + container.each_association do |nested_key, value| + yield [key, nested_key].flatten, value + end + else + yield key, container + end + end + end + + # call-seq: + # multimap.each_container_with_default { |container| block } => map + # + # Calls <i>block</i> for every container in <i>map</i> including + # the default, passing the container as a parameter. + # + # map = NestedMultimap.new + # map["a"] = 100 + # map["a", "b"] = 101 + # map["a"] = 102 + # map.each_container_with_default { |container| puts container } + # + # <em>produces:</em> + # + # [100, 101, 102] + # [100, 102] + # [] + def each_container_with_default(&block) + @hash.each_value do |container| + iterate_over_container(container, &block) + end + iterate_over_container(default, &block) + self + end + + # call-seq: + # multimap.containers_with_default => array + # + # Returns a new array populated with all the containers from + # <i>map</i> including the default. + # + # map = NestedMultimap.new + # map["a"] = 100 + # map["a", "b"] = 101 + # map["a"] = 102 + # map.containers_with_default #=> [[100, 101, 102], [100, 102], []] + def containers_with_default + containers = [] + each_container_with_default { |container| containers << container } + containers + end + + def inspect #:nodoc: + super.gsub(/\}$/, ", default => #{default.inspect}}") + end + + private + def iterate_over_container(container) + if container.respond_to?(:each_container_with_default) + container.each_container_with_default do |value| + yield value + end + else + yield container + end + end +end + +begin + require 'nested_multimap_ext' +rescue LoadError +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb new file mode 100644 index 0000000000..d38922bcc6 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb @@ -0,0 +1,45 @@ +module Regin + autoload :Alternation, 'regin/alternation' + autoload :Anchor, 'regin/anchor' + autoload :Atom, 'regin/atom' + autoload :Character, 'regin/character' + autoload :CharacterClass, 'regin/character_class' + autoload :Collection, 'regin/collection' + autoload :Expression, 'regin/expression' + autoload :Group, 'regin/group' + autoload :Options, 'regin/options' + autoload :Parser, 'regin/parser' + + class << self + begin + eval('foo = /(?<foo>.*)/').named_captures + + # Returns true if the interpreter is using the Oniguruma Regexp lib + # and supports named captures. + # + # /(?<foo>bar)/ + def regexp_supports_named_captures? + true + end + rescue SyntaxError, NoMethodError + def regexp_supports_named_captures? #:nodoc: + false + end + end + + # Parses Regexp and returns a Expression data structure. + def parse(regexp) + Parser.parse_regexp(regexp) + end + + # Recompiles Regexp by parsing it and turning it back into a Regexp. + # + # (In the future Regin will perform some Regexp optimizations + # such as removing unnecessary captures and options) + def compile(source) + regexp = Regexp.compile(source) + expression = parse(regexp) + Regexp.compile(expression.to_s(true), expression.flags) + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb new file mode 100644 index 0000000000..ce4f52bfdb --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb @@ -0,0 +1,40 @@ +module Regin + class Alternation < Collection + def initialize(*args) + args, options = extract_options(args) + + if args.length == 1 && args.first.instance_of?(Array) + super(args.first) + else + super(args) + end + + if options.key?(:ignorecase) + @array.map! { |e| e.dup(:ignorecase => options[:ignorecase]) } + end + end + + # Returns true if expression could be treated as a literal string. + # + # Alternation groups are never literal. + def literal? + false + end + + def flags + 0 + end + + def dup(options = {}) + self.class.new(to_a, options) + end + + def to_s(parent = false) + map { |e| e.to_s(parent) }.join('|') + end + + def inspect #:nodoc: + to_s.inspect + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb new file mode 100644 index 0000000000..05520dd5e0 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb @@ -0,0 +1,4 @@ +module Regin + class Anchor < Atom + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb new file mode 100644 index 0000000000..eb1923a5a1 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb @@ -0,0 +1,59 @@ +module Regin + class Atom + attr_reader :value, :ignorecase + + def initialize(value, options = {}) + @value = value + @ignorecase = options[:ignorecase] + end + + def option_names + %w( ignorecase ) + end + + # Returns true if expression could be treated as a literal string. + def literal? + false + end + + def casefold? + ignorecase ? true : false + end + + def dup(options = {}) + original_options = option_names.inject({}) do |h, m| + h[m.to_sym] = send(m) + h + end + self.class.new(value, original_options.merge(options)) + end + + def to_s(parent = false) + "#{value}" + end + + def inspect #:nodoc: + "#<#{self.class.to_s.sub('Regin::', '')} #{to_s.inspect}>" + end + + def ==(other) #:nodoc: + case other + when String + other == to_s + else + eql?(other) + end + end + + def eql?(other) #:nodoc: + other.instance_of?(self.class) && + self.value.eql?(other.value) && + (!!self.ignorecase).eql?(!!other.ignorecase) + end + + def freeze #:nodoc: + value.freeze + super + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb new file mode 100644 index 0000000000..12a9199d2a --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb @@ -0,0 +1,56 @@ +module Regin + class Character < Atom + attr_reader :quantifier + + def initialize(value, options = {}) + @quantifier = options[:quantifier] + super + end + + def option_names + %w( quantifier ) + super + end + + # Returns true if expression could be treated as a literal string. + # + # A Character is literal is there is no quantifier attached to it. + def literal? + quantifier.nil? && !ignorecase + end + + def to_s(parent = false) + if !parent && ignorecase + "(?i-mx:#{value})#{quantifier}" + else + "#{value}#{quantifier}" + end + end + + def to_regexp(anchored = false) + re = to_s(true) + re = "\\A#{re}\\Z" if anchored + Regexp.compile(re, ignorecase) + end + + def match(char) + to_regexp(true).match(char) + end + + def include?(char) + if ignorecase + value.downcase == char.downcase + else + value == char + end + end + + def eql?(other) #:nodoc: + super && quantifier.eql?(other.quantifier) + end + + def freeze #:nodoc: + quantifier.freeze if quantifier + super + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb new file mode 100644 index 0000000000..caed5ef9d0 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb @@ -0,0 +1,55 @@ +module Regin + class CharacterClass < Character + def initialize(value, options = {}) + @negate = options[:negate] + super + end + + def option_names + %w( negate ) + super + end + + attr_reader :negate + + def negated? + negate ? true : false + end + + # Returns true if expression could be treated as a literal string. + # + # A CharacterClass is never literal. + def literal? + false + end + + def bracketed? + value != '.' && value !~ /^\\[dDsSwW]$/ + end + + def to_s(parent = false) + if bracketed? + if !parent && ignorecase + "(?i-mx:[#{negate && '^'}#{value}])#{quantifier}" + else + "[#{negate && '^'}#{value}]#{quantifier}" + end + else + super + end + end + + def include?(char) + re = quantifier ? to_s.sub(/#{Regexp.escape(quantifier)}$/, '') : to_s + Regexp.compile("\\A#{re}\\Z").match(char) + end + + def eql?(other) #:nodoc: + super && negate == other.negate + end + + def freeze #:nodoc: + negate.freeze if negate + super + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb new file mode 100644 index 0000000000..b60353268a --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb @@ -0,0 +1,83 @@ +module Regin + class Collection + include Enumerable + + def initialize(*args) + @array = Array.new(*args) + end + + def each + @array.each{ |item| yield item } + end + + def [](i) + @array[i] + end + + def length + @array.length + end + alias_method :size, :length + + def first + @array.first + end + + def last + @array.last + end + + def +(other) + ary = other.is_a?(self.class) ? other.internal_array : other + self.class.new(@array + ary) + end + + def to_regexp(anchored = false) + re = to_s(true) + re = "\\A#{re}\\Z" if anchored + Regexp.compile(re, flags) + end + + def match(char) + to_regexp.match(char) + end + + def include?(char) + any? { |e| e.include?(char) } + end + + def ==(other) #:nodoc: + case other + when String + other == to_s + when Array + other == @array + else + eql?(other) + end + end + + def eql?(other) #:nodoc: + other.instance_of?(self.class) && @array.eql?(other.internal_array) + end + + def freeze #:nodoc: + each { |e| e.freeze } + @array.freeze + super + end + + protected + def internal_array #:nodoc: + @array + end + + def extract_options(args) + if args.last.is_a?(Hash) + return args[0..-2], args.last + else + return args, {} + end + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb new file mode 100644 index 0000000000..18e4965097 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb @@ -0,0 +1,126 @@ +module Regin + class Expression < Collection + attr_reader :ignorecase, :multiline, :extended + + def initialize(*args) + args, options = extract_options(args) + + @multiline = @ignorecase = @extended = nil + + if args.length == 1 && args.first.instance_of?(Array) + super(args.first) + else + args = args.map { |e| e.instance_of?(String) ? Character.new(e) : e } + super(args) + end + + self.multiline = options[:multiline] if options.key?(:multiline) + self.ignorecase = options[:ignorecase] if options.key?(:ignorecase) + self.extended = options[:extended] if options.key?(:extended) + end + + # Returns true if expression could be treated as a literal string. + # + # A Expression is literal if all its elements are literal. + def literal? + !ignorecase && all? { |e| e.literal? } + end + + def anchored? + anchored_to_start? && anchored_to_end? + end + + def anchored_to_start? + first.is_a?(Anchor) && first == '\A' + end + + def anchored_to_end? + last.is_a?(Anchor) && last == '\Z' + end + + def anchored_to_line? + anchored_to_start_of_line? && anchored_to_end_of_line? + end + + def anchored_to_start_of_line? + anchored_to_start? || (first.is_a?(Anchor) && first == '^') + end + + def anchored_to_end_of_line? + anchored_to_end? || (last.is_a?(Anchor) && last == '$') + end + + def options? + options.any?(true) + end + + def flags + options.to_i + end + + def +(other) + ary = other.is_a?(self.class) ? other.internal_array : other + ary = @array + ary + [options.to_h(true)] + self.class.new(*ary) + end + + def dup(options = {}) + expression = super() + expression.multiline = options[:multiline] if options.key?(:multiline) + expression.ignorecase = options[:ignorecase] if options.key?(:ignorecase) + expression.extended = options[:extended] if options.key?(:extended) + expression + end + + def to_s(parent = false) + if parent || !options? + map { |e| e.to_s(parent) }.join + else + with, without = [], [] + multiline ? (with << 'm') : (without << 'm') + ignorecase ? (with << 'i') : (without << 'i') + extended ? (with << 'x') : (without << 'x') + + with = with.join + without = without.any? ? "-#{without.join}" : '' + + "(?#{with}#{without}:#{map { |e| e.to_s(true) }.join})" + end + end + + def inspect #:nodoc: + "#<Expression #{to_s.inspect}>" + end + + def casefold? + ignorecase + end + + def eql?(other) #:nodoc: + super && + !!self.multiline == !!other.multiline && + !!self.ignorecase == !!other.ignorecase && + !!self.extended == !!other.extended + end + + protected + def options + Options.new(multiline, ignorecase, extended) + end + + def multiline=(multiline) + @multiline = multiline + end + + def ignorecase=(ignorecase) + if @ignorecase.nil? + @array.map! { |e| e.dup(:ignorecase => ignorecase) } + @ignorecase = ignorecase + end + end + + def extended=(extended) + @extended = extended + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb new file mode 100644 index 0000000000..d682148bd9 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb @@ -0,0 +1,90 @@ +module Regin + class Group + attr_reader :expression, :quantifier, :capture, :index, :name + + def initialize(expression, options = {}) + @quantifier = @index = @name = nil + @capture = true + @expression = expression.dup(options) + + @quantifier = options[:quantifier] if options.key?(:quantifier) + @capture = options[:capture] if options.key?(:capture) + @index = options[:index] if options.key?(:index) + @name = options[:name] if options.key?(:name) + end + + def option_names + %w( quantifier capture index name ) + end + + # Returns true if expression could be treated as a literal string. + # + # A Group is literal if its expression is literal and it has no quantifier. + def literal? + quantifier.nil? && expression.literal? + end + + def to_s(parent = false) + if !expression.options? + "(#{capture ? '' : '?:'}#{expression.to_s(parent)})#{quantifier}" + elsif capture == false + "#{expression.to_s}#{quantifier}" + else + "(#{expression.to_s})#{quantifier}" + end + end + + def to_regexp(anchored = false) + re = to_s + re = "\\A#{re}\\Z" if anchored + Regexp.compile(re) + end + + def dup(options = {}) + original_options = option_names.inject({}) do |h, m| + h[m.to_sym] = send(m) + h + end + self.class.new(expression, original_options.merge(options)) + end + + def inspect #:nodoc: + to_s.inspect + end + + def match(char) + to_regexp.match(char) + end + + def include?(char) + expression.include?(char) + end + + def capture? + capture + end + + def ==(other) #:nodoc: + case other + when String + other == to_s + else + eql?(other) + end + end + + def eql?(other) #:nodoc: + other.is_a?(self.class) && + self.expression == other.expression && + self.quantifier == other.quantifier && + self.capture == other.capture && + self.index == other.index && + self.name == other.name + end + + def freeze #:nodoc: + expression.freeze if expression + super + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb new file mode 100644 index 0000000000..03ba29d9a5 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb @@ -0,0 +1,55 @@ +module Regin + class Options + def self.from_int(flags) + multiline = flags & Regexp::MULTILINE != 0 + ignorecase = flags & Regexp::IGNORECASE != 0 + extended = flags & Regexp::EXTENDED != 0 + + new(multiline, ignorecase, extended) + end + + attr_reader :multiline, :ignorecase, :extended + + def initialize(*args) + if args.first.is_a?(Hash) + @multiline = args[0][:multiline] + @ignorecase = args[0][:ignorecase] + @extended = args[0][:extended] + else + @multiline = args[0] + @ignorecase = args[1] + @extended = args[2] + end + end + + def any?(explicit = false) + if explicit + !multiline.nil? || !ignorecase.nil? || !extended.nil? + else + multiline || ignorecase || extended + end + end + + def to_h(explicit = false) + if explicit + options = {} + options[:multiline] = multiline unless multiline.nil? + options[:ignorecase] = ignorecase unless ignorecase.nil? + options[:extended] = extended unless extended.nil? + options + else + { :multiline => multiline, + :ignorecase => ignorecase, + :extended => extended } + end + end + + def to_i + flag = 0 + flag |= Regexp::MULTILINE if multiline + flag |= Regexp::IGNORECASE if ignorecase + flag |= Regexp::EXTENDED if extended + flag + end + end +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb new file mode 100644 index 0000000000..0bb9b87e9c --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb @@ -0,0 +1,415 @@ +# +# DO NOT MODIFY!!!! +# This file is automatically generated by Racc 1.4.6 +# from Racc grammer file "". +# + +require 'racc/parser.rb' +module Regin + class Parser < Racc::Parser #:nodoc: all + +def self.parse_regexp(regexp) + options = Options.from_int(regexp.options) + + parser = new + parser.options_stack << options.to_h + + expression = parser.scan_str(regexp.source) + expression = expression.dup(options.to_h) if options.any? + expression +end + +attr_accessor :options_stack + +def initialize + @capture_index = 0 + @capture_index_stack = [] + @options_stack = [] +end + +##### State transition tables begin ### + +racc_action_table = [ + 2, 18, 19, 19, 8, 10, 11, 13, 48, 19, + 2, 45, 3, 5, 8, 10, 11, 13, 64, 47, + 2, 55, 3, 5, 8, 10, 11, 13, 29, 19, + 2, 16, 3, 5, 8, 10, 11, 13, 61, 19, + 2, 63, 3, 5, 8, 10, 11, 13, 60, 36, + 2, 34, 3, 5, 8, 10, 11, 13, 28, 49, + 2, nil, 3, 5, 8, 10, 11, 13, nil, nil, + 2, nil, 3, 5, 8, 10, 11, 13, nil, 26, + 42, 43, 3, 5, 37, 38, 40, 21, 44, 37, + 38, 40, 22, 23, 24, 14, nil, 15, 31, 32, + 16, nil, 33, 46, 32, nil, nil, 33, 51, 37, + 38, 40, 58, 37, 38, 40, 37, 38, 40, 37, + 38, 40, 37, 38, 40, 37, 38, 40, 37, 38, + 40 ] + +racc_action_check = [ + 0, 4, 27, 4, 0, 0, 0, 0, 36, 56, + 49, 27, 0, 0, 49, 49, 49, 49, 56, 36, + 43, 48, 49, 49, 43, 43, 43, 43, 15, 53, + 6, 15, 43, 43, 6, 6, 6, 6, 53, 52, + 42, 55, 6, 6, 42, 42, 42, 42, 52, 24, + 35, 18, 42, 42, 35, 35, 35, 35, 14, 39, + 19, nil, 35, 35, 19, 19, 19, 19, nil, nil, + 13, nil, 19, 19, 13, 13, 13, 13, nil, 13, + 26, 26, 13, 13, 54, 54, 54, 9, 26, 26, + 26, 26, 9, 9, 9, 2, nil, 2, 17, 17, + 2, nil, 17, 30, 30, nil, nil, 30, 41, 41, + 41, 41, 50, 50, 50, 50, 44, 44, 44, 59, + 59, 59, 58, 58, 58, 51, 51, 51, 62, 62, + 62 ] + +racc_action_pointer = [ + -3, nil, 91, nil, 1, nil, 27, nil, nil, 75, + nil, nil, nil, 67, 53, 22, nil, 93, 51, 57, + nil, nil, nil, nil, 40, nil, 67, 0, nil, nil, + 98, nil, nil, nil, nil, 47, -1, nil, nil, 46, + nil, 87, 37, 17, 94, nil, nil, nil, 12, 7, + 91, 103, 37, 27, 62, 21, 7, nil, 100, 97, + nil, nil, 106, nil, nil, nil, nil, nil ] + +racc_action_default = [ + -37, -13, -37, -19, -37, -20, -2, -4, -11, -6, + -12, -14, -7, -37, -37, -29, -28, -37, -37, -37, + -3, -23, -21, -22, -37, -5, -37, -37, -8, -29, + -37, -9, -27, -26, 68, -1, -37, -34, -35, -37, + -36, -37, -37, -37, -37, -15, -10, -25, -37, -37, + -37, -37, -37, -37, -37, -37, -37, -33, -37, -37, + -17, -18, -37, -24, -16, -32, -31, -30 ] + +racc_goto_table = [ + 4, 41, 20, 35, 17, 39, 25, nil, nil, nil, + nil, nil, nil, 27, nil, nil, 50, 30, nil, 54, + nil, nil, nil, nil, nil, 57, 59, nil, nil, 62, + nil, 20, nil, 65, 66, nil, nil, 67, nil, nil, + nil, nil, 52, 53, nil, nil, nil, nil, nil, 56 ] + +racc_goto_check = [ + 1, 10, 3, 2, 7, 9, 5, nil, nil, nil, + nil, nil, nil, 1, nil, nil, 10, 7, nil, 10, + nil, nil, nil, nil, nil, 10, 10, nil, nil, 10, + nil, 3, nil, 10, 10, nil, nil, 10, nil, nil, + nil, nil, 1, 1, nil, nil, nil, nil, nil, 1 ] + +racc_goto_pointer = [ + nil, 0, -16, -4, nil, -3, nil, 2, nil, -21, + -25 ] + +racc_goto_default = [ + nil, nil, 6, 7, 9, nil, 12, nil, 1, nil, + nil ] + +racc_reduce_table = [ + 0, 0, :racc_error, + 3, 26, :_reduce_1, + 1, 26, :_reduce_2, + 2, 27, :_reduce_3, + 1, 27, :_reduce_4, + 2, 28, :_reduce_5, + 1, 28, :_reduce_none, + 1, 29, :_reduce_none, + 3, 29, :_reduce_8, + 3, 29, :_reduce_9, + 4, 29, :_reduce_10, + 1, 29, :_reduce_11, + 1, 29, :_reduce_12, + 1, 29, :_reduce_13, + 1, 29, :_reduce_14, + 3, 31, :_reduce_15, + 6, 31, :_reduce_16, + 5, 31, :_reduce_17, + 5, 31, :_reduce_18, + 1, 33, :_reduce_none, + 1, 33, :_reduce_none, + 1, 30, :_reduce_none, + 1, 30, :_reduce_none, + 1, 30, :_reduce_none, + 5, 30, :_reduce_24, + 3, 30, :_reduce_25, + 2, 32, :_reduce_26, + 2, 32, :_reduce_27, + 1, 32, :_reduce_none, + 1, 32, :_reduce_none, + 4, 34, :_reduce_30, + 4, 34, :_reduce_31, + 4, 34, :_reduce_32, + 3, 34, :_reduce_33, + 1, 35, :_reduce_34, + 1, 35, :_reduce_35, + 1, 35, :_reduce_36 ] + +racc_reduce_n = 37 + +racc_shift_n = 68 + +racc_token_table = { + false => 0, + :error => 1, + :BAR => 2, + :LBRACK => 3, + :CTYPE => 4, + :RBRACK => 5, + :NEGATE => 6, + :CCLASS => 7, + :DOT => 8, + :CHAR => 9, + :LPAREN => 10, + :RPAREN => 11, + :QMARK => 12, + :COLON => 13, + :NAME => 14, + :L_ANCHOR => 15, + :R_ANCHOR => 16, + :STAR => 17, + :PLUS => 18, + :LCURLY => 19, + :RCURLY => 20, + :MINUS => 21, + :MULTILINE => 22, + :IGNORECASE => 23, + :EXTENDED => 24 } + +racc_nt_base = 25 + +racc_use_result_var = true + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ + "$end", + "error", + "BAR", + "LBRACK", + "CTYPE", + "RBRACK", + "NEGATE", + "CCLASS", + "DOT", + "CHAR", + "LPAREN", + "RPAREN", + "QMARK", + "COLON", + "NAME", + "L_ANCHOR", + "R_ANCHOR", + "STAR", + "PLUS", + "LCURLY", + "RCURLY", + "MINUS", + "MULTILINE", + "IGNORECASE", + "EXTENDED", + "$start", + "expression", + "subexpression", + "quantified_atom", + "atom", + "quantifier", + "group", + "bracket_expression", + "anchor", + "options", + "modifier" ] + +Racc_debug_parser = false + +##### State transition tables end ##### + +# reduce 0 omitted + +def _reduce_1(val, _values, result) + # TODO remove this conditional by breaking + # it into another production + if val[0][0].is_a?(Regin::Alternation) + alt = val[0][0] + [Expression.new(val[2])] + else + alt = Alternation.new(val[0], Expression.new(val[2])) + end + result = Expression.new(alt) + + result +end + +def _reduce_2(val, _values, result) + result = Expression.new(val[0]) + result +end + +def _reduce_3(val, _values, result) + result = val[0] + [val[1]] + result +end + +def _reduce_4(val, _values, result) + result = [val[0]] + result +end + +def _reduce_5(val, _values, result) + result = val[0].dup(:quantifier => val[1]) + result +end + +# reduce 6 omitted + +# reduce 7 omitted + +def _reduce_8(val, _values, result) + result = CharacterClass.new(val[1]) + result +end + +def _reduce_9(val, _values, result) + result = CharacterClass.new(val[1]) + result +end + +def _reduce_10(val, _values, result) + result = CharacterClass.new(val[2], :negate => true) + result +end + +def _reduce_11(val, _values, result) + result = CharacterClass.new(val[0]) + result +end + +def _reduce_12(val, _values, result) + result = CharacterClass.new('.') + result +end + +def _reduce_13(val, _values, result) + result = Anchor.new(val[0]) + result +end + +def _reduce_14(val, _values, result) + result = Character.new(val[0]) + result +end + +def _reduce_15(val, _values, result) + result = Group.new(val[1], :index => @capture_index_stack.pop) + + result +end + +def _reduce_16(val, _values, result) + result = Group.new(val[4], val[2].merge(:capture => false)) + @options_stack.pop + + result +end + +def _reduce_17(val, _values, result) + result = Group.new(val[3], :capture => false); + + result +end + +def _reduce_18(val, _values, result) + result = Group.new(val[3], :name => val[2], :index => @capture_index_stack.pop); + + result +end + +# reduce 19 omitted + +# reduce 20 omitted + +# reduce 21 omitted + +# reduce 22 omitted + +# reduce 23 omitted + +def _reduce_24(val, _values, result) + result = val.join + result +end + +def _reduce_25(val, _values, result) + result = val.join + result +end + +def _reduce_26(val, _values, result) + result = val.join + result +end + +def _reduce_27(val, _values, result) + result = val.join + result +end + +# reduce 28 omitted + +# reduce 29 omitted + +def _reduce_30(val, _values, result) + @options_stack << result = { val[1] => false, val[2] => false, val[3] => false } + + result +end + +def _reduce_31(val, _values, result) + @options_stack << result = { val[0] => true, val[2] => false, val[3] => false } + + result +end + +def _reduce_32(val, _values, result) + @options_stack << result = { val[0] => true, val[1] => true, val[3] => false } + + result +end + +def _reduce_33(val, _values, result) + @options_stack << result = { val[0] => true, val[1] => true, val[2] => true } + + result +end + +def _reduce_34(val, _values, result) + result = :multiline + result +end + +def _reduce_35(val, _values, result) + result = :ignorecase + result +end + +def _reduce_36(val, _values, result) + result = :extended + result +end + +def _reduce_none(val, _values, result) + val[0] +end + + end # class Parser +end # module Regin + +require 'regin/tokenizer' diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb new file mode 100644 index 0000000000..59e4ffb611 --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb @@ -0,0 +1,213 @@ +#-- +# DO NOT MODIFY!!!! +# This file is automatically generated by rex 1.0.5.beta1 +# from lexical definition file "lib/regin/tokenizer.rex". +#++ + +require 'racc/parser' +class Regin::Parser < Racc::Parser + require 'strscan' + + class ScanError < StandardError ; end + + attr_reader :lineno + attr_reader :filename + attr_accessor :state + + def scan_setup(str) + @ss = StringScanner.new(str) + @lineno = 1 + @state = nil + end + + def action + yield + end + + def scan_str(str) + scan_setup(str) + do_parse + end + alias :scan :scan_str + + def load_file( filename ) + @filename = filename + open(filename, "r") do |f| + scan_setup(f.read) + end + end + + def scan_file( filename ) + load_file(filename) + do_parse + end + + + def next_token + return if @ss.eos? + + text = @ss.peek(1) + @lineno += 1 if text == "\n" + token = case @state + when nil + case + when (text = @ss.scan(/\\[dDsSwW]/)) + action { [:CCLASS, text] } + + when (text = @ss.scan(/\^|\\A/)) + action { [:L_ANCHOR, text] } + + when (text = @ss.scan(/\$|\\Z/)) + action { [:R_ANCHOR, text] } + + when (text = @ss.scan(/<(\w+)>/)) + action { [:NAME, @ss[1]] } + + when (text = @ss.scan(/\(/)) + action { + @capture_index_stack << @capture_index + @capture_index += 1 + @state = :OPTIONS if @ss.peek(1) == '?'; + [:LPAREN, text] + } + + + when (text = @ss.scan(/\)/)) + action { [:RPAREN, text] } + + when (text = @ss.scan(/\[/)) + action { @state = :CCLASS; [:LBRACK, text] } + + when (text = @ss.scan(/\{/)) + action { [:LCURLY, text] } + + when (text = @ss.scan(/\}/)) + action { [:RCURLY, text] } + + when (text = @ss.scan(/\|/)) + action { [:BAR, text] } + + when (text = @ss.scan(/\./)) + action { [:DOT, text] } + + when (text = @ss.scan(/\?/)) + action { [:QMARK, text] } + + when (text = @ss.scan(/\+(?:\?)/)) + action { [:PLUS, text] } + + when (text = @ss.scan(/\*(?:\?)/)) + action { [:STAR, text] } + + when (text = @ss.scan(/\#/)) + action { + if @options_stack[-1][:extended] + @state = :COMMENT; + next_token + else + [:CHAR, text] + end + } + + + when (text = @ss.scan(/\s|\n/)) + action { + if @options_stack[-1][:extended] + next_token + else + [:CHAR, text] + end + } + + + when (text = @ss.scan(/\\(.)/)) + action { [:CHAR, @ss[1]] } + + when (text = @ss.scan(/./)) + action { [:CHAR, text] } + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '" + text + "'" + end # if + + when :CCLASS + case + when (text = @ss.scan(/\]/)) + action { @state = nil; [:RBRACK, text] } + + when (text = @ss.scan(/\^/)) + action { [:NEGATE, text] } + + when (text = @ss.scan(/:(alnum|alpha|ascii|blank|cntrl|digit|graph|lower|print|punct|space|upper|word|xdigit):/)) + action { [:CTYPE, text] } + + when (text = @ss.scan(/\\-/)) + action { [:CHAR, text] } + + when (text = @ss.scan(/\\(.)/)) + action { [:CHAR, @ss[1]] } + + when (text = @ss.scan(/./)) + action { [:CHAR, text] } + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '" + text + "'" + end # if + + when :OPTIONS + case + when (text = @ss.scan(/\?/)) + action { + @state = nil unless @ss.peek(1) =~ /-|m|i|x|:/ + [:QMARK, text] + } + + + when (text = @ss.scan(/\-/)) + action { [:MINUS, text] } + + when (text = @ss.scan(/m/)) + action { [:MULTILINE, text] } + + when (text = @ss.scan(/i/)) + action { [:IGNORECASE, text] } + + when (text = @ss.scan(/x/)) + action { [:EXTENDED, text] } + + when (text = @ss.scan(/\:/)) + action { + @capture_index_stack.pop + @capture_index -= 1 + @state = nil; + [:COLON, text] + } + + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '" + text + "'" + end # if + + when :COMMENT + case + when (text = @ss.scan(/\n/)) + action { @state = nil; next_token } + + when (text = @ss.scan(/./)) + action { next_token } + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '" + text + "'" + end # if + + else + raise ScanError, "undefined state: '" + state.to_s + "'" + end # case state + token + end # def next_token + +end # class diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb new file mode 100644 index 0000000000..7ad2a5a25e --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb @@ -0,0 +1,3 @@ +module Regin + Version = '0.3.3' +end diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb new file mode 100644 index 0000000000..a3688b102e --- /dev/null +++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb @@ -0,0 +1,5 @@ +module Rack + module Mount + Version = '0.6.6.pre' + end +end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 4d06ca0d89..956c88a553 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -3,16 +3,19 @@ require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/array/wrap' require 'active_support/ordered_options' +require 'action_view/log_subscriber' module ActionView #:nodoc: class NonConcattingString < ActiveSupport::SafeBuffer end + # = Action View Base + # # Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used. # If the template file has a <tt>.rjs</tt> extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator. # - # = ERb + # == ERb # # You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the # following loop for names: @@ -32,7 +35,7 @@ module ActionView #:nodoc: # # <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>. # - # == Using sub templates + # === Using sub templates # # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts): @@ -54,7 +57,7 @@ module ActionView #:nodoc: # # <title><%= @page_title %></title> # - # == Passing local variables to sub templates + # === Passing local variables to sub templates # # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values: # @@ -74,7 +77,7 @@ module ActionView #:nodoc: # # Testing using <tt>defined? headline</tt> will not work. This is an implementation restriction. # - # == Template caching + # === Template caching # # By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will # check the file's modification time and recompile it. @@ -202,8 +205,12 @@ module ActionView #:nodoc: value.dup : ActionView::PathSet.new(Array.wrap(value)) end + def assign(new_assigns) # :nodoc: + self.assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) } + end + def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc: - self.assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) } + assign(assigns_for_first_render) self.helpers = self.class.helpers || Module.new if @_controller = controller diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb index 88efd4b34f..39d88333e8 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionpack/lib/action_view/context.rb @@ -2,13 +2,12 @@ module ActionView module CompiledTemplates #:nodoc: # holds compiled template code end - - # Action View contexts are supplied to Action Controller - # to render template. The default Action View context - # is ActionView::Base. + # = Action View Context + # + # Action View contexts are supplied to Action Controller to render template. + # The default Action View context is ActionView::Base. # - # In order to work with ActionController, a Context - # must implement: + # In order to work with ActionController, a Context must implement: # # Context#render_partial[options] # - responsible for setting options[:_template] @@ -21,16 +20,14 @@ module ActionView # options<Hash>:: See _render_template_with_layout in ActionView::Base # partial<Boolean>:: Whether or not the template to render is a partial # - # An Action View context can also mix in Action View's - # helpers. In order to mix in helpers, a context must - # implement: + # An Action View context can also mix in Action View's helpers. In order to + # mix in helpers, a context must implement: # # Context#controller # - Returns an instance of AbstractController # - # In any case, a context must mix in ActionView::Context, - # which stores compiled template and provides the output - # buffer. + # In any case, a context must mix in ActionView::Context, which stores compiled + # template and provides the output buffer. module Context include CompiledTemplates attr_accessor :output_buffer diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index 8054de0af6..6bb0875bc3 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/enumerable' require 'active_support/core_ext/object/blank' module ActionView + # = Active Model Helpers module Helpers module ActiveModelHelper %w(input form error_messages_for error_message_on).each do |method| @@ -35,12 +36,16 @@ module ActionView end end - %w(tag content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth| + %w(content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth| module_eval "def #{meth}(*) error_wrapping(super) end", __FILE__, __LINE__ end + def tag(type, options, *) + tag_generate_errors?(options) ? error_wrapping(super) : super + end + def error_wrapping(html_tag) - if object.respond_to?(:errors) && object.errors.respond_to?(:full_messages) && object.errors[@method_name].any? + if object_has_errors? Base.field_error_proc.call(html_tag, self) else html_tag @@ -50,6 +55,16 @@ module ActionView def error_message object.errors[@method_name] end + + private + + def object_has_errors? + object.respond_to?(:errors) && object.errors.respond_to?(:full_messages) && error_message.any? + end + + def tag_generate_errors?(options) + options['type'] != 'hidden' + end end class FormBuilder diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 25426a5547..a3c43d3e93 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -6,6 +6,7 @@ require 'active_support/core_ext/file' require 'active_support/core_ext/object/blank' module ActionView + # = Action View Asset Tag Helpers module Helpers #:nodoc: # This module provides methods for generating HTML that links views to assets such # as images, javascripts, stylesheets, and feeds. These methods do not verify @@ -193,7 +194,19 @@ module ActionView # RewriteEngine On # RewriteRule ^/release-\d+/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L] module AssetTagHelper - JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls', 'rails'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES) + mattr_reader :javascript_expansions + @@javascript_expansions = { } + + mattr_reader :stylesheet_expansions + @@stylesheet_expansions = {} + + # You can enable or disable the asset tag timestamps cache. + # With the cache enabled, the asset tag helper methods will make fewer + # expensive file system calls. However this prevents you from modifying + # any asset files while the server is running. + # + # ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false + mattr_accessor :cache_asset_timestamps # Returns a link tag that browsers and news readers can use to auto-detect # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or @@ -350,8 +363,6 @@ module ActionView end end - @@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup } - # Register one or more javascript files to be included when <tt>symbol</tt> # is passed to <tt>javascript_include_tag</tt>. This method is typically intended # to be called from plugin initialization to register javascript files @@ -367,8 +378,6 @@ module ActionView @@javascript_expansions.merge!(expansions) end - @@stylesheet_expansions = {} - # Register one or more stylesheet files to be included when <tt>symbol</tt> # is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended # to be called from plugin initialization to register stylesheet files @@ -384,18 +393,6 @@ module ActionView @@stylesheet_expansions.merge!(expansions) end - # Register one or more additional JavaScript files to be included when - # <tt>javascript_include_tag :defaults</tt> is called. This method is - # typically intended to be called from plugin initialization to register additional - # .js files that the plugin installed in <tt>public/javascripts</tt>. - def self.register_javascript_include_default(*sources) - @@javascript_expansions[:defaults].concat(sources) - end - - def self.reset_javascript_include_default #:nodoc: - @@javascript_expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup - end - # Computes the path to a stylesheet asset in the public stylesheets directory. # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs). # Full paths from the document root will be passed through. @@ -706,23 +703,8 @@ module ActionView tag("audio", options) end - def self.cache_asset_timestamps - @@cache_asset_timestamps - end - - # You can enable or disable the asset tag timestamps cache. - # With the cache enabled, the asset tag helper methods will make fewer - # expensive file system calls. However this prevents you from modifying - # any asset files while the server is running. - # - # ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false - def self.cache_asset_timestamps=(value) - @@cache_asset_timestamps = value - end - - @@cache_asset_timestamps = true - private + def rewrite_extension?(source, dir, ext) source_ext = File.extname(source)[1..-1] ext && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}")))) diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index 52806f206a..cb5a1404ff 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -1,10 +1,12 @@ require 'set' -# Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other -# template languages). module ActionView + # = Action View Atom Feed Helpers module Helpers #:nodoc: module AtomFeedHelper + # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other + # template languages). + # # Full usage example: # # config/routes.rb: diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index 8251ed18f4..f544a9d147 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -1,8 +1,10 @@ module ActionView + # = Action View Cache Helper module Helpers - # This helper to exposes a method for caching of view fragments. - # See ActionController::Caching::Fragments for usage instructions. module CacheHelper + # This helper to exposes a method for caching of view fragments. + # See ActionController::Caching::Fragments for usage instructions. + # # A method for caching fragments of a view rather than an entire # action or page. This technique is useful caching pieces like # menus, lists of news topics, static HTML fragments, and so on. diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index edc6afc622..f9105ca364 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -1,9 +1,11 @@ require 'active_support/core_ext/object/blank' module ActionView + # = Action View Capture Helper module Helpers # CaptureHelper exposes methods to let you extract generated markup which # can be used in other parts of a template or layout file. + # # It provides a method to capture blocks into variables through capture and # a way to capture a block of markup for use in a layout through content_for. module CaptureHelper @@ -176,9 +178,7 @@ module ActionView def flush_output_buffer #:nodoc: if output_buffer && !output_buffer.empty? response.body_parts << output_buffer - new = '' - new.force_encoding(output_buffer.encoding) if new.respond_to?(:force_encoding) - self.output_buffer = new + self.output_buffer = output_buffer[0,0] nil end end diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionpack/lib/action_view/helpers/csrf_helper.rb index 41c6b67f91..3d03f6aac6 100644 --- a/actionpack/lib/action_view/helpers/csrf_helper.rb +++ b/actionpack/lib/action_view/helpers/csrf_helper.rb @@ -1,7 +1,9 @@ module ActionView + # = Action View CSRF Helper module Helpers module CsrfHelper - # Returns a meta tag with the request forgery protection token for forms to use. Put this in your head. + # Returns a meta tag with the cross-site request forgery protection token + # for forms to use. Place this in your head. def csrf_meta_tag if protect_against_forgery? %(<meta name="csrf-param" content="#{Rack::Utils.escape_html(request_forgery_protection_token)}"/>\n<meta name="csrf-token" content="#{Rack::Utils.escape_html(form_authenticity_token)}"/>).html_safe diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 7d846a01dd..f097b9a5a3 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -4,6 +4,8 @@ require 'active_support/core_ext/hash/slice' module ActionView module Helpers + # = Action View Date Helpers + # # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the # select-type methods share a number of common options that are as follows: # @@ -580,10 +582,10 @@ module ActionView extend ActiveSupport::Memoizable include ActionView::Helpers::TagHelper - DEFAULT_PREFIX = 'date'.freeze unless const_defined?('DEFAULT_PREFIX') + DEFAULT_PREFIX = 'date'.freeze POSITION = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 - }.freeze unless const_defined?('POSITION') + }.freeze def initialize(datetime, options = {}, html_options = {}) @options = options.dup @@ -894,8 +896,10 @@ module ActionView # Returns the separator for a given datetime component def separator(type) case type - when :month, :day - @options[:date_separator] + when :month + @options[:discard_month] ? "" : @options[:date_separator] + when :day + @options[:discard_day] ? "" : @options[:date_separator] when :hour (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator] when :minute diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index e637dc1474..1491cb073f 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -1,6 +1,8 @@ module ActionView + # = Action View Debug Helper + # + # Provides a set of methods for making it easier to debug Rails objects. module Helpers - # Provides a set of methods for making it easier to debug Rails objects. module DebugHelper # Returns a YAML representation of +object+ wrapped with <pre> and </pre>. # If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead. diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index b3db3151d3..d1b10a9281 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -7,6 +7,7 @@ require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/object/blank' module ActionView + # = Action View Form Helpers module Helpers # Form helpers are designed to make working with resources much easier # compared to using vanilla HTML. @@ -92,7 +93,7 @@ module ActionView # # error handling # end # - # That's how you tipically work with resources. + # That's how you typically work with resources. module FormHelper extend ActiveSupport::Concern @@ -123,7 +124,6 @@ module ActionView # model: # # <%= form_for :person do |f| %> - # <%= f.error_messages %> # First name: <%= f.text_field :first_name %><br /> # Last name : <%= f.text_field :last_name %><br /> # Biography : <%= f.text_area :biography %><br /> @@ -220,15 +220,15 @@ module ActionView # <% end %> # # === Unobtrusive JavaScript - # - # Specifying: - # + # + # Specifying: + # # :remote => true # # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its - # behaviour. The expected default behaviour is an XMLHttpRequest in the background instead of the regular + # behaviour. The expected default behaviour is an XMLHttpRequest in the background instead of the regular # POST arrangement, but ultimately the behaviour is the choice of the JavaScript driver implementor. - # Even though it's using JavaScript to serialize the form elements, the form submission will work just like + # Even though it's using JavaScript to serialize the form elements, the form submission will work just like # a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>). # # Example: @@ -269,7 +269,7 @@ module ActionView # <tt>labelling_form</tt>. # # The custom FormBuilder class is automatically merged with the options - # of a nested fields_for call, unless it's explicitely set. + # of a nested fields_for call, unless it's explicitly set. # # In many cases you will want to wrap the above in another helper, so you # could do something like the following: @@ -302,7 +302,7 @@ module ActionView args.unshift object end - options[:html][:remote] = true if options.delete(:remote) + (options[:html] ||= {})[:remote] = true if options.delete(:remote) output = form_tag(options.delete(:url) || {}, options.delete(:html) || {}) output << fields_for(object_name, *(args << options), &proc) @@ -717,7 +717,7 @@ module ActionView # # To prevent this the helper generates an auxiliary hidden field before # the very check box. The hidden field has the same name and its - # attributes mimick an unchecked check box. + # attributes mimic an unchecked check box. # # This way, the client either sends only the hidden field (representing # the check box is unchecked), or both fields. Since the HTML specification @@ -838,9 +838,9 @@ module ActionView attr_reader :method_name, :object_name - DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS) - DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS) - DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS) + DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze + DEFAULT_RADIO_OPTIONS = { }.freeze + DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze def initialize(object_name, method_name, template_object, object = nil) @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup @@ -897,7 +897,7 @@ module ActionView options.delete("size") end options["type"] ||= field_type - options["value"] ||= value_before_type_cast(object) unless field_type == "file" + options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file" options["value"] &&= html_escape(options["value"]) add_default_name_and_id(options) tag("input", options) @@ -1030,7 +1030,7 @@ module ActionView private def add_default_name_and_id_for_value(tag_value, options) unless tag_value.nil? - pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase + pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase specified_id = options["id"] add_default_name_and_id(options) options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present? @@ -1042,14 +1042,14 @@ module ActionView def add_default_name_and_id(options) if options.has_key?("index") options["name"] ||= tag_name_with_index(options["index"]) - options["id"] = options.fetch("id", tag_id_with_index(options["index"])) + options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) } options.delete("index") elsif defined?(@auto_index) options["name"] ||= tag_name_with_index(@auto_index) - options["id"] = options.fetch("id", tag_id_with_index(@auto_index)) + options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) } else options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '') - options["id"] = options.fetch("id", tag_id) + options["id"] = options.fetch("id"){ tag_id } end end @@ -1180,7 +1180,7 @@ module ActionView # <%= 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". # diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index fe71d2cdf7..6f9d14de8b 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -4,6 +4,7 @@ require 'action_view/helpers/form_helper' require 'active_support/core_ext/object/blank' module ActionView + # = Action View Form Option Helpers module Helpers # Provides a number of methods for turning different kinds of containers into a set of option tags. # == Options @@ -398,7 +399,7 @@ module ActionView options_for_select += "<optgroup label=\"#{html_escape(group_label_string)}\">" options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key) options_for_select += '</optgroup>' - end + end.html_safe end # Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but @@ -412,8 +413,8 @@ module ActionView # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags, # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>. - # * +prompt+ - set to true or a prompt string. When the select element doesn’t have a value yet, this - # prepends an option with a generic prompt — "Please select" — or the given prompt string. + # * +prompt+ - set to true or a prompt string. When the select element doesn't have a value yet, this + # prepends an option with a generic prompt - "Please select" - or the given prompt string. # # Sample usage (Array): # grouped_options = [ diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 796268628a..4c1b751160 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/object/returning' require 'active_support/core_ext/object/blank' module ActionView + # = Action View Form Tag Helpers module Helpers # Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like # FormHelper does. Instead, you provide the names and values manually. @@ -528,23 +529,34 @@ module ActionView def html_options_for_form(url_for_options, options, *parameters_for_url) returning options.stringify_keys do |html_options| html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart") + # The following URL is unescaped, this is just a hash of options, and it is the + # responsability of the caller to escape all the values. html_options["action"] = url_for(url_for_options, *parameters_for_url) + html_options["accept-charset"] = "UTF-8" html_options["data-remote"] = true if html_options.delete("remote") end end def extra_tags_for_form(html_options) - case method = html_options.delete("method").to_s - when /^get$/i # must be case-insentive, but can't use downcase as might be nil + snowman_tag = tag(:input, :type => "hidden", + :name => "_snowman", :value => "☃".html_safe) + + method = html_options.delete("method").to_s + + method_tag = case method + when /^get$/i # must be case-insensitive, but can't use downcase as might be nil html_options["method"] = "get" '' when /^post$/i, "", nil html_options["method"] = "post" - protect_against_forgery? ? content_tag(:div, token_tag, :style => 'margin:0;padding:0;display:inline') : '' + token_tag else html_options["method"] = "post" - content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0;display:inline') + tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag end + + tags = snowman_tag << method_tag + content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline') end def form_tag_html(html_options) diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index b0a7718f22..84f53b842c 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -1,6 +1,7 @@ require 'action_view/helpers/tag_helper' module ActionView + # = Action View JavaScript Helpers module Helpers # Provides functionality for working with JavaScript in your views. # diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 3b0dfb561c..37e5d91d8b 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/float/rounding' require 'active_support/core_ext/object/blank' module ActionView + # = Action View Number Helpers module Helpers #:nodoc: # Provides methods for converting numbers into formatted strings. @@ -332,7 +333,7 @@ module ActionView # number_to_human_size(483989, :precision => 2) # => 470 KB # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB # - # Unsignificant zeros after the fractional separator are stripped out by default (set + # Non-significant zeros after the fractional separator are stripped out by default (set # <tt>:strip_insignificant_zeros</tt> to +false+ to change that): # number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB" # number_to_human_size(524288000, :precision=>5) # => "500 MB" diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index a798c3eaef..5c0ff5d59c 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -1,9 +1,9 @@ require 'set' require 'active_support/json' -require 'active_support/core_ext/object/returning' require 'active_support/core_ext/object/blank' module ActionView + # = Action View Prototype Helpers module Helpers # Prototype[http://www.prototypejs.org/] is a JavaScript library that provides # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation, @@ -94,14 +94,12 @@ module ActionView # See JavaScriptGenerator for information on updating multiple elements # on the page in an Ajax response. module PrototypeHelper - unless const_defined? :CALLBACKS - CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded, - :interactive, :complete, :failure, :success ] + - (100..599).to_a) - AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url, - :asynchronous, :method, :insertion, :position, - :form, :with, :update, :script, :type ]).merge(CALLBACKS) - end + CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded, + :interactive, :complete, :failure, :success ] + + (100..599).to_a) + AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url, + :asynchronous, :method, :insertion, :position, + :form, :with, :update, :script, :type ]).merge(CALLBACKS) # Returns the JavaScript needed for a remote function. # Takes the same arguments as link_to_remote. @@ -133,7 +131,7 @@ module ActionView url_options = options[:url] url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash) - function << "'#{escape_javascript(url_for(url_options))}'" + function << "'#{html_escape(escape_javascript(url_for(url_options)))}'" function << ", #{javascript_options})" function = "#{options[:before]}; #{function}" if options[:before] @@ -229,7 +227,7 @@ module ActionView # <script> tag. module GeneratorMethods def to_s #:nodoc: - returning javascript = @lines * $/ do + (@lines * $/).tap do |javascript| if ActionView::Base.debug_rjs source = javascript.dup javascript.replace "try {\n#{source}\n} catch (e) " @@ -531,9 +529,9 @@ module ActionView end def record(line) - returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do - self << line - end + line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" + self << line + line end def render(*options) diff --git a/actionpack/lib/action_view/helpers/raw_output_helper.rb b/actionpack/lib/action_view/helpers/raw_output_helper.rb index 8c7f41177d..da7599fa8f 100644 --- a/actionpack/lib/action_view/helpers/raw_output_helper.rb +++ b/actionpack/lib/action_view/helpers/raw_output_helper.rb @@ -1,6 +1,15 @@ module ActionView #:nodoc: + # = Action View Raw Output Helper module Helpers #:nodoc: module RawOutputHelper + # This method outputs without escaping a string. Since escaping tags is + # now default, this can be used when you don't want Rails to automatically + # escape tags. This is not recommended if the data is coming from the user's + # input. + # + # For example: + # + # <%=raw @user.name %> def raw(stringish) stringish.to_s.html_safe end diff --git a/actionpack/lib/action_view/helpers/record_identification_helper.rb b/actionpack/lib/action_view/helpers/record_identification_helper.rb index 6c235bff3d..372f1cb8aa 100644 --- a/actionpack/lib/action_view/helpers/record_identification_helper.rb +++ b/actionpack/lib/action_view/helpers/record_identification_helper.rb @@ -1,4 +1,7 @@ module ActionView + # = Action View Record Identification Helpers + # + # See ActionController::RecordIdentifier for documentation on these methods. module Helpers module RecordIdentificationHelper # See ActionController::RecordIdentifier.partial_path -- this is just a delegate to that for convenient access in the view. diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index a9cf15f418..7433f08777 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -1,4 +1,5 @@ module ActionView + # = Action View Record Tag Helpers module Helpers module RecordTagHelper # Produces a wrapper DIV element with id and class parameters that diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index f173523f6a..b47818a22a 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -2,19 +2,25 @@ require 'action_controller/vendor/html-scanner' require 'action_view/helpers/tag_helper' module ActionView + # = Action View Sanitize Helpers module Helpers #:nodoc: # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. # These helper methods extend Action View making them callable within your template files. module SanitizeHelper - # This +sanitize+ helper will html encode all tags and strip all attributes that aren't specifically allowed. - # It also strips href/src tags with invalid protocols, like javascript: especially. It does its best to counter any - # tricks that hackers may use, like throwing in unicode/ascii/hex values to get past the javascript: filters. Check out + # This +sanitize+ helper will html encode all tags and strip all attributes that + # aren't specifically allowed. + # + # It also strips href/src tags with invalid protocols, like javascript: especially. + # It does its best to counter any tricks that hackers may use, like throwing in + # unicode/ascii/hex values to get past the javascript: filters. Check out # the extensive test suite. # # <%= sanitize @article.body %> # - # You can add or remove tags/attributes if you want to customize it a bit. See ActionView::Base for full docs on the - # available options. You can add tags/attributes for single uses of +sanitize+ by passing either the <tt>:attributes</tt> or <tt>:tags</tt> options: + # You can add or remove tags/attributes if you want to customize it a bit. + # See ActionView::Base for full docs on the available options. You can add + # tags/attributes for single uses of +sanitize+ by passing either the + # <tt>:attributes</tt> or <tt>:tags</tt> options: # # Normal Use # diff --git a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb index 37319cca1b..8610c2469e 100644 --- a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb +++ b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb @@ -2,9 +2,11 @@ require 'action_view/helpers/javascript_helper' require 'active_support/json' module ActionView + # = Action View Scriptaculous Helpers module Helpers - # Provides a set of helpers for calling Scriptaculous JavaScript - # functions, including those which create Ajax controls and visual effects. + # Provides a set of helpers for calling Scriptaculous[http://script.aculo.us/] + # JavaScript functions, including those which create Ajax controls and visual + # effects. # # To be able to use these helpers, you must include the Prototype # JavaScript framework and the Scriptaculous JavaScript library in your @@ -12,12 +14,11 @@ module ActionView # for more information on including the necessary JavaScript. # # The Scriptaculous helpers' behavior can be tweaked with various options. + # # See the documentation at http://script.aculo.us for more information on # using these helpers in your application. module ScriptaculousHelper - unless const_defined? :TOGGLE_EFFECTS - TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind] - end + TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind] # Returns a JavaScript snippet to be used on the Ajax callbacks for # starting visual effects. diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index 66277f79fe..5d032b32a7 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/object/blank' require 'set' module ActionView + # = Action View Tag Helpers module Helpers #:nodoc: # Provides methods to generate HTML tags programmatically when you can't use # a Builder. By default, they output XHTML compliant tags. @@ -121,7 +122,7 @@ module ActionView attrs << %(#{key}="#{key}") if value elsif !value.nil? final_value = value.is_a?(Array) ? value.join(" ") : value - final_value = escape_once(final_value) if escape + final_value = html_escape(final_value) if escape attrs << %(#{key}="#{final_value}") end end diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index c7f96597b9..0be8a2c36e 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/string/filters' require 'action_view/helpers/tag_helper' module ActionView + # = Action View Text Helpers module Helpers #:nodoc: # The TextHelper module provides a set of methods for filtering, formatting # and transforming strings, which can reduce the amount of inline Ruby code in @@ -52,7 +53,7 @@ module ActionView # truncate("Once upon a time in a world far far away", :length => 17) # # => "Once upon a ti..." # - # truncate("Once upon a time in a world far far away", :lenght => 17, :separator => ' ') + # truncate("Once upon a time in a world far far away", :length => 17, :separator => ' ') # # => "Once upon a..." # # truncate("And they found that many people were sleeping better.", :length => 25, :omission => '... (continued)') diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index 0d2b2aa7b1..dac9c28ab7 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -1,21 +1,28 @@ require 'action_view/helpers/tag_helper' module ActionView + # = Action View Translation Helpers module Helpers module TranslationHelper - # Delegates to I18n#translate but also performs three additional functions. First, it'll catch MissingTranslationData exceptions - # and turn them into inline spans that contains the missing key, such that you can see in a view what is missing where. + # Delegates to I18n#translate but also performs three additional functions. + # First, it'll catch MissingTranslationData exceptions and turn them into + # inline spans that contains the missing key, such that you can see in a + # view what is missing where. # - # Second, it'll scope the key by the current partial if the key starts with a period. So if you call translate(".foo") from the - # people/index.html.erb template, you'll actually be calling I18n.translate("people.index.foo"). This makes it less repetitive - # to translate many keys within the same partials and gives you a simple framework for scoping them consistently. If you don't - # prepend the key with a period, nothing is converted. + # Second, it'll scope the key by the current partial if the key starts + # with a period. So if you call <tt>translate(".foo")</tt> from the + # <tt>people/index.html.erb</tt> template, you'll actually be calling + # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive + # to translate many keys within the same partials and gives you a simple framework + # for scoping them consistently. If you don't prepend the key with a period, + # nothing is converted. # - # Third, it’ll mark the translation as safe HTML if the key has the suffix "_html" or the last element of the key is the word - # "html". For example, calling translate("footer_html") or translate("footer.html") will return a safe HTML string that won’t - # be escaped by other HTML helper methods. This naming convention helps to identify translations that include HTML tags so that + # Third, it'll mark the translation as safe HTML if the key has the suffix + # "_html" or the last element of the key is the word "html". For example, + # calling translate("footer_html") or translate("footer.html") will return + # a safe HTML string that won't be escaped by other HTML helper methods. This + # naming convention helps to identify translations that include HTML tags so that # you know what kind of output to expect when you call translate in a template. - def translate(key, options = {}) translation = I18n.translate(scope_key_by_partial(key), options.merge!(:raise => true)) if html_safe_translation_key?(key) && translation.respond_to?(:html_safe) diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 210f148c02..b8d6dc22f2 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/hash/keys' require 'action_dispatch' module ActionView + # = Action View URL Helpers module Helpers #:nodoc: # Provides a set of methods for making links and getting URLs that # depend on the routing subsystem (see ActionDispatch::Routing). @@ -12,7 +13,7 @@ module ActionView module UrlHelper # This helper may be included in any class that includes the # URL helpers of a router (router.url_helpers). Some methods - # provided here will only work in the context of a request + # provided here will only work in the4 context of a request # (link_to_unless_current, for instance), which must be provided # as a method called #request on the context. @@ -37,9 +38,6 @@ module ActionView # <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action" # instead of the fully qualified URL like "http://example.com/controller/action". # - # When called from a view, +url_for+ returns an HTML escaped url. If you - # need an unescaped url, pass <tt>:escape => false</tt> in the +options+. - # # ==== Options # * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path. # * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified). @@ -49,7 +47,6 @@ module ActionView # * <tt>:protocol</tt> - Overrides the default (current) protocol if provided. # * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present). # * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present). - # * <tt>:escape</tt> - Determines whether the returned URL will be HTML escaped or not (<tt>true</tt> by default). # # ==== Relying on named routes # @@ -71,10 +68,7 @@ module ActionView # <%= url_for(:action => 'play', :anchor => 'player') %> # # => /messages/play/#player # - # <%= url_for(:action => 'checkout', :anchor => 'tax&ship') %> - # # => /testing/jump/#tax&ship - # - # <%= url_for(:action => 'checkout', :anchor => 'tax&ship', :escape => false) %> + # <%= url_for(:action => 'jump', :anchor => 'tax&ship') %> # # => /testing/jump/#tax&ship # # <%= url_for(Workshop.new) %> @@ -99,21 +93,17 @@ module ActionView options ||= {} url = case options when String - escape = true options when Hash options = { :only_path => options[:host].nil? }.update(options.symbolize_keys) - escape = options.key?(:escape) ? options.delete(:escape) : false super when :back - escape = false controller.request.env["HTTP_REFERER"] || 'javascript:history.back()' else - escape = false polymorphic_path(options) end - escape ? escape_once(url).html_safe : url + url end # Creates a link tag of the given +name+ using a URL created by the set @@ -253,8 +243,8 @@ module ActionView tag_options = nil end - href_attr = "href=\"#{url}\"" unless href - "<a #{href_attr}#{tag_options}>#{ERB::Util.h(name || url)}</a>".html_safe + href_attr = "href=\"#{html_escape(url)}\"" unless href + "<a #{href_attr}#{tag_options}>#{html_escape(name || url)}</a>".html_safe end end @@ -338,7 +328,7 @@ module ActionView html_options.merge!("type" => "submit", "value" => name) - ("<form method=\"#{form_method}\" action=\"#{escape_once url}\" #{"data-remote=\"true\"" if remote} class=\"button_to\"><div>" + + ("<form method=\"#{form_method}\" action=\"#{html_escape(url)}\" #{"data-remote=\"true\"" if remote} class=\"button_to\"><div>" + method_tag + tag("input", html_options) + request_token_tag + "</div></form>").html_safe end @@ -484,24 +474,27 @@ module ActionView # :subject => "This is an example email" # # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a> def mail_to(email_address, name = nil, html_options = {}) + email_address = html_escape(email_address) + html_options = html_options.stringify_keys encode = html_options.delete("encode").to_s cc, bcc, subject, body = html_options.delete("cc"), html_options.delete("bcc"), html_options.delete("subject"), html_options.delete("body") - string = '' - extras = '' - 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_obfuscated = html_escape(email_address) + extras = [] + 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.empty? ? '' : '?' + html_escape(extras.join('&')) + + email_address_obfuscated = email_address.dup email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at") email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot") + string = '' + if encode == "javascript" - "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c| + "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))}');".each_byte do |c| string << sprintf("%%%x", c) end "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe @@ -518,9 +511,9 @@ module ActionView char = c.chr string << (char =~ /\w/ ? sprintf("%%%x", c) : char) end - content_tag "a", name || email_address_encoded.html_safe, html_options.merge({ "href" => "#{string}#{extras}" }) + content_tag "a", name || email_address_encoded.html_safe, html_options.merge("href" => "#{string}#{extras}".html_safe) else - content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" }) + content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe) end end @@ -573,7 +566,7 @@ module ActionView "in a #request method" end - url_string = CGI.unescapeHTML(url_for(options)) + url_string = url_for(options) # We ignore any extra parameters in the request_uri if the # submitted url doesn't have any either. This lets the function diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb new file mode 100644 index 0000000000..443a0eafd1 --- /dev/null +++ b/actionpack/lib/action_view/log_subscriber.rb @@ -0,0 +1,28 @@ +module ActionView + # = Action View Log Subscriber + # + # Provides functionality so that Rails can output logs from Action View. + class LogSubscriber < ActiveSupport::LogSubscriber + def render_template(event) + message = "Rendered #{from_rails_root(event.payload[:identifier])}" + message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] + message << (" (%.1fms)" % event.duration) + info(message) + end + alias :render_partial :render_template + alias :render_collection :render_template + + # TODO: Ideally, ActionView should have its own logger so it does not depend on AC.logger + def logger + ActionController::Base.logger if defined?(ActionController::Base) + end + + protected + + def from_rails_root(string) + string.sub("#{Rails.root}/", "").sub(/^app\/views\//, "") + end + end +end + +ActionView::LogSubscriber.attach_to :action_view diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 823226cb9c..3ea8b86af1 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -2,6 +2,8 @@ require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/object/blank' module ActionView + # = Action View Lookup Context + # # LookupContext is the object responsible to hold all information required to lookup # templates, i.e. view paths and details. The LookupContext is also responsible to # generate a key, given to view paths, used in the resolver cache lookup. Since diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index 7f5e5d11b8..9857d688d2 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -1,4 +1,5 @@ module ActionView #:nodoc: + # = Action View PathSet class PathSet < Array #:nodoc: %w(initialize << concat insert push unshift).each do |method| class_eval <<-METHOD, __FILE__, __LINE__ + 1 diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb index c606a71e18..33dfcbb803 100644 --- a/actionpack/lib/action_view/railtie.rb +++ b/actionpack/lib/action_view/railtie.rb @@ -2,11 +2,11 @@ require "action_view" require "rails" module ActionView + # = Action View Railtie class Railtie < Rails::Railtie config.action_view = ActiveSupport::OrderedOptions.new - - require "action_view/railties/log_subscriber" - log_subscriber :action_view, ActionView::Railties::LogSubscriber.new + config.action_view.stylesheet_expansions = {} + config.action_view.javascript_expansions = { :defaults => ['prototype', 'effects', 'dragdrop', 'controls', 'rails'] } initializer "action_view.cache_asset_timestamps" do |app| unless app.config.cache_classes @@ -16,6 +16,18 @@ module ActionView end end + initializer "action_view.javascript_expansions" do |app| + ActiveSupport.on_load(:action_view) do + ActionView::Helpers::AssetTagHelper.register_javascript_expansion( + app.config.action_view.delete(:javascript_expansions) + ) + + ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion( + app.config.action_view.delete(:stylesheet_expansions) + ) + end + end + initializer "action_view.set_configs" do |app| ActiveSupport.on_load(:action_view) do app.config.action_view.each do |k,v| diff --git a/actionpack/lib/action_view/railties/log_subscriber.rb b/actionpack/lib/action_view/railties/log_subscriber.rb deleted file mode 100644 index 9487a10706..0000000000 --- a/actionpack/lib/action_view/railties/log_subscriber.rb +++ /dev/null @@ -1,24 +0,0 @@ -module ActionView - module Railties - class LogSubscriber < Rails::LogSubscriber - def render_template(event) - message = "Rendered #{from_rails_root(event.payload[:identifier])}" - message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] - 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/layouts.rb b/actionpack/lib/action_view/render/layouts.rb index a9dfc0cced..a474783a20 100644 --- a/actionpack/lib/action_view/render/layouts.rb +++ b/actionpack/lib/action_view/render/layouts.rb @@ -1,4 +1,5 @@ module ActionView + # = Action View Layouts module Layouts # Returns the contents that are yielded to a layout, given a name or a block. # @@ -55,7 +56,7 @@ module ActionView end # This is the method which actually finds the layout using details in the lookup - # context object. If no layout is found, it checkes if at least a layout with + # context object. If no layout is found, it checks if at least a layout with # the given name exists across all details before raising the error. def find_layout(layout) begin diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 85f67d4f14..459aae94a2 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -1,6 +1,8 @@ require 'active_support/core_ext/object/blank' module ActionView + # = Action View Partials + # # There's also a convenience method for rendering sub templates within the current controller that depends on a # single object (we call this kind of sub templates for partials). It relies on the fact that partials should # follow the naming convention of being prefixed with an underscore -- as to separate them from regular @@ -316,7 +318,7 @@ module ActionView object.class.model_name.partial_path.dup.tap do |partial| path = @view.controller_path - partial.insert(0, "#{File.dirname(path)}/") if path.include?(?/) + partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/) end end end diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index 4d35296932..5320245173 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/object/try' module ActionView + # = Action View Rendering module Rendering # Returns the result of a render that's dictated by the options hash. The primary options are: # diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 53ad24fdc6..40ff1f2182 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -3,6 +3,7 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/kernel/singleton_class' module ActionView + # = Action View Template class Template extend ActiveSupport::Autoload @@ -155,11 +156,12 @@ module ActionView end def inspect - if defined?(Rails.root) - identifier.sub("#{Rails.root}/", '') - else - identifier - end + @inspect ||= + if defined?(Rails.root) + identifier.sub("#{Rails.root}/", '') + else + identifier + end end private @@ -266,9 +268,11 @@ module ActionView end def build_method_name(locals) - # TODO: is locals.keys.hash reliably the same? - @method_names[locals.keys.hash] ||= - "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_") + @method_names[locals.keys.hash] ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_") + end + + def identifier_method_name + @identifier_method_name ||= inspect.gsub(/[^a-z_]/, '_') end end end diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index e50de7e5af..b1839b65e5 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -1,6 +1,7 @@ require "active_support/core_ext/enumerable" module ActionView + # = Action View Errors class ActionViewError < StandardError #:nodoc: end diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb index 6228d7ac39..84d6474dd1 100644 --- a/actionpack/lib/action_view/template/handlers.rb +++ b/actionpack/lib/action_view/template/handlers.rb @@ -1,4 +1,5 @@ module ActionView #:nodoc: + # = Action View Template Handlers class Template module Handlers #:nodoc: autoload :ERB, 'action_view/template/handlers/erb' diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index ef44925951..c9e20ca14e 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -3,6 +3,7 @@ require "active_support/core_ext/class" require "action_view/template" module ActionView + # = Action View Resolver class Resolver def initialize @cached = Hash.new { |h1,k1| h1[k1] = @@ -98,7 +99,7 @@ module ActionView def initialize(path) raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver) super() - @path = Pathname.new(path).expand_path + @path = File.expand_path(path) end def eql?(resolver) diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index 269340514c..51be831dfb 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -1,4 +1,5 @@ module ActionView #:nodoc: + # = Action View Text Template class Template class Text < String #:nodoc: attr_accessor :mime_type diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 15d424be74..e5614c9e3b 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -4,6 +4,7 @@ require 'action_controller/test_case' require 'action_view' module ActionView + # = Action View Test Case class TestCase < ActiveSupport::TestCase class TestController < ActionController::Base include ActionDispatch::TestProcess @@ -84,6 +85,7 @@ module ActionView def setup_with_controller @controller = ActionView::TestCase::TestController.new + @request = @controller.request @output_buffer = ActiveSupport::SafeBuffer.new @rendered = '' @@ -97,10 +99,15 @@ module ActionView end def render(options = {}, local_assigns = {}, &block) - @rendered << output = _view.render(options, local_assigns, &block) + view.assign(_assigns) + @rendered << output = view.render(options, local_assigns, &block) output end + def locals + @locals ||= {} + end + included do setup :setup_with_controller end @@ -130,36 +137,51 @@ module ActionView end end - def _view - @_view ||= begin - view = ActionView::Base.new(ActionController::Base.view_paths, _assigns, @controller) + module Locals + attr_accessor :locals + + def _render_partial(options) + locals[options[:partial]] = options[:locals] + super(options) + end + end + + # The instance of ActionView::Base that is used by +render+. + def view + @view ||= begin + view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller) view.singleton_class.send :include, _helpers view.singleton_class.send :include, @controller._router.url_helpers view.singleton_class.send :delegate, :alert, :notice, :to => "request.flash" + view.extend(Locals) + view.locals = self.locals view.output_buffer = self.output_buffer view end end + alias_method :_view, :view + EXCLUDE_IVARS = %w{ + @_assertion_wrapped @_result + @controller + @layouts + @locals + @method_name @output_buffer + @partials @rendered + @request + @routes @templates - @view_context_class - @layouts - @partials - @controller - - @method_name - @fixture_cache - @loaded_fixtures @test_passed + @view + @view_context_class } def _instance_variables - instance_variables - EXCLUDE_IVARS - instance_variables + instance_variables.map(&:to_s) - EXCLUDE_IVARS end def _assigns diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index 0ce1dc506b..232a1679e0 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -47,8 +47,12 @@ module AbstractController end def index - self.response_body = @text - end + self.response_body = @text.to_s + end + end + + class Callback2Overwrite < Callback2 + before_filter :first, :except => :index end class TestCallbacks2 < ActiveSupport::TestCase @@ -70,6 +74,12 @@ module AbstractController @controller.process(:index) assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz") end + + test "before_filter with overwritten condition" do + @controller = Callback2Overwrite.new + result = @controller.process(:index) + assert_equal "", @controller.response_body + end end class Callback3 < ControllerWithCallbacks diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 84c5395610..c8477fb83f 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -38,6 +38,17 @@ end require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late +module Rails +end + +# Monkey patch the old router initialization to be silenced. +class ActionDispatch::Routing::DeprecatedMapper + def initialize_with_silencer(*args) + ActiveSupport::Deprecation.silence { initialize_without_silencer(*args) } + end + alias_method_chain :initialize, :silencer +end + ActiveSupport::Dependencies.hook! # Show backtraces for deprecated behavior for quicker cleanup. @@ -202,6 +213,21 @@ class ActionController::IntegrationTest < ActiveSupport::TestCase self.class.app = old_app silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) } end + + def with_autoload_path(path) + path = File.join(File.dirname(__FILE__), "fixtures", path) + if ActiveSupport::Dependencies.autoload_paths.include?(path) + yield + else + begin + ActiveSupport::Dependencies.autoload_paths << path + yield + ensure + ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path} + ActiveSupport::Dependencies.clear + end + end + end end # Temporary base class diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb index 6d4b8e1e40..bdd1a0a15c 100644 --- a/actionpack/test/activerecord/active_record_store_test.rb +++ b/actionpack/test/activerecord/active_record_store_test.rb @@ -17,7 +17,6 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest end def get_session_id - session[:foo] render :text => "#{request.session_options[:id]}" end @@ -58,6 +57,10 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest get '/get_session_value' assert_response :success assert_equal 'foo: "baz"', response.body + + get '/call_reset_session' + assert_response :success + assert_not_equal [], headers['Set-Cookie'] end end end @@ -92,6 +95,34 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest end end + def test_getting_session_value_after_session_reset + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + session_cookie = cookies.send(:hash_for)['_session_id'] + + get '/call_reset_session' + assert_response :success + assert_not_equal [], headers['Set-Cookie'] + + cookies << session_cookie # replace our new session_id with our old, pre-reset session_id + + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from the database" + end + end + + def test_getting_from_nonexistent_session + with_test_route_set do + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + assert_nil cookies['_session_id'], "should only create session on write, not read" + end + end + def test_getting_session_id with_test_route_set do get '/set_session_value' @@ -101,7 +132,19 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest get '/get_session_id' assert_response :success - assert_equal session_id, response.body + assert_equal session_id, response.body, "should be able to read session id without accessing the session hash" + end + end + + def test_doesnt_write_session_cookie_if_session_id_is_already_exists + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + + get '/get_session_value' + assert_response :success + assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists" end end diff --git a/actionpack/test/activerecord/controller_runtime_test.rb b/actionpack/test/activerecord/controller_runtime_test.rb index 331f861d8f..cfd86d704d 100644 --- a/actionpack/test/activerecord/controller_runtime_test.rb +++ b/actionpack/test/activerecord/controller_runtime_test.rb @@ -1,8 +1,8 @@ require 'active_record_unit' require 'active_record/railties/controller_runtime' require 'fixtures/project' -require 'rails/log_subscriber/test_helper' -require 'action_controller/railties/log_subscriber' +require 'active_support/log_subscriber/test_helper' +require 'action_controller/log_subscriber' ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime @@ -13,18 +13,18 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase end end - include Rails::LogSubscriber::TestHelper + include ActiveSupport::LogSubscriber::TestHelper tests LogSubscriberController def setup super @old_logger = ActionController::Base.logger - Rails::LogSubscriber.add(:action_controller, ActionController::Railties::LogSubscriber.new) + ActionController::LogSubscriber.attach_to :action_controller end def teardown super - Rails::LogSubscriber.log_subscribers.clear + ActiveSupport::LogSubscriber.log_subscribers.clear ActionController::Base.logger = @old_logger end diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index 9f5e8ec657..90a1ef982c 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -381,6 +381,7 @@ class PolymorphicRoutesTest < ActionController::TestCase with_test_routes do @series.save assert_equal "http://example.com/series/#{@series.id}", polymorphic_url(@series) + assert_equal "http://example.com/series", polymorphic_url(Series.new) end end @@ -407,18 +408,18 @@ class PolymorphicRoutesTest < ActionController::TestCase def with_admin_test_routes(options = {}) with_routing do |set| - set.draw do |map| - map.namespace :admin do |admin| - admin.resources :projects do |projects| - projects.resources :tasks - projects.resource :bid do |bid| - bid.resources :tasks + set.draw do + namespace :admin do + resources :projects do + resources :tasks + resource :bid do + resources :tasks end end - admin.resources :taxes do |taxes| - taxes.resources :faxes + resources :taxes do + resources :faxes end - admin.resources :series + resources :series end end @@ -429,12 +430,12 @@ class PolymorphicRoutesTest < ActionController::TestCase def with_admin_and_site_test_routes(options = {}) with_routing do |set| - set.draw do |map| - map.namespace :admin do |admin| - admin.resources :projects do |projects| - projects.namespace :site do |site| - site.resources :tasks do |tasks| - tasks.resources :steps + set.draw do + namespace :admin do + resources :projects do + namespace :site do + resources :tasks do + resources :steps end end end diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 4f58b5d968..ae270b751e 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -2,9 +2,6 @@ require 'abstract_unit' require 'logger' require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late -module Rails -end - # Provide some controller to run the tests on. module Submodule class ContainedEmptyController < ActionController::Base diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 5c636cbab8..4be09f8c83 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -236,6 +236,15 @@ class FlashIntegrationTest < ActionController::IntegrationTest end end + def test_just_using_flash_does_not_stream_a_cookie_back + with_test_route_set do + get '/use_flash' + assert_response :success + assert_nil @response.headers["Set-Cookie"] + assert_equal "flash: ", @response.body + end + end + private # Overwrite get to send SessionSecret in env hash @@ -247,10 +256,15 @@ class FlashIntegrationTest < ActionController::IntegrationTest def with_test_route_set with_routing do |set| set.draw do |map| - match ':action', :to => ActionDispatch::Session::CookieStore.new( - FlashIntegrationTest::TestController, :key => FlashIntegrationTest::SessionKey, :secret => FlashIntegrationTest::SessionSecret - ) + match ':action', :to => FlashIntegrationTest::TestController + end + + @app = self.class.build_app(set) do |middleware| + middleware.use ActionDispatch::Session::CookieStore, :key => SessionKey + middleware.use ActionDispatch::Flash + middleware.delete "ActionDispatch::ShowExceptions" end + yield end end diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index b11eba2f89..0a18741f0c 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -1,6 +1,6 @@ require "abstract_unit" -require "rails/log_subscriber/test_helper" -require "action_controller/railties/log_subscriber" +require "active_support/log_subscriber/test_helper" +require "action_controller/log_subscriber" module Another class LogSubscribersController < ActionController::Base @@ -37,7 +37,7 @@ end class ACLogSubscriberTest < ActionController::TestCase tests Another::LogSubscribersController - include Rails::LogSubscriber::TestHelper + include ActiveSupport::LogSubscriber::TestHelper def setup super @@ -47,12 +47,12 @@ class ACLogSubscriberTest < ActionController::TestCase @cache_path = File.expand_path('../temp/test_cache', File.dirname(__FILE__)) ActionController::Base.page_cache_directory = @cache_path @controller.cache_store = :file_store, @cache_path - Rails::LogSubscriber.add(:action_controller, ActionController::Railties::LogSubscriber.new) + ActionController::LogSubscriber.attach_to :action_controller end def teardown super - Rails::LogSubscriber.log_subscribers.clear + ActiveSupport::LogSubscriber.log_subscribers.clear FileUtils.rm_rf(@cache_path) ActionController::Base.logger = @old_logger end diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb index 0b40f8ce95..8fa5d20372 100644 --- a/actionpack/test/controller/new_base/base_test.rb +++ b/actionpack/test/controller/new_base/base_test.rb @@ -31,9 +31,17 @@ module Dispatching end class EmptyController < ActionController::Base ; end + class SubEmptyController < EmptyController ; end + class NonDefaultPathController < ActionController::Base + def self.controller_path; "i_am_not_default"; end + end module Submodule class ContainedEmptyController < ActionController::Base ; end + class ContainedSubEmptyController < ContainedEmptyController ; end + class ContainedNonDefaultPathController < ActionController::Base + def self.controller_path; "i_am_extremly_not_default"; end + end end class BaseTest < Rack::TestCase @@ -65,16 +73,46 @@ module Dispatching assert_equal EmptyController.controller_path, EmptyController.new.controller_path end + test "non-default controller path" do + assert_equal 'i_am_not_default', NonDefaultPathController.controller_path + assert_equal NonDefaultPathController.controller_path, NonDefaultPathController.new.controller_path + end + + test "sub controller path" do + assert_equal 'dispatching/sub_empty', SubEmptyController.controller_path + assert_equal SubEmptyController.controller_path, SubEmptyController.new.controller_path + end + test "namespaced controller path" do assert_equal 'dispatching/submodule/contained_empty', Submodule::ContainedEmptyController.controller_path assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path end + test "namespaced non-default controller path" do + assert_equal 'i_am_extremly_not_default', Submodule::ContainedNonDefaultPathController.controller_path + assert_equal Submodule::ContainedNonDefaultPathController.controller_path, Submodule::ContainedNonDefaultPathController.new.controller_path + end + + test "namespaced sub controller path" do + assert_equal 'dispatching/submodule/contained_sub_empty', Submodule::ContainedSubEmptyController.controller_path + assert_equal Submodule::ContainedSubEmptyController.controller_path, Submodule::ContainedSubEmptyController.new.controller_path + end + test "controller name" do assert_equal 'empty', EmptyController.controller_name assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name end + test "non-default path controller name" do + assert_equal 'non_default_path', NonDefaultPathController.controller_name + assert_equal 'contained_non_default_path', Submodule::ContainedNonDefaultPathController.controller_name + end + + test "sub controller name" do + assert_equal 'sub_empty', SubEmptyController.controller_name + assert_equal 'contained_sub_empty', Submodule::ContainedSubEmptyController.controller_name + end + test "action methods" do assert_equal Set.new(%w( index diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb index 813dedc80d..6a84475758 100644 --- a/actionpack/test/controller/record_identifier_test.rb +++ b/actionpack/test/controller/record_identifier_test.rb @@ -13,6 +13,19 @@ class Comment end end +class Sheep + extend ActiveModel::Naming + include ActiveModel::Conversion + + attr_reader :id + def to_key; id ? [id] : nil end + def save; @id = 1 end + def new_record?; @id.nil? end + def name + @id.nil? ? 'new sheep' : "sheep ##{@id}" + end +end + class Comment::Nested < Comment; end class Test::Unit::TestCase @@ -20,7 +33,7 @@ class Test::Unit::TestCase def comments_url 'http://www.example.com/comments' end - + def comment_url(comment) "http://www.example.com/comments/#{comment.id}" end @@ -35,6 +48,7 @@ class RecordIdentifierTest < Test::Unit::TestCase @record = @klass.new @singular = 'comment' @plural = 'comments' + @uncountable = Sheep end def test_dom_id_with_new_record @@ -58,7 +72,7 @@ class RecordIdentifierTest < Test::Unit::TestCase def test_dom_class assert_equal @singular, dom_class(@record) end - + def test_dom_class_with_prefix assert_equal "custom_prefix_#{@singular}", dom_class(@record, :custom_prefix) end @@ -79,6 +93,11 @@ class RecordIdentifierTest < Test::Unit::TestCase assert_equal @plural, plural_class_name(@klass) end + def test_uncountable + assert_equal true, uncountable?(@uncountable) + assert_equal false, uncountable?(@klass) + end + private def method_missing(method, *args) RecordIdentifier.send(method, *args) diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index e3ed097c67..a57a12f271 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -7,6 +7,10 @@ module Fun # :ported: def hello_world end + + def nested_partial_with_form_builder + render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}, Proc.new {}) + end end end @@ -1230,6 +1234,13 @@ class RenderTest < ActionController::TestCase assert_match(/<label/, @response.body) assert_template('test/_labelling_form') end + + def test_nested_partial_with_form_builder + @controller = Fun::GamesController.new + get :nested_partial_with_form_builder + assert_match(/<label/, @response.body) + assert_template('fun/games/_form') + end def test_partial_collection get :partial_collection diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 907acf9573..4b30b0af36 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -34,9 +34,15 @@ module AbstractController ) 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')) + def test_anchor_should_escape_unsafe_pchar + assert_equal('/c/a#%23anchor', + W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('#anchor')) + ) + end + + def test_anchor_should_not_escape_safe_pchar + assert_equal('/c/a#name=user&email=user@domain.com', + W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('name=user&email=user@domain.com')) ) end diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 40c5ac2d09..e3ec5cf182 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -89,15 +89,21 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest assert_equal 19512, file.size end + # Pending fix in Rack 1.2.2 + # http://rack.lighthouseapp.com/projects/22435-rack/tickets/79-multipart-handling-incorrectly-assuming-file-upload test "parses mixed files" do - params = parse_multipart('mixed_files') - assert_equal %w(files foo), params.keys.sort - assert_equal 'bar', params['foo'] - - # Rack doesn't handle multipart/mixed for us. - files = params['files'] - files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding) - assert_equal 19756, files.size + if Rack.release <= '1.2.1' + $stderr.puts 'multipart/mixed parsing pending fix in Rack 1.2.2' + else + params = parse_multipart('mixed_files') + assert_equal %w(files foo), params.keys.sort + assert_equal 'bar', params['foo'] + + # Rack doesn't handle multipart/mixed for us. + files = params['files'] + files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding) + assert_equal 19756, files.size + end end test "does not create tempfile if no file has been selected" do diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb index 69dbd7f528..0bcef81534 100644 --- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -141,6 +141,29 @@ class UrlEncodedParamsParsingTest < ActionController::IntegrationTest post "/parse", actual assert_response :ok assert_equal(expected, TestController.last_request_parameters) + assert_utf8(TestController.last_request_parameters) + end + end + + def assert_utf8(object) + return unless "ruby".encoding_aware? + + correct_encoding = Encoding.default_internal + + unless object.is_a?(Hash) + assert_equal correct_encoding, object.encoding, "#{object.inspect} should have been UTF-8" + return + end + + object.each do |k,v| + case v + when Hash + assert_utf8(v) + when Array + v.each {|el| assert_utf8(el) } + else + assert_utf8(v) + end end end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index e294703e72..26bd641cd6 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -16,6 +16,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest Routes = routes Routes.draw do default_url_options :host => "rubyonrails.org" + resources_path_names :correlation_indexes => "info_about_correlation_indexes" controller :sessions do get 'login' => :new @@ -34,6 +35,13 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + scope "bookmark", :controller => "bookmarks", :as => :bookmark do + get :new, :path => "build" + post :create, :path => "create", :as => "" + put :update + get "remove", :action => :destroy, :as => :remove + end + match 'account/logout' => redirect("/logout"), :as => :logout_redirect match 'account/login', :to => redirect("/login") @@ -68,12 +76,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get 'admin/accounts' => "queenbee#accounts" end - scope 'pt', :name_prefix => 'pt' do + get 'admin/passwords' => "queenbee#passwords", :constraints => ::TestRoutingMapper::IpRestrictor + + scope 'pt', :as => 'pt' do resources :projects, :path_names => { :edit => 'editar', :new => 'novo' }, :path => 'projetos' do post :preview, :on => :new + put :close, :on => :member, :path => 'fechar' + get :open, :on => :new, :path => 'abrir' end - resource :admin, :path_names => { :new => 'novo' }, :path => 'administrador' do + resource :admin, :path_names => { :new => 'novo', :activate => 'ativar' }, :path => 'administrador' do post :preview, :on => :new + put :activate, :on => :member end resources :products, :path_names => { :new => 'novo' } do new do @@ -84,6 +97,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resources :projects, :controller => :project do resources :involvements, :attachments + get :correlation_indexes, :on => :collection resources :participants do put :update_all, :on => :collection @@ -110,6 +124,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end member do + get :some_path_with_name put :accessible_projects post :resend, :generate_new_password end @@ -142,6 +157,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resources :comments, :except => :destroy end + resource :past, :only => :destroy + resource :present, :only => :update + resource :future, :only => :create + resources :relationships, :only => [:create, :destroy] + resources :friendships, :only => [:update] + shallow do namespace :api do resources :teams do @@ -174,6 +195,36 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + resources :customers do + get "recent" => "customers#recent", :as => :recent, :on => :collection + get "profile" => "customers#profile", :as => :profile, :on => :member + post "preview" => "customers#preview", :as => :preview, :on => :new + resource :avatar do + get "thumbnail(.:format)" => "avatars#thumbnail", :as => :thumbnail, :on => :member + end + resources :invoices do + get "outstanding" => "invoices#outstanding", :as => :outstanding, :on => :collection + get "overdue", :to => :overdue, :on => :collection + get "print" => "invoices#print", :as => :print, :on => :member + post "preview" => "invoices#preview", :as => :preview, :on => :new + end + resources :notes, :shallow => true do + get "preview" => "notes#preview", :as => :preview, :on => :new + get "print" => "notes#print", :as => :print, :on => :member + end + end + + namespace :api do + resources :customers do + get "recent" => "customers#recent", :as => :recent, :on => :collection + get "profile" => "customers#profile", :as => :profile, :on => :member + post "preview" => "customers#preview", :as => :preview, :on => :new + end + scope(':version', :version => /.+/) do + resources :users, :id => /.+?/, :format => /json|xml/ + end + end + match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp match 'people/:id/update', :to => 'people#update', :as => :update_person @@ -207,10 +258,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + namespace :users, :path => 'usuarios' do + root :to => 'home#index' + end + controller :articles do - scope '/articles', :name_prefix => 'article' do + scope '/articles', :as => 'article' do scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do - match '/:id', :to => :with_id + match '/:id', :to => :with_id, :as => "" end end end @@ -243,8 +298,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ } - scope :module => 'api' do + scope :module => :api do resource :token + resources :errors, :shallow => true do + resources :notices + end end scope :path => 'api' do @@ -252,6 +310,21 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest match '/' => 'mes#index' end + namespace :private do + root :to => redirect('/private/index') + match "index", :to => 'private#index' + end + + get "(/:username)/followers" => "followers#index" + get "/groups(/user/:username)" => "groups#index" + get "(/user/:username)/photos" => "photos#index" + + scope '(groups)' do + scope '(discussions)' do + resources :messages + end + end + match "whatever/:controller(/:action(/:id))" resource :profile do @@ -261,6 +334,21 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest post :preview end end + + resources :content + + scope :constraints => { :id => /\d+/ } do + get '/tickets', :to => 'tickets#index', :as => :tickets + end + + scope :constraints => { :id => /\d{4}/ } do + resources :movies do + resources :reviews + resource :trailer + end + end + + match '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/ end end @@ -373,6 +461,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_namespace_redirect + with_test_routes do + get '/private' + assert_equal 301, @response.status + assert_equal 'http://www.example.com/private/index', @response.headers['Location'] + assert_equal 'Moved Permanently', @response.body + end + end + def test_session_singleton_resource with_test_routes do get '/session' @@ -455,6 +552,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_bookmarks + with_test_routes do + get '/bookmark/build' + assert_equal 'bookmarks#new', @response.body + assert_equal '/bookmark/build', new_bookmark_path + + post '/bookmark/create' + assert_equal 'bookmarks#create', @response.body + assert_equal '/bookmark/create', bookmark_path + + put '/bookmark' + assert_equal 'bookmarks#update', @response.body + assert_equal '/bookmark', update_bookmark_path + + get '/bookmark/remove' + assert_equal 'bookmarks#destroy', @response.body + assert_equal '/bookmark/remove', bookmark_remove_path + end + end + def test_admin with_test_routes do get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'} @@ -468,6 +585,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} assert_equal 'pass', @response.headers['X-Cascade'] + + get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'} + assert_equal 'queenbee#passwords', @response.body + + get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'} + assert_equal 'pass', @response.headers['X-Cascade'] end end @@ -662,6 +785,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_projects_with_resources_path_names + with_test_routes do + get '/projects/info_about_correlation_indexes' + assert_equal 'project#correlation_indexes', @response.body + assert_equal '/projects/info_about_correlation_indexes', correlation_indexes_projects_path + end + end + def test_projects_posts with_test_routes do get '/projects/1/posts' @@ -729,6 +860,38 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_resource_routes_only_create_update_destroy + with_test_routes do + delete '/past' + assert_equal 'pasts#destroy', @response.body + assert_equal '/past', past_path + + put '/present' + assert_equal 'presents#update', @response.body + assert_equal '/present', present_path + + post '/future' + assert_equal 'futures#create', @response.body + assert_equal '/future', future_path + end + end + + def test_resources_routes_only_create_update_destroy + with_test_routes do + post '/relationships' + assert_equal 'relationships#create', @response.body + assert_equal '/relationships', relationships_path + + delete '/relationships/1' + assert_equal 'relationships#destroy', @response.body + assert_equal '/relationships/1', relationship_path(1) + + put '/friendships/1' + assert_equal 'friendships#update', @response.body + assert_equal '/friendships/1', friendship_path(1) + end + end + def test_resource_with_slugs_in_ids with_test_routes do get '/posts/rails-rocks' @@ -763,6 +926,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get '/pt/administrador/novo' assert_equal 'admins#new', @response.body assert_equal '/pt/administrador/novo', new_pt_admin_path + + put '/pt/administrador/ativar' + assert_equal 'admins#activate', @response.body + assert_equal '/pt/administrador/ativar', activate_pt_admin_path + end + end + + def test_path_option_override + with_test_routes do + get '/pt/projetos/novo/abrir' + assert_equal 'projects#open', @response.body + assert_equal '/pt/projetos/novo/abrir', open_new_pt_project_path + + put '/pt/projetos/1/fechar' + assert_equal 'projects#close', @response.body + assert_equal '/pt/projetos/1/fechar', close_pt_project_path(1) end end @@ -843,7 +1022,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/account/admin/subscription', account_admin_subscription_path end end - + def test_namespace_nested_in_resources with_test_routes do get '/clients/1/google/account' @@ -856,6 +1035,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_namespace_with_options + with_test_routes do + get '/usuarios' + assert_equal '/usuarios', users_root_path + assert_equal 'users/home#index', @response.body + end + end + def test_articles_with_id with_test_routes do get '/articles/rails/1' @@ -1257,6 +1444,190 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_custom_resource_routes_are_scoped + with_test_routes do + assert_equal '/customers/recent', recent_customers_path + assert_equal '/customers/1/profile', profile_customer_path(:id => '1') + assert_equal '/customers/new/preview', preview_new_customer_path + assert_equal '/customers/1/avatar/thumbnail.jpg', thumbnail_customer_avatar_path(:customer_id => '1', :format => :jpg) + assert_equal '/customers/1/invoices/outstanding', outstanding_customer_invoices_path(:customer_id => '1') + assert_equal '/customers/1/invoices/2/print', print_customer_invoice_path(:customer_id => '1', :id => '2') + assert_equal '/customers/1/invoices/new/preview', preview_new_customer_invoice_path(:customer_id => '1') + assert_equal '/customers/1/notes/new/preview', preview_new_customer_note_path(:customer_id => '1') + assert_equal '/notes/1/print', print_note_path(:id => '1') + assert_equal '/api/customers/recent', recent_api_customers_path + assert_equal '/api/customers/1/profile', profile_api_customer_path(:id => '1') + assert_equal '/api/customers/new/preview', preview_new_api_customer_path + + get '/customers/1/invoices/overdue' + assert_equal 'invoices#overdue', @response.body + end + end + + def test_shallow_nested_routes_ignore_module + with_test_routes do + get '/errors/1/notices' + assert_equal 'api/notices#index', @response.body + assert_equal '/errors/1/notices', error_notices_path(:error_id => '1') + + get '/notices/1' + assert_equal 'api/notices#show', @response.body + assert_equal '/notices/1', notice_path(:id => '1') + end + end + + def test_non_greedy_regexp + with_test_routes do + get '/api/1.0/users' + assert_equal 'api/users#index', @response.body + assert_equal '/api/1.0/users', api_users_path(:version => '1.0') + + get '/api/1.0/users.json' + assert_equal 'api/users#index', @response.body + assert_equal true, @request.format.json? + assert_equal '/api/1.0/users.json', api_users_path(:version => '1.0', :format => :json) + + get '/api/1.0/users/first.last' + assert_equal 'api/users#show', @response.body + assert_equal 'first.last', @request.params[:id] + assert_equal '/api/1.0/users/first.last', api_user_path(:version => '1.0', :id => 'first.last') + + get '/api/1.0/users/first.last.xml' + assert_equal 'api/users#show', @response.body + assert_equal 'first.last', @request.params[:id] + assert_equal true, @request.format.xml? + assert_equal '/api/1.0/users/first.last.xml', api_user_path(:version => '1.0', :id => 'first.last', :format => :xml) + end + end + + def test_glob_parameter_accepts_regexp + with_test_routes do + get '/en/path/to/existing/file.html' + assert_equal 200, @response.status + end + end + + def test_resources_controller_name_is_not_pluralized + with_test_routes do + get '/content' + assert_equal 'content#index', @response.body + end + end + + def test_url_generator_for_optional_prefix_dynamic_segment + with_test_routes do + get '/bob/followers' + assert_equal 'followers#index', @response.body + assert_equal 'http://www.example.com/bob/followers', + url_for(:controller => "followers", :action => "index", :username => "bob") + + get '/followers' + assert_equal 'followers#index', @response.body + assert_equal 'http://www.example.com/followers', + url_for(:controller => "followers", :action => "index", :username => nil) + end + end + + def test_url_generator_for_optional_suffix_static_and_dynamic_segment + with_test_routes do + get '/groups/user/bob' + assert_equal 'groups#index', @response.body + assert_equal 'http://www.example.com/groups/user/bob', + url_for(:controller => "groups", :action => "index", :username => "bob") + + get '/groups' + assert_equal 'groups#index', @response.body + assert_equal 'http://www.example.com/groups', + url_for(:controller => "groups", :action => "index", :username => nil) + end + end + + def test_url_generator_for_optional_prefix_static_and_dynamic_segment + with_test_routes do + get 'user/bob/photos' + assert_equal 'photos#index', @response.body + assert_equal 'http://www.example.com/user/bob/photos', + url_for(:controller => "photos", :action => "index", :username => "bob") + + get 'photos' + assert_equal 'photos#index', @response.body + assert_equal 'http://www.example.com/photos', + url_for(:controller => "photos", :action => "index", :username => nil) + end + end + + def test_url_recognition_for_optional_static_segments + with_test_routes do + get '/groups/discussions/messages' + assert_equal 'messages#index', @response.body + + get '/groups/discussions/messages/1' + assert_equal 'messages#show', @response.body + + get '/groups/messages' + assert_equal 'messages#index', @response.body + + get '/groups/messages/1' + assert_equal 'messages#show', @response.body + + get '/discussions/messages' + assert_equal 'messages#index', @response.body + + get '/discussions/messages/1' + assert_equal 'messages#show', @response.body + + get '/messages' + assert_equal 'messages#index', @response.body + + get '/messages/1' + assert_equal 'messages#show', @response.body + end + end + + def test_router_removes_invalid_conditions + with_test_routes do + get '/tickets' + assert_equal 'tickets#index', @response.body + assert_equal '/tickets', tickets_path + end + end + + def test_constraints_are_merged_from_scope + with_test_routes do + get '/movies/0001' + assert_equal 'movies#show', @response.body + assert_equal '/movies/0001', movie_path(:id => '0001') + + get '/movies/00001' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ movie_path(:id => '00001') } + + get '/movies/0001/reviews' + assert_equal 'reviews#index', @response.body + assert_equal '/movies/0001/reviews', movie_reviews_path(:movie_id => '0001') + + get '/movies/00001/reviews' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ movie_reviews_path(:movie_id => '00001') } + + get '/movies/0001/reviews/0001' + assert_equal 'reviews#show', @response.body + assert_equal '/movies/0001/reviews/0001', movie_review_path(:movie_id => '0001', :id => '0001') + + get '/movies/00001/reviews/0001' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ movie_path(:movie_id => '00001', :id => '00001') } + + get '/movies/0001/trailer' + assert_equal 'trailers#show', @response.body + assert_equal '/movies/0001/trailer', movie_trailer_path(:movie_id => '0001') + + get '/movies/00001/trailer' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ movie_trailer_path(:movie_id => '00001') } + end + end + private def with_test_routes yield diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index b4380f7818..fd63f5ad5e 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -83,7 +83,7 @@ class CookieStoreTest < ActionController::IntegrationTest get '/get_session_id' assert_response :success - assert_equal "id: #{session_id}", response.body + assert_equal "id: #{session_id}", response.body, "should be able to read session id without accessing the session hash" end end @@ -96,6 +96,31 @@ class CookieStoreTest < ActionController::IntegrationTest end end + # {:foo=>#<SessionAutoloadTest::Foo bar:"baz">, :session_id=>"ce8b0752a6ab7c7af3cdb8a80e6b9e46"} + SignedSerializedCookie = "BAh7BzoIZm9vbzodU2Vzc2lvbkF1dG9sb2FkVGVzdDo6Rm9vBjoJQGJhciIIYmF6Og9zZXNzaW9uX2lkIiVjZThiMDc1MmE2YWI3YzdhZjNjZGI4YTgwZTZiOWU0Ng==--2bf3af1ae8bd4e52b9ac2099258ace0c380e601c" + + def test_deserializes_unloaded_classes_on_get_id + with_test_route_set do + with_autoload_path "session_autoload_test" do + cookies[SessionKey] = SignedSerializedCookie + get '/get_session_id' + assert_response :success + assert_equal 'id: ce8b0752a6ab7c7af3cdb8a80e6b9e46', response.body, "should auto-load unloaded class" + end + end + end + + def test_deserializes_unloaded_classes_on_get_value + with_test_route_set do + with_autoload_path "session_autoload_test" do + cookies[SessionKey] = SignedSerializedCookie + get '/get_session_value' + assert_response :success + assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class" + end + end + end + def test_close_raises_when_data_overflows with_test_route_set do assert_raise(ActionDispatch::Cookies::CookieOverflow) { @@ -141,6 +166,15 @@ class CookieStoreTest < ActionController::IntegrationTest end end + def test_getting_from_nonexistent_session + with_test_route_set do + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + assert_nil headers['Set-Cookie'], "should only create session on write, not read" + end + end + def test_persistent_session_id with_test_route_set do cookies[SessionKey] = SignedBar @@ -196,21 +230,21 @@ class CookieStoreTest < ActionController::IntegrationTest def test_session_store_without_domain with_test_route_set do get '/set_session_value' - assert_no_match /domain\=/, headers['Set-Cookie'] + assert_no_match(/domain\=/, headers['Set-Cookie']) end end def test_session_store_with_nil_domain with_test_route_set(:domain => nil) do get '/set_session_value' - assert_no_match /domain\=/, headers['Set-Cookie'] + assert_no_match(/domain\=/, headers['Set-Cookie']) end end def test_session_store_with_all_domains with_test_route_set(:domain => :all) do get '/set_session_value' - assert_match /domain=\.example\.com/, headers['Set-Cookie'] + assert_match(/domain=\.example\.com/, headers['Set-Cookie']) end end @@ -238,4 +272,5 @@ class CookieStoreTest < ActionController::IntegrationTest yield end end + end diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb index 8858a398e0..9bd6f9b8c4 100644 --- a/actionpack/test/dispatch/session/mem_cache_store_test.rb +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -11,13 +11,17 @@ class MemCacheStoreTest < ActionController::IntegrationTest session[:foo] = "bar" head :ok end + + def set_serialized_session_value + session[:foo] = SessionAutoloadTest::Foo.new + head :ok + end def get_session_value render :text => "foo: #{session[:foo].inspect}" end def get_session_id - session[:foo] render :text => "#{request.session_options[:id]}" end @@ -56,6 +60,34 @@ class MemCacheStoreTest < ActionController::IntegrationTest end end + def test_getting_session_value_after_session_reset + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + session_cookie = cookies.send(:hash_for)['_session_id'] + + get '/call_reset_session' + assert_response :success + assert_not_equal [], headers['Set-Cookie'] + + cookies << session_cookie # replace our new session_id with our old, pre-reset session_id + + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from memcached" + end + end + + def test_getting_from_nonexistent_session + with_test_route_set do + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + assert_nil cookies['_session_id'], "should only create session on write, not read" + end + end + def test_setting_session_value_after_session_reset with_test_route_set do get '/set_session_value' @@ -86,7 +118,38 @@ class MemCacheStoreTest < ActionController::IntegrationTest get '/get_session_id' assert_response :success - assert_equal session_id, response.body + assert_equal session_id, response.body, "should be able to read session id without accessing the session hash" + end + end + + def test_deserializes_unloaded_class + with_test_route_set do + with_autoload_path "session_autoload_test" do + get '/set_serialized_session_value' + assert_response :success + assert cookies['_session_id'] + end + with_autoload_path "session_autoload_test" do + get '/get_session_id' + assert_response :success + end + with_autoload_path "session_autoload_test" do + get '/get_session_value' + assert_response :success + assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class" + end + end + end + + def test_doesnt_write_session_cookie_if_session_id_is_already_exists + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + + get '/get_session_value' + assert_response :success + assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists" end end diff --git a/actionpack/test/fixtures/fun/games/_form.erb b/actionpack/test/fixtures/fun/games/_form.erb new file mode 100644 index 0000000000..01107f1cb2 --- /dev/null +++ b/actionpack/test/fixtures/fun/games/_form.erb @@ -0,0 +1 @@ +<%= form.label :title %> diff --git a/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb b/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb new file mode 100644 index 0000000000..4ee7a24561 --- /dev/null +++ b/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb @@ -0,0 +1,10 @@ +module SessionAutoloadTest + class Foo + def initialize(bar='baz') + @bar = bar + end + def inspect + "#<#{self.class} bar:#{@bar.inspect}>" + end + end +end diff --git a/actionpack/test/template/active_model_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb index b1705072c2..6ab244d178 100644 --- a/actionpack/test/template/active_model_helper_test.rb +++ b/actionpack/test/template/active_model_helper_test.rb @@ -39,6 +39,13 @@ class ActiveModelHelperTest < ActionView::TestCase ) end + def test_hidden_field_does_not_render_errors + assert_dom_equal( + %(<input id="post_author_name" name="post[author_name]" type="hidden" value="" />), + hidden_field("post", "author_name") + ) + end + def test_field_error_proc old_proc = ActionView::Base.field_error_proc ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 633641514e..6d5e4893c4 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -44,7 +44,7 @@ class AssetTagHelperTest < ActionView::TestCase @controller.request = @request - ActionView::Helpers::AssetTagHelper::reset_javascript_include_default + ActionView::Helpers::AssetTagHelper::register_javascript_expansion :defaults => ['prototype', 'effects', 'dragdrop', 'controls', 'rails'] end def url_for(*args) @@ -256,19 +256,6 @@ class AssetTagHelperTest < ActionView::TestCase assert javascript_include_tag("prototype").html_safe? end - def test_register_javascript_include_default - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'bank' - assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults) - end - - def test_register_javascript_include_default_mixed_defaults - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'bank' - ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'robber', '/elsewhere/cools.js' - assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults) - end - def test_custom_javascript_expansions ENV["RAILS_ASSET_ID"] = "" ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"] @@ -286,6 +273,11 @@ class AssetTagHelperTest < ActionView::TestCase assert_raise(ArgumentError) { javascript_include_tag('first', :monkey, 'last') } end + def test_reset_javascript_expansions + ActionView::Helpers::AssetTagHelper.javascript_expansions.clear + assert_raise(ArgumentError) { javascript_include_tag(:defaults) } + end + def test_stylesheet_path ENV["RAILS_ASSET_ID"] = "" StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } @@ -923,7 +915,7 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase @request = Struct.new(:protocol).new("gopher://") @controller.request = @request - ActionView::Helpers::AssetTagHelper::reset_javascript_include_default + ActionView::Helpers::AssetTagHelper.javascript_expansions.clear end def url_for(options) diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 053fcc4d24..a1db49d4d0 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -882,6 +882,33 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :start_year => 2003, :end_year => 2005, :prefix => "date[first]"}) end + def test_select_date_with_separator_and_discard_day + expected = %(<select id="date_first_year" name="date[first][year]">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << " / " + + expected << %(<select id="date_first_month" name="date[first][month]">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="1" />\n) + + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :discard_day => true, :start_year => 2003, :end_year => 2005, :prefix => "date[first]"}) + end + + def test_select_date_with_separator_discard_month_and_day + expected = %(<select id="date_first_year" name="date[first][year]">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << %(<input type="hidden" id="date_first_month" name="date[first][month]" value="8" />\n) + expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="16" />\n) + + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :discard_month => true, :discard_day => true, :start_year => 2003, :end_year => 2005, :prefix => "date[first]"}) + end + def test_select_datetime expected = %(<select id="date_first_year" name="date[first][year]">\n) expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) diff --git a/actionpack/test/template/erb/form_for_test.rb b/actionpack/test/template/erb/form_for_test.rb index ec6e872735..e722b40a9a 100644 --- a/actionpack/test/template/erb/form_for_test.rb +++ b/actionpack/test/template/erb/form_for_test.rb @@ -5,7 +5,7 @@ module ERBTest class TagHelperTest < BlockTestCase test "form_for works" do output = render_content "form_for(:staticpage, :url => {:controller => 'blah', :action => 'update'})", "" - assert_equal "<form action=\"/blah/update\" method=\"post\"></form>", output + assert_match %r{<form.*action="/blah/update".*method="post">.*</form>}, output end end end diff --git a/actionpack/test/template/erb/tag_helper_test.rb b/actionpack/test/template/erb/tag_helper_test.rb index 64a88bde1d..d073100986 100644 --- a/actionpack/test/template/erb/tag_helper_test.rb +++ b/actionpack/test/template/erb/tag_helper_test.rb @@ -28,8 +28,8 @@ module ERBTest end test "percent equals works with form tags" do - expected_output = "<form action=\"foo\" method=\"post\">hello</form>" - maybe_deprecated { assert_equal expected_output, render_content("form_tag('foo')", "<%= 'hello' %>") } + expected_output = %r{<form.*action="foo".*method="post">.*hello*</form>} + maybe_deprecated { assert_match expected_output, render_content("form_tag('foo')", "<%= 'hello' %>") } end test "percent equals works with fieldset tags" do diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 2f3869994c..4b9e41803f 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -188,6 +188,11 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, text_field("post", "title", :maxlength => 35, :size => nil) end + def test_text_field_with_nil_value + expected = '<input id="post_title" name="post[title]" size="30" type="text" />' + assert_dom_equal expected, text_field("post", "title", :value => nil) + end + def test_text_field_doesnt_change_param_values object_name = 'post[]' expected = '<input id="post_123_title" name="post[123][title]" size="30" type="text" value="Hello World" />' @@ -208,6 +213,11 @@ class FormHelperTest < ActionView::TestCase hidden_field("post", "title") end + def test_hidden_field_with_nil_value + expected = '<input id="post_title" name="post[title]" type="hidden" />' + assert_dom_equal expected, hidden_field("post", "title", :value => nil) + end + def test_text_field_with_options assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="Something Else" />', hidden_field("post", "title", :value => "Something Else") @@ -300,6 +310,11 @@ class FormHelperTest < ActionView::TestCase ) end + def test_radio_button_with_negative_integer_value + assert_dom_equal('<input id="post_secret_-1" name="post[secret]" type="radio" value="-1" />', + radio_button("post", "secret", "-1")) + end + def test_radio_button_respects_passed_in_id assert_dom_equal('<input checked="checked" id="foo" name="post[secret]" type="radio" value="1" />', radio_button("post", "secret", "1", :id=>"foo") @@ -568,7 +583,8 @@ class FormHelperTest < ActionView::TestCase end expected = - "<form action='http://www.example.com' id='create-post' method='post'>" + + "<form accept-charset='UTF-8' action='http://www.example.com' id='create-post' method='post'>" + + snowman + "<label for='post_title'>The Title</label>" + "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + @@ -589,15 +605,14 @@ class FormHelperTest < ActionView::TestCase concat f.submit('Create post') end - expected = - "<form class='other_name_edit' method='post' action='/posts/123' id='create-post'>" + - "<div style='margin:0;padding:0;display:inline'><input name='_method' value='put' type='hidden' /></div>" + + expected = whole_form("/posts/123", "create-post", "other_name_edit", :method => "put") do "<label for='other_name_title'>Title</label>" + "<input name='other_name[title]' size='30' id='other_name_title' value='Hello World' type='text' />" + "<textarea name='other_name[body]' id='other_name_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + "<input name='other_name[secret]' value='0' type='hidden' />" + "<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" + - "<input name='commit' id='other_name_submit' value='Create post' type='submit' /></form>" + "<input name='commit' id='other_name_submit' value='Create post' type='submit' />" + end assert_dom_equal expected, output_buffer end @@ -611,14 +626,12 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "<form action='http://www.example.com' id='create-post' method='post'>" + - "<div style='margin:0;padding:0;display:inline'><input name='_method' type='hidden' value='put' /></div>" + + expected = whole_form("http://www.example.com", "create-post", nil, "put") do "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + - "</form>" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + end assert_dom_equal expected, output_buffer end @@ -632,14 +645,31 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "<form action='http://www.example.com' id='create-post' method='post' data-remote='true'>" + - "<div style='margin:0;padding:0;display:inline'><input name='_method' type='hidden' value='put' /></div>" + + expected = whole_form("http://www.example.com", "create-post", nil, :method => "put", :remote => true) do "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + - "</form>" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + end + + assert_dom_equal expected, output_buffer + end + + def test_form_for_with_remote_without_html + assert_deprecated do + form_for(:post, @post, :remote => true) do |f| + concat f.text_field(:title) + concat f.text_area(:body) + concat f.check_box(:secret) + end + end + + expected = whole_form("http://www.example.com", nil, nil, :remote => true) do + "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[secret]' type='hidden' value='0' />" + + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + end assert_dom_equal expected, output_buffer end @@ -651,13 +681,12 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = - "<form action='http://www.example.com' id='create-post' method='post'>" + + expected = whole_form("http://www.example.com", "create-post") do "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + - "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + - "</form>" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + end assert_dom_equal expected, output_buffer end @@ -672,14 +701,13 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "<form action='http://www.example.com' method='post'>" + + expected = whole_form do "<label for='post_123_title'>Title</label>" + "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" + "<textarea name='post[123][body]' id='post_123_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + "<input name='post[123][secret]' type='hidden' value='0' />" + - "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />" + - "</form>" + "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />" + end assert_dom_equal expected, output_buffer end @@ -693,13 +721,12 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "<form action='http://www.example.com' method='post'>" + + expected = whole_form do "<input name='post[][title]' size='30' type='text' id='post__title' value='Hello World' />" + "<textarea name='post[][body]' id='post__body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + "<input name='post[][secret]' type='hidden' value='0' />" + - "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />" + - "</form>" + "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />" + end assert_dom_equal expected, output_buffer end @@ -714,9 +741,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = "<form action='http://www.example.com' method='post'>" + - "<input name='commit' id='post_submit' type='submit' value='Create Post' />" + - "</form>" + expected = whole_form do + "<input name='commit' id='post_submit' type='submit' value='Create Post' />" + end + assert_dom_equal expected, output_buffer ensure I18n.locale = old_locale @@ -731,9 +759,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = "<form action='http://www.example.com' method='post'>" + - "<input name='commit' id='post_submit' type='submit' value='Confirm Post changes' />" + - "</form>" + expected = whole_form do + "<input name='commit' id='post_submit' type='submit' value='Confirm Post changes' />" + end + assert_dom_equal expected, output_buffer ensure I18n.locale = old_locale @@ -746,9 +775,10 @@ class FormHelperTest < ActionView::TestCase 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>" + expected = whole_form do + "<input name='commit' class='extra' id='post_submit' type='submit' value='Save changes' />" + end + assert_dom_equal expected, output_buffer ensure I18n.locale = old_locale @@ -763,9 +793,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = "<form action='http://www.example.com' method='post'>" + - "<input name='commit' id='another_post_submit' type='submit' value='Update your Post' />" + - "</form>" + expected = whole_form do + "<input name='commit' id='another_post_submit' type='submit' value='Update your Post' />" + end + assert_dom_equal expected, output_buffer ensure I18n.locale = old_locale @@ -780,9 +811,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = "<form action='http://www.example.com' method='post'>" + - "<input name='post[comment][title]' size='30' type='text' id='post_comment_title' value='Hello World' />" + - "</form>" + expected = whole_form do + "<input name='post[comment][title]' size='30' type='text' id='post_comment_title' value='Hello World' />" + end assert_dom_equal expected, output_buffer end @@ -797,10 +828,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = "<form action='http://www.example.com' method='post'>" + - "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" + - "<input name='post[123][comment][][name]' size='30' type='text' id='post_123_comment__name' value='new comment' />" + - "</form>" + expected = whole_form do + "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" + + "<input name='post[123][comment][][name]' size='30' type='text' id='post_123_comment__name' value='new comment' />" + end assert_dom_equal expected, output_buffer end @@ -815,10 +846,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = "<form action='http://www.example.com' method='post'>" + - "<input name='post[1][title]' size='30' type='text' id='post_1_title' value='Hello World' />" + - "<input name='post[1][comment][1][name]' size='30' type='text' id='post_1_comment_1_name' value='new comment' />" + - "</form>" + expected = whole_form do + "<input name='post[1][title]' size='30' type='text' id='post_1_title' value='Hello World' />" + + "<input name='post[1][comment][1][name]' size='30' type='text' id='post_1_comment_1_name' value='new comment' />" + end assert_dom_equal expected, output_buffer end @@ -832,9 +863,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = "<form action='http://www.example.com' method='post'>" + - "<input name='post[1][comment][title]' size='30' type='text' id='post_1_comment_title' value='Hello World' />" + - "</form>" + expected = whole_form do + "<input name='post[1][comment][title]' size='30' type='text' id='post_1_comment_title' value='Hello World' />" + end assert_dom_equal expected, output_buffer end @@ -848,9 +879,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = "<form action='http://www.example.com' method='post'>" + - "<input name='post[1][comment][5][title]' size='30' type='text' id='post_1_comment_5_title' value='Hello World' />" + - "</form>" + expected = whole_form do + "<input name='post[1][comment][5][title]' size='30' type='text' id='post_1_comment_5_title' value='Hello World' />" + end assert_dom_equal expected, output_buffer end @@ -864,9 +895,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = "<form action='http://www.example.com' method='post'>" + - "<input name='post[123][comment][title]' size='30' type='text' id='post_123_comment_title' value='Hello World' />" + - "</form>" + expected = whole_form do + "<input name='post[123][comment][title]' size='30' type='text' id='post_123_comment_title' value='Hello World' />" + end assert_dom_equal expected, output_buffer end @@ -880,9 +911,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = "<form action='http://www.example.com' method='post'>" + - "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />" + - "</form>" + expected = whole_form do + "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />" + end assert_dom_equal expected, output_buffer end @@ -896,9 +927,9 @@ class FormHelperTest < ActionView::TestCase end end - expected = "<form action='http://www.example.com' method='post'>" + - "<input name='post[123][comment][123][title]' size='30' type='text' id='post_123_comment_123_title' value='Hello World' />" + - "</form>" + expected = whole_form do + "<input name='post[123][comment][123][title]' size='30' type='text' id='post_123_comment_123_title' value='Hello World' />" + end assert_dom_equal expected, output_buffer end @@ -917,12 +948,11 @@ class FormHelperTest < ActionView::TestCase } end - expected = "<form action='http://www.example.com' method='post'>" + - "<input name='post[123][comment][5][title]' size='30' type='text' id='post_123_comment_5_title' value='Hello World' />" + - "</form>" + - "<form action='http://www.example.com' method='post'>" + - "<input name='post[1][comment][123][title]' size='30' type='text' id='post_1_comment_123_title' value='Hello World' />" + - "</form>" + expected = whole_form do + "<input name='post[123][comment][5][title]' size='30' type='text' id='post_123_comment_5_title' value='Hello World' />" + end + whole_form do + "<input name='post[1][comment][123][title]' size='30' type='text' id='post_1_comment_123_title' value='Hello World' />" + end assert_dom_equal expected, output_buffer end @@ -940,10 +970,10 @@ class FormHelperTest < ActionView::TestCase 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_name" name="post[author_attributes][name]" size="30" type="text" value="new author" />' + - '</form>' + expected = whole_form do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="new author" />' + end assert_dom_equal expected, output_buffer end @@ -971,11 +1001,11 @@ class FormHelperTest < ActionView::TestCase 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_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + - '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + - '</form>' + expected = whole_form do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + end assert_dom_equal expected, output_buffer end @@ -993,11 +1023,11 @@ class FormHelperTest < ActionView::TestCase 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" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + - '</form>' + expected = whole_form do + '<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" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + end assert_dom_equal expected, output_buffer end @@ -1016,13 +1046,13 @@ class FormHelperTest < ActionView::TestCase 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_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + - '</form>' + expected = whole_form do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + end assert_dom_equal expected, output_buffer end @@ -1042,13 +1072,13 @@ class FormHelperTest < ActionView::TestCase 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" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + - '</form>' + expected = whole_form do + '<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" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + end assert_dom_equal expected, output_buffer end @@ -1067,11 +1097,11 @@ class FormHelperTest < ActionView::TestCase 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_name" name="post[comments_attributes][0][name]" size="30" type="text" value="new comment" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + - '</form>' + expected = whole_form do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="new comment" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + end assert_dom_equal expected, output_buffer end @@ -1090,12 +1120,12 @@ class FormHelperTest < ActionView::TestCase 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_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + - '</form>' + expected = whole_form do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + end assert_dom_equal expected, output_buffer end @@ -1110,9 +1140,9 @@ class FormHelperTest < ActionView::TestCase 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" />' + - '</form>' + expected = whole_form do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + end assert_dom_equal expected, output_buffer end @@ -1129,13 +1159,13 @@ class FormHelperTest < ActionView::TestCase 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_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + - '</form>' + expected = whole_form do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + end assert_dom_equal expected, output_buffer end @@ -1153,13 +1183,13 @@ class FormHelperTest < ActionView::TestCase 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_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + - '</form>' + expected = whole_form do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + end assert_dom_equal expected, output_buffer end @@ -1178,12 +1208,12 @@ class FormHelperTest < ActionView::TestCase 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_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + - '</form>' + expected = whole_form do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + end assert_dom_equal expected, output_buffer assert_equal yielded_comments, @post.comments @@ -1200,10 +1230,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = '<form action="http://www.example.com" method="post">' + - '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" size="30" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' + - '</form>' + expected = whole_form do + '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" size="30" type="text" value="comment #321" />' + + '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' + end assert_dom_equal expected, output_buffer end @@ -1238,20 +1268,20 @@ class FormHelperTest < ActionView::TestCase end end - expected = '<form action="http://www.example.com" method="post">' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="commentrelevance #314" />' + - '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + - '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" size="30" type="text" value="tag #123" />' + - '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #3141" />' + - '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' + - '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' + - '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" size="30" type="text" value="tag #456" />' + - '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #31415" />' + - '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' + - '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' + - '</form>' + expected = whole_form do + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + + '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="commentrelevance #314" />' + + '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + + '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" size="30" type="text" value="tag #123" />' + + '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #3141" />' + + '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' + + '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' + + '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" size="30" type="text" value="tag #456" />' + + '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #31415" />' + + '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' + + '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' + end assert_dom_equal expected, output_buffer end @@ -1391,7 +1421,8 @@ class FormHelperTest < ActionView::TestCase end expected = - "<form action='http://www.example.com' id='create-post' method='post'>" + + "<form accept-charset='UTF-8' action='http://www.example.com' id='create-post' method='post'>" + + snowman + "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + "<input name='parent_post[secret]' type='hidden' value='0' />" + @@ -1414,11 +1445,11 @@ class FormHelperTest < ActionView::TestCase end expected = - "<form action='http://www.example.com' id='create-post' method='post'>" + - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + - "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />" + - "</form>" + whole_form("http://www.example.com", "create-post") do + "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />" + end assert_dom_equal expected, output_buffer end @@ -1442,16 +1473,42 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "<form action='http://www.example.com' method='post'>" + - "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" + - "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" + - "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" + - "</form>" + expected = whole_form do + "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" + + "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" + + "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" + end assert_dom_equal expected, output_buffer end + def snowman(method = nil) + txt = %{<div style="margin:0;padding:0;display:inline">} + txt << %{<input name="_snowman" type="hidden" value="☃" />} + txt << %{<input name="_method" type="hidden" value="#{method}" />} if method + txt << %{</div>} + end + + def form_text(action = "http://www.example.com", id = nil, html_class = nil, remote = nil) + txt = %{<form accept-charset="UTF-8" action="#{action}"} + txt << %{ data-remote="true"} if remote + txt << %{ class="#{html_class}"} if html_class + txt << %{ id="#{id}"} if id + txt << %{ method="post">} + end + + def whole_form(action = "http://www.example.com", id = nil, html_class = nil, options = nil) + contents = block_given? ? yield : "" + + if options.is_a?(Hash) + method, remote = options.values_at(:method, :remote) + else + method = options + end + + form_text(action, id, html_class, remote) + snowman(method) + contents + "</form>" + end + def test_default_form_builder old_default_form_builder, ActionView::Base.default_form_builder = ActionView::Base.default_form_builder, LabelledFormBuilder @@ -1464,12 +1521,11 @@ class FormHelperTest < ActionView::TestCase end end - expected = - "<form action='http://www.example.com' method='post'>" + + expected = whole_form do "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" + "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" + - "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" + - "</form>" + "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" + end assert_dom_equal expected, output_buffer ensure @@ -1542,7 +1598,7 @@ class FormHelperTest < ActionView::TestCase assert_deprecated do form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end end - expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\"></form>" + expected = whole_form("http://www.example.com", "some_form", "some_class") assert_dom_equal expected, output_buffer end @@ -1552,7 +1608,8 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post, :url => 'http://www.otherdomain.com') do |f| end end - assert_equal '<form action="http://www.otherdomain.com" method="post"></form>', output_buffer + assert_equal whole_form("http://www.otherdomain.com"), output_buffer + # assert_equal '<form action="http://www.otherdomain.com" method="post"></form>', output_buffer end def test_form_for_with_hash_url_option @@ -1569,14 +1626,15 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post, :url => @post) do |f| end end - expected = "<form action=\"/posts/123\" method=\"post\"></form>" + expected = whole_form("/posts/123") + # expected = "<form action=\"/posts/123\" method=\"post\"></form>" assert_equal expected, output_buffer end def test_form_for_with_existing_object form_for(@post) do |f| end - expected = "<form action=\"/posts/123\" class=\"edit_post\" id=\"edit_post_123\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"_method\" type=\"hidden\" value=\"put\" /></div></form>" + expected = whole_form("/posts/123", "edit_post_123", "edit_post", "put") assert_equal expected, output_buffer end @@ -1587,7 +1645,7 @@ class FormHelperTest < ActionView::TestCase form_for(post) do |f| end - expected = "<form action=\"/posts\" class=\"new_post\" id=\"new_post\" method=\"post\"></form>" + expected = whole_form("/posts", "new_post", "new_post") assert_equal expected, output_buffer end @@ -1595,14 +1653,14 @@ class FormHelperTest < ActionView::TestCase @comment.save form_for([@post, @comment]) {} - expected = %(<form action="#{comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="_method" type="hidden" value="put" /></div></form>) + expected = whole_form(comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put") assert_dom_equal expected, output_buffer end def test_form_for_with_new_object_in_list form_for([@post, @comment]) {} - expected = %(<form action="#{comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>) + expected = whole_form(comments_path(@post), "new_comment", "new_comment") assert_dom_equal expected, output_buffer end @@ -1610,21 +1668,21 @@ class FormHelperTest < ActionView::TestCase @comment.save form_for([:admin, @post, @comment]) {} - expected = %(<form action="#{admin_comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="_method" type="hidden" value="put" /></div></form>) + expected = whole_form(admin_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put") assert_dom_equal expected, output_buffer end def test_form_for_with_new_object_and_namespace_in_list form_for([:admin, @post, @comment]) {} - expected = %(<form action="#{admin_comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>) + expected = whole_form(admin_comments_path(@post), "new_comment", "new_comment") assert_dom_equal expected, output_buffer end def test_form_for_with_existing_object_and_custom_url form_for(@post, :url => "/super_posts") do |f| end - expected = "<form action=\"/super_posts\" class=\"edit_post\" id=\"edit_post_123\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"_method\" type=\"hidden\" value=\"put\" /></div></form>" + expected = whole_form("/super_posts", "edit_post_123", "edit_post", "put") assert_equal expected, output_buffer end diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 19b73aa810..65b5f5ccc1 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -177,17 +177,16 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_option_groups_from_collection_for_select - @continents = [ - Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ), - Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) - ] - assert_dom_equal( "<optgroup label=\"<Africa>\"><option value=\"<sa>\"><South Africa></option>\n<option value=\"so\">Somalia</option></optgroup><optgroup label=\"Europe\"><option value=\"dk\" selected=\"selected\">Denmark</option>\n<option value=\"ie\">Ireland</option></optgroup>", - option_groups_from_collection_for_select(@continents, "countries", "continent_name", "country_id", "country_name", "dk") + option_groups_from_collection_for_select(dummy_continents, "countries", "continent_name", "country_id", "country_name", "dk") ) end + def test_option_groups_from_collection_for_select_returns_html_safe_string + assert option_groups_from_collection_for_select(dummy_continents, "countries", "continent_name", "country_id", "country_name", "dk").html_safe? + end + def test_grouped_options_for_select_with_array assert_dom_equal( "<optgroup label=\"North America\"><option value=\"US\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\"><option value=\"GB\">Great Britain</option>\n<option value=\"Germany\">Germany</option></optgroup>", @@ -824,31 +823,21 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_grouped_collection_select - @continents = [ - Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ), - Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) - ] - @post = Post.new @post.origin = 'dk' assert_dom_equal( %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>}, - grouped_collection_select("post", "origin", @continents, :countries, :continent_name, :country_id, :country_name) + grouped_collection_select("post", "origin", dummy_continents, :countries, :continent_name, :country_id, :country_name) ) end def test_grouped_collection_select_under_fields_for - @continents = [ - Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ), - Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) - ] - @post = Post.new @post.origin = 'dk' output_buffer = fields_for :post, @post do |f| - concat f.grouped_collection_select("origin", @continents, :countries, :continent_name, :country_id, :country_name) + concat f.grouped_collection_select("origin", dummy_continents, :countries, :continent_name, :country_id, :country_name) end assert_dom_equal( @@ -864,4 +853,9 @@ class FormOptionsHelperTest < ActionView::TestCase Post.new("Babe went home", "Babe", "To a little house", "shh!"), Post.new("Cabe went home", "Cabe", "To a little house", "shh!") ] end + + def dummy_continents + [ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ), + Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) ] + end end diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index b75863bb6a..8a0f352bc0 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -8,6 +8,36 @@ class FormTagHelperTest < ActionView::TestCase @controller = BasicController.new end + def snowman(options = {}) + method = options[:method] + + txt = %{<div style="margin:0;padding:0;display:inline">} + txt << %{<input name="_snowman" type="hidden" value="☃" />} + txt << %{<input name="_method" type="hidden" value="#{method}" />} if method + txt << %{</div>} + end + + def form_text(action = "http://www.example.com", options = {}) + remote, enctype, html_class, id = options.values_at(:remote, :enctype, :html_class, :id) + + txt = %{<form accept-charset="UTF-8" action="#{action}"} + txt << %{ enctype="multipart/form-data"} if enctype + txt << %{ data-remote="true"} if remote + txt << %{ class="#{html_class}"} if html_class + txt << %{ id="#{id}"} if id + txt << %{ method="post">} + end + + def whole_form(action = "http://www.example.com", options = {}) + out = form_text(action, options) + snowman(options) + + if block_given? + out << yield << "</form>" + end + + out + end + def url_for(options) if options.is_a?(Hash) "http://www.example.com" @@ -31,51 +61,57 @@ class FormTagHelperTest < ActionView::TestCase def test_form_tag actual = form_tag - expected = %(<form action="http://www.example.com" method="post">) + expected = whole_form assert_dom_equal expected, actual end def test_form_tag_multipart actual = form_tag({}, { 'multipart' => true }) - expected = %(<form action="http://www.example.com" enctype="multipart/form-data" method="post">) + expected = whole_form("http://www.example.com", :enctype => true) assert_dom_equal expected, actual end def test_form_tag_with_method_put actual = form_tag({}, { :method => :put }) - expected = %(<form action="http://www.example.com" method="post"><div style='margin:0;padding:0;display:inline'><input type="hidden" name="_method" value="put" /></div>) + expected = whole_form("http://www.example.com", :method => :put) assert_dom_equal expected, actual end def test_form_tag_with_method_delete actual = form_tag({}, { :method => :delete }) - expected = %(<form action="http://www.example.com" method="post"><div style='margin:0;padding:0;display:inline'><input type="hidden" name="_method" value="delete" /></div>) + + expected = whole_form("http://www.example.com", :method => :delete) assert_dom_equal expected, actual end def test_form_tag_with_remote actual = form_tag({}, :remote => true) - expected = %(<form action="http://www.example.com" method="post" data-remote="true">) + + expected = whole_form("http://www.example.com", :remote => true) assert_dom_equal expected, actual end def test_form_tag_with_remote_false actual = form_tag({}, :remote => false) - expected = %(<form action="http://www.example.com" method="post">) + + expected = whole_form assert_dom_equal expected, actual end def test_form_tag_with_block_in_erb - output_buffer = form_tag("http://example.com") { concat "Hello world!" } + output_buffer = form_tag("http://www.example.com") { concat "Hello world!" } - expected = %(<form action="http://example.com" method="post">Hello world!</form>) + expected = whole_form { "Hello world!" } assert_dom_equal expected, output_buffer end def test_form_tag_with_block_and_method_in_erb - output_buffer = form_tag("http://example.com", :method => :put) { concat "Hello world!" } + output_buffer = form_tag("http://www.example.com", :method => :put) { concat "Hello world!" } + + expected = whole_form("http://www.example.com", :method => "put") do + "Hello world!" + end - expected = %(<form action="http://example.com" method="post"><div style='margin:0;padding:0;display:inline'><input type="hidden" name="_method" value="put" /></div>Hello world!</form>) assert_dom_equal expected, output_buffer end diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb index a6e760b0b6..c9edde8892 100644 --- a/actionpack/test/template/html-scanner/sanitizer_test.rb +++ b/actionpack/test/template/html-scanner/sanitizer_test.rb @@ -257,6 +257,10 @@ class SanitizerTest < ActionController::TestCase assert_sanitized %{<a href=\"http://www.domain.com?var1=1&var2=2\">my link</a>} end + def test_should_sanitize_neverending_attribute + assert_sanitized "<span class=\"\\", "<span class=\"\\\">" + end + protected def assert_sanitized(input, expected = nil) @sanitizer ||= HTML::WhiteListSanitizer.new diff --git a/actionpack/test/template/log_subscriber_test.rb b/actionpack/test/template/log_subscriber_test.rb index 5076dfa70f..eb1e548672 100644 --- a/actionpack/test/template/log_subscriber_test.rb +++ b/actionpack/test/template/log_subscriber_test.rb @@ -1,22 +1,22 @@ require "abstract_unit" -require "rails/log_subscriber/test_helper" -require "action_view/railties/log_subscriber" +require "active_support/log_subscriber/test_helper" +require "action_view/log_subscriber" require "controller/fake_models" class AVLogSubscriberTest < ActiveSupport::TestCase - include Rails::LogSubscriber::TestHelper + include ActiveSupport::LogSubscriber::TestHelper def setup super @old_logger = ActionController::Base.logger @view = ActionView::Base.new(ActionController::Base.view_paths, {}) Rails.stubs(:root).returns(File.expand_path(FIXTURE_LOAD_PATH)) - Rails::LogSubscriber.add(:action_view, ActionView::Railties::LogSubscriber.new) + ActionView::LogSubscriber.attach_to :action_view end def teardown super - Rails::LogSubscriber.log_subscribers.clear + ActiveSupport::LogSubscriber.log_subscribers.clear ActionController::Base.logger = @old_logger end diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb index ec5fe3d1d7..507cdca8d0 100644 --- a/actionpack/test/template/tag_helper_test.rb +++ b/actionpack/test/template/tag_helper_test.rb @@ -95,9 +95,9 @@ class TagHelperTest < ActionView::TestCase assert_equal '1 < 2 & 3', escape_once('1 < 2 & 3') end - def test_double_escaping_attributes + def test_tag_honors_html_safe_for_param_values ['1&2', '1 < 2', '“test“'].each do |escaped| - assert_equal %(<a href="#{escaped}" />), tag('a', :href => escaped) + assert_equal %(<a href="#{escaped}" />), tag('a', :href => escaped.html_safe) end end diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index 9b50ea8a42..a0c46f8a59 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -37,8 +37,12 @@ module ActionView include SharedTests test_case = self - test "memoizes the _view" do - assert_same _view, _view + test "memoizes the view" do + assert_same view, view + end + + test "exposes view as _view for backwards compatibility" do + assert_same _view, view end test "works without testing a helper module" do @@ -61,13 +65,13 @@ module ActionView end test "delegates notice to request.flash" do - _view.request.flash.expects(:notice).with("this message") - _view.notice("this message") + view.request.flash.expects(:notice).with("this message") + view.notice("this message") end test "delegates alert to request.flash" do - _view.request.flash.expects(:alert).with("this message") - _view.alert("this message") + view.request.flash.expects(:alert).with("this message") + view.alert("this message") end end @@ -136,7 +140,7 @@ module ActionView helper HelperThatInvokesProtectAgainstForgery test "protect_from_forgery? in any helpers returns false" do - assert !_view.help_me + assert !view.help_me end end @@ -200,6 +204,15 @@ module ActionView assert_match /Hello: EloyHello: Manfred/, render(:file => 'test/list') end + test "is able to render partials from templates and also use instance variables after view has been referenced" do + @controller.controller_path = "test" + + view + + @customers = [stub(:name => 'Eloy'), stub(:name => 'Manfred')] + assert_match /Hello: EloyHello: Manfred/, render(:file => 'test/list') + end + end class AssertionsTest < ActionView::TestCase @@ -218,4 +231,26 @@ module ActionView end end end + + class RenderTemplateTest < ActionView::TestCase + test "supports specifying partials" do + controller.controller_path = "test" + render(:template => "test/calling_partial_with_layout") + assert_template :partial => "_partial_for_use_in_layout" + end + + test "supports specifying locals (passing)" do + controller.controller_path = "test" + render(:template => "test/calling_partial_with_layout") + assert_template :partial => "_partial_for_use_in_layout", :locals => { :name => "David" } + end + + test "supports specifying locals (failing)" do + controller.controller_path = "test" + render(:template => "test/calling_partial_with_layout") + assert_raise ActiveSupport::TestCase::Assertion, /Somebody else.*David/m do + assert_template :partial => "_partial_for_use_in_layout", :locals => { :name => "Somebody Else" } + end + end + end end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index d173c5be0a..d22b9fe406 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -435,6 +435,7 @@ class TextHelperTest < ActionView::TestCase z39_scheme = 'z39.50r://host:696/db' chrome_scheme = 'chrome://package/section/path' view_source = 'view-source:http://en.wikipedia.org/wiki/URI_scheme' + assert_equal generate_result(file_scheme), auto_link(file_scheme) assert_equal generate_result(z39_scheme), auto_link(z39_scheme) assert_equal generate_result(chrome_scheme), auto_link(chrome_scheme) assert_equal generate_result(view_source), auto_link(view_source) diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 299d6dd5bd..765beebfa3 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -40,19 +40,8 @@ class UrlHelperTest < ActiveSupport::TestCase end alias url_hash hash_for - def test_url_for_escapes_urls + def test_url_for_does_not_escape_urls assert_equal "/?a=b&c=d", url_for(abcd) - assert_equal "/?a=b&c=d", url_for(abcd(:escape => true)) - assert_equal "/?a=b&c=d", url_for(abcd(:escape => false)) - end - - def test_url_for_escaping_is_safety_aware - assert url_for(abcd(:escape => true)).html_safe?, "escaped urls should be html_safe?" - assert !url_for(abcd(:escape => false)).html_safe?, "non-escaped urls should not be html_safe?" - end - - def test_url_for_escapes_url_once - assert_equal "/?a=b&c=d", url_for("/?a=b&c=d") end def test_url_for_with_back @@ -67,11 +56,6 @@ class UrlHelperTest < ActiveSupport::TestCase assert_equal 'javascript:history.back()', url_for(:back) end - def test_url_for_from_hash_doesnt_escape_ampersand - path = url_for(hash_for(:foo => :bar, :baz => :quux)) - assert_equal '/?baz=quux&foo=bar', sort_query_string_params(path) - end - # todo: missing test cases def test_button_to_with_straight_url assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com") @@ -81,8 +65,8 @@ class UrlHelperTest < ActiveSupport::TestCase assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&q2=v2") end - def test_button_to_with_escaped_query - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&q2=v2") + def test_button_to_with_html_safe_URL + assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&q2=v2".html_safe) end def test_button_to_with_query_and_no_name @@ -151,13 +135,12 @@ class UrlHelperTest < ActiveSupport::TestCase def test_link_tag_with_query expected = %{<a href="http://www.example.com?q1=v1&q2=v2">Hello</a>} - assert_dom_equal expected, link_to("Hello", "http://www.example.com?q1=v1&q2=v2") + assert_dom_equal expected, link_to("Hello", "http://www.example.com?q1=v1&q2=v2") end def test_link_tag_with_query_and_no_name - link = link_to(nil, "http://www.example.com?q1=v1&q2=v2") expected = %{<a href="http://www.example.com?q1=v1&q2=v2">http://www.example.com?q1=v1&q2=v2</a>} - assert_dom_equal expected, link + assert_dom_equal expected, link_to(nil, "http://www.example.com?q1=v1&q2=v2") end def test_link_tag_with_back @@ -312,7 +295,7 @@ class UrlHelperTest < ActiveSupport::TestCase @request = request_for_url("/?order=desc&page=1") assert current_page?(hash_for(:order => "desc", :page => "1")) - assert current_page?("http://www.example.com/?order=desc&page=1") + assert current_page?("http://www.example.com/?order=desc&page=1") end def test_link_unless_current @@ -345,7 +328,7 @@ class UrlHelperTest < ActiveSupport::TestCase link_to_unless_current("Showing", "http://www.example.com/?order=asc") @request = request_for_url("/?order=desc") - assert_equal %{<a href="/?order=desc&page=2\">Showing</a>}, + assert_equal %{<a href="/?order=desc&page=2\">Showing</a>}, link_to_unless_current("Showing", hash_for(:order => "desc", :page => 2)) assert_equal %{<a href="http://www.example.com/?order=desc&page=2">Showing</a>}, link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=2") @@ -415,7 +398,7 @@ class UrlHelperTest < ActiveSupport::TestCase private def sort_query_string_params(uri) path, qs = uri.split('?') - qs = qs.split('&').sort.join('&') if qs + qs = qs.split('&').sort.join('&') if qs qs ? "#{path}?#{qs}" : path end end @@ -429,6 +412,10 @@ class UrlHelperControllerTest < ActionController::TestCase map.connect ":controller/:action/:id" # match "/:controller(/:action(/:id))" + + match 'url_helper_controller_test/url_helper/normalize_recall_params', + :to => UrlHelperController.action(:normalize_recall), + :as => :normalize_recall_params end def show_url_for @@ -447,6 +434,14 @@ class UrlHelperControllerTest < ActionController::TestCase render :inline => '<%= url_for(nil) %>' end + def normalize_recall_params + render :inline => '<%= normalize_recall_params_path %>' + end + + def recall_params_not_changed + render :inline => '<%= url_for(:action => :show_url_for) %>' + end + def rescue_action(e) raise e end end @@ -488,6 +483,16 @@ class UrlHelperControllerTest < ActionController::TestCase get :show_named_route, :kind => 'url' assert_equal 'http://testtwo.host/url_helper_controller_test/url_helper/show_named_route', @response.body end + + def test_recall_params_should_be_normalized_when_using_block_route + get :normalize_recall_params + assert_equal '/url_helper_controller_test/url_helper/normalize_recall_params', @response.body + end + + def test_recall_params_should_not_be_changed_when_using_normal_route + get :recall_params_not_changed + assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body + end end class TasksController < ActionController::Base |