aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG.md6
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb19
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb1
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb4
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb5
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb135
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb2
-rw-r--r--actionpack/lib/action_controller/test_case.rb1
-rw-r--r--actionpack/lib/action_dispatch.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb70
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb11
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb2
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb29
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb5
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb2
-rw-r--r--actionpack/lib/action_view/template/resolver.rb8
-rw-r--r--actionpack/test/abstract/abstract_controller_test.rb4
-rw-r--r--actionpack/test/abstract/collector_test.rb4
-rw-r--r--actionpack/test/abstract/helper_test.rb4
-rw-r--r--actionpack/test/abstract/layouts_test.rb33
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb2
-rw-r--r--actionpack/test/controller/redirect_test.rb20
-rw-r--r--actionpack/test/controller/render_test.rb39
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb2
-rw-r--r--actionpack/test/dispatch/routing_test.rb18
-rw-r--r--actionpack/test/dispatch/ssl_test.rb157
-rw-r--r--actionpack/test/dispatch/static_test.rb4
-rw-r--r--actionpack/test/fixtures/layouts/with_html_partial.html.erb1
-rw-r--r--actionpack/test/fixtures/reply.rb2
-rw-r--r--actionpack/test/fixtures/test/_partial_only_html.html1
-rw-r--r--actionpack/test/fixtures/test/with_html_partial.html.erb1
-rw-r--r--actionpack/test/fixtures/test/with_partial.html.erb1
-rw-r--r--actionpack/test/fixtures/test/with_partial.text.erb1
-rw-r--r--actionpack/test/fixtures/test/with_xml_template.html.erb1
-rw-r--r--actionpack/test/template/active_model_helper_test.rb4
-rw-r--r--actionpack/test/template/date_helper_test.rb28
38 files changed, 558 insertions, 73 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 6c9f3e0e67..92efb060a2 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* Added ActionDispatch::SSL middleware that when included force all the requests to be under HTTPS protocol. *Rafael Mendonça França*
+
* Add `include_hidden` option to select tag. With `:include_hidden => false` select with `multiple` attribute doesn't generate hidden input with blank value. *Vasiliy Ermolovich*
* Removed default `size` option from the `text_field`, `search_field`, `telephone_field`, `url_field`, `email_field` helpers. *Philip Arndt*
@@ -120,8 +122,8 @@
* `favicon_link_tag` helper will now use the favicon in app/assets by default. *Lucas Caton*
-* `ActionView::Helpers::TextHelper#highlight` now defaults to the
- HTML5 `mark` element. *Brian Cardarella*
+* `ActionView::Helpers::TextHelper#highlight` now defaults to the
+ HTML5 `mark` element. *Brian Cardarella*
## Rails 3.2.3 (unreleased) ##
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 683a2628a4..6f13ebe0d0 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -89,7 +89,7 @@ module AbstractController
# class TillController < BankController
# layout false
#
- # In these examples, we have three implicit lookup scenrios:
+ # In these examples, we have three implicit lookup scenarios:
# * The BankController uses the "bank" layout.
# * The ExchangeController uses the "exchange" layout.
# * The CurrencyController inherits the layout from BankController.
@@ -128,7 +128,14 @@ module AbstractController
# If you want to use an inline method, such as a proc, do something like this:
#
# class WeblogController < ActionController::Base
- # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
+ # layout proc { |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
+ # end
+ #
+ # If an argument isn't given to the proc, it's evaluated in the context of
+ # the current controller anyway.
+ #
+ # class WeblogController < ActionController::Base
+ # layout proc { logged_in? ? "writer_layout" : "reader_layout" }
# end
#
# Of course, the most common way of specifying a layout is still just as a plain template name:
@@ -299,12 +306,12 @@ module AbstractController
end
RUBY
when Proc
- define_method :_layout_from_proc, &_layout
- "_layout_from_proc(self)"
+ define_method :_layout_from_proc, &_layout
+ _layout.arity == 0 ? "_layout_from_proc" : "_layout_from_proc(self)"
when false
nil
when true
- raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
+ raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil"
when nil
name_clause
end
@@ -364,7 +371,7 @@ module AbstractController
when false, nil then nil
else
raise ArgumentError,
- "String, true, or false, expected for `layout'; you passed #{name.inspect}"
+ "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}"
end
end
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index ddc93464cd..7d73c6af8d 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -105,6 +105,7 @@ module AbstractController
# Find and renders a template based on the options given.
# :api: private
def _render_template(options) #:nodoc:
+ lookup_context.rendered_format = nil if options[:formats]
view_renderer.render(view_context, options)
end
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index ba96735e56..ceac11bbfb 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -170,14 +170,14 @@ module ActionController #:nodoc:
options.reverse_merge!(:format => @extension) if options.is_a?(Hash)
end
- path = controller.url_for(options).split(%r{://}).last
+ path = controller.url_for(options).split('://', 2).last
@path = normalize!(path)
end
private
def normalize!(path)
path << 'index' if path[-1] == ?/
- path << ".#{extension}" if extension and !path.split('?').first.ends_with?(".#{extension}")
+ path << ".#{extension}" if extension and !path.split('?', 2).first.ends_with?(".#{extension}")
URI.parser.unescape(path)
end
end
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
index 808a6fe5f3..bb176ca3f9 100644
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ b/actionpack/lib/action_controller/caching/sweeping.rb
@@ -54,6 +54,11 @@ module ActionController #:nodoc:
class Sweeper < ActiveRecord::Observer #:nodoc:
attr_accessor :controller
+ def initialize(*args)
+ super
+ @controller = nil
+ end
+
def before(controller)
self.controller = controller
callback(:before) if controller.perform_caching
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 73e044a092..fbb5d01e86 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -195,20 +195,106 @@ module ActionController #:nodoc:
end
end
- # respond_with wraps a resource around a responder for default representation.
- # First it invokes respond_to, if a response cannot be found (ie. no block
- # for the request was given and template was not available), it instantiates
- # an ActionController::Responder with the controller and resource.
+ # For a given controller action, respond_with generates an appropriate
+ # response based on the mime-type requested by the client.
#
- # ==== Example
+ # If the method is called with just a resource, as in this example -
#
- # def index
- # @users = User.all
- # respond_with(@users)
+ # class PeopleController < ApplicationController
+ # respond_to :html, :xml, :json
+ #
+ # def index
+ # @people = Person.all
+ # respond_with @people
+ # end
# end
#
- # It also accepts a block to be given. It's used to overwrite a default
- # response:
+ # then the mime-type of the response is typically selected based on the
+ # request's Accept header and the set of available formats declared
+ # by previous calls to the controller's class method +respond_to+. Alternatively
+ # the mime-type can be selected by explicitly setting <tt>request.format</tt> in
+ # the controller.
+ #
+ # If an acceptable format is not identified, the application returns a
+ # '406 - not acceptable' status. Otherwise, the default response is to render
+ # a template named after the current action and the selected format,
+ # e.g. <tt>index.html.erb</tt>. If no template is available, the behavior
+ # depends on the selected format:
+ #
+ # * for an html response - if the request method is +get+, an exception
+ # is raised but for other requests such as +post+ the response
+ # depends on whether the resource has any validation errors (i.e.
+ # assuming that an attempt has been made to save the resource,
+ # e.g. by a +create+ action) -
+ # 1. If there are no errors, i.e. the resource
+ # was saved successfully, the response +redirect+'s to the resource
+ # i.e. its +show+ action.
+ # 2. If there are validation errors, the response
+ # renders a default action, which is <tt>:new</tt> for a
+ # +post+ request or <tt>:edit</tt> for +put+.
+ # Thus an example like this -
+ #
+ # respond_to :html, :xml
+ #
+ # def create
+ # @user = User.new(params[:user])
+ # flash[:notice] = 'User was successfully created.' if @user.save
+ # respond_with(@user)
+ # end
+ #
+ # is equivalent, in the absence of <tt>create.html.erb</tt>, to -
+ #
+ # def create
+ # @user = User.new(params[:user])
+ # respond_to do |format|
+ # if @user.save
+ # flash[:notice] = 'User was successfully created.'
+ # format.html { redirect_to(@user) }
+ # format.xml { render :xml => @user }
+ # else
+ # format.html { render :action => "new" }
+ # format.xml { render :xml => @user }
+ # end
+ # end
+ # end
+ #
+ # * for a javascript request - if the template isn't found, an exception is
+ # raised.
+ # * for other requests - i.e. data formats such as xml, json, csv etc, if
+ # the resource passed to +respond_with+ responds to <code>to_<format></code>,
+ # the method attempts to render the resource in the requested format
+ # directly, e.g. for an xml request, the response is equivalent to calling
+ # <code>render :xml => resource</code>.
+ #
+ # === Nested resources
+ #
+ # As outlined above, the +resources+ argument passed to +respond_with+
+ # can play two roles. It can be used to generate the redirect url
+ # for successful html requests (e.g. for +create+ actions when
+ # no template exists), while for formats other than html and javascript
+ # it is the object that gets rendered, by being converted directly to the
+ # required format (again assuming no template exists).
+ #
+ # For redirecting successful html requests, +respond_with+ also supports
+ # the use of nested resources, which are supplied in the same way as
+ # in <code>form_for</code> and <code>polymorphic_url</code>. For example -
+ #
+ # def create
+ # @project = Project.find(params[:project_id])
+ # @task = @project.comments.build(params[:task])
+ # flash[:notice] = 'Task was successfully created.' if @task.save
+ # respond_with(@project, @task)
+ # end
+ #
+ # This would cause +respond_with+ to redirect to <code>project_task_url</code>
+ # instead of <code>task_url</code>. For request formats other than html or
+ # javascript, if multiple resources are passed in this way, it is the last
+ # one specified that is rendered.
+ #
+ # === Customizing response behavior
+ #
+ # Like +respond_to+, +respond_with+ may also be called with a block that
+ # can be used to overwrite any of the default responses, e.g. -
#
# def create
# @user = User.new(params[:user])
@@ -219,13 +305,24 @@ module ActionController #:nodoc:
# end
# end
#
- # All options given to respond_with are sent to the underlying responder,
- # except for the option :responder itself. Since the responder interface
- # is quite simple (it just needs to respond to call), you can even give
- # a proc to it.
- #
- # In order to use respond_with, first you need to declare the formats your
- # controller responds to in the class level with a call to <tt>respond_to</tt>.
+ # The argument passed to the block is an ActionController::MimeResponds::Collector
+ # object which stores the responses for the formats defined within the
+ # block. Note that formats with responses defined explicitly in this way
+ # do not have to first be declared using the class method +respond_to+.
+ #
+ # Also, a hash passed to +respond_with+ immediately after the specified
+ # resource(s) is interpreted as a set of options relevant to all
+ # formats. Any option accepted by +render+ can be used, e.g.
+ # respond_with @people, :status => 200
+ # However, note that these options are ignored after an unsuccessful attempt
+ # to save a resource, e.g. when automatically rendering <tt>:new</tt>
+ # after a post request.
+ #
+ # Two additional options are relevant specifically to +respond_with+ -
+ # 1. <tt>:location</tt> - overwrites the default redirect location used after
+ # a successful html +post+ request.
+ # 2. <tt>:action</tt> - overwrites the default render action used after an
+ # unsuccessful html +post+ request.
#
def respond_with(*resources, &block)
raise "In order to use respond_with, first you need to declare the formats your " <<
@@ -283,8 +380,8 @@ module ActionController #:nodoc:
end
end
- # A container of responses available for requests with different mime-types
- # sent to the current controller action.
+ # A container for responses available from the current controller for
+ # requests for different mime-types sent to a particular action.
#
# The public controller methods +respond_with+ and +respond_to+ may be called
# with a block that is used to define responses to different mime-types, e.g.
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index b07742e0e1..3ffb7ef426 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -93,7 +93,7 @@ module ActionController
_compute_redirect_to_location options.call
else
url_for(options)
- end.gsub(/[\r\n]/, '')
+ end.gsub(/[\0\r\n]/, '')
end
end
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 7ba8319e4c..9bd2e622ad 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -479,7 +479,6 @@ module ActionController
@request.session["flash"].sweep
@controller.request = @request
- @controller.params.merge!(parameters)
build_request_uri(action, parameters)
@controller.class.class_eval { include Testing }
@controller.recycle!
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index a9542a7d1b..e3b04ac097 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -61,6 +61,7 @@ module ActionDispatch
autoload :Reloader
autoload :RemoteIp
autoload :ShowExceptions
+ autoload :SSL
autoload :Static
end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 836136eb95..ab740a0190 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -9,7 +9,7 @@ module ActionDispatch
# of ShowExceptions. Everytime there is an exception, ShowExceptions will
# store the exception in env["action_dispatch.exception"], rewrite the
# PATH_INFO to the exception status code and call the rack app.
- #
+ #
# If the application returns a "X-Cascade" pass response, this middleware
# will send an empty response as result with the correct status code.
# If any exception happens inside the exceptions app, this middleware
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
new file mode 100644
index 0000000000..9098f4e170
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -0,0 +1,70 @@
+module ActionDispatch
+ class SSL
+ YEAR = 31536000
+
+ def self.default_hsts_options
+ { :expires => YEAR, :subdomains => false }
+ end
+
+ def initialize(app, options = {})
+ @app = app
+
+ @hsts = options.fetch(:hsts, {})
+ @hsts = {} if @hsts == true
+ @hsts = self.class.default_hsts_options.merge(@hsts) if @hsts
+
+ @host = options[:host]
+ @port = options[:port]
+ end
+
+ def call(env)
+ request = Request.new(env)
+
+ if request.ssl?
+ status, headers, body = @app.call(env)
+ headers = hsts_headers.merge(headers)
+ flag_cookies_as_secure!(headers)
+ [status, headers, body]
+ else
+ redirect_to_https(request)
+ end
+ end
+
+ private
+ def redirect_to_https(request)
+ url = URI(request.url)
+ url.scheme = "https"
+ url.host = @host if @host
+ url.port = @port if @port
+ headers = hsts_headers.merge('Content-Type' => 'text/html',
+ 'Location' => url.to_s)
+
+ [301, headers, []]
+ end
+
+ # http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
+ def hsts_headers
+ if @hsts
+ value = "max-age=#{@hsts[:expires]}"
+ value += "; includeSubDomains" if @hsts[:subdomains]
+ { 'Strict-Transport-Security' => value }
+ else
+ {}
+ end
+ end
+
+ def flag_cookies_as_secure!(headers)
+ if cookies = headers['Set-Cookie']
+ cookies = cookies.split("\n")
+
+ headers['Set-Cookie'] = cookies.map { |cookie|
+ if cookie !~ /;\s+secure(;|$)/
+ "#{cookie}; secure"
+ else
+ cookie
+ end
+ }.join("\n")
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 63b7422287..9073e6582d 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -39,6 +39,7 @@ module ActionDispatch
end
def escape_glob_chars(path)
+ path.force_encoding('binary') if path.respond_to? :force_encoding
path.gsub(/[*?{}\[\]]/, "\\\\\\&")
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index cd215034dc..20cdf67cf0 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,6 +1,6 @@
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/object/inclusion'
+require 'active_support/core_ext/enumerable'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
@@ -880,17 +880,18 @@ module ActionDispatch
# 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]
- RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
+ RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param]
CANONICAL_ACTIONS = %w(index create new show update destroy)
class Resource #:nodoc:
- attr_reader :controller, :path, :options
+ attr_reader :controller, :path, :options, :param
def initialize(entities, options = {})
@name = entities.to_s
@path = (options[:path] || @name).to_s
@controller = (options[:controller] || @name).to_s
@as = options[:as]
+ @param = options[:param] || :id
@options = options
end
@@ -935,7 +936,7 @@ module ActionDispatch
alias :collection_scope :path
def member_scope
- "#{path}/:id"
+ "#{path}/:#{param}"
end
def new_scope(new_path)
@@ -943,7 +944,7 @@ module ActionDispatch
end
def nested_scope
- "#{path}/:#{singular}_id"
+ "#{path}/:#{singular}_#{param}"
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 094cfbfc76..a5e7a8c715 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -83,7 +83,7 @@ module ActionDispatch
refer
else
@controller.url_for(fragment)
- end.gsub(/[\r\n]/, '')
+ end.gsub(/[\0\r\n]/, '')
end
end
end
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 2d37923825..2bfc6371f5 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -625,13 +625,18 @@ module ActionView
# time_tag Date.today, :pubdate => true # =>
# <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
#
- def time_tag(date_or_time, *args)
+ # <%= time_tag Time.now do %>
+ # <span>Right now</span>
+ # <% end %>
+ # # => <time datetime="2010-11-04T17:55:45+01:00"><span>Right now</span></time>
+ #
+ def time_tag(date_or_time, *args, &block)
options = args.extract_options!
format = options.delete(:format) || :long
content = args.first || I18n.l(date_or_time, :format => format)
datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339
- content_tag(:time, content, options.reverse_merge(:datetime => datetime))
+ content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block)
end
end
@@ -669,11 +674,7 @@ module ActionView
@options[:discard_minute] ||= true if @options[:discard_hour]
@options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute]
- # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
- # valid (otherwise it could be 31 and February wouldn't be a valid date)
- if @datetime && @options[:discard_day] && !@options[:discard_month]
- @datetime = @datetime.change(:day => 1)
- end
+ set_day_if_discarded
if @options[:tag] && @options[:ignore_date]
select_time
@@ -696,11 +697,7 @@ module ActionView
@options[:discard_month] ||= true unless order.include?(:month)
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
- # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
- # valid (otherwise it could be 31 and February wouldn't be a valid date)
- if @datetime && @options[:discard_day] && !@options[:discard_month]
- @datetime = @datetime.change(:day => 1)
- end
+ set_day_if_discarded
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
@@ -802,6 +799,14 @@ module ActionView
end
end
+ # If the day is hidden, the day should be set to the 1st so all month and year choices are
+ # valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid.
+ def set_day_if_discarded
+ if @datetime && @options[:discard_day]
+ @datetime = @datetime.change(:day => 1)
+ end
+ end
+
# Returns translated month names, but also ensures that a custom month
# name array has a leading nil element.
def month_names
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 29f556502b..4a641fada3 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -334,7 +334,7 @@ module ActionView
remote = html_options.delete('remote')
method = html_options.delete('method').to_s
- method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : ""
+ method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : ''.html_safe
form_method = method == 'get' ? 'get' : 'post'
form_options = html_options.delete('form') || {}
@@ -347,7 +347,8 @@ module ActionView
html_options = convert_options_to_data_attributes(options, html_options)
html_options.merge!("type" => "submit", "value" => name || url)
- "#{tag(:form, form_options, true)}<div>#{method_tag}#{tag("input", html_options)}#{request_token_tag}</div></form>".html_safe
+ inner_tags = method_tag.safe_concat tag('input', html_options).safe_concat request_token_tag
+ content_tag('form', content_tag('div', inner_tags), form_options)
end
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index 245a19deec..232667ec01 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -32,7 +32,7 @@ module ActionView
#
# <%= render :partial => "account", :object => @buyer %>
#
- # would provide the +@buyer+ object to the partial, available under the local variable +account+ and is
+ # would provide the <tt>@buyer</tt> object to the partial, available under the local variable +account+ and is
# equivalent to:
#
# <%= render :partial => "account", :locals => { :account => @buyer } %>
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 7fa86866a7..8ea2e5bfe4 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -176,7 +176,7 @@ module ActionView
end
end
- # A resolver that loads files from the filesystem. It allows to set your own
+ # A resolver that loads files from the filesystem. It allows setting your own
# resolving pattern. Such pattern can be a glob string supported by some variables.
#
# ==== Examples
@@ -192,7 +192,7 @@ module ActionView
#
# FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}")
#
- # If you don't specify pattern then the default will be used.
+ # If you don't specify a pattern then the default will be used.
#
# In order to use any of the customized resolvers above in a Rails application, you just need
# to configure ActionController::Base.view_paths in an initializer, for example:
@@ -204,10 +204,10 @@ module ActionView
#
# ==== Pattern format and variables
#
- # Pattern have to be a valid glob string, and it allows you to use the
+ # Pattern has to be a valid glob string, and it allows you to use the
# following variables:
#
- # * <tt>:prefix</tt> - usualy the controller path
+ # * <tt>:prefix</tt> - usually the controller path
# * <tt>:action</tt> - name of the action
# * <tt>:locale</tt> - possible locale versions
# * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb
index 30d6a7ae14..62f82a4c7a 100644
--- a/actionpack/test/abstract/abstract_controller_test.rb
+++ b/actionpack/test/abstract/abstract_controller_test.rb
@@ -29,7 +29,7 @@ module AbstractController
# Test Render mixin
# ====
class RenderingController < AbstractController::Base
- include ::AbstractController::Rendering
+ include AbstractController::Rendering
def _prefixes
[]
@@ -153,7 +153,7 @@ module AbstractController
# ====
# self._layout is used when defined
class WithLayouts < PrefixedViews
- include Layouts
+ include AbstractController::Layouts
private
def self.layout(formats)
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
index 2ebcebbbb7..c14d24905b 100644
--- a/actionpack/test/abstract/collector_test.rb
+++ b/actionpack/test/abstract/collector_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
module AbstractController
module Testing
class MyCollector
- include Collector
+ include AbstractController::Collector
attr_accessor :responses
def initialize
@@ -54,4 +54,4 @@ module AbstractController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb
index b28a5b5afb..9a7445de7b 100644
--- a/actionpack/test/abstract/helper_test.rb
+++ b/actionpack/test/abstract/helper_test.rb
@@ -7,7 +7,7 @@ module AbstractController
class ControllerWithHelpers < AbstractController::Base
include AbstractController::Rendering
- include Helpers
+ include AbstractController::Helpers
def with_module
render :inline => "Module <%= included_method %>"
@@ -44,7 +44,7 @@ module AbstractController
class AbstractHelpersBlock < ControllerWithHelpers
helper do
- include ::AbstractController::Testing::HelperyTest
+ include AbstractController::Testing::HelperyTest
end
end
diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb
index e07a6de4a9..58795aa327 100644
--- a/actionpack/test/abstract/layouts_test.rb
+++ b/actionpack/test/abstract/layouts_test.rb
@@ -72,6 +72,27 @@ module AbstractControllerTests
end
end
+ class WithZeroArityProc < Base
+ layout proc { "overwrite" }
+
+ def index
+ render :template => ActionView::Template::Text.new("Hello zero arity proc!")
+ end
+ end
+
+ class WithProcInContextOfInstance < Base
+ def an_instance_method; end
+
+ layout proc {
+ break unless respond_to? :an_instance_method
+ "overwrite"
+ }
+
+ def index
+ render :template => ActionView::Template::Text.new("Hello again zero arity proc!")
+ end
+ end
+
class WithSymbol < Base
layout :hello
@@ -221,6 +242,18 @@ module AbstractControllerTests
assert_equal "Overwrite Hello proc!", controller.response_body
end
+ test "when layout is specified as a proc without parameters it works just the same" do
+ controller = WithZeroArityProc.new
+ controller.process(:index)
+ assert_equal "Overwrite Hello zero arity proc!", controller.response_body
+ end
+
+ test "when layout is specified as a proc without parameters the block is evaluated in the context of an instance" do
+ controller = WithProcInContextOfInstance.new
+ controller.process(:index)
+ assert_equal "Overwrite Hello again zero arity proc!", controller.response_body
+ end
+
test "when layout is specified as a symbol, call the requested method and use the layout returned" do
controller = WithSymbol.new
controller.process(:index)
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index a91e3cafa5..828ea5b0fb 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -274,6 +274,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
end
def decode_credentials(header)
- ActionController::HttpAuthentication::Digest.decode_credentials(@response.headers['WWW-Authenticate'])
+ ActionController::HttpAuthentication::Digest.decode_credentials(header)
end
end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index b1d76150f8..6dab42d75d 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -103,6 +103,14 @@ class RedirectController < ActionController::Base
redirect_to proc { {:action => "hello_world"} }
end
+ def redirect_with_header_break
+ redirect_to "/lol\r\nwat"
+ end
+
+ def redirect_with_null_bytes
+ redirect_to "\000/lol\r\nwat"
+ end
+
def rescue_errors(e) raise e end
protected
@@ -120,6 +128,18 @@ class RedirectTest < ActionController::TestCase
assert_equal "http://test.host/redirect/hello_world", redirect_to_url
end
+ def test_redirect_with_header_break
+ get :redirect_with_header_break
+ assert_response :redirect
+ assert_equal "http://test.host/lolwat", redirect_to_url
+ end
+
+ def test_redirect_with_null_bytes
+ get :redirect_with_null_bytes
+ assert_response :redirect
+ assert_equal "http://test.host/lolwat", redirect_to_url
+ end
+
def test_redirect_with_no_status
get :simple_redirect
assert_response 302
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 8167fc2fd2..09d9e65d38 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -559,6 +559,23 @@ class TestController < ActionController::Base
render :template => "test/hello_world"
end
+ def render_to_string_with_template_and_html_partial
+ @text = render_to_string :template => "test/with_partial", :formats => [:text]
+ @html = render_to_string :template => "test/with_partial", :formats => [:html]
+ render :template => "test/with_html_partial"
+ end
+
+ def render_to_string_and_render_with_different_formats
+ @html = render_to_string :template => "test/with_partial", :formats => [:html]
+ render :template => "test/with_partial", :formats => [:text]
+ end
+
+ def render_template_within_a_template_with_other_format
+ render :template => "test/with_xml_template",
+ :formats => [:html],
+ :layout => "with_html_partial"
+ end
+
def partial_with_counter
render :partial => "counter", :locals => { :counter_counter => 5 }
end
@@ -1268,6 +1285,28 @@ class RenderTest < ActionController::TestCase
assert_equal "text/html", @response.content_type
end
+ def test_render_to_string_with_template_and_html_partial
+ get :render_to_string_with_template_and_html_partial
+ assert_equal "**only partial**\n", assigns(:text)
+ assert_equal "<strong>only partial</strong>\n", assigns(:html)
+ assert_equal "<strong>only html partial</strong>\n", @response.body
+ assert_equal "text/html", @response.content_type
+ end
+
+ def test_render_to_string_and_render_with_different_formats
+ get :render_to_string_and_render_with_different_formats
+ assert_equal "<strong>only partial</strong>\n", assigns(:html)
+ assert_equal "**only partial**\n", @response.body
+ assert_equal "text/plain", @response.content_type
+ end
+
+ def test_render_template_within_a_template_with_other_format
+ get :render_template_within_a_template_with_other_format
+ expected = "only html partial<p>This is grand!</p>"
+ assert_equal expected, @response.body.strip
+ assert_equal "text/html", @response.content_type
+ end
+
def test_partial_with_counter
get :partial_with_counter
assert_equal "5", @response.body
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 8d4b76849f..7b722bd3d7 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -112,7 +112,7 @@ module RequestForgeryProtectionTests
assert_not_blocked do
get :form_for_remote
end
- assert_no_match /authenticity_token/, response.body
+ assert_no_match(/authenticity_token/, response.body)
end
def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 700666600b..cc4279d9dd 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -475,6 +475,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get :preview, :on => :member
end
+ resources :profiles, :param => :username do
+ get :details, :on => :member
+ resources :messages
+ end
+
scope :as => "routes" do
get "/c/:id", :as => :collision, :to => "collision#show"
get "/collision", :to => "collision#show"
@@ -2183,6 +2188,19 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal "/posts/1/admin", post_admin_root_path(:post_id => '1')
end
+ def test_custom_param
+ get '/profiles/bob'
+ assert_equal 'profiles#show', @response.body
+ assert_equal 'bob', @request.params[:username]
+
+ get '/profiles/bob/details'
+ assert_equal 'bob', @request.params[:username]
+
+ get '/profiles/bob/messages/34'
+ assert_equal 'bob', @request.params[:profile_username]
+ assert_equal '34', @request.params[:id]
+ end
+
private
def with_https
old_https = https?
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
new file mode 100644
index 0000000000..6f075a9074
--- /dev/null
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -0,0 +1,157 @@
+require 'abstract_unit'
+
+class SSLTest < ActionDispatch::IntegrationTest
+ def default_app
+ lambda { |env|
+ headers = {'Content-Type' => "text/html"}
+ headers['Set-Cookie'] = "id=1; path=/\ntoken=abc; path=/; secure; HttpOnly"
+ [200, headers, ["OK"]]
+ }
+ end
+
+ def app
+ @app ||= ActionDispatch::SSL.new(default_app)
+ end
+ attr_writer :app
+
+ def test_allows_https_url
+ get "https://example.org/path?key=value"
+ assert_response :success
+ end
+
+ def test_allows_https_proxy_header_url
+ get "http://example.org/", {}, 'HTTP_X_FORWARDED_PROTO' => "https"
+ assert_response :success
+ end
+
+ def test_redirects_http_to_https
+ get "http://example.org/path?key=value"
+ assert_response :redirect
+ assert_equal "https://example.org/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_hsts_header_by_default
+ get "https://example.org/"
+ assert_equal "max-age=31536000",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_hsts_header
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => true)
+ get "https://example.org/"
+ assert_equal "max-age=31536000",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_disable_hsts_header
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => false)
+ get "https://example.org/"
+ refute response.headers['Strict-Transport-Security']
+ end
+
+ def test_hsts_expires
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => { :expires => 500 })
+ get "https://example.org/"
+ assert_equal "max-age=500",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_hsts_include_subdomains
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => { :subdomains => true })
+ get "https://example.org/"
+ assert_equal "max-age=31536000; includeSubDomains",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_flag_cookies_as_secure
+ get "https://example.org/"
+ assert_equal ["id=1; path=/; secure", "token=abc; path=/; secure; HttpOnly" ],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_flag_cookies_as_secure_at_end_of_line
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ headers = {
+ 'Content-Type' => "text/html",
+ 'Set-Cookie' => "problem=def; path=/; HttpOnly; secure"
+ }
+ [200, headers, ["OK"]]
+ })
+
+ get "https://example.org/"
+ assert_equal ["problem=def; path=/; HttpOnly; secure"],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_flag_cookies_as_secure_with_more_spaces_before
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ headers = {
+ 'Content-Type' => "text/html",
+ 'Set-Cookie' => "problem=def; path=/; HttpOnly; secure"
+ }
+ [200, headers, ["OK"]]
+ })
+
+ get "https://example.org/"
+ assert_equal ["problem=def; path=/; HttpOnly; secure"],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_flag_cookies_as_secure_with_more_spaces_after
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ headers = {
+ 'Content-Type' => "text/html",
+ 'Set-Cookie' => "problem=def; path=/; secure; HttpOnly"
+ }
+ [200, headers, ["OK"]]
+ })
+
+ get "https://example.org/"
+ assert_equal ["problem=def; path=/; secure; HttpOnly"],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_no_cookies
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ [200, {'Content-Type' => "text/html"}, ["OK"]]
+ })
+ get "https://example.org/"
+ assert !response.headers['Set-Cookie']
+ end
+
+ def test_redirect_to_host
+ self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org")
+ get "http://example.org/path?key=value"
+ assert_equal "https://ssl.example.org/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_port
+ self.app = ActionDispatch::SSL.new(default_app, :port => 8443)
+ get "http://example.org/path?key=value"
+ assert_equal "https://example.org:8443/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_host_and_port
+ self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org", :port => 8443)
+ get "http://example.org/path?key=value"
+ assert_equal "https://ssl.example.org:8443/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_secure_host_when_on_subdomain
+ self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org")
+ get "http://ssl.example.org/path?key=value"
+ assert_equal "https://ssl.example.org/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_secure_subdomain_when_on_deep_subdomain
+ self.app = ActionDispatch::SSL.new(default_app, :host => "example.co.uk")
+ get "http://double.rainbow.what.does.it.mean.example.co.uk/path?key=value"
+ assert_equal "https://example.co.uk/path?key=value",
+ response.headers['Location']
+ end
+end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 092ca3e20a..112f470786 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -7,6 +7,10 @@ module StaticTests
assert_equal "Hello, World!", get("/nofile").body
end
+ def test_handles_urls_with_bad_encoding
+ assert_equal "Hello, World!", get("/doorkeeper%E3E4").body
+ end
+
def test_sets_cache_control
response = get("/index.html")
assert_html "/index.html", response
diff --git a/actionpack/test/fixtures/layouts/with_html_partial.html.erb b/actionpack/test/fixtures/layouts/with_html_partial.html.erb
new file mode 100644
index 0000000000..fd2896aeaa
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/with_html_partial.html.erb
@@ -0,0 +1 @@
+<%= render :partial => "partial_only_html" %><%= yield %>
diff --git a/actionpack/test/fixtures/reply.rb b/actionpack/test/fixtures/reply.rb
index 19cba93673..0d3b0a7c98 100644
--- a/actionpack/test/fixtures/reply.rb
+++ b/actionpack/test/fixtures/reply.rb
@@ -1,5 +1,5 @@
class Reply < ActiveRecord::Base
- scope :base
+ scope :base, -> { scoped }
belongs_to :topic, :include => [:replies]
belongs_to :developer
diff --git a/actionpack/test/fixtures/test/_partial_only_html.html b/actionpack/test/fixtures/test/_partial_only_html.html
new file mode 100644
index 0000000000..d2d630bd40
--- /dev/null
+++ b/actionpack/test/fixtures/test/_partial_only_html.html
@@ -0,0 +1 @@
+only html partial \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/with_html_partial.html.erb b/actionpack/test/fixtures/test/with_html_partial.html.erb
new file mode 100644
index 0000000000..d84d909d64
--- /dev/null
+++ b/actionpack/test/fixtures/test/with_html_partial.html.erb
@@ -0,0 +1 @@
+<strong><%= render :partial => "partial_only_html" %></strong>
diff --git a/actionpack/test/fixtures/test/with_partial.html.erb b/actionpack/test/fixtures/test/with_partial.html.erb
new file mode 100644
index 0000000000..7502364cf5
--- /dev/null
+++ b/actionpack/test/fixtures/test/with_partial.html.erb
@@ -0,0 +1 @@
+<strong><%= render :partial => "partial_only" %></strong>
diff --git a/actionpack/test/fixtures/test/with_partial.text.erb b/actionpack/test/fixtures/test/with_partial.text.erb
new file mode 100644
index 0000000000..5f068ebf27
--- /dev/null
+++ b/actionpack/test/fixtures/test/with_partial.text.erb
@@ -0,0 +1 @@
+**<%= render :partial => "partial_only" %>**
diff --git a/actionpack/test/fixtures/test/with_xml_template.html.erb b/actionpack/test/fixtures/test/with_xml_template.html.erb
new file mode 100644
index 0000000000..e54a7cd001
--- /dev/null
+++ b/actionpack/test/fixtures/test/with_xml_template.html.erb
@@ -0,0 +1 @@
+<%= render :template => "test/greeting", :formats => :xml %>
diff --git a/actionpack/test/template/active_model_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb
index 18468ee91a..24511df444 100644
--- a/actionpack/test/template/active_model_helper_test.rb
+++ b/actionpack/test/template/active_model_helper_test.rb
@@ -43,14 +43,14 @@ class ActiveModelHelperTest < ActionView::TestCase
def test_date_select_with_errors
assert_dom_equal(
- %(<div class="field_with_errors"><select id="post_updated_at_1i" name="post[updated_at(1i)]">\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n</select>\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="15" />\n</div>),
+ %(<div class="field_with_errors"><select id="post_updated_at_1i" name="post[updated_at(1i)]">\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n</select>\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="1" />\n</div>),
date_select("post", "updated_at", :discard_month => true, :discard_day => true, :start_year => 2004, :end_year => 2005)
)
end
def test_datetime_select_with_errors
assert_dom_equal(
- %(<div class="field_with_errors"><input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="2004" />\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="15" />\n<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n<option selected="selected" value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select id="post_updated_at_5i" name="post[updated_at(5i)]">\n<option selected="selected" value="00">00</option>\n</select>\n</div>),
+ %(<div class="field_with_errors"><input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="2004" />\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="1" />\n<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n<option selected="selected" value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select id="post_updated_at_5i" name="post[updated_at(5i)]">\n<option selected="selected" value="00">00</option>\n</select>\n</div>),
datetime_select("post", "updated_at", :discard_year => true, :discard_month => true, :discard_day => true, :minute_step => 60)
)
end
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 9e2f4ec347..f622097b57 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -711,7 +711,7 @@ class DateHelperTest < ActionView::TestCase
# Since the order is incomplete nothing will be shown
expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n)
expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
- expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n)
+ expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="1" />\n)
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :order => [:day])
end
@@ -943,7 +943,7 @@ class DateHelperTest < ActionView::TestCase
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)
+ 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_month => true, :discard_day => true, :start_year => 2003, :end_year => 2005, :prefix => "date[first]"})
end
@@ -1396,6 +1396,20 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on", :order => [ :month, :year ])
end
+ def test_date_select_without_day_and_month
+ @post = Post.new
+ @post.written_on = Date.new(2004, 2, 29)
+
+ expected = "<input type=\"hidden\" id=\"post_written_on_2i\" name=\"post[written_on(2i)]\" value=\"2\" />\n"
+ expected << "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" />\n"
+
+ expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", :order => [ :year ])
+ end
+
def test_date_select_without_day_with_separator
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
@@ -2427,7 +2441,7 @@ class DateHelperTest < ActionView::TestCase
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) }
expected << "</select>\n"
expected << %{<input type="hidden" id="post_updated_at_2i" name="post[updated_at(2i)]" value="6" />\n}
- expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="15" />\n}
+ expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="1" />\n}
expected << " &mdash; "
@@ -2448,7 +2462,7 @@ class DateHelperTest < ActionView::TestCase
expected = %{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" />\n}
expected << %{<input type="hidden" id="post_updated_at_2i" name="post[updated_at(2i)]" value="6" />\n}
- expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="15" />\n}
+ expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="1" />\n}
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) }
@@ -2467,7 +2481,7 @@ class DateHelperTest < ActionView::TestCase
expected = %{<input type="hidden" id="post_updated_at_1i" disabled="disabled" name="post[updated_at(1i)]" value="2004" />\n}
expected << %{<input type="hidden" id="post_updated_at_2i" disabled="disabled" name="post[updated_at(2i)]" value="6" />\n}
- expected << %{<input type="hidden" id="post_updated_at_3i" disabled="disabled" name="post[updated_at(3i)]" value="15" />\n}
+ expected << %{<input type="hidden" id="post_updated_at_3i" disabled="disabled" name="post[updated_at(3i)]" value="1" />\n}
expected << %{<select id="post_updated_at_4i" disabled="disabled" name="post[updated_at(4i)]">\n}
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) }
@@ -2865,6 +2879,10 @@ class DateHelperTest < ActionView::TestCase
assert_match(/<time.*>Right now<\/time>/, time_tag(Time.now, 'Right now'))
end
+ def test_time_tag_with_given_block
+ assert_match(/<time.*><span>Right now<\/span><\/time>/, time_tag(Time.now){ '<span>Right now</span>'.html_safe })
+ end
+
def test_time_tag_with_different_format
time = Time.now
expected = "<time datetime=\"#{time.xmlschema}\">#{I18n.l(time, :format => :short)}</time>"