actionpack 1.12.5 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (179) hide show
  1. data/CHANGELOG +517 -15
  2. data/MIT-LICENSE +1 -1
  3. data/README +18 -20
  4. data/Rakefile +7 -4
  5. data/examples/address_book_controller.rb +3 -3
  6. data/examples/blog_controller.cgi +3 -3
  7. data/examples/debate_controller.cgi +5 -5
  8. data/lib/action_controller.rb +2 -2
  9. data/lib/action_controller/assertions.rb +73 -311
  10. data/lib/action_controller/{deprecated_assertions.rb → assertions/deprecated_assertions.rb} +32 -8
  11. data/lib/action_controller/assertions/dom_assertions.rb +25 -0
  12. data/lib/action_controller/assertions/model_assertions.rb +12 -0
  13. data/lib/action_controller/assertions/response_assertions.rb +140 -0
  14. data/lib/action_controller/assertions/routing_assertions.rb +82 -0
  15. data/lib/action_controller/assertions/selector_assertions.rb +571 -0
  16. data/lib/action_controller/assertions/tag_assertions.rb +117 -0
  17. data/lib/action_controller/base.rb +334 -163
  18. data/lib/action_controller/benchmarking.rb +3 -6
  19. data/lib/action_controller/caching.rb +83 -22
  20. data/lib/action_controller/cgi_ext/cgi_ext.rb +0 -7
  21. data/lib/action_controller/cgi_ext/cgi_methods.rb +167 -173
  22. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +43 -22
  23. data/lib/action_controller/cgi_process.rb +50 -27
  24. data/lib/action_controller/components.rb +21 -25
  25. data/lib/action_controller/cookies.rb +10 -9
  26. data/lib/action_controller/{dependencies.rb → deprecated_dependencies.rb} +9 -27
  27. data/lib/action_controller/filters.rb +448 -225
  28. data/lib/action_controller/flash.rb +24 -20
  29. data/lib/action_controller/helpers.rb +2 -5
  30. data/lib/action_controller/integration.rb +40 -16
  31. data/lib/action_controller/layout.rb +11 -8
  32. data/lib/action_controller/macros/auto_complete.rb +3 -2
  33. data/lib/action_controller/macros/in_place_editing.rb +3 -2
  34. data/lib/action_controller/mime_responds.rb +41 -29
  35. data/lib/action_controller/mime_type.rb +68 -10
  36. data/lib/action_controller/pagination.rb +4 -3
  37. data/lib/action_controller/request.rb +22 -14
  38. data/lib/action_controller/rescue.rb +25 -22
  39. data/lib/action_controller/resources.rb +302 -0
  40. data/lib/action_controller/response.rb +20 -2
  41. data/lib/action_controller/response.rb.rej +17 -0
  42. data/lib/action_controller/routing.rb +1165 -567
  43. data/lib/action_controller/scaffolding.rb +30 -31
  44. data/lib/action_controller/session/active_record_store.rb +2 -0
  45. data/lib/action_controller/session/drb_store.rb +4 -0
  46. data/lib/action_controller/session/mem_cache_store.rb +4 -0
  47. data/lib/action_controller/session_management.rb +6 -9
  48. data/lib/action_controller/status_codes.rb +89 -0
  49. data/lib/action_controller/streaming.rb +6 -15
  50. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +5 -5
  51. data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -2
  52. data/lib/action_controller/templates/rescues/routing_error.rhtml +4 -4
  53. data/lib/action_controller/templates/rescues/template_error.rhtml +1 -1
  54. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  55. data/lib/action_controller/test_process.rb +52 -30
  56. data/lib/action_controller/url_rewriter.rb +63 -29
  57. data/lib/action_controller/vendor/html-scanner/html/document.rb +1 -0
  58. data/lib/action_controller/vendor/html-scanner/html/node.rb +3 -4
  59. data/lib/action_controller/vendor/html-scanner/html/selector.rb +822 -0
  60. data/lib/action_controller/verification.rb +22 -11
  61. data/lib/action_pack.rb +1 -1
  62. data/lib/action_pack/version.rb +2 -2
  63. data/lib/action_view.rb +1 -1
  64. data/lib/action_view/base.rb +46 -43
  65. data/lib/action_view/compiled_templates.rb +1 -1
  66. data/lib/action_view/helpers/active_record_helper.rb +54 -17
  67. data/lib/action_view/helpers/asset_tag_helper.rb +97 -46
  68. data/lib/action_view/helpers/capture_helper.rb +1 -1
  69. data/lib/action_view/helpers/date_helper.rb +258 -136
  70. data/lib/action_view/helpers/debug_helper.rb +1 -1
  71. data/lib/action_view/helpers/deprecated_helper.rb +34 -0
  72. data/lib/action_view/helpers/form_helper.rb +75 -35
  73. data/lib/action_view/helpers/form_options_helper.rb +7 -5
  74. data/lib/action_view/helpers/form_tag_helper.rb +44 -6
  75. data/lib/action_view/helpers/java_script_macros_helper.rb +59 -46
  76. data/lib/action_view/helpers/javascript_helper.rb +71 -10
  77. data/lib/action_view/helpers/javascripts/controls.js +41 -23
  78. data/lib/action_view/helpers/javascripts/dragdrop.js +105 -76
  79. data/lib/action_view/helpers/javascripts/effects.js +293 -163
  80. data/lib/action_view/helpers/javascripts/prototype.js +897 -389
  81. data/lib/action_view/helpers/javascripts/prototype.js.rej +561 -0
  82. data/lib/action_view/helpers/number_helper.rb +111 -65
  83. data/lib/action_view/helpers/prototype_helper.rb +84 -109
  84. data/lib/action_view/helpers/scriptaculous_helper.rb +5 -0
  85. data/lib/action_view/helpers/tag_helper.rb +69 -16
  86. data/lib/action_view/helpers/text_helper.rb +149 -112
  87. data/lib/action_view/helpers/url_helper.rb +200 -107
  88. data/lib/action_view/template_error.rb +66 -42
  89. data/test/abstract_unit.rb +4 -2
  90. data/test/active_record_unit.rb +84 -56
  91. data/test/activerecord/active_record_assertions_test.rb +26 -18
  92. data/test/activerecord/active_record_store_test.rb +4 -36
  93. data/test/activerecord/pagination_test.rb +1 -6
  94. data/test/controller/action_pack_assertions_test.rb +230 -113
  95. data/test/controller/addresses_render_test.rb +2 -6
  96. data/test/controller/assert_select_test.rb +576 -0
  97. data/test/controller/base_test.rb +73 -3
  98. data/test/controller/caching_test.rb +228 -0
  99. data/test/controller/capture_test.rb +12 -10
  100. data/test/controller/cgi_test.rb +89 -12
  101. data/test/controller/components_test.rb +24 -2
  102. data/test/controller/content_type_test.rb +139 -0
  103. data/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb +0 -0
  104. data/test/controller/controller_fixtures/app/controllers/user_controller.rb +0 -0
  105. data/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb +0 -0
  106. data/test/controller/cookie_test.rb +33 -25
  107. data/test/controller/deprecated_instance_variables_test.rb +48 -0
  108. data/test/controller/deprecation/deprecated_base_methods_test.rb +60 -0
  109. data/test/controller/fake_controllers.rb +0 -1
  110. data/test/controller/filters_test.rb +301 -16
  111. data/test/controller/flash_test.rb +19 -2
  112. data/test/controller/helper_test.rb +2 -2
  113. data/test/controller/integration_test.rb +154 -0
  114. data/test/controller/layout_test.rb +115 -1
  115. data/test/controller/mime_responds_test.rb +94 -0
  116. data/test/controller/mime_type_test.rb +9 -0
  117. data/test/controller/new_render_test.rb +161 -11
  118. data/test/controller/raw_post_test.rb +52 -15
  119. data/test/controller/redirect_test.rb +27 -14
  120. data/test/controller/render_test.rb +76 -29
  121. data/test/controller/request_test.rb +55 -4
  122. data/test/controller/resources_test.rb +274 -0
  123. data/test/controller/routing_test.rb +1533 -824
  124. data/test/controller/selector_test.rb +628 -0
  125. data/test/controller/send_file_test.rb +9 -1
  126. data/test/controller/session_management_test.rb +51 -0
  127. data/test/controller/test_test.rb +113 -29
  128. data/test/controller/url_rewriter_test.rb +86 -17
  129. data/test/controller/verification_test.rb +19 -17
  130. data/test/controller/webservice_test.rb +0 -7
  131. data/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml +1 -0
  132. data/test/fixtures/content_type/render_default_for_rhtml.rhtml +1 -0
  133. data/test/fixtures/content_type/render_default_for_rjs.rjs +1 -0
  134. data/test/fixtures/content_type/render_default_for_rxml.rxml +1 -0
  135. data/test/fixtures/deprecated_instance_variables/_cookies_ivar.rhtml +1 -0
  136. data/test/fixtures/deprecated_instance_variables/_cookies_method.rhtml +1 -0
  137. data/test/fixtures/deprecated_instance_variables/_flash_ivar.rhtml +1 -0
  138. data/test/fixtures/deprecated_instance_variables/_flash_method.rhtml +1 -0
  139. data/test/fixtures/deprecated_instance_variables/_headers_ivar.rhtml +1 -0
  140. data/test/fixtures/deprecated_instance_variables/_headers_method.rhtml +1 -0
  141. data/test/fixtures/deprecated_instance_variables/_params_ivar.rhtml +1 -0
  142. data/test/fixtures/deprecated_instance_variables/_params_method.rhtml +1 -0
  143. data/test/fixtures/deprecated_instance_variables/_request_ivar.rhtml +1 -0
  144. data/test/fixtures/deprecated_instance_variables/_request_method.rhtml +1 -0
  145. data/test/fixtures/deprecated_instance_variables/_response_ivar.rhtml +1 -0
  146. data/test/fixtures/deprecated_instance_variables/_response_method.rhtml +1 -0
  147. data/test/fixtures/deprecated_instance_variables/_session_ivar.rhtml +1 -0
  148. data/test/fixtures/deprecated_instance_variables/_session_method.rhtml +1 -0
  149. data/test/fixtures/multipart/binary_file +0 -0
  150. data/test/fixtures/public/javascripts/application.js +1 -0
  151. data/test/fixtures/test/_hello.rxml +1 -0
  152. data/test/fixtures/test/hello_world_container.rxml +3 -0
  153. data/test/fixtures/topic.rb +2 -2
  154. data/test/template/active_record_helper_test.rb +83 -12
  155. data/test/template/asset_tag_helper_test.rb +75 -95
  156. data/test/template/compiled_templates_test.rb +1 -0
  157. data/test/template/date_helper_test.rb +873 -181
  158. data/test/template/deprecated_helper_test.rb +36 -0
  159. data/test/template/deprecated_instance_variables_test.rb +43 -0
  160. data/test/template/form_helper_test.rb +77 -1
  161. data/test/template/form_options_helper_test.rb +4 -0
  162. data/test/template/form_tag_helper_test.rb +66 -2
  163. data/test/template/java_script_macros_helper_test.rb +4 -1
  164. data/test/template/javascript_helper_test.rb +29 -0
  165. data/test/template/number_helper_test.rb +63 -27
  166. data/test/template/prototype_helper_test.rb +77 -34
  167. data/test/template/tag_helper_test.rb +34 -6
  168. data/test/template/text_helper_test.rb +69 -34
  169. data/test/template/url_helper_test.rb +168 -16
  170. data/test/testing_sandbox.rb +7 -22
  171. metadata +66 -20
  172. data/filler.txt +0 -50
  173. data/lib/action_controller/code_generation.rb +0 -235
  174. data/lib/action_controller/vendor/xml_simple.rb +0 -1019
  175. data/test/controller/caching_filestore.rb +0 -74
  176. data/test/fixtures/application_root/app/controllers/a_class_that_contains_a_controller/poorly_placed_controller.rb +0 -7
  177. data/test/fixtures/application_root/app/controllers/module_that_holds_controllers/nested_controller.rb +0 -3
  178. data/test/fixtures/application_root/app/models/a_class_that_contains_a_controller.rb +0 -7
  179. data/test/fixtures/dont_load.rb +0 -3
