actionpack 2.1.2 → 2.2.2

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 (200) hide show
  1. data/CHANGELOG +223 -7
  2. data/README +6 -12
  3. data/Rakefile +11 -11
  4. data/lib/action_controller.rb +9 -9
  5. data/lib/action_controller/assertions/response_assertions.rb +29 -78
  6. data/lib/action_controller/assertions/routing_assertions.rb +33 -33
  7. data/lib/action_controller/assertions/selector_assertions.rb +9 -5
  8. data/lib/action_controller/base.rb +227 -161
  9. data/lib/action_controller/benchmarking.rb +37 -24
  10. data/lib/action_controller/caching/actions.rb +53 -21
  11. data/lib/action_controller/caching/fragments.rb +10 -36
  12. data/lib/action_controller/caching/sweeping.rb +3 -3
  13. data/lib/action_controller/cgi_ext/session.rb +2 -22
  14. data/lib/action_controller/cgi_process.rb +8 -46
  15. data/lib/action_controller/components.rb +4 -1
  16. data/lib/action_controller/cookies.rb +10 -0
  17. data/lib/action_controller/dispatcher.rb +49 -15
  18. data/lib/action_controller/filters.rb +48 -10
  19. data/lib/action_controller/headers.rb +16 -14
  20. data/lib/action_controller/helpers.rb +2 -2
  21. data/lib/action_controller/http_authentication.rb +1 -1
  22. data/lib/action_controller/integration.rb +57 -60
  23. data/lib/action_controller/layout.rb +27 -53
  24. data/lib/action_controller/mime_responds.rb +5 -1
  25. data/lib/action_controller/mime_type.rb +64 -42
  26. data/lib/action_controller/mime_types.rb +2 -1
  27. data/lib/action_controller/performance_test.rb +16 -0
  28. data/lib/action_controller/polymorphic_routes.rb +16 -9
  29. data/lib/action_controller/rack_process.rb +303 -0
  30. data/lib/action_controller/request.rb +205 -97
  31. data/lib/action_controller/request_forgery_protection.rb +2 -2
  32. data/lib/action_controller/request_profiler.rb +0 -0
  33. data/lib/action_controller/rescue.rb +20 -115
  34. data/lib/action_controller/resources.rb +186 -83
  35. data/lib/action_controller/response.rb +140 -26
  36. data/lib/action_controller/routing.rb +28 -30
  37. data/lib/action_controller/routing/builder.rb +45 -54
  38. data/lib/action_controller/routing/optimisations.rb +31 -21
  39. data/lib/action_controller/routing/recognition_optimisation.rb +33 -27
  40. data/lib/action_controller/routing/route.rb +162 -147
  41. data/lib/action_controller/routing/route_set.rb +8 -7
  42. data/lib/action_controller/routing/routing_ext.rb +4 -1
  43. data/lib/action_controller/routing/segments.rb +50 -21
  44. data/lib/action_controller/session/cookie_store.rb +3 -2
  45. data/lib/action_controller/session/drb_server.rb +7 -7
  46. data/lib/action_controller/session_management.rb +6 -2
  47. data/lib/action_controller/streaming.rb +15 -8
  48. data/lib/action_controller/templates/rescues/diagnostics.erb +2 -2
  49. data/lib/action_controller/templates/rescues/template_error.erb +2 -2
  50. data/lib/action_controller/test_case.rb +66 -2
  51. data/lib/action_controller/test_process.rb +71 -66
  52. data/lib/action_controller/translation.rb +13 -0
  53. data/lib/action_controller/url_rewriter.rb +90 -13
  54. data/lib/action_controller/vendor/html-scanner/html/node.rb +9 -2
  55. data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +1 -1
  56. data/lib/action_controller/vendor/html-scanner/html/selector.rb +2 -2
  57. data/lib/action_controller/verification.rb +2 -2
  58. data/lib/action_pack/version.rb +1 -1
  59. data/lib/action_view.rb +19 -11
  60. data/lib/action_view/base.rb +184 -150
  61. data/lib/action_view/helpers.rb +38 -0
  62. data/lib/action_view/helpers/active_record_helper.rb +56 -27
  63. data/lib/action_view/helpers/asset_tag_helper.rb +356 -153
  64. data/lib/action_view/helpers/atom_feed_helper.rb +74 -19
  65. data/lib/action_view/helpers/benchmark_helper.rb +3 -3
  66. data/lib/action_view/helpers/cache_helper.rb +1 -2
  67. data/lib/action_view/helpers/capture_helper.rb +19 -44
  68. data/lib/action_view/helpers/date_helper.rb +486 -296
  69. data/lib/action_view/helpers/debug_helper.rb +20 -13
  70. data/lib/action_view/helpers/form_helper.rb +71 -30
  71. data/lib/action_view/helpers/form_options_helper.rb +15 -85
  72. data/lib/action_view/helpers/form_tag_helper.rb +61 -38
  73. data/lib/action_view/helpers/javascript_helper.rb +80 -89
  74. data/lib/action_view/helpers/number_helper.rb +179 -74
  75. data/lib/action_view/helpers/prototype_helper.rb +216 -201
  76. data/lib/action_view/helpers/record_tag_helper.rb +4 -5
  77. data/lib/action_view/helpers/sanitize_helper.rb +65 -33
  78. data/lib/action_view/helpers/scriptaculous_helper.rb +2 -2
  79. data/lib/action_view/helpers/tag_helper.rb +39 -22
  80. data/lib/action_view/helpers/text_helper.rb +212 -118
  81. data/lib/action_view/helpers/translation_helper.rb +21 -0
  82. data/lib/action_view/helpers/url_helper.rb +100 -58
  83. data/lib/action_view/inline_template.rb +13 -14
  84. data/lib/action_view/locale/en.yml +91 -0
  85. data/lib/action_view/partials.rb +100 -55
  86. data/lib/action_view/paths.rb +125 -0
  87. data/lib/action_view/renderable.rb +102 -0
  88. data/lib/action_view/renderable_partial.rb +48 -0
  89. data/lib/action_view/template.rb +90 -101
  90. data/lib/action_view/template_error.rb +11 -21
  91. data/lib/action_view/template_handler.rb +8 -28
  92. data/lib/action_view/template_handlers.rb +45 -0
  93. data/lib/action_view/template_handlers/builder.rb +5 -15
  94. data/lib/action_view/template_handlers/erb.rb +9 -6
  95. data/lib/action_view/template_handlers/rjs.rb +2 -17
  96. data/lib/action_view/test_case.rb +7 -4
  97. data/test/abstract_unit.rb +4 -1
  98. data/test/active_record_unit.rb +28 -30
  99. data/test/activerecord/render_partial_with_record_identification_test.rb +25 -12
  100. data/test/controller/action_pack_assertions_test.rb +8 -37
  101. data/test/controller/addresses_render_test.rb +0 -3
  102. data/test/controller/assert_select_test.rb +51 -24
  103. data/test/controller/base_test.rb +4 -4
  104. data/test/controller/caching_test.rb +136 -66
  105. data/test/controller/capture_test.rb +1 -21
  106. data/test/controller/cgi_test.rb +157 -10
  107. data/test/controller/components_test.rb +41 -25
  108. data/test/controller/content_type_test.rb +49 -17
  109. data/test/controller/cookie_test.rb +1 -1
  110. data/test/controller/deprecation/deprecated_base_methods_test.rb +0 -3
  111. data/test/controller/dispatcher_test.rb +9 -1
  112. data/test/controller/filter_params_test.rb +2 -2
  113. data/test/controller/filters_test.rb +13 -13
  114. data/test/controller/html-scanner/cdata_node_test.rb +15 -0
  115. data/test/controller/html-scanner/node_test.rb +21 -0
  116. data/test/controller/html-scanner/sanitizer_test.rb +14 -0
  117. data/test/controller/integration_test.rb +167 -6
  118. data/test/controller/layout_test.rb +11 -68
  119. data/test/controller/logging_test.rb +46 -0
  120. data/test/controller/mime_responds_test.rb +61 -59
  121. data/test/controller/mime_type_test.rb +6 -6
  122. data/test/controller/polymorphic_routes_test.rb +37 -2
  123. data/test/controller/rack_test.rb +323 -0
  124. data/test/controller/redirect_test.rb +72 -71
  125. data/test/controller/render_test.rb +1120 -108
  126. data/test/controller/request_forgery_protection_test.rb +66 -52
  127. data/test/controller/request_test.rb +103 -146
  128. data/test/controller/rescue_test.rb +20 -24
  129. data/test/controller/resources_test.rb +408 -25
  130. data/test/controller/routing_test.rb +1774 -1774
  131. data/test/controller/send_file_test.rb +0 -4
  132. data/test/controller/session/cookie_store_test.rb +53 -1
  133. data/test/controller/test_test.rb +15 -37
  134. data/test/controller/translation_test.rb +26 -0
  135. data/test/controller/url_rewriter_test.rb +27 -28
  136. data/test/controller/view_paths_test.rb +48 -47
  137. data/test/fixtures/_top_level_partial.html.erb +1 -0
  138. data/test/fixtures/_top_level_partial_only.erb +1 -0
  139. data/test/fixtures/developers/_developer.erb +1 -0
  140. data/test/fixtures/fun/games/_game.erb +1 -0
  141. data/test/fixtures/fun/serious/games/_game.erb +1 -0
  142. data/test/fixtures/functional_caching/formatted_fragment_cached.html.erb +3 -0
  143. data/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs +6 -0
  144. data/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder +5 -0
  145. data/test/fixtures/functional_caching/inline_fragment_cached.html.erb +2 -0
  146. data/test/fixtures/layouts/_column.html.erb +2 -0
  147. data/test/fixtures/projects/_project.erb +1 -0
  148. data/test/fixtures/public/javascripts/subdir/subdir.js +1 -0
  149. data/test/fixtures/public/stylesheets/subdir/subdir.css +1 -0
  150. data/test/fixtures/replies/_reply.erb +1 -0
  151. data/test/fixtures/test/_counter.html.erb +1 -0
  152. data/test/fixtures/test/_customer.erb +1 -1
  153. data/test/fixtures/test/_customer_with_var.erb +1 -0
  154. data/test/fixtures/test/_layout_for_block_with_args.html.erb +3 -0
  155. data/test/fixtures/test/_local_inspector.html.erb +1 -0
  156. data/test/fixtures/test/_partial_with_only_html_version.html.erb +1 -0
  157. data/test/fixtures/test/hello.builder +1 -1
  158. data/test/fixtures/test/hyphen-ated.erb +1 -0
  159. data/test/fixtures/test/implicit_content_type.atom.builder +2 -0
  160. data/test/fixtures/test/nested_layout.erb +3 -0
  161. data/test/fixtures/test/non_erb_block_content_for.builder +1 -1
  162. data/test/fixtures/test/sub_template_raise.html.erb +1 -0
  163. data/test/fixtures/test/template.erb +1 -0
  164. data/test/fixtures/test/using_layout_around_block_with_args.html.erb +1 -0
  165. data/test/template/active_record_helper_i18n_test.rb +46 -0
  166. data/test/template/active_record_helper_test.rb +24 -24
  167. data/test/template/asset_tag_helper_test.rb +161 -29
  168. data/test/template/atom_feed_helper_test.rb +114 -5
  169. data/test/template/compiled_templates_test.rb +59 -0
  170. data/test/template/date_helper_i18n_test.rb +113 -0
  171. data/test/template/date_helper_test.rb +403 -109
  172. data/test/template/form_helper_test.rb +213 -154
  173. data/test/template/form_options_helper_test.rb +249 -897
  174. data/test/template/form_tag_helper_test.rb +80 -32
  175. data/test/template/javascript_helper_test.rb +17 -18
  176. data/test/template/number_helper_i18n_test.rb +54 -0
  177. data/test/template/number_helper_test.rb +43 -13
  178. data/test/template/prototype_helper_test.rb +101 -84
  179. data/test/template/record_tag_helper_test.rb +24 -20
  180. data/test/template/render_test.rb +193 -0
  181. data/test/template/sanitize_helper_test.rb +3 -3
  182. data/test/template/tag_helper_test.rb +34 -14
  183. data/test/template/text_helper_test.rb +83 -9
  184. data/test/template/translation_helper_test.rb +28 -0
  185. data/test/template/url_helper_test.rb +55 -18
  186. metadata +57 -18
  187. data/lib/action_view/helpers/javascripts/controls.js +0 -963
  188. data/lib/action_view/helpers/javascripts/dragdrop.js +0 -972
  189. data/lib/action_view/helpers/javascripts/effects.js +0 -1120
  190. data/lib/action_view/helpers/javascripts/prototype.js +0 -4225
  191. data/lib/action_view/partial_template.rb +0 -70
  192. data/lib/action_view/template_finder.rb +0 -177
  193. data/lib/action_view/template_handlers/compilable.rb +0 -128
  194. data/test/controller/custom_handler_test.rb +0 -45
  195. data/test/controller/new_render_test.rb +0 -945
  196. data/test/fixtures/test/block_content_for.erb +0 -2
  197. data/test/fixtures/test/erb_content_for.erb +0 -2
  198. data/test/template/deprecated_erb_variable_test.rb +0 -9
  199. data/test/template/template_finder_test.rb +0 -73
  200. data/test/template/template_object_test.rb +0 -95
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other
2
4
  # template languages).
