actionview 4.2.11.1 → 6.0.4

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

Potentially problematic release.


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

Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +201 -192
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -8
  5. data/lib/action_view/base.rb +144 -37
  6. data/lib/action_view/buffers.rb +18 -1
  7. data/lib/action_view/cache_expiry.rb +53 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker.rb +54 -20
  10. data/lib/action_view/digestor.rb +88 -85
  11. data/lib/action_view/flows.rb +11 -12
  12. data/lib/action_view/gem_version.rb +6 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +16 -11
  14. data/lib/action_view/helpers/asset_tag_helper.rb +241 -82
  15. data/lib/action_view/helpers/asset_url_helper.rb +171 -67
  16. data/lib/action_view/helpers/atom_feed_helper.rb +19 -17
  17. data/lib/action_view/helpers/cache_helper.rb +112 -42
  18. data/lib/action_view/helpers/capture_helper.rb +20 -13
  19. data/lib/action_view/helpers/controller_helper.rb +15 -4
  20. data/lib/action_view/helpers/csp_helper.rb +26 -0
  21. data/lib/action_view/helpers/csrf_helper.rb +8 -6
  22. data/lib/action_view/helpers/date_helper.rb +230 -129
  23. data/lib/action_view/helpers/debug_helper.rb +7 -6
  24. data/lib/action_view/helpers/form_helper.rb +755 -129
  25. data/lib/action_view/helpers/form_options_helper.rb +130 -75
  26. data/lib/action_view/helpers/form_tag_helper.rb +116 -71
  27. data/lib/action_view/helpers/javascript_helper.rb +30 -14
  28. data/lib/action_view/helpers/number_helper.rb +84 -59
  29. data/lib/action_view/helpers/output_safety_helper.rb +36 -4
  30. data/lib/action_view/helpers/rendering_helper.rb +11 -8
  31. data/lib/action_view/helpers/sanitize_helper.rb +30 -31
  32. data/lib/action_view/helpers/tag_helper.rb +201 -75
  33. data/lib/action_view/helpers/tags/base.rb +138 -98
  34. data/lib/action_view/helpers/tags/check_box.rb +20 -19
  35. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
  37. data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
  38. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
  39. data/lib/action_view/helpers/tags/collection_select.rb +4 -2
  40. data/lib/action_view/helpers/tags/color_field.rb +4 -3
  41. data/lib/action_view/helpers/tags/date_field.rb +2 -1
  42. data/lib/action_view/helpers/tags/date_select.rb +37 -36
  43. data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +2 -1
  45. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  47. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  48. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  49. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/label.rb +3 -2
  51. data/lib/action_view/helpers/tags/month_field.rb +2 -1
  52. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  54. data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
  55. data/lib/action_view/helpers/tags/radio_button.rb +7 -6
  56. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +14 -9
  58. data/lib/action_view/helpers/tags/select.rb +11 -10
  59. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +4 -2
  61. data/lib/action_view/helpers/tags/text_field.rb +8 -8
  62. data/lib/action_view/helpers/tags/time_field.rb +2 -1
  63. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  65. data/lib/action_view/helpers/tags/translator.rb +15 -16
  66. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  67. data/lib/action_view/helpers/tags/week_field.rb +2 -1
  68. data/lib/action_view/helpers/tags.rb +3 -1
  69. data/lib/action_view/helpers/text_helper.rb +56 -38
  70. data/lib/action_view/helpers/translation_helper.rb +91 -47
  71. data/lib/action_view/helpers/url_helper.rb +160 -105
  72. data/lib/action_view/helpers.rb +5 -3
  73. data/lib/action_view/layouts.rb +65 -61
  74. data/lib/action_view/log_subscriber.rb +61 -10
  75. data/lib/action_view/lookup_context.rb +147 -89
  76. data/lib/action_view/model_naming.rb +3 -1
  77. data/lib/action_view/path_set.rb +28 -23
  78. data/lib/action_view/railtie.rb +62 -6
  79. data/lib/action_view/record_identifier.rb +53 -26
  80. data/lib/action_view/renderer/abstract_renderer.rb +71 -13
  81. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  82. data/lib/action_view/renderer/partial_renderer.rb +239 -225
  83. data/lib/action_view/renderer/renderer.rb +22 -8
  84. data/lib/action_view/renderer/streaming_template_renderer.rb +54 -54
  85. data/lib/action_view/renderer/template_renderer.rb +79 -73
  86. data/lib/action_view/rendering.rb +68 -44
  87. data/lib/action_view/routing_url_for.rb +33 -22
  88. data/lib/action_view/tasks/cache_digests.rake +25 -0
  89. data/lib/action_view/template/error.rb +44 -29
  90. data/lib/action_view/template/handlers/builder.rb +12 -13
  91. data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
  92. data/lib/action_view/template/handlers/erb.rb +24 -86
  93. data/lib/action_view/template/handlers/html.rb +11 -0
  94. data/lib/action_view/template/handlers/raw.rb +4 -4
  95. data/lib/action_view/template/handlers.rb +38 -8
  96. data/lib/action_view/template/html.rb +19 -10
  97. data/lib/action_view/template/inline.rb +22 -0
  98. data/lib/action_view/template/raw_file.rb +28 -0
  99. data/lib/action_view/template/resolver.rb +217 -193
  100. data/lib/action_view/template/sources/file.rb +17 -0
  101. data/lib/action_view/template/sources.rb +13 -0
  102. data/lib/action_view/template/text.rb +11 -10
  103. data/lib/action_view/template/types.rb +18 -18
  104. data/lib/action_view/template.rb +146 -90
  105. data/lib/action_view/test_case.rb +52 -32
  106. data/lib/action_view/testing/resolvers.rb +46 -34
  107. data/lib/action_view/unbound_template.rb +31 -0
  108. data/lib/action_view/version.rb +3 -1
  109. data/lib/action_view/view_paths.rb +48 -31
  110. data/lib/action_view.rb +11 -8
  111. data/lib/assets/compiled/rails-ujs.js +746 -0
  112. metadata +38 -29
  113. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  114. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,12 +1,15 @@