@@ -3,20 +3,36 @@ require File.dirname(__FILE__) + '/url_helper'
3
3
  require File.dirname(__FILE__) + '/tag_helper'
4
4
 
5
5
  module ActionView
6
- module Helpers
7
- # Provides methods for linking a HTML page together with other assets, such as javascripts, stylesheets, and feeds.
6
+ module Helpers #:nodoc:
7
+ # Provides methods for linking an HTML page together with other assets such
8
+ # as images, javascripts, stylesheets, and feeds. You can direct Rails to
9
+ # link to assets from a dedicated assets server by setting ActionController::Base.asset_host
10
+ # in your environment.rb. These methods do not verify the assets exist before
11
+ # linking to them.
12
+ #
13
+ # ActionController::Base.asset_host = "http://assets.example.com"
14
+ # image_tag("rails.png")
15
+ # => <img src="http://assets.example.com/images/rails.png" alt="Rails" />
16
+ # stylesheet_include_tag("application")
17
+ # => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="Stylesheet" type="text/css" />
8
18
  module AssetTagHelper
9
- # Returns a link tag that browsers and news readers can use to auto-detect a RSS or ATOM feed for this page. The +type+ can
10
- # either be <tt>:rss</tt> (default) or <tt>:atom</tt> and the +options+ follow the url_for style of declaring a link target.
19
+ # Returns a link tag that browsers and news readers can use to auto-detect
20
+ # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
21
+ # <tt>:atom</tt>. Control the link options in url_for format using the
22
+ # +url_options+. You can modify the LINK tag itself in +tag_options+.
11
23
  #