3
5
  module ActionView
@@ -17,7 +19,7 @@ module ActionView
17
19
  # # GET /posts.atom
18
20
  # def index
19
21
  # @posts = Post.find(:all)
20
- #
22
+ #
21
23
  # respond_to do |format|
22
24
  # format.html
23
25
  # format.atom
@@ -29,12 +31,12 @@ module ActionView
29
31
  # atom_feed do |feed|
30
32
  # feed.title("My great blog!")
31
33
  # feed.updated((@posts.first.created_at))
32
- #
34
+ #
33
35
  # for post in @posts
34
36
  # feed.entry(post) do |entry|
35
37
  # entry.title(post.title)
36
38
  # entry.content(post.body, :type => 'html')
37
- #
39
+ #
38
40
  # entry.author do |author|
39
41
  # author.name("DHH")
40
42
  # end
@@ -47,9 +49,11 @@ module ActionView
47
49
  # * <tt>:language</tt>: Defaults to "en-US".
48
50
  # * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
49
51
  # * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
50
- # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
51
- # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
52
+ # * <tt>:id</tt>: The id for this feed. Defaults to "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}"
53
+ # * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
54
+ # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
52
55
  # 2005 is used (as an "I don't care" value).
56
+ # * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]}
53
57
  #
54
58
  # Other namespaces can be added to the root element:
55
59
  #
@@ -73,36 +77,90 @@ module ActionView
73
77
  # end
74
78
  # end
75
79
  #
80
+ # The Atom spec defines five elements (content rights title subtitle
81
+ # summary) which may directly contain xhtml content if :type => 'xhtml'
82
+ # is specified as an attribute. If so, this helper will take care of
83
+ # the enclosing div and xhtml namespace declaration. Example usage:
76
84
  #
77
- # atom_feed yields an AtomFeedBuilder instance.
85
+ # entry.summary :type => 'xhtml' do |xhtml|
86
+ # xhtml.p pluralize(order.line_items.count, "line item")
87
+ # xhtml.p "Shipped to #{order.address}"
88
+ # xhtml.p "Paid by #{order.pay_type}"
89
+ # end
90
+ #
91
+ #
92
+ # atom_feed yields an AtomFeedBuilder instance. Nested elements yield
93
+ # an AtomBuilder instance.
78
94
  def atom_feed(options = {}, &block)
79
95
  if options[:schema_date]
80
96
  options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime)
81
97
  else
82
98
  options[:schema_date] = "2005" # The Atom spec copyright date
83
99
  end
84
-
100
+
85
101
  xml = options[:xml] || eval("xml", block.binding)
86
102
  xml.instruct!
103
+ if options[:instruct]
104
+ options[:instruct].each do |target,attrs|
105
+ if attrs.respond_to?(:keys)
106
+ xml.instruct!(target, attrs)
107
+ elsif attrs.respond_to?(:each)
108
+ attrs.each { |attr_group| xml.instruct!(target, attr_group) }
109
+ end
110
+ end
111
+ end
87
112
 
88
113
  feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'}
89
114
  feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)}
90
115
 
91
116
  xml.feed(feed_opts) do
92
- xml.id("tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}")
117
+ xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}")
93
118
  xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port))
94
119
  xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url)