1
- require 'date'
2
- require 'action_view/helpers/tag_helper'
3
- require 'active_support/core_ext/array/extract_options'
4
- require 'active_support/core_ext/date/conversions'
5
- require 'active_support/core_ext/hash/slice'
6
- require 'active_support/core_ext/object/with_options'
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "action_view/helpers/tag_helper"
5
+ require "active_support/core_ext/array/extract_options"
6
+ require "active_support/core_ext/date/conversions"
7
+ require "active_support/core_ext/hash/slice"
8
+ require "active_support/core_ext/object/acts_like"
9
+ require "active_support/core_ext/object/with_options"
7
10
 
8
11
  module ActionView
9
- module Helpers
12
+ module Helpers #:nodoc:
10
13
  # = Action View Date Helpers
11
14
  #
12
15
  # The Date Helper primarily creates select/option tags for different kinds of dates and times or date and time
@@ -68,71 +71,88 @@ module ActionView
68
71
  # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years
69
72
  # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years
70
73
  # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
74
+ #
75
+ # With the <tt>scope</tt> option, you can define a custom scope for Rails
76
+ # to look up the translation.
77
+ #
78
+ # For example you can define the following in your locale (e.g. en.yml).
79
+ #
80
+ # datetime:
81
+ # distance_in_words:
82
+ # short:
83
+ # about_x_hours:
84
+ # one: 'an hour'
85
+ # other: '%{count} hours'
86
+ #
87
+ # See https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en.yml
88
+ # for more examples.
89
+ #
90
+ # Which will then result in the following:
91
+ #
92
+ # from_time = Time.now
93
+ # distance_of_time_in_words(from_time, from_time + 50.minutes, scope: 'datetime.distance_in_words.short') # => "an hour"
94
+ # distance_of_time_in_words(from_time, from_time + 3.hours, scope: 'datetime.distance_in_words.short') # => "3 hours"
71
95
  def distance_of_time_in_words(from_time, to_time = 0, options = {})
72
96
  options = {
73
97
  scope: :'datetime.distance_in_words'
74
98
  }.merge!(options)
75
99
 
76
- from_time = from_time.to_time if from_time.respond_to?(:to_time)
77
- to_time = to_time.to_time if to_time.respond_to?(:to_time)
100
+ from_time = normalize_distance_of_time_argument_to_time(from_time)
101
+ to_time = normalize_distance_of_time_argument_to_time(to_time)
78
102
  from_time, to_time = to_time, from_time if from_time > to_time
79
- distance_in_minutes = ((to_time - from_time)/60.0).round
103
+ distance_in_minutes = ((to_time - from_time) / 60.0).round
80
104
  distance_in_seconds = (to_time - from_time).round
81
105
 
82
- I18n.with_options :locale => options[:locale], :scope => options[:scope] do |locale|
106
+ I18n.with_options locale: options[:locale], scope: options[:scope] do |locale|
83
107
  case distance_in_minutes