12
- # Examples:
13
- # auto_discovery_link_tag # =>
24
+ # Tag Options:
25
+ # * <tt>:rel</tt> - Specify the relation of this link, defaults to "alternate"
26
+ # * <tt>:type</tt> - Override the auto-generated mime type
27
+ # * <tt>:title</tt> - Specify the title of the link, defaults to the +type+
28
+ #
29
+ # auto_discovery_link_tag # =>
14
30
  # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.curenthost.com/controller/action" />
15
- # auto_discovery_link_tag(:atom) # =>
31
+ # auto_discovery_link_tag(:atom) # =>
16
32
  # <link rel="alternate" type="application/atom+xml" title="ATOM" href="http://www.curenthost.com/controller/action" />
17
- # auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
33
+ # auto_discovery_link_tag(:rss, {:action => "feed"}) # =>
18
34
  # <link rel="alternate" type="application/rss+xml" title="RSS" href="http://www.curenthost.com/controller/feed" />
19
- # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
35
+ # auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"}) # =>
20
36
  # <link rel="alternate" type="application/rss+xml" title="My RSS" href="http://www.curenthost.com/controller/feed" />
21
37
  def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {})
22
38
  tag(
@@ -28,9 +44,14 @@ module ActionView
28
44
  )
29
45
  end
30
46
 
31
- # Returns path to a javascript asset. Example:
47
+ # Computes the path to a javascript asset in the public javascripts directory.
48
+ # If the +source+ filename has no extension, .js will be appended.
49
+ # Full paths from the document root will be passed through.
50
+ # Used internally by javascript_include_tag to build the script path.
32
51
  #
33
52
  # javascript_path "xmlhr" # => /javascripts/xmlhr.js
53
+ # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
54
+ # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
34
55
  def javascript_path(source)
35
56
  compute_public_path(source, 'javascripts', 'js')
36
57
  end
@@ -38,7 +59,15 @@ module ActionView
38
59
  JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
39
60
  @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
40
61
 
41
- # Returns a script include tag per source given as argument. Examples:
62
+ # Returns an html script tag for each of the +sources+ provided. You
63
+ # can pass in the filename (.js extension is optional) of javascript files
64
+ # that exist in your public/javascripts directory for inclusion into the
65
+ # current page or you can pass the full path relative to your document
66
+ # root. To include the Prototype and Scriptaculous javascript libraries in
67
+ # your application, pass <tt>:defaults</tt> as the source. When using
68
+ # :defaults, if an <tt>application.js</tt> file exists in your public
69
+ # javascripts directory, it will be included as well. You can modify the
70
+ # html attributes of the script tag by passing a hash as the last argument.
42
71
  #
43
72
  # javascript_include_tag "xmlhr" # =>
44
73
  # <script type="text/javascript" src="/javascripts/xmlhr.js"></script>
@@ -52,11 +81,6 @@ module ActionView
52
81
  # <script type="text/javascript" src="/javascripts/effects.js"></script>
53
82
  # ...
54
83
  # <script type="text/javascript" src="/javascripts/application.js"></script> *see below
55
- #
56
- # If there's an <tt>application.js</tt> file in your <tt>public/javascripts</tt> directory,
57
- # <tt>javascript_include_tag :defaults</tt> will automatically include it. This file
58
- # facilitates the inclusion of small snippets of JavaScript code, along the lines of
59
- # <tt>controllers/application.rb</tt> and <tt>helpers/application_helper.rb</tt>.
60
84
  def javascript_include_tag(*sources)
61
85
  options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
62
86
 
@@ -69,18 +93,16 @@ module ActionView
69
93
  sources << "application" if defined?(RAILS_ROOT) && File.exists?("#{RAILS_ROOT}/public/javascripts/application.js")
70
94
  end
71
95
 
72
- sources.collect { |source|
96
+ sources.collect do |source|
73
97
  source = javascript_path(source)
74
98
  content_tag("script", "", { "type" => "text/javascript", "src" => source }.merge(options))
75
- }.join("\n")
99
+ end.join("\n")
76
100
  end
77
101
 
78
102
  # Register one or more additional JavaScript files to be included when