95
-
120
+
96
121
  yield AtomFeedBuilder.new(xml, self, options)
97
122
  end
98
123
  end
99
124
 
125
+ class AtomBuilder
126
+ XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
127
+
128
+ def initialize(xml)
129
+ @xml = xml
130
+ end
131
+
132
+ private
133
+ # Delegate to xml builder, first wrapping the element in a xhtml
134
+ # namespaced div element if the method and arguments indicate
135
+ # that an xhtml_block? is desired.
136
+ def method_missing(method, *arguments, &block)
137
+ if xhtml_block?(method, arguments)
138
+ @xml.__send__(method, *arguments) do
139
+ @xml.div(:xmlns => 'http://www.w3.org/1999/xhtml') do |xhtml|
140
+ block.call(xhtml)
141
+ end
142
+ end
143
+ else
144
+ @xml.__send__(method, *arguments, &block)
145
+ end
146
+ end
147
+
148
+ # True if the method name matches one of the five elements defined
149
+ # in the Atom spec as potentially containing XHTML content and
150
+ # if :type => 'xhtml' is, in fact, specified.
151
+ def xhtml_block?(method, arguments)
152
+ if XHTML_TAG_NAMES.include?(method.to_s)
153
+ last = arguments.last
154
+ last.is_a?(Hash) && last[:type].to_s == 'xhtml'
155
+ end
156
+ end
157
+ end
100
158
 
101
- class AtomFeedBuilder
159
+ class AtomFeedBuilder < AtomBuilder
102
160
  def initialize(xml, view, feed_options = {})
103
161
  @xml, @view, @feed_options = xml, view, feed_options
104
162
  end
105
-
163
+
106
164
  # Accepts a Date or Time object and inserts it in the proper format. If nil is passed, current time in UTC is used.
107
165
  def updated(date_or_time = nil)
108
166
  @xml.updated((date_or_time || Time.now.utc).xmlschema)
@@ -115,9 +173,10 @@ module ActionView
115
173
  # * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
116
174
  # * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
117
175
  # * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
176
+ # * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
118
177
  def entry(record, options = {})
119
- @xml.entry do
120
- @xml.id("tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
178
+ @xml.entry do
179
+ @xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
121
180
 
122
181
  if options[:published] || (record.respond_to?(:created_at) && record.created_at)
123
182
  @xml.published((options[:published] || record.created_at).xmlschema)
@@ -129,15 +188,11 @@ module ActionView
129
188
 
130
189
  @xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:url] || @view.polymorphic_url(record))
131
190
 
132
- yield @xml
191
+ yield AtomBuilder.new(@xml)
133
192
  end
134
193
  end
135
-
136
- private
137
- def method_missing(method, *arguments, &block)
138
- @xml.__send__(method, *arguments, &block)
139
- end
140
194
  end
195
+
141
196
  end
142
197
  end
143
198
  end
@@ -15,15 +15,15 @@ module ActionView
15
15
  # <%= expensive_files_operation %>
16
16
  # <% end %>
17
17
  #
18
- # That would add something like "Process data files (0.34523)" to the log,
18
+ # That would add something like "Process data files (345.2ms)" to the log,
19
19
  # which you can then use to compare timings when optimizing your code.
20
20
  #
21
21
  # You may give an optional logger level as the second argument
22
22
  # (:debug, :info, :warn, :error); the default value is :info.
23
23
  def benchmark(message = "Benchmarking", level = :info)
24
24
  if controller.logger
25
- real = Benchmark.realtime { yield }
26
- controller.logger.send(level, "#{message} (#{'%.5f' % real})")
25
+ seconds = Benchmark.realtime { yield }
26
+ controller.logger.send(level, "#{message} (#{'%.1f' % (seconds * 1000)}ms)")
27
27
  else
28
28
  yield
29
29
  end
@@ -32,8 +32,7 @@ module ActionView
32
32
  # <i>Topics listed alphabetically</i>
33
33
  # <% end %>
34
34
  def cache(name = {}, options = nil, &block)
35
- handler = Template.handler_class_for_extension(current_render_extension.to_sym)
36
- handler.new(@controller).cache_fragment(block, name, options)
35
+ @controller.fragment_for(output_buffer, name, options, &block)
37
36
  end
38
37
  end
39
38
  end
@@ -31,20 +31,15 @@ module ActionView
31
31
  # </body></html>
32
32
  #
33
33
  def capture(*args, &block)
34
- # execute the block
35
- begin
36
- buffer = eval(ActionView::Base.erb_variable, block.binding)
37
- rescue
38
- buffer = nil
39
- end
40
-
41
- if buffer.nil?
42
- capture_block(*args, &block).to_s
34
+ # Return captured buffer in erb.
35
+ if block_called_from_erb?(block)
36
+ with_output_buffer { block.call(*args) }
43
37
  else
44
- capture_erb_with_buffer(buffer, *args, &block).to_s
38
+ # Return block result otherwise, but protect buffer also.
39
+ with_output_buffer { return block.call(*args) }
45
40
  end
46
41
  end
47
-
42
+
48
43
  # Calling content_for stores a block of markup in an identifier for later use.
49
44
  # You can make subsequent calls to the stored content in other templates or the layout
50
45
  # by passing the identifier as an argument to <tt>yield</tt>.
@@ -121,41 +116,21 @@ module ActionView
121
116
  # named <tt>@content_for_#{name_of_the_content_block}</tt>. The preferred usage is now
122
117
  # <tt><%= yield :footer %></tt>.
123
118
  def content_for(name, content = nil, &block)
124
- existing_content_for = instance_variable_get("@content_for_#{name}").to_s
125
- new_content_for = existing_content_for + (block_given? ? capture(&block) : content)
126
- instance_variable_set("@content_for_#{name}", new_content_for)
119
+ ivar = "@content_for_#{name}"
120
+ content = capture(&block) if block_given?
121
+ instance_variable_set(ivar, "#{instance_variable_get(ivar)}#{content}")
122
+ nil
127
123
  end
128
124
 
129
- private
130
- def capture_block(*args, &block)
131
- block.call(*args)
132
- end
133
-
134
- def capture_erb(*args, &block)
135
- buffer = eval(ActionView::Base.erb_variable, block.binding)
136
- capture_erb_with_buffer(buffer, *args, &block)
137
- end
138
-
139
- def capture_erb_with_buffer(buffer, *args, &block)
140
- pos = buffer.length
141
- block.call(*args)
142
-
143
- # extract the block
144
- data = buffer[pos..-1]
145
-
146
- # replace it in the original with empty string
147
- buffer[pos..-1] = ''
148
-
149
- data
150
- end
151
-
152
- def erb_content_for(name, &block)
153
- eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_erb(&block)"
154
- end
155
-
156
- def block_content_for(name, &block)
157
- eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_block(&block)"
158
- end
125
+ # Use an alternate output buffer for the duration of the block.
126
+ # Defaults to a new empty string.
127
+ def with_output_buffer(buf = '') #:nodoc:
128
+ self.output_buffer, old_buffer = buf, output_buffer
129
+ yield
130
+ output_buffer
131
+ ensure
132
+ self.output_buffer = old_buffer
133
+ end
159
134
  end
160
135
  end
161
136
  end
@@ -3,18 +3,16 @@ require 'action_view/helpers/tag_helper'
3
3
 
4
4
  module ActionView
5
5
  module Helpers
6
- # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the select-type methods
7
- # share a number of common options that are as follows:
6
+ # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the
7
+ # select-type methods share a number of common options that are as follows:
8
8
  #
9
- # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" would give
10
- # birthday[month] instead of date[month] if passed to the select_month method.
9
+ # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday"
10
+ # would give birthday[month] instead of date[month] if passed to the select_month method.
11
11
  # * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date.
12
- # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, the select_month
13
- # method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of "date[month]".
12
+ # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true,
13
+ # the select_month method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of
14
+ # "date[month]".
14
15
  module DateHelper
15
- include ActionView::Helpers::TagHelper
16
- DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX')
17
-
18
16
  # Reports the approximate distance in time between two Time or Date objects or integers as seconds.
19
17
  # Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs
20
18
  # Distances are reported based on the following table:
@@ -51,40 +49,45 @@ module ActionView
51
49
  # distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
52
50
  # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
53
51
  # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