84
- when 0..1
85
- return distance_in_minutes == 0 ?
86
- locale.t(:less_than_x_minutes, :count => 1) :
87
- locale.t(:x_minutes, :count => distance_in_minutes) unless options[:include_seconds]
88
-
89
- case distance_in_seconds
90
- when 0..4 then locale.t :less_than_x_seconds, :count => 5
91
- when 5..9 then locale.t :less_than_x_seconds, :count => 10
92
- when 10..19 then locale.t :less_than_x_seconds, :count => 20
93
- when 20..39 then locale.t :half_a_minute
94
- when 40..59 then locale.t :less_than_x_minutes, :count => 1
95
- else locale.t :x_minutes, :count => 1
96
- end
97
-
98
- when 2...45 then locale.t :x_minutes, :count => distance_in_minutes
99
- when 45...90 then locale.t :about_x_hours, :count => 1
108
+ when 0..1
109
+ return distance_in_minutes == 0 ?
110
+ locale.t(:less_than_x_minutes, count: 1) :
111
+ locale.t(:x_minutes, count: distance_in_minutes) unless options[:include_seconds]
112
+
113
+ case distance_in_seconds
114
+ when 0..4 then locale.t :less_than_x_seconds, count: 5
115
+ when 5..9 then locale.t :less_than_x_seconds, count: 10
116
+ when 10..19 then locale.t :less_than_x_seconds, count: 20
117
+ when 20..39 then locale.t :half_a_minute
118
+ when 40..59 then locale.t :less_than_x_minutes, count: 1
119
+ else locale.t :x_minutes, count: 1
120
+ end
121
+
122
+ when 2...45 then locale.t :x_minutes, count: distance_in_minutes
123
+ when 45...90 then locale.t :about_x_hours, count: 1
100
124
  # 90 mins up to 24 hours
101
- when 90...1440 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
125
+ when 90...1440 then locale.t :about_x_hours, count: (distance_in_minutes.to_f / 60.0).round
102
126
  # 24 hours up to 42 hours
103
- when 1440...2520 then locale.t :x_days, :count => 1
127
+ when 1440...2520 then locale.t :x_days, count: 1
104
128
  # 42 hours up to 30 days
105
- when 2520...43200 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
129
+ when 2520...43200 then locale.t :x_days, count: (distance_in_minutes.to_f / 1440.0).round
106
130
  # 30 days up to 60 days
107
- when 43200...86400 then locale.t :about_x_months, :count => (distance_in_minutes.to_f / 43200.0).round
131
+ when 43200...86400 then locale.t :about_x_months, count: (distance_in_minutes.to_f / 43200.0).round
108
132
  # 60 days up to 365 days
109
- when 86400...525600 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
133
+ when 86400...525600 then locale.t :x_months, count: (distance_in_minutes.to_f / 43200.0).round
134
+ else
135
+ from_year = from_time.year
136
+ from_year += 1 if from_time.month >= 3
137
+ to_year = to_time.year
138
+ to_year -= 1 if to_time.month < 3
139
+ leap_years = (from_year > to_year) ? 0 : (from_year..to_year).count { |x| Date.leap?(x) }
140
+ minute_offset_for_leap_year = leap_years * 1440
141
+ # Discount the leap year days when calculating year distance.
142
+ # e.g. if there are 20 leap year days between 2 dates having the same day
143
+ # and month then the based on 365 days calculation
144
+ # the distance in years will come out to over 80 years when in written
145
+ # English it would read better as about 80 years.
146
+ minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
147
+ remainder = (minutes_with_offset % MINUTES_IN_YEAR)
148
+ distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR)
149
+ if remainder < MINUTES_IN_QUARTER_YEAR
150
+ locale.t(:about_x_years, count: distance_in_years)
151
+ elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR
152
+ locale.t(:over_x_years, count: distance_in_years)
110
153
  else
111
- if from_time.acts_like?(:time) && to_time.acts_like?(:time)
112
- fyear = from_time.year
113
- fyear += 1 if from_time.month >= 3
114
- tyear = to_time.year
115
- tyear -= 1 if to_time.month < 3
116
- leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
117
- minute_offset_for_leap_year = leap_years * 1440
118
- # Discount the leap year days when calculating year distance.
119
- # e.g. if there are 20 leap year days between 2 dates having the same day
120
- # and month then the based on 365 days calculation
121
- # the distance in years will come out to over 80 years when in written
122
- # English it would read better as about 80 years.
123
- minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
124
- else
125
- minutes_with_offset = distance_in_minutes
126
- end
127
- remainder = (minutes_with_offset % MINUTES_IN_YEAR)
128
- distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR)
129
- if remainder < MINUTES_IN_QUARTER_YEAR
130
- locale.t(:about_x_years, :count => distance_in_years)
131
- elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR
132
- locale.t(:over_x_years, :count => distance_in_years)
133
- else
134
- locale.t(:almost_x_years, :count => distance_in_years + 1)
135
- end
154
+ locale.t(:almost_x_years, count: distance_in_years + 1)
155
+ end
136
156
  end