79
- #
80
- # javascript_include_tag :defaults
81
- #
82
- # is called. This method is intended to be called only from plugin initialization
83
- # to register extra .js files the plugin installed in <tt>public/javascripts</tt>.
103
+ # <tt>javascript_include_tag :defaults</tt> is called. This method is
104
+ # only intended to be called from plugin initialization to register additional
105
+ # .js files that the plugin installed in <tt>public/javascripts</tt>.
84
106
  def self.register_javascript_include_default(*sources)
85
107
  @@javascript_default_sources.concat(sources)
86
108
  end
@@ -89,14 +111,21 @@ module ActionView
89
111
  @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
90
112
  end
91
113
 
92
- # Returns path to a stylesheet asset. Example:
114
+ # Computes the path to a stylesheet asset in the public stylesheets directory.
115
+ # If the +source+ filename has no extension, .css will be appended.
116
+ # Full paths from the document root will be passed through.
117
+ # Used internally by stylesheet_link_tag to build the stylesheet path.
93
118
  #
94
119
  # stylesheet_path "style" # => /stylesheets/style.css
120
+ # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
121
+ # stylesheet_path "/dir/style.css" # => /dir/style.css
95
122
  def stylesheet_path(source)
96
123
  compute_public_path(source, 'stylesheets', 'css')
97
124
  end
98
125
 
99
- # Returns a css link tag per source given as argument. Examples:
126
+ # Returns a stylesheet link tag for the sources specified as arguments. If
127
+ # you don't specify an extension, .css will be appended automatically.
128
+ # You can modify the link attributes by passing a hash as the last argument.
100
129
  #
101
130
  # stylesheet_link_tag "style" # =>
102
131
  # <link href="/stylesheets/style.css" media="screen" rel="Stylesheet" type="text/css" />
@@ -109,31 +138,50 @@ module ActionView
109
138
  # <link href="/css/stylish.css" media="screen" rel="Stylesheet" type="text/css" />
110
139
  def stylesheet_link_tag(*sources)
111
140
  options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
112
- sources.collect { |source|
141
+ sources.collect do |source|
113
142
  source = stylesheet_path(source)
114
143
  tag("link", { "rel" => "Stylesheet", "type" => "text/css", "media" => "screen", "href" => source }.merge(options))
115
- }.join("\n")
144
+ end.join("\n")
116
145
  end
117
146
 
118
- # Returns path to an image asset. Example:
147
+ # Computes the path to an image asset in the public images directory.
148
+ # Full paths from the document root will be passed through.
149
+ # Used internally by image_tag to build the image path. Passing
150
+ # a filename without an extension is deprecated.
119
151
  #
120
- # The +src+ can be supplied as a...
121
- # * full path, like "/my_images/image.gif"
122
- # * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
123
- # * file name without extension, like "logo", that gets expanded to "/images/logo.png"
152
+ # image_path("edit.png") # => /images/edit.png
153
+ # image_path("icons/edit.png") # => /images/icons/edit.png
154
+ # image_path("/icons/edit.png") # => /icons/edit.png
124
155
  def image_path(source)
156
+ unless (source.split("/").last || source).include?(".") || source.blank?
157
+ ActiveSupport::Deprecation.warn(
158
+ "You've called image_path with a source that doesn't include an extension. " +
159
+ "In Rails 2.0, that will not result in .png automatically being appended. " +
160
+ "So you should call image_path('#{source}.png') instead", caller
161
+ )
162
+ end
163
+
125
164
  compute_public_path(source, 'images', 'png')
126
165
  end
127
166
 
128
- # Returns an image tag converting the +options+ into html options on the tag, but with these special cases:
167
+ # Returns an html image tag for the +source+. The +source+ can be a full
168
+ # path or a file that exists in your public images directory. Note that
169
+ # specifying a filename without the extension is now deprecated in Rails.
170
+ # You can add html attributes using the +options+. The +options+ supports
171
+ # two additional keys for convienence and conformance:
129
172
  #
130
- # * <tt>:alt</tt> - If no alt text is given, the file name part of the +src+ is used (capitalized and without the extension)
131
- # * <tt>:size</tt> - Supplied as "XxY", so "30x45" becomes width="30" and height="45"
173
+ # * <tt>:alt</tt> - If no alt text is given, the file name part of the
174
+ # +source+ is used (capitalized and without the extension)
175
+ # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
176
+ # width="30" and height="45". <tt>:size</tt> will be ignored if the
177
+ # value is not in the correct format.
132
178
  #
133
- # The +src+ can be supplied as a...
134
- # * full path, like "/my_images/image.gif"
135
- # * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
136
- # * file name without extension, like "logo", that gets expanded to "/images/logo.png"
179
+ # image_tag("icon.png") # =>
180
+ # <img src="/images/icon.png" alt="Icon" />
181
+ # image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
182
+ # <img src="/images/icon.png" width="16" height="10" alt="Edit Entry" />
183
+ # image_tag("/icons/icon.gif", :size => "16x16") # =>
184
+ # <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
137
185
  def image_tag(source, options = {})
138
186
  options.symbolize_keys!
139
187
 
@@ -141,8 +189,8 @@ module ActionView
141
189
  options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize
142
190
 
143
191
  if options[:size]
144
- options[:width], options[:height] = options[:size].split("x")
145
- options.delete :size
192
+ options[:width], options[:height] = options[:size].split("x") if options[:size] =~ %r{^\d+x\d+$}
193
+ options.delete(:size)
146
194
  end
147
195
 
148
196
  tag("img", options)
@@ -150,11 +198,14 @@ module ActionView
150
198
 
151
199
  private
152
200
  def compute_public_path(source, dir, ext)