54
- # distance_of_time_in_words(from_time, from_time + 4.years + 15.days + 30.minutes + 5.seconds) # => over 4 years
52
+ # distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => over 4 years
55
53
  #
56
54
  # to_time = Time.now + 6.years + 19.days
57
55
  # distance_of_time_in_words(from_time, to_time, true) # => over 6 years
58
56
  # distance_of_time_in_words(to_time, from_time, true) # => over 6 years
59
57
  # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
60
58
  #
61
- def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
59
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
62
60
  from_time = from_time.to_time if from_time.respond_to?(:to_time)
63
61
  to_time = to_time.to_time if to_time.respond_to?(:to_time)
64
62
  distance_in_minutes = (((to_time - from_time).abs)/60).round
65
63
  distance_in_seconds = ((to_time - from_time).abs).round
66
64
 
67
- case distance_in_minutes
68
- when 0..1
69
- return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
70
- case distance_in_seconds
71
- when 0..4 then 'less than 5 seconds'
72
- when 5..9 then 'less than 10 seconds'
73
- when 10..19 then 'less than 20 seconds'
74
- when 20..39 then 'half a minute'
75
- when 40..59 then 'less than a minute'
76
- else '1 minute'
77
- end
78
-
79
- when 2..44 then "#{distance_in_minutes} minutes"
80
- when 45..89 then 'about 1 hour'
81
- when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
82
- when 1440..2879 then '1 day'
83
- when 2880..43199 then "#{(distance_in_minutes / 1440).round} days"
84
- when 43200..86399 then 'about 1 month'
85
- when 86400..525599 then "#{(distance_in_minutes / 43200).round} months"
86
- when 525600..1051199 then 'about 1 year'
87
- else "over #{(distance_in_minutes / 525600).round} years"
65
+ I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
66
+ case distance_in_minutes
67
+ when 0..1
68
+ return distance_in_minutes == 0 ?
69
+ locale.t(:less_than_x_minutes, :count => 1) :
70
+ locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds
71
+
72
+ case distance_in_seconds
73
+ when 0..4 then locale.t :less_than_x_seconds, :count => 5
74
+ when 5..9 then locale.t :less_than_x_seconds, :count => 10
75
+ when 10..19 then locale.t :less_than_x_seconds, :count => 20
76
+ when 20..39 then locale.t :half_a_minute
77
+ when 40..59 then locale.t :less_than_x_minutes, :count => 1
78
+ else locale.t :x_minutes, :count => 1
79
+ end
80
+
81
+ when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
82
+ when 45..89 then locale.t :about_x_hours, :count => 1
83
+ when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
84
+ when 1440..2879 then locale.t :x_days, :count => 1
85
+ when 2880..43199 then locale.t :x_days, :count => (distance_in_minutes / 1440).round
86
+ when 43200..86399 then locale.t :about_x_months, :count => 1
87
+ when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes / 43200).round
88
+ when 525600..1051199 then locale.t :about_x_years, :count => 1
89
+ else locale.t :over_x_years, :count => (distance_in_minutes / 525600).round
90
+ end
88
91
  end
89
92
  end
90
93
 
@@ -102,17 +105,37 @@ module ActionView
102
105
 
103
106
  alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
104
107
 
105
- # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by
106
- # +method+) on an object assigned to the template (identified by +object+). It's possible to tailor the selects through the +options+ hash,
107
- # which accepts all the keys that each of the individual select builders do (like <tt>:use_month_numbers</tt> for select_month) as well as a range of
108
- # discard options. The discard options are <tt>:discard_year</tt>, <tt>:discard_month</tt> and <tt>:discard_day</tt>. Set to true, they'll
109
- # drop the respective select. Discarding the month select will also automatically discard the day select. It's also possible to explicitly
110
- # set the order of the tags using the <tt>:order</tt> option with an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in
111
- # the desired order. Symbols may be omitted and the respective select is not included.
112
- #
113
- # Pass the <tt>:default</tt> option to set the default date. Use a Time object or a Hash of <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, and <tt>:second</tt>.
114
- #
115
- # Passing <tt>:disabled => true</tt> as part of the +options+ will make elements inaccessible for change.
108
+ # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based
109
+ # attribute (identified by +method+) on an object assigned to the template (identified by +object+). You can
110
+ # the output in the +options+ hash.
111
+ #
112
+ # ==== Options
113
+ # * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g.
114
+ # "2" instead of "February").
115
+ # * <tt>:use_short_month</tt> - Set to true if you want to use the abbreviated month name instead of the full
116
+ # name (e.g. "Feb" instead of "February").
117
+ # * <tt>:add_month_number</tt> - Set to true if you want to show both, the month's number and name (e.g.
118
+ # "2 - February" instead of "February").
119
+ # * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names.
120
+ # Note: You can also use Rails' new i18n functionality for this.
121
+ # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
122
+ # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Time.now.year - 5</tt>.
123
+ # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Time.now.year + 5</tt>.
124
+ # * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
125
+ # as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
126
+ # first of the given month in order to not create invalid dates like 31 February.
127
+ # * <tt>:discard_month</tt> - Set to true if you don't want to show a month select. This includes the month
128
+ # as a hidden field instead of showing a select field. Also note that this implicitly sets :discard_day to true.
129
+ # * <tt>:discard_year</tt> - Set to true if you don't want to show a year select. This includes the year
130
+ # as a hidden field instead of showing a select field.
131
+ # * <tt>:order</tt> - Set to an array containing <tt>:day</tt>, <tt>:month</tt> and <tt>:year</tt> do
132
+ # customize the order in which the select fields are shown. If you leave out any of the symbols, the respective
133
+ # select will not be shown (like when you set <tt>:discard_xxx => true</tt>. Defaults to the order defined in
134
+ # the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails).
135
+ # * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty
136
+ # dates.
137
+ # * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil.
138
+ # * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled.
116
139
  #
117
140
  # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
118
141
  #
@@ -128,7 +151,7 @@ module ActionView
128
151
  #
129
152
  # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute,
130
153
  # # with the year in the year drop down box starting at 1995, numbers used for months instead of words,
131
- # # and without a day select box.
154
+ # # and without a day select box.
132
155
  # date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true,
133
156
  # :discard_day => true, :include_blank => true)
134
157
  #
@@ -150,34 +173,38 @@ module ActionView
150
173
  #
151
174
  # The selects are prepared for multi-parameter assignment to an Active Record object.
152
175
  #
153
- # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
154
- # choices are valid.
176
+ # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
177
+ # all month choices are valid.
155
178
  def date_select(object_name, method, options = {}, html_options = {})
156
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options, html_options)
179
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
157
180
  end
158
181
 
159
- # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified
160
- # time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+).
161
- # You can include the seconds with <tt>:include_seconds</tt>.
162
- #
182
+ # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
183
+ # specified time-based attribute (identified by +method+) on an object assigned to the template (identified by
184
+ # +object+). You can include the seconds with <tt>:include_seconds</tt>.
185
+ #
186
+ # This method will also generate 3 input hidden tags, for the actual year, month and day unless the option
187
+ # <tt>:ignore_date</tt> is set to +true+.
188
+ #
163
189
  # If anything is passed in the html_options hash it will be applied to every select tag in the set.
164
190
  #
165
191
  # ==== Examples
166
192
  # # Creates a time select tag that, when POSTed, will be stored in the post variable in the sunrise attribute
167
193
  # time_select("post", "sunrise")
168
194
  #
169
- # # Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted attribute
195
+ # # Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted
196
+ # # attribute
170
197
  # time_select("order", "submitted")
171
198
  #
172
199
  # # Creates a time select tag that, when POSTed, will be stored in the mail variable in the sent_at attribute
173
200
  # time_select("mail", "sent_at")
174
201
  #
175
- # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in
176
- # # the sunrise attribute.
202
+ # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in
203
+ # # the sunrise attribute.
177
204
  # time_select("post", "start_time", :include_seconds => true)
178
205
  #
179
- # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in
180
- # # the submission_time attribute.
206
+ # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in
207
+ # # the submission_time attribute.
181
208
  # time_select("entry", "submission_time", :include_seconds => true)
182
209
  #
183
210
  # # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45.
@@ -185,43 +212,46 @@ module ActionView
185
212
  #
186
213
  # The selects are prepared for multi-parameter assignment to an Active Record object.
187
214
  #