137
157
  end
138
158
  end
@@ -177,12 +197,15 @@ module ActionView
177
197
  # and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example.
178
198
  # See <tt>Kernel.sprintf</tt> for documentation on format sequences.
179
199
  # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
200
+ # * <tt>:time_separator</tt> - Specifies a string to separate the time fields. Default is "" (i.e. nothing).
201
+ # * <tt>:datetime_separator</tt>- Specifies a string to separate the date and time fields. Default is "" (i.e. nothing).
180
202
  # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt> if
181
203
  # you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to
182
204
  # the current selected year minus 5.
183
205
  # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if
184
206
  # you are creating new record. While editing existing record, <tt>:end_year</tt> defaults to
185
207
  # the current selected year plus 5.
208
+ # * <tt>:year_format</tt> - Set format of years for year select. Lambda should be passed.
186
209
  # * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
187
210
  # as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
188
211
  # first of the given month in order to not create invalid dates like 31 February.
@@ -196,15 +219,18 @@ module ActionView
196
219
  # the respective locale (e.g. [:year, :month, :day] in the en locale that ships with Rails).
197
220
  # * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty
198
221
  # dates.
199
- # * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil.
222
+ # * <tt>:default</tt> - Set a default date if the affected date isn't set or is +nil+.
200
223
  # * <tt>:selected</tt> - Set a date that overrides the actual value.
201
224
  # * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled.
202
225
  # * <tt>:prompt</tt> - Set to true (for a generic prompt), a prompt string or a hash of prompt strings
203
226
  # for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>.
204
227
  # Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds)
205
228
  # or the given prompt string.
206
- # * <tt>:with_css_classes</tt> - Set to true if you want assign different styles for 'select' tags. This option
207
- # automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second' for your 'select' tags.
229
+ # * <tt>:with_css_classes</tt> - Set to true or a hash of strings. Use true if you want to assign generic styles for
230
+ # select tags. This automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second'. A hash of
231
+ # strings for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, <tt>:second</tt>
232
+ # will extend the select type with the given value. Use +html_options+ to modify every select tag in the set.
233
+ # * <tt>:use_hidden</tt> - Set to true if you only want to generate hidden input tags.
208
234
  #
209
235
  # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
210
236
  #
@@ -240,7 +266,7 @@ module ActionView
240
266
  # date_select("article", "written_on", default: 3.days.from_now)
241
267
  #
242
268
  # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
243
- # # which is set in the form with todays date, regardless of the value in the Active Record object.
269
+ # # which is set in the form with today's date, regardless of the value in the Active Record object.
244
270
  # date_select("article", "written_on", selected: Date.today)
245
271
  #
246
272
  # # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute
@@ -250,6 +276,9 @@ module ActionView
250
276
  # # Generates a date select with custom prompts.
251
277
  # date_select("article", "written_on", prompt: { day: 'Select day', month: 'Select month', year: 'Select year' })
252
278
  #
279
+ # # Generates a date select with custom year format.
280
+ # date_select("article", "written_on", year_format: ->(year) { "Heisei #{year - 1988}" })
281
+ #
253
282
  # The selects are prepared for multi-parameter assignment to an Active Record object.
254
283
  #
255
284
  # 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
@@ -276,16 +305,16 @@ module ActionView
276
305
  # # the sunrise attribute.
277
306
  # time_select("article", "start_time", include_seconds: true)
278
307
  #
279
- # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30 and 45.
280
- # time_select 'game', 'game_time', {minute_step: 15}
308
+ # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30, and 45.
309
+ # time_select 'game', 'game_time', { minute_step: 15 }
281
310
  #
282
311
  # # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
283
- # time_select("article", "written_on", prompt: {hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds'})
284
- # time_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours
312
+ # time_select("article", "written_on", prompt: { hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds' })
313
+ # time_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours
285
314
  # time_select("article", "written_on", prompt: true) # generic prompts for all
286
315
  #
287
316
  # # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
288
- # time_select 'game', 'game_time', {ampm: true}
317
+ # time_select 'game', 'game_time', { ampm: true }
289
318
  #