153
- source = "/#{dir}/#{source}" unless source.first == "/" || source.include?(":")
154
- source << ".#{ext}" unless source.split("/").last.include?(".")
155
- source << '?' + rails_asset_id(source) if defined?(RAILS_ROOT) && %r{^[-a-z]+://} !~ source
156
- source = "#{@controller.request.relative_url_root}#{source}" unless %r{^[-a-z]+://} =~ source
157
- source = ActionController::Base.asset_host + source unless source.include?(":")
201
+ source = source.dup
202
+ source << ".#{ext}" if File.extname(source).blank?
203
+ unless source =~ %r{^[-a-z]+://}
204
+ source = "/#{dir}/#{source}" unless source[0] == ?/
205
+ asset_id = rails_asset_id(source)
206
+ source << '?' + asset_id if defined?(RAILS_ROOT) && !asset_id.blank?
207
+ source = "#{ActionController::Base.asset_host}#{@controller.request.relative_url_root}#{source}"
208
+ end
158
209
  source
159
210
  end
160
211
 
@@ -89,7 +89,7 @@ module ActionView
89
89
  # named @@content_for_#{name_of_the_content_block}@. So <tt><%= content_for('footer') %></tt>
90
90
  # would be avaiable as <tt><%= @content_for_footer %></tt>. The preferred notation now is
91
91
  # <tt><%= yield :footer %></tt>.
92
- def content_for(name, &block)
92
+ def content_for(name, content = nil, &block)
93
93
  eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)"
94
94
  end
95
95
 
@@ -13,14 +13,38 @@ module ActionView
13
13
  module DateHelper
14
14
  DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX')
15
15
 
16
- # Reports the approximate distance in time between two Time objects or integers.
17
- # For example, if the distance is 47 minutes, it'll return
18
- # "about 1 hour". See the source for the complete wording list.
16
+ # Reports the approximate distance in time between two Time or Date objects or integers as seconds.
17
+ # Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs
18
+ # Distances are reported base on the following table:
19
19
  #
20
- # Integers are interpreted as seconds. So,
21
- # <tt>distance_of_time_in_words(50)</tt> returns "less than a minute".
20
+ # 0 <-> 29 secs # => less than a minute
21
+ # 30 secs <-> 1 min, 29 secs # => 1 minute
22
+ # 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
23
+ # 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
24
+ # 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
25
+ # 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day
26
+ # 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
27
+ # 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
28
+ # 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 31 secs # => [2..12] months
29
+ # 1 yr minus 30 secs <-> 2 yrs minus 31 secs # => about 1 year
30
+ # 2 yrs minus 30 secs <-> max time or date # => over [2..X] years
22
31
  #
23
- # Set <tt>include_seconds</tt> to true if you want more detailed approximations if distance < 1 minute
32
+ # With include_seconds = true and the difference < 1 minute 29 seconds
33
+ # 0-4 secs # => less than 5 seconds
34
+ # 5-9 secs # => less than 10 seconds
35
+ # 10-19 secs # => less than 20 seconds
36
+ # 20-39 secs # => half a minute
37
+ # 40-59 secs # => less than a minute
38
+ # 60-89 secs # => 1 minute
39
+ #
40
+ # Examples:
41
+ #
42
+ # from_time = Time.now
43
+ # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
44
+ # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
45
+ # distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
46
+ #
47
+ # Note: Rails calculates one year as 365.25 days.
24
48
  def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
25
49
  from_time = from_time.to_time if from_time.respond_to?(:to_time)
26
50
  to_time = to_time.to_time if to_time.respond_to?(:to_time)
@@ -29,29 +53,33 @@ module ActionView
29
53
 
30
54
  case distance_in_minutes
31
55
  when 0..1
32
- return (distance_in_minutes==0) ? 'less than a minute' : '1 minute' unless include_seconds
56
+ return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
33
57
  case distance_in_seconds
34
- when 0..5 then 'less than 5 seconds'
35
- when 6..10 then 'less than 10 seconds'
36
- when 11..20 then 'less than 20 seconds'
37
- when 21..40 then 'half a minute'
38
- when 41..59 then 'less than a minute'
58
+ when 0..4 then 'less than 5 seconds'
59
+ when 5..9 then 'less than 10 seconds'
60
+ when 10..19 then 'less than 20 seconds'
61
+ when 20..39 then 'half a minute'
62
+ when 40..59 then 'less than a minute'
39
63
  else '1 minute'
40
64
  end
41
-
42
- when 2..45 then "#{distance_in_minutes} minutes"
43
- when 46..90 then 'about 1 hour'
44
- when 90..1440 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
45
- when 1441..2880 then '1 day'
46
- else "#{(distance_in_minutes / 1440).round} days"
65
+
66
+ when 2..44 then "#{distance_in_minutes} minutes"
67
+ when 45..89 then 'about 1 hour'
68
+ when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
69
+ when 1440..2879 then '1 day'
70
+ when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
71
+ when 43200..86399 then 'about 1 month'
72
+ when 86400..525959 then "#{(distance_in_minutes / 43200).round} months"
73
+ when 525960..1051919 then 'about 1 year'
74
+ else "over #{(distance_in_minutes / 525960).round} years"
47
75
  end
48
76
  end
49
-
77
+
50
78
  # Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
51
79
  def time_ago_in_words(from_time, include_seconds = false)
52
80
  distance_of_time_in_words(from_time, Time.now, include_seconds)
53
81
  end
54
-
82
+
55
83
  alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
56
84
 
57
85
  # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by
@@ -80,6 +108,19 @@ module ActionView
80
108
  InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options)
81
109
  end
82
110
 
111
+ # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified
112
+ # time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+).
113
+ # You can include the seconds with <tt>:include_seconds</tt>.
114
+ # Examples:
115
+ #
116
+ # time_select("post", "sunrise")
117
+ # time_select("post", "start_time", :include_seconds => true)
118
+ #
119
+ # The selects are prepared for multi-parameter assignment to an Active Record object.
120
+ def time_select(object_name, method, options = {})
121
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_time_select_tag(options)
122
+ end
123
+
83
124
  # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
84
125
  # attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples:
85
126
  #
@@ -91,36 +132,55 @@ module ActionView
91
132
  InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options)
92
133
  end
93
134
 