188
- # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
189
- # choices are valid.
215
+ # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
216
+ # all month choices are valid.
190
217
  def time_select(object_name, method, options = {}, html_options = {})
191
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_time_select_tag(options, html_options)
218
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options)
192
219
  end
193
220
 
194
- # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
195
- # attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples:
221
+ # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a
222
+ # specified datetime-based attribute (identified by +method+) on an object assigned to the template (identified
223
+ # by +object+). Examples:
196
224
  #
197
225
  # If anything is passed in the html_options hash it will be applied to every select tag in the set.
198
226
  #
199
227
  # ==== Examples
200
- # # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on attribute
228
+ # # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on
229
+ # # attribute
201
230
  # datetime_select("post", "written_on")
202
231
  #
203
- # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
232
+ # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
204
233
  # # post variable in the written_on attribute.
205
234
  # datetime_select("post", "written_on", :start_year => 1995)
206
235
  #
207
- # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will be stored in the
208
- # # trip variable in the departing attribute.
236
+ # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will
237
+ # # be stored in the trip variable in the departing attribute.
209
238
  # datetime_select("trip", "departing", :default => 3.days.from_now)
210
239
  #
211
- # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable as the written_on
212
- # # attribute.
240
+ # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable
241
+ # # as the written_on attribute.
213
242
  # datetime_select("post", "written_on", :discard_type => true)
214
243
  #
215
244
  # The selects are prepared for multi-parameter assignment to an Active Record object.
216
245
  def datetime_select(object_name, method, options = {}, html_options = {})
217
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options, html_options)
246
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
218
247
  end
219
248
 
220
- # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
221
- # It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
222
- # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
223
- # will be appended onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt> and <tt>:time_separator</tt>
224
- # keys to the +options+ to control visual display of the elements.
249
+ # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the
250
+ # +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with
251
+ # an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not
252
+ # supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add
253
+ # <tt>:date_separator</tt>, <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to
254
+ # control visual display of the elements.
225
255
  #
226
256
  # If anything is passed in the html_options hash it will be applied to every select tag in the set.
227
257
  #
@@ -242,7 +272,12 @@ module ActionView
242
272
  # # with a '/' between each date field.
243
273
  # select_datetime(my_date_time, :date_separator => '/')
244
274
  #
245
- # # Generates a datetime select that discards the type of the field and defaults to the datetime in
275
+ # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today)
276
+ # # with a date fields separated by '/', time fields separated by '' and the date and time fields
277
+ # # separated by a comma (',').
278
+ # select_datetime(my_date_time, :date_separator => '/', :time_separator => '', :datetime_separator => ',')
279
+ #
280
+ # # Generates a datetime select that discards the type of the field and defaults to the datetime in
246
281
  # # my_date_time (four days after today)
247
282
  # select_datetime(my_date_time, :discard_type => true)
248
283
  #
@@ -251,14 +286,13 @@ module ActionView
251
286
  # select_datetime(my_date_time, :prefix => 'payday')
252
287
  #
253
288
  def select_datetime(datetime = Time.current, options = {}, html_options = {})
254
- separator = options[:datetime_separator] || ''
255
- select_date(datetime, options, html_options) + separator + select_time(datetime, options, html_options)
256
- end
289
+ DateTimeSelector.new(datetime, options, html_options).select_datetime
290
+ end
257
291
 
258
292
  # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
259
293
  # It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
260
- # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
261
- # will be appended onto the <tt>:order</tt> passed in.
294
+ # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol,
295
+ # it will be appended onto the <tt>:order</tt> passed in.
262
296
  #
263
297
  # If anything is passed in the html_options hash it will be applied to every select tag in the set.
264
298
  #
@@ -275,27 +309,24 @@ module ActionView
275
309
  # # with the fields ordered year, month, day rather than month, day, year.
276
310
  # select_date(my_date, :order => [:year, :month, :day])
277
311
  #
278
- # # Generates a date select that discards the type of the field and defaults to the date in
312
+ # # Generates a date select that discards the type of the field and defaults to the date in
279
313
  # # my_date (six days after today)
280
- # select_datetime(my_date_time, :discard_type => true)
314
+ # select_date(my_date, :discard_type => true)
315
+ #
316
+ # # Generates a date select that defaults to the date in my_date,
317
+ # # which has fields separated by '/'
318
+ # select_date(my_date, :date_separator => '/')
281
319
  #
282
320
  # # Generates a date select that defaults to the datetime in my_date (six days after today)
283
321
  # # prefixed with 'payday' rather than 'date'
284
- # select_datetime(my_date_time, :prefix => 'payday')
322
+ # select_date(my_date, :prefix => 'payday')
285
323
  #
286
324
  def select_date(date = Date.current, options = {}, html_options = {})
287
- options[:order] ||= []
288
- [:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) }
289
-
290
- select_date = ''
291
- options[:order].each do |o|
292
- select_date << self.send("select_#{o}", date, options, html_options)
293
- end
294
- select_date
325
+ DateTimeSelector.new(date, options, html_options).select_date
295
326
  end
296
327
 
297
328
  # Returns a set of html select-tags (one for hour and minute)
298
- # You can set <tt>:time_separator</tt> key to format the output, and
329
+ # You can set <tt>:time_separator</tt> key to format the output, and
299
330
  # the <tt>:include_seconds</tt> option to include an input for seconds.
300
331
  #
301
332
  # If anything is passed in the html_options hash it will be applied to every select tag in the set.
@@ -310,7 +341,7 @@ module ActionView
310
341
  # select_time()
311
342
  #
312
343
  # # Generates a time select that defaults to the time in my_time,
313
- # # which has fields separated by ':'
344
+ # # which has fields separated by ':'
314
345
  # select_time(my_time, :time_separator => ':')
315
346
  #
316
347
  # # Generates a time select that defaults to the time in my_time,
@@ -322,8 +353,7 @@ module ActionView
322
353
  # select_time(my_time, :time_separator => ':', :include_seconds => true)
323
354
  #
324
355
  def select_time(datetime = Time.current, options = {}, html_options = {})
325
- separator = options[:time_separator] || ''
326
- select_hour(datetime, options, html_options) + separator + select_minute(datetime, options, html_options) + (options[:include_seconds] ? separator + select_second(datetime, options, html_options) : '')
356
+ DateTimeSelector.new(datetime, options, html_options).select_time
327
357
  end
328
358
 
329
359
  # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
@@ -338,31 +368,18 @@ module ActionView
338
368
  #
339
369
  # # Generates a select field for seconds that defaults to the number given
340
370
  # select_second(33)
341
- #
371
+ #
342
372
  # # Generates a select field for seconds that defaults to the seconds for the time in my_time
343
373
  # # that is named 'interval' rather than 'second'
344
374
  # select_second(my_time, :field_name => 'interval')
345
375
  #
346
376
  def select_second(datetime, options = {}, html_options = {})
347
- val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : ''
348
- if options[:use_hidden]
349
- options[:include_seconds] ? hidden_html(options[:field_name] || 'second', val, options) : ''
350
- else
351
- second_options = []
352
- 0.upto(59) do |second|
353
- second_options << ((val == second) ?
354
- content_tag(:option, leading_zero_on_single_digits(second), :value => leading_zero_on_single_digits(second), :selected => "selected") :
355
- content_tag(:option, leading_zero_on_single_digits(second), :value => leading_zero_on_single_digits(second))
356
- )
357
- second_options << "\n"
358
- end
359
- select_html(options[:field_name] || 'second', second_options.join, options, html_options)
360
- end
377
+ DateTimeSelector.new(datetime, options, html_options).select_second
361
378
  end
362
379
 
363
380
  # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
364
- # Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute selected
365
- # The <tt>minute</tt> can also be substituted for a minute number.
381
+ # Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute
382
+ # selected. The <tt>minute</tt> can also be substituted for a minute number.
366
383
  # Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
367
384
  #
368
385
  # ==== Examples
@@ -373,26 +390,13 @@ module ActionView
373
390
  #
374
391
  # # Generates a select field for minutes that defaults to the number given
375
392
  # select_minute(14)
376
- #
393
+ #
377
394
  # # Generates a select field for minutes that defaults to the minutes for the time in my_time
378
395
  # # that is named 'stride' rather than 'second'
379
396
  # select_minute(my_time, :field_name => 'stride')
380
397
  #
381
398
  def select_minute(datetime, options = {}, html_options = {})