290
319
  # The selects are prepared for multi-parameter assignment to an Active Record object.
291
320
  #
@@ -321,8 +350,8 @@ module ActionView
321
350
  # datetime_select("article", "written_on", discard_type: true)
322
351
  #
323
352
  # # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
324
- # datetime_select("article", "written_on", prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
325
- # datetime_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours
353
+ # datetime_select("article", "written_on", prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
354
+ # datetime_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours
326
355
  # datetime_select("article", "written_on", prompt: true) # generic prompts for all
327
356
  #
328
357
  # The selects are prepared for multi-parameter assignment to an Active Record object.
@@ -372,8 +401,8 @@ module ActionView
372
401
  # select_datetime(my_date_time, prefix: 'payday')
373
402
  #
374
403
  # # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
375
- # select_datetime(my_date_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
376
- # select_datetime(my_date_time, prompt: {hour: true}) # generic prompt for hours
404
+ # select_datetime(my_date_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
405
+ # select_datetime(my_date_time, prompt: { hour: true }) # generic prompt for hours
377
406
  # select_datetime(my_date_time, prompt: true) # generic prompts for all
378
407
  def select_datetime(datetime = Time.current, options = {}, html_options = {})
379
408
  DateTimeSelector.new(datetime, options, html_options).select_datetime
@@ -411,8 +440,8 @@ module ActionView
411
440
  # select_date(my_date, prefix: 'payday')
412
441
  #
413
442
  # # Generates a date select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
414
- # select_date(my_date, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
415
- # select_date(my_date, prompt: {hour: true}) # generic prompt for hours
443
+ # select_date(my_date, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
444
+ # select_date(my_date, prompt: { hour: true }) # generic prompt for hours
416
445
  # select_date(my_date, prompt: true) # generic prompts for all
417
446
  def select_date(date = Date.current, options = {}, html_options = {})
418
447
  DateTimeSelector.new(date, options, html_options).select_date
@@ -451,8 +480,8 @@ module ActionView
451
480
  # select_time(my_time, start_hour: 2, end_hour: 14)
452
481
  #
453
482
  # # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts.
454
- # select_time(my_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
455
- # select_time(my_time, prompt: {hour: true}) # generic prompt for hours
483
+ # select_time(my_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
484
+ # select_time(my_time, prompt: { hour: true }) # generic prompt for hours
456
485
  # select_time(my_time, prompt: true) # generic prompts for all
457
486
  def select_time(datetime = Time.current, options = {}, html_options = {})
458
487
  DateTimeSelector.new(datetime, options, html_options).select_time
@@ -462,7 +491,7 @@ module ActionView
462
491
  # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
463
492
  # Override the field name using the <tt>:field_name</tt> option, 'second' by default.
464
493
  #
465
- # my_time = Time.now + 16.minutes
494
+ # my_time = Time.now + 16.seconds
466
495
  #
467
496
  # # Generates a select field for seconds that defaults to the seconds for the time in my_time.
468
497
  # select_second(my_time)
@@ -486,7 +515,7 @@ module ActionView
486
515
  # selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
487
516
  # Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
488
517
  #
489
- # my_time = Time.now + 6.hours
518
+ # my_time = Time.now + 10.minutes
490
519
  #
491
520
  # # Generates a select field for minutes that defaults to the minutes for the time in my_time.
492
521
  # select_minute(my_time)
@@ -643,8 +672,6 @@ module ActionView
643
672
  # <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
644
673
  # time_tag Date.yesterday, 'Yesterday' # =>
645
674
  # <time datetime="2010-11-03">Yesterday</time>
646
- # time_tag Date.today, pubdate: true # =>
647
- # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
648
675
  # time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # =>
649
676
  # <time datetime="2010-W44">November 04, 2010</time>
650
677
  #
@@ -655,19 +682,29 @@ module ActionView
655
682
  def time_tag(date_or_time, *args, &block)
656
683
  options = args.extract_options!
657
684
  format = options.delete(:format) || :long
658
- content = args.first || I18n.l(date_or_time, :format => format)
659
- datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601
685
+ content = args.first || I18n.l(date_or_time, format: format)
660
686
 
661
- content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block)
687
+ content_tag("time", content, options.reverse_merge(datetime: date_or_time.iso8601), &block)
662
688
  end
689
+
690
+ private
691
+ def normalize_distance_of_time_argument_to_time(value)
692
+ if value.is_a?(Numeric)
693
+ Time.at(value)
694
+ elsif value.respond_to?(:to_time)
695
+ value.to_time
696
+ else
697
+ raise ArgumentError, "#{value.inspect} can't be converted to a Time value"
698
+ end
699
+ end
663
700
  end