135
+ # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
136
+ # It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
137
+ # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
138
+ # will be appened onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt> and <tt>:time_separator</tt>
139
+ # keys to the +options+ to control visual display of the elements.
140
+ def select_datetime(datetime = Time.now, options = {})
141
+ separator = options[:datetime_separator] || ''
142
+ select_date(datetime, options) + separator + select_time(datetime, options)
143
+ end
144
+
94
145
  # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
146
+ # It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
147
+ # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
148
+ # will be appened onto the <tt>:order</tt> passed in.
95
149
  def select_date(date = Date.today, options = {})
96
- select_year(date, options) + select_month(date, options) + select_day(date, options)
97
- end
150
+ options[:order] ||= []
151
+ [:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) }
98
152
 
99
- # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
100
- def select_datetime(datetime = Time.now, options = {})
101
- select_year(datetime, options) + select_month(datetime, options) + select_day(datetime, options) +
102
- select_hour(datetime, options) + select_minute(datetime, options)
153
+ select_date = ''
154
+ options[:order].each do |o|
155
+ select_date << self.send("select_#{o}", date, options)
156
+ end
157
+ select_date
103
158
  end
104
159
 
105
160
  # Returns a set of html select-tags (one for hour and minute)
161
+ # You can set <tt>:add_separator</tt> key to format the output.
106
162
  def select_time(datetime = Time.now, options = {})
107
- h = select_hour(datetime, options) + select_minute(datetime, options) + (options[:include_seconds] ? select_second(datetime, options) : '')
163
+ separator = options[:time_separator] || ''
164
+ select_hour(datetime, options) + separator + select_minute(datetime, options) + (options[:include_seconds] ? separator + select_second(datetime, options) : '')
108
165
  end
109
166
 
110
167
  # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
111
168
  # The <tt>second</tt> can also be substituted for a second number.
112
169
  # Override the field name using the <tt>:field_name</tt> option, 'second' by default.
113
170
  def select_second(datetime, options = {})