382
- val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : ''
383
- if options[:use_hidden]
384
- hidden_html(options[:field_name] || 'minute', val, options)
385
- else
386
- minute_options = []
387
- 0.step(59, options[:minute_step] || 1) do |minute|
388
- minute_options << ((val == minute) ?
389
- content_tag(:option, leading_zero_on_single_digits(minute), :value => leading_zero_on_single_digits(minute), :selected => "selected") :
390
- content_tag(:option, leading_zero_on_single_digits(minute), :value => leading_zero_on_single_digits(minute))
391
- )
392
- minute_options << "\n"
393
- end
394
- select_html(options[:field_name] || 'minute', minute_options.join, options, html_options)
395
- end
399
+ DateTimeSelector.new(datetime, options, html_options).select_minute
396
400
  end
397
401
 
398
402
  # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
@@ -402,31 +406,18 @@ module ActionView
402
406
  # ==== Examples
403
407
  # my_time = Time.now + 6.hours
404
408
  #
405
- # # Generates a select field for minutes that defaults to the minutes for the time in my_time
406
- # select_minute(my_time)
409
+ # # Generates a select field for hours that defaults to the hour for the time in my_time
410
+ # select_hour(my_time)
407
411
  #
408
- # # Generates a select field for minutes that defaults to the number given
409
- # select_minute(14)
410
- #
411
- # # Generates a select field for minutes that defaults to the minutes for the time in my_time
412
+ # # Generates a select field for hours that defaults to the number given
413
+ # select_hour(13)
414
+ #
415
+ # # Generates a select field for hours that defaults to the minutes for the time in my_time
412
416
  # # that is named 'stride' rather than 'second'
413
- # select_minute(my_time, :field_name => 'stride')
417
+ # select_hour(my_time, :field_name => 'stride')
414
418
  #
415
419
  def select_hour(datetime, options = {}, html_options = {})
416
- val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : ''
417
- if options[:use_hidden]
418
- hidden_html(options[:field_name] || 'hour', val, options)
419
- else
420
- hour_options = []
421
- 0.upto(23) do |hour|
422
- hour_options << ((val == hour) ?
423
- content_tag(:option, leading_zero_on_single_digits(hour), :value => leading_zero_on_single_digits(hour), :selected => "selected") :
424
- content_tag(:option, leading_zero_on_single_digits(hour), :value => leading_zero_on_single_digits(hour))
425
- )
426
- hour_options << "\n"
427
- end
428
- select_html(options[:field_name] || 'hour', hour_options.join, options, html_options)
429
- end
420
+ DateTimeSelector.new(datetime, options, html_options).select_hour
430
421
  end
431
422
 
432
423
  # Returns a select tag with options for each of the days 1 through 31 with the current day selected.
@@ -441,36 +432,23 @@ module ActionView
441
432
  #
442
433
  # # Generates a select field for days that defaults to the number given
443
434
  # select_day(5)
444
- #
435
+ #
445
436
  # # Generates a select field for days that defaults to the day for the date in my_date
446
437
  # # that is named 'due' rather than 'day'
447
438
  # select_day(my_time, :field_name => 'due')
448
439
  #
449
440
  def select_day(date, options = {}, html_options = {})
450
- val = date ? (date.kind_of?(Fixnum) ? date : date.day) : ''
451
- if options[:use_hidden]
452
- hidden_html(options[:field_name] || 'day', val, options)
453
- else
454
- day_options = []
455
- 1.upto(31) do |day|
456
- day_options << ((val == day) ?
457
- content_tag(:option, day, :value => day, :selected => "selected") :
458
- content_tag(:option, day, :value => day)
459
- )
460
- day_options << "\n"
461
- end
462
- select_html(options[:field_name] || 'day', day_options.join, options, html_options)
463
- end
441
+ DateTimeSelector.new(date, options, html_options).select_day
464
442
  end
465
443
 
466
- # Returns a select tag with options for each of the months January through December with the current month selected.
467
- # The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
468
- # (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
469
- # set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names,
470
- # set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer to show month names as abbreviations,
471
- # set the <tt>:use_short_month</tt> key in +options+ to true. If you want to use your own month names, set the
472
- # <tt>:use_month_names</tt> key in +options+ to an array of 12 month names. Override the field name using the
473
- # <tt>:field_name</tt> option, 'month' by default.
444
+ # Returns a select tag with options for each of the months January through December with the current month
445
+ # selected. The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are
446
+ # used as values (what's submitted to the server). It's also possible to use month numbers for the presentation
447
+ # instead of names -- set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you
448
+ # want both numbers and names, set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer
449
+ # to show month names as abbreviations, set the <tt>:use_short_month</tt> key in +options+ to true. If you want
450
+ # to use your own month names, set the <tt>:use_month_names</tt> key in +options+ to an array of 12 month names.
451
+ # Override the field name using the <tt>:field_name</tt> option, 'month' by default.
474
452
  #
475
453
  # ==== Examples
476
454
  # # Generates a select field for months that defaults to the current month that
@@ -482,7 +460,7 @@ module ActionView
482
460
  # select_month(Date.today, :field_name => 'start')
483
461
  #
484
462
  # # Generates a select field for months that defaults to the current month that
485
- # # will use keys like "1", "3".
463
+ # # will use keys like "1", "3".
486
464
  # select_month(Date.today, :use_month_numbers => true)
487
465
  #
488
466
  # # Generates a select field for months that defaults to the current month that
@@ -498,36 +476,14 @@ module ActionView
498
476
  # select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
499
477
  #
500
478
  def select_month(date, options = {}, html_options = {})
501
- val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
502
- if options[:use_hidden]
503
- hidden_html(options[:field_name] || 'month', val, options)
504
- else
505
- month_options = []
506
- month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES)
507
- month_names.unshift(nil) if month_names.size < 13
508
- 1.upto(12) do |month_number|
509
- month_name = if options[:use_month_numbers]
510
- month_number
511
- elsif options[:add_month_numbers]
512
- month_number.to_s + ' - ' + month_names[month_number]
513
- else
514
- month_names[month_number]
515
- end
516
-
517
- month_options << ((val == month_number) ?
518
- content_tag(:option, month_name, :value => month_number, :selected => "selected") :
519
- content_tag(:option, month_name, :value => month_number)
520
- )
521
- month_options << "\n"
522
- end
523
- select_html(options[:field_name] || 'month', month_options.join, options, html_options)
524
- end
479
+ DateTimeSelector.new(date, options, html_options).select_month
525
480
  end
526
481
 
527
- # 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
528
- # can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the +options+. Both ascending and descending year
529
- # lists are supported by making <tt>:start_year</tt> less than or greater than <tt>:end_year</tt>. The <tt>date</tt> can also be
530
- # substituted for a year given as a number. Override the field name using the <tt>:field_name</tt> option, 'year' by default.
482
+ # Returns a select tag with options for each of the five years on each side of the current, which is selected.
483
+ # The five year radius can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the
484
+ # +options+. Both ascending and descending year lists are supported by making <tt>:start_year</tt> less than or
485
+ # greater than <tt>:end_year</tt>. The <tt>date</tt> can also be substituted for a year given as a number.
486
+ # Override the field name using the <tt>:field_name</tt> option, 'year' by default.
531
487
  #
532
488
  # ==== Examples
533
489
  # # Generates a select field for years that defaults to the current year that
@@ -547,150 +503,384 @@ module ActionView
547
503
  # select_year(2006, :start_year => 2000, :end_year => 2010)
548
504
  #
549
505
  def select_year(date, options = {}, html_options = {})