664
701
 
665
702
  class DateTimeSelector #:nodoc:
666
703
  include ActionView::Helpers::TagHelper
667
704
 
668
- DEFAULT_PREFIX = 'date'.freeze
705
+ DEFAULT_PREFIX = "date"
669
706
  POSITION = {
670
- :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6
707
+ year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6
671
708
  }.freeze
672
709
 
673
710
  AMPM_TRANSLATION = Hash[
@@ -683,8 +720,8 @@ module ActionView
683
720
  @options = options.dup
684
721
  @html_options = html_options.dup
685
722
  @datetime = datetime
686
- @options[:datetime_separator] ||= ' &mdash; '
687
- @options[:time_separator] ||= ' : '
723
+ @options[:datetime_separator] ||= " &mdash; "
724
+ @options[:time_separator] ||= " : "
688
725
  end
689
726
 
690
727
  def select_datetime
@@ -754,7 +791,7 @@ module ActionView
754
791
  if @options[:use_hidden] || @options[:discard_minute]
755
792
  build_hidden(:minute, min)
756
793
  else
757
- build_options_and_select(:minute, min, :step => @options[:minute_step])
794
+ build_options_and_select(:minute, min, step: @options[:minute_step])
758
795
  end
759
796
  end
760
797
 
@@ -774,7 +811,7 @@ module ActionView
774
811
  if @options[:use_hidden] || @options[:discard_day]
775
812
  build_hidden(:day, day || 1)
776
813
  else
777
- build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers])
814
+ build_options_and_select(:day, day, start: 1, end: 31, leading_zeros: false, use_two_digit_numbers: @options[:use_two_digit_numbers])
778
815
  end
779
816
  end
780
817
 
@@ -784,17 +821,17 @@ module ActionView
784
821
  else
785
822
  month_options = []
786
823
  1.upto(12) do |month_number|
787
- options = { :value => month_number }
824
+ options = { value: month_number }
788
825
  options[:selected] = "selected" if month == month_number
789
- month_options << content_tag(:option, month_name(month_number), options) + "\n"
826
+ month_options << content_tag("option", month_name(month_number), options) + "\n"
790
827
  end
791
828
  build_select(:month, month_options.join)
792
829
  end
793
830
  end
794
831
 
795
832
  def select_year
796
- if !@datetime || @datetime == 0
797
- val = '1'
833
+ if !year || @datetime == 0
834
+ val = "1"
798
835
  middle_year = Date.today.year
799
836
  else
800
837
  val = middle_year = year
@@ -814,14 +851,19 @@ module ActionView
814
851
  raise ArgumentError, "There are too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter."
815
852
  end
816
853
 
817
- build_options_and_select(:year, val, options)
854
+ build_select(:year, build_year_options(val, options))
818
855
  end
819
856
  end
820
857
 
821
858
  private
822
859
  %w( sec min hour day month year ).each do |method|
823
860
  define_method(method) do
824
- @datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime
861
+ case @datetime
862
+ when Hash then @datetime[method.to_sym]
863
+ when Numeric then @datetime
864
+ when nil then nil
865
+ else @datetime.send(method)
866
+ end
825
867
  end
826
868
  end
827
869
 
@@ -829,12 +871,12 @@ module ActionView
829
871
  # valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid.
830
872
  def set_day_if_discarded
831
873
  if @datetime && @options[:discard_day]
832
- @datetime = @datetime.change(:day => 1)
874
+ @datetime = @datetime.change(day: 1)
833
875
  end
834
876
  end
835
877
 
836
878
  # Returns translated month names, but also ensures that a custom month
837
- # name array has a leading nil element.
879
+ # name array has a leading +nil+ element.
838
880
  def month_names
839
881
  @month_names ||= begin
840
882
  month_names = @options[:use_month_names] || translated_month_names
@@ -854,7 +896,7 @@ module ActionView
854
896
  # "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
855
897
  def translated_month_names
856
898
  key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
857
- I18n.translate(key, :locale => @options[:locale])
899
+ I18n.translate(key, locale: @options[:locale])
858
900
  end
859
901
 
860
902
  # Looks up month names by number (1-based):
@@ -882,23 +924,38 @@ module ActionView
882
924
  if @options[:use_month_numbers]
883
925
  number
884
926
  elsif @options[:use_two_digit_numbers]
885
- '%02d' % number
927
+ "%02d" % number
886
928
  elsif @options[:add_month_numbers]