114
- second_options = []
115
-
116
- 0.upto(59) do |second|
117
- second_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) == second) ?
118
- %(<option value="#{leading_zero_on_single_digits(second)}" selected="selected">#{leading_zero_on_single_digits(second)}</option>\n) :
119
- %(<option value="#{leading_zero_on_single_digits(second)}">#{leading_zero_on_single_digits(second)}</option>\n)
120
- )
171
+ val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : ''
172
+ if options[:use_hidden]
173
+ options[:include_seconds] ? hidden_html(options[:field_name] || 'second', val, options) : ''
174
+ else
175
+ second_options = []
176
+ 0.upto(59) do |second|
177
+ second_options << ((val == second) ?
178
+ %(<option value="#{leading_zero_on_single_digits(second)}" selected="selected">#{leading_zero_on_single_digits(second)}</option>\n) :
179
+ %(<option value="#{leading_zero_on_single_digits(second)}">#{leading_zero_on_single_digits(second)}</option>\n)
180
+ )
181
+ end
182
+ select_html(options[:field_name] || 'second', second_options, options)
121
183
  end
122
-
123
- select_html(options[:field_name] || 'second', second_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
124
184
  end
125
185
 
126
186
  # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
@@ -128,84 +188,100 @@ module ActionView
128
188
  # The <tt>minute</tt> can also be substituted for a minute number.
129
189
  # Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
130
190
  def select_minute(datetime, options = {})
131
- minute_options = []
132
-
133
- 0.step(59, options[:minute_step] || 1) do |minute|
134
- minute_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.min) == minute) ?
135
- %(<option value="#{leading_zero_on_single_digits(minute)}" selected="selected">#{leading_zero_on_single_digits(minute)}</option>\n) :
136
- %(<option value="#{leading_zero_on_single_digits(minute)}">#{leading_zero_on_single_digits(minute)}</option>\n)
137
- )
138
- end
139
-
140
- select_html(options[:field_name] || 'minute', minute_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
191
+ val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : ''
192
+ if options[:use_hidden]
193
+ hidden_html(options[:field_name] || 'minute', val, options)
194
+ else
195
+ minute_options = []
196
+ 0.step(59, options[:minute_step] || 1) do |minute|
197
+ minute_options << ((val == minute) ?
198
+ %(<option value="#{leading_zero_on_single_digits(minute)}" selected="selected">#{leading_zero_on_single_digits(minute)}</option>\n) :
199
+ %(<option value="#{leading_zero_on_single_digits(minute)}">#{leading_zero_on_single_digits(minute)}</option>\n)
200
+ )
201
+ end
202
+ select_html(options[:field_name] || 'minute', minute_options, options)
203
+ end
141
204
  end
142
205
 
143
206
  # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
144
207
  # The <tt>hour</tt> can also be substituted for a hour number.
145
208
  # Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
146
209
  def select_hour(datetime, options = {})
147
- hour_options = []
148
-
149
- 0.upto(23) do |hour|
150
- hour_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) == hour) ?
151
- %(<option value="#{leading_zero_on_single_digits(hour)}" selected="selected">#{leading_zero_on_single_digits(hour)}</option>\n) :
152
- %(<option value="#{leading_zero_on_single_digits(hour)}">#{leading_zero_on_single_digits(hour)}</option>\n)
153
- )
210
+ val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : ''
211
+ if options[:use_hidden]
212
+ hidden_html(options[:field_name] || 'hour', val, options)
213
+ else
214
+ hour_options = []
215
+ 0.upto(23) do |hour|
216
+ hour_options << ((val == hour) ?
217
+ %(<option value="#{leading_zero_on_single_digits(hour)}" selected="selected">#{leading_zero_on_single_digits(hour)}</option>\n) :
218
+ %(<option value="#{leading_zero_on_single_digits(hour)}">#{leading_zero_on_single_digits(hour)}</option>\n)
219
+ )
220
+ end
221
+ select_html(options[:field_name] || 'hour', hour_options, options)
154
222
  end
155
-
156
- select_html(options[:field_name] || 'hour', hour_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
157
223
  end
158
224
 
159
225
  # Returns a select tag with options for each of the days 1 through 31 with the current day selected.
160
226
  # The <tt>date</tt> can also be substituted for a hour number.
161
227
  # Override the field name using the <tt>:field_name</tt> option, 'day' by default.
162
228
  def select_day(date, options = {})
163
- day_options = []
164
-
165
- 1.upto(31) do |day|
166
- day_options << ((date && (date.kind_of?(Fixnum) ? date : date.day) == day) ?
167
- %(<option value="#{day}" selected="selected">#{day}</option>\n) :
168
- %(<option value="#{day}">#{day}</option>\n)
169
- )
229
+ val = date ? (date.kind_of?(Fixnum) ? date : date.day) : ''
230
+ if options[:use_hidden]
231
+ hidden_html(options[:field_name] || 'day', val, options)
232
+ else
233
+ day_options = []
234
+ 1.upto(31) do |day|
235
+ day_options << ((val == day) ?
236
+ %(<option value="#{day}" selected="selected">#{day}</option>\n) :
237
+ %(<option value="#{day}">#{day}</option>\n)
238
+ )
239
+ end
240
+ select_html(options[:field_name] || 'day', day_options, options)
170
241
  end
171
-
172
- select_html(options[:field_name] || 'day', day_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
173
242
  end
174
243
 
175
244
  # Returns a select tag with options for each of the months January through December with the current month selected.
176
245
  # The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
177
246
  # (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
178
247
  # set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names,
179
- # set the <tt>:add_month_numbers</tt> key in +options+ to true. Examples:
248
+ # set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer to show month names as abbreviations,
249
+ # set the <tt>:use_short_month</tt> key in +options+ to true. If you want to use your own month names, set the
250
+ # <tt>:use_month_names</tt> key in +options+ to an array of 12 month names.
251
+ #
252
+ # Examples:
180
253
  #
181
254
  # select_month(Date.today) # Will use keys like "January", "March"
182
255
  # select_month(Date.today, :use_month_numbers => true) # Will use keys like "1", "3"
183
256
  # select_month(Date.today, :add_month_numbers => true) # Will use keys like "1 - January", "3 - March"
257
+ # select_month(Date.today, :use_short_month => true) # Will use keys like "Jan", "Mar"
258
+ # select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...)) # Will use keys like "Januar", "Marts"
184
259
  #
185
260
  # Override the field name using the <tt>:field_name</tt> option, 'month' by default.
186
- #
187
- # If you would prefer to show month names as abbreviations, set the
188
- # <tt>:use_short_month</tt> key in +options+ to true.
189
261
  def select_month(date, options = {})
190
- month_options = []
191
- month_names = options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES
192
-
193
- 1.upto(12) do |month_number|
194
- month_name = if options[:use_month_numbers]
195
- month_number
196
- elsif options[:add_month_numbers]
197
- month_number.to_s + ' - ' + month_names[month_number]
198
- else
199
- month_names[month_number]
200
- end
262
+ val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
263
+ if options[:use_hidden]
264
+ hidden_html(options[:field_name] || 'month', val, options)
265
+ else
266
+ month_options = []
267
+ month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES)
268
+ month_names.unshift(nil) if month_names.size < 13
269
+ 1.upto(12) do |month_number|
270
+ month_name = if options[:use_month_numbers]
271
+ month_number
272
+ elsif options[:add_month_numbers]
273
+ month_number.to_s + ' - ' + month_names[month_number]
274
+ else
275
+ month_names[month_number]
276
+ end
201
277
 
202
- month_options << ((date && (date.kind_of?(Fixnum) ? date : date.month) == month_number) ?
203
- %(<option value="#{month_number}" selected="selected">#{month_name}</option>\n) :
204
- %(<option value="#{month_number}">#{month_name}</option>\n)
205
- )
278
+ month_options << ((val == month_number) ?
279
+ %(<option value="#{month_number}" selected="selected">#{month_name}</option>\n) :
280
+ %(<option value="#{month_number}">#{month_name}</option>\n)
281
+ )
282
+ end
283
+ select_html(options[:field_name] || 'month', month_options, options)
206
284
  end
207
-
208
- select_html(options[:field_name] || 'month', month_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
209
285
  end
210
286
 
211
287
  # Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
@@ -215,37 +291,51 @@ module ActionView
215
291
  #
216
292
  # select_year(Date.today, :start_year => 1992, :end_year => 2007) # ascending year values
217
293
  # select_year(Date.today, :start_year => 2005, :end_year => 1900) # descending year values
294
+ # select_year(2006, :start_year => 2000, :end_year => 2010)
218
295
  #
219
296
  # Override the field name using the <tt>:field_name</tt> option, 'year' by default.
220
297
  def select_year(date, options = {})
221
- year_options = []
222
- y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
223
-
224
- start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
225
- step_val = start_year < end_year ? 1 : -1
226
-
227
- start_year.step(end_year, step_val) do |year|
228
- year_options << ((date && (date.kind_of?(Fixnum) ? date : date.year) == year) ?
229
- %(<option value="#{year}" selected="selected">#{year}</option>\n) :
230
- %(<option value="#{year}">#{year}</option>\n)
231
- )
298
+ val = date ? (date.kind_of?(Fixnum) ? date : date.year) : ''
299
+ if options[:use_hidden]
300
+ hidden_html(options[:field_name] || 'year', val, options)
301
+ else
302
+ year_options = []
303
+ y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
304
+
305
+ start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
306
+ step_val = start_year < end_year ? 1 : -1
307
+ start_year.step(end_year, step_val) do |year|
308
+ year_options << ((val == year) ?
309
+ %(<option value="#{year}" selected="selected">#{year}</option>\n) :
310
+ %(<option value="#{year}">#{year}</option>\n)
311
+ )
312
+ end
313
+ select_html(options[:field_name] || 'year', year_options, options)
232
314
  end
233
-
234
- select_html(options[:field_name] || 'year', year_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
235
315
  end
236
316
 
237
317
  private
238
- def select_html(type, options, prefix = nil, include_blank = false, discard_type = false, disabled = false)
239
- select_html = %(<select name="#{prefix || DEFAULT_PREFIX})
240
- select_html << "[#{type}]" unless discard_type
241
- select_html << %(")
242
- select_html << %( disabled="disabled") if disabled
318
+
319
+ def select_html(type, html_options, options)
320
+ name_and_id_from_options(options, type)
321
+ select_html = %(<select id="#{options[:id]}" name="#{options[:name]}")
322
+ select_html << %( disabled="disabled") if options[:disabled]
243
323
  select_html << %(>\n)
244
- select_html << %(<option value=""></option>\n) if include_blank
245
- select_html << options.to_s
324
+ select_html << %(<option value=""></option>\n) if options[:include_blank]
325
+ select_html << html_options.to_s
246
326
  select_html << "</select>\n"
247
327
  end
248
328
 
329
+ def hidden_html(type, value, options)
330
+ name_and_id_from_options(options, type)
331
+ hidden_html = %(<input type="hidden" id="#{options[:id]}" name="#{options[:name]}" value="#{value}" />\n)
332
+ end
333
+
334
+ def name_and_id_from_options(options, type)
335
+ options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
336
+ options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
337
+ end
338
+
249
339
  def leading_zero_on_single_digits(number)
250
340
  number > 9 ? number : "0#{number}"
251
341
  end
@@ -255,43 +345,71 @@ module ActionView
255
345
  include DateHelper
256
346
 
257
347
  def to_date_select_tag(options = {})
258
- defaults = { :discard_type => true }
259
- options = defaults.merge(options)
260
- options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
261
- date = options[:include_blank] ? (value || 0) : (value || Date.today)
348
+ date_or_time_select options.merge(:discard_hour => true)
349
+ end
262
350
 
263
- date_select = ''
264
- options[:order] = [:month, :year, :day] if options[:month_before_year] # For backwards compatibility
265
- options[:order] ||= [:year, :month, :day]
351
+ def to_time_select_tag(options = {})
352
+ date_or_time_select options.merge(:discard_year => true, :discard_month => true)
353
+ end
266
354
 
267
- position = {:year => 1, :month => 2, :day => 3}
355
+ def to_datetime_select_tag(options = {})
356
+ date_or_time_select options
357
+ end
268
358
 
269
- discard = {}
270
- discard[:year] = true if options[:discard_year]
271
- discard[:month] = true if options[:discard_month]
272
- discard[:day] = true if options[:discard_day] or options[:discard_month]
359
+ private
360
+ def date_or_time_select(options)
361
+ defaults = { :discard_type => true }
362
+ options = defaults.merge(options)
363
+ datetime = value(object)
364
+ datetime ||= Time.now unless options[:include_blank]
365
+
366
+ position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
367
+
368
+ order = (options[:order] ||= [:year, :month, :day])
369
+
370
+ # Discard explicit and implicit by not being included in the :order
371
+ discard = {}
372
+ discard[:year] = true if options[:discard_year] or !order.include?(:year)
373
+ discard[:month] = true if options[:discard_month] or !order.include?(:month)
374
+ discard[:day] = true if options[:discard_day] or discard[:month] or !order.include?(:day)
375
+ discard[:hour] = true if options[:discard_hour]
376
+ discard[:minute] = true if options[:discard_minute] or discard[:hour]
377
+ discard[:second] = true unless options[:include_seconds] && !discard[:minute]
378
+
379
+ # Maintain valid dates by including hidden fields for discarded elements
380
+ [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
381
+ # Ensure proper ordering of :hour, :minute and :second
382
+ [:hour, :minute, :second].each { |o| order.delete(o); order.push(o) }
383
+
384
+ date_or_time_select = ''
385
+ order.reverse.each do |param|
386
+ # Send hidden fields for discarded elements once output has started
387
+ # This ensures AR can reconstruct valid dates using ParseDate
388
+ next if discard[param] && date_or_time_select.empty?
389
+
390
+ date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param]))))
391
+ date_or_time_select.insert(0,
392
+ case param
393
+ when :hour then (discard[:year] && discard[:day] ? "" : " &mdash; ")
394
+ when :minute then " : "
395
+ when :second then options[:include_seconds] ? " : " : ""
396
+ else ""
397
+ end)
273
398
 
274
- options[:order].each do |param|
275
- date_select << self.send("select_#{param}", date, options_with_prefix.call(position[param])) unless discard[param]
276
- end
399
+ end
277
400
 
278
- date_select
279
- end
401
+ date_or_time_select
402
+ end
280
403
 
281
- def to_datetime_select_tag(options = {})
282
- defaults = { :discard_type => true }
283
- options = defaults.merge(options)
284
- options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
285
- datetime = options[:include_blank] ? (value || nil) : (value || Time.now)
286
-
287
- datetime_select = select_year(datetime, options_with_prefix.call(1))
288
- datetime_select << select_month(datetime, options_with_prefix.call(2)) unless options[:discard_month]
289
- datetime_select << select_day(datetime, options_with_prefix.call(3)) unless options[:discard_day] || options[:discard_month]
290
- datetime_select << ' &mdash; ' + select_hour(datetime, options_with_prefix.call(4)) unless options[:discard_hour]
291
- datetime_select << ' : ' + select_minute(datetime, options_with_prefix.call(5)) unless options[:discard_minute] || options[:discard_hour]
292
-
293
- datetime_select
294
- end
404
+ def options_with_prefix(position, options)
405
+ prefix = "#{@object_name}"
406
+ if options[:index]
407
+ prefix << "[#{options[:index]}]"
408
+ elsif @auto_index
409
+ prefix << "[#{@auto_index}]"
410
+ end
411
+ options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]")
412
+ end
295
413
  end
296
414
 
297
415
  class FormBuilder
@@ -299,6 +417,10 @@ module ActionView
299
417
  @template.date_select(@object_name, method, options.merge(:object => @object))
300
418
  end
301
419
 
420
+ def time_select(method, options = {})
421
+ @template.time_select(@object_name, method, options.merge(:object => @object))
422
+ end
423
+
302
424
  def datetime_select(method, options = {})
303
425
  @template.datetime_select(@object_name, method, options.merge(:object => @object))
304
426
  end