550
- val = date ? (date.kind_of?(Fixnum) ? date : date.year) : ''
551
- if options[:use_hidden]
552
- hidden_html(options[:field_name] || 'year', val, options)
553
- else
554
- year_options = []
555
- y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
556
-
557
- start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
558
- step_val = start_year < end_year ? 1 : -1
559
- start_year.step(end_year, step_val) do |year|
560
- year_options << ((val == year) ?
561
- content_tag(:option, year, :value => year, :selected => "selected") :
562
- content_tag(:option, year, :value => year)
563
- )
564
- year_options << "\n"
506
+ DateTimeSelector.new(date, options, html_options).select_year
507
+ end
508
+ end
509
+
510
+ class DateTimeSelector #:nodoc:
511
+ extend ActiveSupport::Memoizable
512
+ include ActionView::Helpers::TagHelper
513
+
514
+ DEFAULT_PREFIX = 'date'.freeze unless const_defined?('DEFAULT_PREFIX')
515
+ POSITION = {
516
+ :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6
517
+ }.freeze unless const_defined?('POSITION')
518
+
519
+ def initialize(datetime, options = {}, html_options = {})
520
+ @options = options.dup
521
+ @html_options = html_options.dup
522
+ @datetime = datetime
523
+ end
524
+
525
+ def select_datetime
526
+ # TODO: Remove tag conditional
527
+ # Ideally we could just join select_date and select_date for the tag case
528
+ if @options[:tag] && @options[:ignore_date]
529
+ select_time
530
+ elsif @options[:tag]
531
+ order = date_order.dup
532
+ order -= [:hour, :minute, :second]
533
+
534
+ @options[:discard_year] ||= true unless order.include?(:year)
535
+ @options[:discard_month] ||= true unless order.include?(:month)
536
+ @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
537
+ @options[:discard_minute] ||= true if @options[:discard_hour]
538
+ @options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute]
539
+
540
+ # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
541
+ # valid (otherwise it could be 31 and february wouldn't be a valid date)
542
+ if @datetime && @options[:discard_day] && !@options[:discard_month]
543
+ @datetime = @datetime.change(:day => 1)
565
544
  end
566
- select_html(options[:field_name] || 'year', year_options.join, options, html_options)
545
+
546
+ [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
547
+ order += [:hour, :minute, :second] unless @options[:discard_hour]
548
+
549
+ build_selects_from_types(order)
550
+ else
551
+ "#{select_date}#{@options[:datetime_separator]}#{select_time}"
567
552
  end
568
553
  end
569
554
 
570
- private
555
+ def select_date
556
+ order = date_order.dup
571
557
 
572
- def select_html(type, html_options, options, select_tag_options = {})
573
- name_and_id_from_options(options, type)
574
- select_options = {:id => options[:id], :name => options[:name]}
575
- select_options.merge!(:disabled => 'disabled') if options[:disabled]
576
- select_options.merge!(select_tag_options) unless select_tag_options.empty?
577
- select_html = "\n"
578
- select_html << content_tag(:option, '', :value => '') + "\n" if options[:include_blank]
579
- select_html << html_options.to_s
580
- content_tag(:select, select_html, select_options) + "\n"
558
+ # TODO: Remove tag conditional
559
+ if @options[:tag]
560
+ @options[:discard_hour] = true
561
+ @options[:discard_minute] = true
562
+ @options[:discard_second] = true
563
+
564
+ @options[:discard_year] ||= true unless order.include?(:year)
565
+ @options[:discard_month] ||= true unless order.include?(:month)
566
+ @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
567
+
568
+ # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
569
+ # valid (otherwise it could be 31 and february wouldn't be a valid date)
570
+ if @datetime && @options[:discard_day] && !@options[:discard_month]
571
+ @datetime = @datetime.change(:day => 1)
572
+ end
581
573
  end
582
574
 
583
- def hidden_html(type, value, options)
584
- name_and_id_from_options(options, type)
585
- hidden_html = tag(:input, :type => "hidden", :id => options[:id], :name => options[:name], :value => value) + "\n"
575
+ [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
576
+
577
+ build_selects_from_types(order)
578
+ end
579
+
580
+ def select_time
581
+ order = []
582
+
583
+ # TODO: Remove tag conditional
584
+ if @options[:tag]
585
+ @options[:discard_month] = true
586
+ @options[:discard_year] = true
587
+ @options[:discard_day] = true
588
+ @options[:discard_second] ||= true unless @options[:include_seconds]
589
+
590
+ order += [:year, :month, :day] unless @options[:ignore_date]
586
591
  end
587
592
 
588
- def name_and_id_from_options(options, type)
589
- options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
590
- options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
593
+ order += [:hour, :minute]
594
+ order << :second if @options[:include_seconds]
595
+
596
+ build_selects_from_types(order)
597
+ end
598
+
599
+ def select_second
600
+ if @options[:use_hidden] || @options[:discard_second]
601
+ build_hidden(:second, sec) if @options[:include_seconds]
602
+ else
603
+ build_options_and_select(:second, sec)
591
604
  end
605
+ end
592
606
 
593
- def leading_zero_on_single_digits(number)
594
- number > 9 ? number : "0#{number}"
607
+ def select_minute
608
+ if @options[:use_hidden] || @options[:discard_minute]
609
+ build_hidden(:minute, min)
610
+ else
611
+ build_options_and_select(:minute, min, :step => @options[:minute_step])
595
612
  end
596
- end
613
+ end
597
614
 
598
- class InstanceTag #:nodoc:
599
- include DateHelper
615
+ def select_hour
616
+ if @options[:use_hidden] || @options[:discard_hour]
617
+ build_hidden(:hour, hour)
618
+ else
619
+ build_options_and_select(:hour, hour, :end => 23)
620
+ end
621
+ end
600
622
 
601
- def to_date_select_tag(options = {}, html_options = {})
602
- date_or_time_select(options.merge(:discard_hour => true), html_options)
623
+ def select_day
624
+ if @options[:use_hidden] || @options[:discard_day]
625
+ build_hidden(:day, day)
626
+ else
627
+ build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false)
628
+ end
603
629
  end
604
630
 
605
- def to_time_select_tag(options = {}, html_options = {})
606
- date_or_time_select(options.merge(:discard_year => true, :discard_month => true), html_options)
631
+ def select_month
632
+ if @options[:use_hidden] || @options[:discard_month]
633
+ build_hidden(:month, month)
634
+ else
635
+ month_options = []
636
+ 1.upto(12) do |month_number|
637
+ options = { :value => month_number }
638
+ options[:selected] = "selected" if month == month_number
639
+ month_options << content_tag(:option, month_name(month_number), options) + "\n"
640
+ end
641
+ build_select(:month, month_options.join)
642
+ end
607
643
  end
608
644
 
609
- def to_datetime_select_tag(options = {}, html_options = {})
610
- date_or_time_select(options, html_options)
645
+ def select_year
646
+ if !@datetime || @datetime == 0
647
+ val = ''
648
+ middle_year = Date.today.year
649
+ else
650
+ val = middle_year = year
651
+ end
652
+
653
+ if @options[:use_hidden] || @options[:discard_year]
654
+ build_hidden(:year, val)
655
+ else
656
+ options = {}
657
+ options[:start] = @options[:start_year] || middle_year - 5
658
+ options[:end] = @options[:end_year] || middle_year + 5
659
+ options[:step] = options[:start] < options[:end] ? 1 : -1
660
+ options[:leading_zeros] = false
661
+
662
+ build_options_and_select(:year, val, options)
663
+ end
611
664
  end
612
665
 
613
666
  private
614
- def date_or_time_select(options, html_options = {})
615
- defaults = { :discard_type => true }
616
- options = defaults.merge(options)
617
- datetime = value(object)
618
- datetime ||= default_time_from_options(options[:default]) unless options[:include_blank]
619
-
620
- position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
621
-
622
- order = (options[:order] ||= [:year, :month, :day])
623
-
624
- # Discard explicit and implicit by not being included in the :order
625
- discard = {}
626
- discard[:year] = true if options[:discard_year] or !order.include?(:year)
627
- discard[:month] = true if options[:discard_month] or !order.include?(:month)
628
- discard[:day] = true if options[:discard_day] or discard[:month] or !order.include?(:day)
629
- discard[:hour] = true if options[:discard_hour]
630
- discard[:minute] = true if options[:discard_minute] or discard[:hour]
631
- discard[:second] = true unless options[:include_seconds] && !discard[:minute]
632
-
633
- # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are valid
634
- # (otherwise it could be 31 and february wouldn't be a valid date)
635
- if datetime && discard[:day] && !discard[:month]
636
- datetime = datetime.change(:day => 1)
667
+ %w( sec min hour day month year ).each do |method|
668
+ define_method(method) do
669
+ @datetime.kind_of?(Fixnum) ? @datetime : @datetime.send(method) if @datetime
637
670
  end
671
+ end
638
672
 
639
- # Maintain valid dates by including hidden fields for discarded elements
640
- [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
673
+ # Returns translated month names, but also ensures that a custom month
674
+ # name array has a leading nil element
675
+ def month_names
676
+ month_names = @options[:use_month_names] || translated_month_names
677
+ month_names.unshift(nil) if month_names.size < 13
678
+ month_names
679
+ end
680
+ memoize :month_names
681
+
682
+ # Returns translated month names
683
+ # => [nil, "January", "February", "March",
684
+ # "April", "May", "June", "July",
685
+ # "August", "September", "October",
686
+ # "November", "December"]
687
+ #
688
+ # If :use_short_month option is set
689
+ # => [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
690
+ # "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
691
+ def translated_month_names
692
+ begin
693
+ key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
694
+ I18n.translate(key, :locale => @options[:locale])
695
+ end
696
+ end
697
+
698
+ # Lookup month name for number
699
+ # month_name(1) => "January"
700
+ #
701
+ # If :use_month_numbers option is passed
702
+ # month_name(1) => 1
703
+ #
704
+ # If :add_month_numbers option is passed
705
+ # month_name(1) => "1 - January"
706
+ def month_name(number)
707
+ if @options[:use_month_numbers]
708
+ number
709
+ elsif @options[:add_month_numbers]
710
+ "#{number} - #{month_names[number]}"
711
+ else
712
+ month_names[number]
713
+ end
714
+ end
715
+
716
+ def date_order
717
+ @options[:order] || translated_date_order
718
+ end
719
+ memoize :date_order
720
+
721
+ def translated_date_order
722
+ begin
723
+ I18n.translate(:'date.order', :locale => @options[:locale]) || []
724
+ end
725
+ end
726
+
727
+ # Build full select tag from date type and options
728
+ def build_options_and_select(type, selected, options = {})
729
+ build_select(type, build_options(selected, options))
730
+ end
731
+
732
+ # Build select option html from date value and options
733
+ # build_options(15, :start => 1, :end => 31)
734
+ # => "<option value="1">1</option>
735
+ # <option value=\"2\">2</option>
736
+ # <option value=\"3\">3</option>..."
737
+ def build_options(selected, options = {})
738
+ start = options.delete(:start) || 0
739
+ stop = options.delete(:end) || 59
740
+ step = options.delete(:step) || 1
741
+ leading_zeros = options.delete(:leading_zeros).nil? ? true : false
742
+
743
+ select_options = []
744
+ start.step(stop, step) do |i|
745
+ value = leading_zeros ? sprintf("%02d", i) : i
746
+ tag_options = { :value => value }
747
+ tag_options[:selected] = "selected" if selected == i
748
+ select_options << content_tag(:option, value, tag_options)
749
+ end
750
+ select_options.join("\n") + "\n"
751
+ end
752
+
753
+ # Builds select tag from date type and html select options
754
+ # build_select(:month, "<option value="1">January</option>...")
755
+ # => "<select id="post_written_on_2i" name="post[written_on(2i)]">
756
+ # <option value="1">January</option>...
757
+ # </select>"
758
+ def build_select(type, select_options_as_html)
759
+ select_options = {
760
+ :id => input_id_from_type(type),
761
+ :name => input_name_from_type(type)
762
+ }.merge(@html_options)
763
+ select_options.merge!(:disabled => 'disabled') if @options[:disabled]
764
+
765
+ select_html = "\n"
766
+ select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
767
+ select_html << select_options_as_html.to_s
641
768
 
642
- # Ensure proper ordering of :hour, :minute and :second
643
- [:hour, :minute, :second].each { |o| order.delete(o); order.push(o) }
769
+ content_tag(:select, select_html, select_options) + "\n"
770
+ end
644
771
 
645
- date_or_time_select = ''
646
- order.reverse.each do |param|
647
- # Send hidden fields for discarded elements once output has started
648
- # This ensures AR can reconstruct valid dates using ParseDate
649
- next if discard[param] && date_or_time_select.empty?
772
+ # Builds hidden input tag for date part and value
773
+ # build_hidden(:year, 2008)
774
+ # => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />"
775
+ def build_hidden(type, value)
776
+ tag(:input, {
777
+ :type => "hidden",
778
+ :id => input_id_from_type(type),
779
+ :name => input_name_from_type(type),
780
+ :value => value
781
+ }) + "\n"
782
+ end
650
783
 
651
- date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param])), html_options))
652
- date_or_time_select.insert(0,
653
- case param
654
- when :hour then (discard[:year] && discard[:day] ? "" : " &mdash; ")
655
- when :minute then " : "
656
- when :second then options[:include_seconds] ? " : " : ""
657
- else ""
658
- end)
784
+ # Returns the name attribute for the input tag
785
+ # => post[written_on(1i)]
786
+ def input_name_from_type(type)
787
+ prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
788
+ prefix += "[#{@options[:index]}]" if @options[:index]
659
789
 