887
929
  "#{number} - #{month_names[number]}"
888
930
  elsif format_string = @options[:month_format_string]
889
- format_string % {number: number, name: month_names[number]}
931
+ format_string % { number: number, name: month_names[number] }
890
932
  else
891
933
  month_names[number]
892
934
  end
893
935
  end
894
936
 
937
+ # Looks up year names by number.
938
+ #
939
+ # year_name(1998) # => 1998
940
+ #
941
+ # If the <tt>:year_format</tt> option is passed:
942
+ #
943
+ # year_name(1998) # => "Heisei 10"
944
+ def year_name(number)
945
+ if year_format_lambda = @options[:year_format]
946
+ year_format_lambda.call(number)
947
+ else
948
+ number
949
+ end
950
+ end
951
+
895
952
  def date_order
896
953
  @date_order ||= @options[:order] || translated_date_order
897
954
  end
898
955
 
899
956
  def translated_date_order
900
- date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => [])
901
- date_order = date_order.map { |element| element.to_sym }
957
+ date_order = I18n.translate(:'date.order', locale: @options[:locale], default: [])
958
+ date_order = date_order.map(&:to_sym)
902
959
 
903
960
  forbidden_elements = date_order - [:year, :month, :day]
904
961
  if forbidden_elements.any?
@@ -944,11 +1001,39 @@ module ActionView
944
1001
  select_options = []
945
1002
  start.step(stop, step) do |i|
946
1003
  value = leading_zeros ? sprintf("%02d", i) : i
947
- tag_options = { :value => value }
1004
+ tag_options = { value: value }
948
1005
  tag_options[:selected] = "selected" if selected == i
949
1006
  text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
950
1007
  text = options[:ampm] ? AMPM_TRANSLATION[i] : text
951
- select_options << content_tag(:option, text, tag_options)
1008
+ select_options << content_tag("option", text, tag_options)
1009
+ end
1010
+
1011
+ (select_options.join("\n") + "\n").html_safe
1012
+ end
1013
+
1014
+ # Build select option HTML for year.
1015
+ # If <tt>year_format</tt> option is not passed
1016
+ # build_year_options(1998, start: 1998, end: 2000)
1017
+ # => "<option value="1998" selected="selected">1998</option>
1018
+ # <option value="1999">1999</option>
1019
+ # <option value="2000">2000</option>"
1020
+ #
1021
+ # If <tt>year_format</tt> option is passed
1022
+ # build_year_options(1998, start: 1998, end: 2000, year_format: ->year { "Heisei #{ year - 1988 }" })
1023
+ # => "<option value="1998" selected="selected">Heisei 10</option>
1024
+ # <option value="1999">Heisei 11</option>
1025
+ # <option value="2000">Heisei 12</option>"
1026
+ def build_year_options(selected, options = {})
1027
+ start = options.delete(:start)
1028
+ stop = options.delete(:end)
1029
+ step = options.delete(:step)
1030
+
1031
+ select_options = []
1032
+ start.step(stop, step) do |value|
1033
+ tag_options = { value: value }
1034
+ tag_options[:selected] = "selected" if selected == value
1035
+ text = year_name(value)
1036
+ select_options << content_tag("option", text, tag_options)
952
1037
  end
953
1038
 
954
1039
  (select_options.join("\n") + "\n").html_safe
@@ -961,35 +1046,51 @@ module ActionView
961
1046
  # </select>"
962
1047
  def build_select(type, select_options_as_html)
963
1048
  select_options = {
964
- :id => input_id_from_type(type),
965
- :name => input_name_from_type(type)
1049
+ id: input_id_from_type(type),
1050
+ name: input_name_from_type(type)
966
1051
  }.merge!(@html_options)
967
- select_options[:disabled] = 'disabled' if @options[:disabled]
968
- select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes]
1052
+ select_options[:disabled] = "disabled" if @options[:disabled]
1053
+ select_options[:class] = css_class_attribute(type, select_options[:class], @options[:with_css_classes]) if @options[:with_css_classes]
969
1054
 
970
- select_html = "\n"
971
- select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
1055
+ select_html = +"\n"
1056
+ select_html << content_tag("option", "", value: "") + "\n" if @options[:include_blank]
972
1057
  select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
973
1058
  select_html << select_options_as_html
974
1059
 
975
- (content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe
1060
+ (content_tag("select", select_html.html_safe, select_options) + "\n").html_safe
1061
+ end
1062
+
1063
+ # Builds the css class value for the select element
1064
+ # css_class_attribute(:year, 'date optional', { year: 'my-year' })
1065
+ # => "date optional my-year"
1066
+ def css_class_attribute(type, html_options_class, options) # :nodoc:
1067
+ css_class = \
1068
+ case options
1069
+ when Hash
1070
+ options[type.to_sym]
1071
+ else
1072
+ type
1073
+ end
1074
+
1075
+ [html_options_class, css_class].compact.join(" ")
976
1076
  end
977
1077
 
978
1078
  # Builds a prompt option tag with supplied options or from default options.
979
1079
  # prompt_option_tag(:month, prompt: 'Select month')
980
1080
  # => "<option value="">Select month</option>"
981
1081
  def prompt_option_tag(type, options)
982
- prompt = case options
1082
+ prompt = \
1083
+ case options
983
1084
  when Hash
984
- default_options = {:year => false, :month => false, :day => false, :hour => false, :minute => false, :second => false}
1085
+ default_options = { year: false, month: false, day: false, hour: false, minute: false, second: false }
985
1086
  default_options.merge!(options)[type.to_sym]
986
1087
  when String
987
1088
  options
988
1089
  else
989
- I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale])
990
- end
1090
+ I18n.translate(:"datetime.prompts.#{type}", locale: @options[:locale])
1091
+ end
991
1092
 
992
- prompt ? content_tag(:option, prompt, :value => '') : ''
1093
+ prompt ? content_tag("option", prompt, value: "") : ""
993
1094
  end
994
1095
 
995
1096
  # Builds hidden input tag for date part and value.
@@ -997,12 +1098,12 @@ module ActionView
997
1098
  # => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />"
998
1099
  def build_hidden(type, value)
999
1100
  select_options = {
1000
- :type => "hidden",
1001
- :id => input_id_from_type(type),
1002
- :name => input_name_from_type(type),
1003
- :value => value
1101
+ type: "hidden",
1102
+ id: input_id_from_type(type),
1103
+ name: input_name_from_type(type),
1104
+ value: value
1004
1105
  }.merge!(@html_options.slice(:disabled))
1005
- select_options[:disabled] = 'disabled' if @options[:disabled]
1106
+ select_options[:disabled] = "disabled" if @options[:disabled]
1006
1107
 
1007
1108
  tag(:input, select_options) + "\n".html_safe
1008
1109
  end
@@ -1013,7 +1114,7 @@ module ActionView
1013
1114
  prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
1014
1115
  prefix += "[#{@options[:index]}]" if @options.has_key?(:index)
1015
1116
 
1016
- field_name = @options[:field_name] || type
1117
+ field_name = @options[:field_name] || type.to_s
1017
1118
  if @options[:include_position]
1018
1119
  field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)"
1019
1120
  end
@@ -1024,8 +1125,8 @@ module ActionView
1024
1125
  # Returns the id attribute for the input tag.
1025
1126
  # => "post_written_on_1i"
1026
1127
  def input_id_from_type(type)
1027
- id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
1028
- id = @options[:namespace] + '_' + id if @options[:namespace]
1128
+ id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, "_").gsub(/[\]\)]/, "")
1129
+ id = @options[:namespace] + "_" + id if @options[:namespace]
1029
1130
 
1030
1131
  id
1031
1132
  end
@@ -1033,7 +1134,7 @@ module ActionView
1033
1134
  # Given an ordering of datetime components, create the selection HTML
1034
1135
  # and join them with their appropriate separators.
1035
1136
  def build_selects_from_types(order)
1036
- select = ''
1137
+ select = +""
1037
1138
  first_visible = order.find { |type| !@options[:"discard_#{type}"] }
1038
1139
  order.reverse_each do |type|
1039
1140
  separator = separator(type) unless type == first_visible # don't add before first visible field
@@ -1047,12 +1148,12 @@ module ActionView
1047
1148
  return "" if @options[:use_hidden]
1048
1149
 
1049
1150
  case type
1050
- when :year, :month, :day
1051
- @options[:"discard_#{type}"] ? "" : @options[:date_separator]
1052
- when :hour
1053
- (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
1054
- when :minute, :second
1055
- @options[:"discard_#{type}"] ? "" : @options[:time_separator]
1151
+ when :year, :month, :day
1152
+ @options[:"discard_#{type}"] ? "" : @options[:date_separator]
1153
+ when :hour
1154
+ (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
1155
+ when :minute, :second
1156
+ @options[:"discard_#{type}"] ? "" : @options[:time_separator]
1056
1157
  end
1057
1158
  end
1058
1159
  end