790
+ field_name = @options[:field_name] || type
791
+ if @options[:include_position]
792
+ field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)"
660
793
  end
661
794
 
662
- date_or_time_select
795
+ @options[:discard_type] ? prefix : "#{prefix}[#{field_name}]"
796
+ end
797
+
798
+ # Returns the id attribute for the input tag
799
+ # => "post_written_on_1i"
800
+ def input_id_from_type(type)
801
+ input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
802
+ end
803
+
804
+ # Given an ordering of datetime components, create the selection html
805
+ # and join them with their appropriate seperators
806
+ def build_selects_from_types(order)
807
+ select = ''
808
+ order.reverse.each do |type|
809
+ separator = separator(type) unless type == order.first # don't add on last field
810
+ select.insert(0, separator.to_s + send("select_#{type}").to_s)
811
+ end
812
+ select
663
813
  end
664
814
 
665
- def options_with_prefix(position, options)
666
- prefix = "#{@object_name}"
667
- if options[:index]
668
- prefix << "[#{options[:index]}]"
669
- elsif @auto_index
670
- prefix << "[#{@auto_index}]"
815
+ # Returns the separator for a given datetime component
816
+ def separator(type)
817
+ case type
818
+ when :month, :day
819
+ @options[:date_separator]
820
+ when :hour
821
+ (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
822
+ when :minute
823
+ @options[:time_separator]
824
+ when :second
825
+ @options[:include_seconds] ? @options[:time_separator] : ""
671
826
  end
672
- options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]")
827
+ end
828
+ end
829
+
830
+ class InstanceTag #:nodoc:
831
+ def to_date_select_tag(options = {}, html_options = {})
832
+ datetime_selector(options, html_options).select_date
833
+ end
834
+
835
+ def to_time_select_tag(options = {}, html_options = {})
836
+ datetime_selector(options, html_options).select_time
837
+ end
838
+
839
+ def to_datetime_select_tag(options = {}, html_options = {})
840
+ datetime_selector(options, html_options).select_datetime
841
+ end
842
+
843
+ private
844
+ def datetime_selector(options, html_options)
845
+ datetime = value(object) || default_datetime(options)
846
+
847
+ options = options.dup
848
+ options[:field_name] = @method_name
849
+ options[:include_position] = true
850
+ options[:prefix] ||= @object_name
851
+ options[:index] ||= @auto_index
852
+ options[:datetime_separator] ||= ' &mdash; '
853
+ options[:time_separator] ||= ' : '
854
+
855
+ DateTimeSelector.new(datetime, options.merge(:tag => true), html_options)
673
856
  end
674
857
 
675
- def default_time_from_options(default)
676
- case default
858
+ def default_datetime(options)
859
+ return if options[:include_blank]
860
+
861
+ case options[:default]
677
862
  when nil
678
863
  Time.current
679
864
  when Date, Time
680
- default
865
+ options[:default]
681
866
  else
867
+ default = options[:default].dup
868
+
682
869
  # Rename :minute and :second to :min and :sec
683
870
  default[:min] ||= default[:minute]
684
871
  default[:sec] ||= default[:second]
685
872
 
686
873
  time = Time.current
687
-
874
+
688
875
  [:year, :month, :day, :hour, :min, :sec].each do |key|
689
876
  default[key] ||= time.send(key)
690
877
  end
691
878
 
692
- Time.utc_time(default[:year], default[:month], default[:day], default[:hour], default[:min], default[:sec])
693
- end
879
+ Time.utc_time(
880
+ default[:year], default[:month], default[:day],
881
+ default[:hour], default[:min], default[:sec]
882
+ )
883
+ end
694
884
  end
695
885
  end
696
886