actionview 4.1.13 → 6.1.3.1

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 (124) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +181 -359
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +12 -6
  5. data/lib/action_view/base.rb +115 -43
  6. data/lib/action_view/buffers.rb +22 -4
  7. data/lib/action_view/cache_expiry.rb +52 -0
  8. data/lib/action_view/context.rb +8 -12
  9. data/lib/action_view/dependency_tracker.rb +61 -21
  10. data/lib/action_view/digestor.rb +89 -84
  11. data/lib/action_view/flows.rb +12 -13
  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 +311 -105
  15. data/lib/action_view/helpers/asset_url_helper.rb +197 -80
  16. data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
  17. data/lib/action_view/helpers/cache_helper.rb +109 -45
  18. data/lib/action_view/helpers/capture_helper.rb +20 -22
  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 +245 -140
  23. data/lib/action_view/helpers/debug_helper.rb +14 -17
  24. data/lib/action_view/helpers/form_helper.rb +875 -148
  25. data/lib/action_view/helpers/form_options_helper.rb +128 -82
  26. data/lib/action_view/helpers/form_tag_helper.rb +253 -91
  27. data/lib/action_view/helpers/javascript_helper.rb +37 -15
  28. data/lib/action_view/helpers/number_helper.rb +100 -77
  29. data/lib/action_view/helpers/output_safety_helper.rb +42 -10
  30. data/lib/action_view/helpers/rendering_helper.rb +26 -15
  31. data/lib/action_view/helpers/sanitize_helper.rb +79 -164
  32. data/lib/action_view/helpers/tag_helper.rb +277 -64
  33. data/lib/action_view/helpers/tags/base.rb +143 -92
  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 -30
  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 +3 -2
  42. data/lib/action_view/helpers/tags/date_select.rb +38 -37
  43. data/lib/action_view/helpers/tags/datetime_field.rb +14 -5
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
  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 +41 -22
  51. data/lib/action_view/helpers/tags/month_field.rb +3 -2
  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 +24 -0
  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 +3 -0
  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 +7 -1
  61. data/lib/action_view/helpers/tags/text_field.rb +11 -7
  62. data/lib/action_view/helpers/tags/time_field.rb +3 -2
  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 +39 -0
  66. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  67. data/lib/action_view/helpers/tags/week_field.rb +3 -2
  68. data/lib/action_view/helpers/tags.rb +4 -1
  69. data/lib/action_view/helpers/text_helper.rb +80 -45
  70. data/lib/action_view/helpers/translation_helper.rb +148 -67
  71. data/lib/action_view/helpers/url_helper.rb +289 -147
  72. data/lib/action_view/helpers.rb +5 -3
  73. data/lib/action_view/layouts.rb +68 -63
  74. data/lib/action_view/log_subscriber.rb +80 -13
  75. data/lib/action_view/lookup_context.rb +137 -92
  76. data/lib/action_view/model_naming.rb +4 -2
  77. data/lib/action_view/path_set.rb +30 -16
  78. data/lib/action_view/railtie.rb +62 -13
  79. data/lib/action_view/record_identifier.rb +53 -26
  80. data/lib/action_view/renderer/abstract_renderer.rb +152 -13
  81. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  82. data/lib/action_view/renderer/object_renderer.rb +34 -0
  83. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
  84. data/lib/action_view/renderer/partial_renderer.rb +61 -261
  85. data/lib/action_view/renderer/renderer.rb +67 -6
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
  87. data/lib/action_view/renderer/template_renderer.rb +83 -75
  88. data/lib/action_view/rendering.rb +73 -46
  89. data/lib/action_view/routing_url_for.rb +54 -17
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template/error.rb +44 -29
  92. data/lib/action_view/template/handlers/builder.rb +12 -13
  93. data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
  94. data/lib/action_view/template/handlers/erb.rb +23 -89
  95. data/lib/action_view/template/handlers/html.rb +11 -0
  96. data/lib/action_view/template/handlers/raw.rb +4 -4
  97. data/lib/action_view/template/handlers.rb +22 -9
  98. data/lib/action_view/template/html.rb +10 -11
  99. data/lib/action_view/template/inline.rb +22 -0
  100. data/lib/action_view/template/raw_file.rb +25 -0
  101. data/lib/action_view/template/renderable.rb +24 -0
  102. data/lib/action_view/template/resolver.rb +267 -181
  103. data/lib/action_view/template/sources/file.rb +17 -0
  104. data/lib/action_view/template/sources.rb +13 -0
  105. data/lib/action_view/template/text.rb +8 -10
  106. data/lib/action_view/template/types.rb +18 -18
  107. data/lib/action_view/template.rb +109 -99
  108. data/lib/action_view/test_case.rb +73 -53
  109. data/lib/action_view/testing/resolvers.rb +24 -33
  110. data/lib/action_view/unbound_template.rb +31 -0
  111. data/lib/action_view/version.rb +3 -1
  112. data/lib/action_view/view_paths.rb +74 -44
  113. data/lib/action_view.rb +14 -9
  114. data/lib/assets/compiled/rails-ujs.js +746 -0
  115. metadata +71 -26
  116. data/lib/action_view/helpers/record_tag_helper.rb +0 -108
  117. data/lib/action_view/tasks/dependencies.rake +0 -23
  118. data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
  119. data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
  120. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
  121. data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
  122. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
  123. data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
  124. data/lib/action_view/vendor/html-scanner.rb +0 -20
@@ -1,4 +1,6 @@
1
- require 'action_view/helpers/tags/checkable'
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tags/checkable"
2
4
 
3
5
  module ActionView
4
6
  module Helpers
@@ -15,16 +17,15 @@ module ActionView
15
17
  options = @options.stringify_keys
16
18
  options["type"] = "radio"
17
19
  options["value"] = @tag_value
18
- options["checked"] = "checked" if input_checked?(object, options)
20
+ options["checked"] = "checked" if input_checked?(options)
19
21
  add_default_name_and_id_for_value(@tag_value, options)
20
22
  tag("input", options)
21
23
  end
22
24
 
23
25
  private
24
-
25
- def checked?(value)
26
- value.to_s == @tag_value.to_s
27
- end
26
+ def checked?(value)
27
+ value.to_s == @tag_value.to_s
28
+ end
28
29
  end
29
30
  end
30
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -16,6 +18,7 @@ module ActionView
16
18
  options["incremental"] = true unless options.has_key?("incremental")
17
19
  end
18
20
 
21
+ @options = options
19
22
  super
20
23
  end
21
24
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -13,8 +15,8 @@ module ActionView
13
15
 
14
16
  def render
15
17
  option_tags_options = {
16
- :selected => @options.fetch(:selected) { value(@object) },
17
- :disabled => @options[:disabled]
18
+ selected: @options.fetch(:selected) { value.nil? ? "" : value },
19
+ disabled: @options[:disabled]
18
20
  }
19
21
 
20
22
  option_tags = if grouped_choices?
@@ -27,14 +29,13 @@ module ActionView
27
29
  end
28
30
 
29
31
  private
30
-
31
- # Grouped choices look like this:
32
- #
33
- # [nil, []]
34
- # { nil => [] }
35
- def grouped_choices?
36
- !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
37
- end
32
+ # Grouped choices look like this:
33
+ #
34
+ # [nil, []]
35
+ # { nil => [] }
36
+ def grouped_choices?
37
+ !@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last
38
+ end
38
39
  end
39
40
  end
40
41
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -1,7 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tags/placeholderable"
4
+
1
5
  module ActionView
2
6
  module Helpers
3
7
  module Tags # :nodoc:
4
8
  class TextArea < Base # :nodoc:
9
+ include Placeholderable
10
+
5
11
  def render
6
12
  options = @options.stringify_keys
7
13
  add_default_name_and_id(options)
@@ -10,7 +16,7 @@ module ActionView
10
16
  options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
11
17
  end
12
18
 
13
- content_tag("textarea", options.delete("value") { value_before_type_cast(object) }, options)
19
+ content_tag("textarea", options.delete("value") { value_before_type_cast }, options)
14
20
  end
15
21
  end
16
22
  end
@@ -1,28 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tags/placeholderable"
4
+
1
5
  module ActionView
2
6
  module Helpers
3
7
  module Tags # :nodoc:
4
8
  class TextField < Base # :nodoc:
9
+ include Placeholderable
10
+
5
11
  def render
6
12
  options = @options.stringify_keys
7
13
  options["size"] = options["maxlength"] unless options.key?("size")
8
14
  options["type"] ||= field_type
9
- options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file"
10
- options["value"] &&= ERB::Util.html_escape(options["value"])
15
+ options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file"
11
16
  add_default_name_and_id(options)
12
17
  tag("input", options)
13
18
  end
14
19
 
15
20
  class << self
16
21
  def field_type
17
- @field_type ||= self.name.split("::").last.sub("Field", "").downcase
22
+ @field_type ||= name.split("::").last.sub("Field", "").downcase
18
23
  end
19
24
  end
20
25
 
21
26
  private
22
-
23
- def field_type
24
- self.class.field_type
25
- end
27
+ def field_type
28
+ self.class.field_type
29
+ end
26
30
  end
27
31
  end
28
32
  end
@@ -1,11 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
4
6
  class TimeField < DatetimeField # :nodoc:
5
7
  private
6
-
7
8
  def format_date(value)
8
- value.try(:strftime, "%T.%L")
9
+ value&.strftime("%T.%L")
9
10
  end
10
11
  end
11
12
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -11,7 +13,7 @@ module ActionView
11
13
 
12
14
  def render
13
15
  select_content_tag(
14
- time_zone_options_for_select(value(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
16
+ time_zone_options_for_select(value || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
15
17
  )
16
18
  end
17
19
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ module Helpers
5
+ module Tags # :nodoc:
6
+ class Translator # :nodoc:
7
+ def initialize(object, object_name, method_and_value, scope:)
8
+ @object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
9
+ @method_and_value = method_and_value
10
+ @scope = scope
11
+ @model = object.respond_to?(:to_model) ? object.to_model : nil
12
+ end
13
+
14
+ def translate
15
+ translated_attribute = I18n.t("#{object_name}.#{method_and_value}", default: i18n_default, scope: scope).presence
16
+ translated_attribute || human_attribute_name
17
+ end
18
+
19
+ private
20
+ attr_reader :object_name, :method_and_value, :scope, :model
21
+
22
+ def i18n_default
23
+ if model
24
+ key = model.model_name.i18n_key
25
+ ["#{key}.#{method_and_value}".to_sym, ""]
26
+ else
27
+ ""
28
+ end
29
+ end
30
+
31
+ def human_attribute_name
32
+ if model && model.class.respond_to?(:human_attribute_name)
33
+ model.class.human_attribute_name(method_and_value)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -1,11 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
4
6
  class WeekField < DatetimeField # :nodoc:
5
7
  private
6
-
7
8
  def format_date(value)
8
- value.try(:strftime, "%Y-W%W")
9
+ value&.strftime("%Y-W%V")
9
10
  end
10
11
  end
11
12
  end
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
- module Helpers
4
+ module Helpers #:nodoc:
3
5
  module Tags #:nodoc:
4
6
  extend ActiveSupport::Autoload
5
7
 
6
8
  eager_autoload do
7
9
  autoload :Base
10
+ autoload :Translator
8
11
  autoload :CheckBox
9
12
  autoload :CollectionCheckBoxes
10
13
  autoload :CollectionRadioButtons
@@ -1,5 +1,7 @@
1
- require 'active_support/core_ext/string/filters'
2
- require 'active_support/core_ext/array/extract_options'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/filters"
4
+ require "active_support/core_ext/array/extract_options"
3
5
 
4
6
  module ActionView
5
7
  # = Action View Text Helpers
@@ -11,9 +13,9 @@ module ActionView
11
13
  #
12
14
  # ==== Sanitization
13
15
  #
14
- # Most text helpers by default sanitize the given content, but do not escape it.
15
- # This means HTML tags will appear in the page but all malicious code will be removed.
16
- # Let's look at some examples using the +simple_format+ method:
16
+ # Most text helpers that generate HTML output sanitize the given input by default,
17
+ # but do not escape it. This means HTML tags will appear in the page but all malicious
18
+ # code will be removed. Let's look at some examples using the +simple_format+ method:
17
19
  #
18
20
  # simple_format('<a href="http://example.com/">Example</a>')
19
21
  # # => "<p><a href=\"http://example.com/\">Example</a></p>"
@@ -103,11 +105,16 @@ module ActionView
103
105
  # Highlights one or more +phrases+ everywhere in +text+ by inserting it into
104
106
  # a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
105
107
  # as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
106
- # '<mark>\1</mark>')
108
+ # <tt><mark>\1</mark></tt>) or passing a block that receives each matched term. By default +text+
109
+ # is sanitized to prevent possible XSS attacks. If the input is trustworthy, passing false
110
+ # for <tt>:sanitize</tt> will turn sanitizing off.
107
111
  #
108
112
  # highlight('You searched for: rails', 'rails')
109
113
  # # => You searched for: <mark>rails</mark>
110
114
  #
115
+ # highlight('You searched for: rails', /for|rails/)
116
+ # # => You searched <mark>for</mark>: <mark>rails</mark>
117
+ #
111
118
  # highlight('You searched for: ruby, rails, dhh', 'actionpack')
112
119
  # # => You searched for: ruby, rails, dhh
113
120
  #
@@ -116,15 +123,28 @@ module ActionView
116
123
  #
117
124
  # highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>')
118
125
  # # => You searched for: <a href="search?q=rails">rails</a>
126
+ #
127
+ # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
128
+ # # => You searched for: <a href="search?q=rails">rails</a>
129
+ #
130
+ # highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
131
+ # # => <a href="javascript:alert('no!')">ruby</a> on <mark>rails</mark>
119
132
  def highlight(text, phrases, options = {})
120
133
  text = sanitize(text) if options.fetch(:sanitize, true)
121
134
 
122
135
  if text.blank? || phrases.blank?
123
- text
136
+ text || ""
124
137
  else
125
- highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
126
- match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
127
- text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
138
+ match = Array(phrases).map do |p|
139
+ Regexp === p ? p.to_s : Regexp.escape(p)
140
+ end.join("|")
141
+
142
+ if block_given?
143
+ text.gsub(/(#{match})(?![^<]*?>)/i) { |found| yield found }
144
+ else
145
+ highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
146
+ text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
147
+ end
128
148
  end.html_safe
129
149
  end
130
150
 
@@ -133,7 +153,7 @@ module ActionView
133
153
  # defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
134
154
  # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. Use the
135
155
  # <tt>:separator</tt> option to choose the delimitation. The resulting string will be stripped in any case. If the +phrase+
136
- # isn't found, nil is returned.
156
+ # isn't found, +nil+ is returned.
137
157
  #
138
158
  # excerpt('This is an example', 'an', radius: 5)
139
159
  # # => ...s is an exam...
@@ -155,23 +175,27 @@ module ActionView
155
175
  def excerpt(text, phrase, options = {})
156
176
  return unless text && phrase
157
177
 
158
- separator = options[:separator] || ''
159
- phrase = Regexp.escape(phrase)
160
- regex = /#{phrase}/i
178
+ separator = options.fetch(:separator, nil) || ""
179
+ case phrase
180
+ when Regexp
181
+ regex = phrase
182
+ else
183
+ regex = /#{Regexp.escape(phrase)}/i
184
+ end
161
185
 
162
186
  return unless matches = text.match(regex)
163
187
  phrase = matches[0]
164
188
 
165
189
  unless separator.empty?
166
190
  text.split(separator).each do |value|
167
- if value.match(regex)
168
- regex = phrase = value
191
+ if value.match?(regex)
192
+ phrase = value
169
193
  break
170
194
  end
171
195
  end
172
196
  end
173
197
 
174
- first_part, second_part = text.split(regex, 2)
198
+ first_part, second_part = text.split(phrase, 2)
175
199
 
176
200
  prefix, first_part = cut_excerpt_part(:first, first_part, separator, options)
177
201
  postfix, second_part = cut_excerpt_part(:second, second_part, separator, options)
@@ -182,7 +206,12 @@ module ActionView
182
206
 
183
207
  # Attempts to pluralize the +singular+ word unless +count+ is 1. If
184
208
  # +plural+ is supplied, it will use that when count is > 1, otherwise
185
- # it will use the Inflector to determine the plural form.
209
+ # it will use the Inflector to determine the plural form for the given locale,
210
+ # which defaults to I18n.locale
211
+ #
212
+ # The word will be pluralized using rules defined for the locale
213
+ # (you must define your own inflection rules for languages other than English).
214
+ # See ActiveSupport::Inflector.pluralize
186
215
  #
187
216
  # pluralize(1, 'person')
188
217
  # # => 1 person
@@ -190,16 +219,19 @@ module ActionView
190
219
  # pluralize(2, 'person')
191
220
  # # => 2 people
192
221
  #
193
- # pluralize(3, 'person', 'users')
222
+ # pluralize(3, 'person', plural: 'users')
194
223
  # # => 3 users
195
224
  #
196
225
  # pluralize(0, 'person')
197
226
  # # => 0 people
198
- def pluralize(count, singular, plural = nil)
199
- word = if (count == 1 || count =~ /^1(\.0+)?$/)
227
+ #
228
+ # pluralize(2, 'Person', locale: :de)
229
+ # # => 2 Personen
230
+ def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
231
+ word = if count == 1 || count.to_s.match?(/^1(\.0+)?$/)
200
232
  singular
201
233
  else
202
- plural || singular.pluralize
234
+ plural || singular.pluralize(locale)
203
235
  end
204
236
 
205
237
  "#{count || 0} #{word}"
@@ -220,19 +252,23 @@ module ActionView
220
252
  #
221
253
  # word_wrap('Once upon a time', line_width: 1)
222
254
  # # => Once\nupon\na\ntime
223
- def word_wrap(text, options = {})
224
- line_width = options.fetch(:line_width, 80)
225
-
255
+ #
256
+ # You can also specify a custom +break_sequence+ ("\n" by default)
257
+ #
258
+ # word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n")
259
+ # # => Once\r\nupon\r\na\r\ntime
260
+ def word_wrap(text, line_width: 80, break_sequence: "\n")
226
261
  text.split("\n").collect! do |line|
227
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
228
- end * "\n"
262
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").rstrip : line
263
+ end * break_sequence
229
264
  end
230
265
 
231
266
  # Returns +text+ transformed into HTML using simple formatting rules.
232
- # Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a
233
- # paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is
234
- # considered as a linebreak and a <tt><br /></tt> tag is appended. This
235
- # method does not remove the newlines from the +text+.
267
+ # Two or more consecutive newlines(<tt>\n\n</tt> or <tt>\r\n\r\n</tt>) are
268
+ # considered a paragraph and wrapped in <tt><p></tt> tags. One newline
269
+ # (<tt>\n</tt> or <tt>\r\n</tt>) is considered a linebreak and a
270
+ # <tt><br /></tt> tag is appended. This method does not remove the
271
+ # newlines from the +text+.
236
272
  #
237
273
  # You can pass any HTML attributes into <tt>html_options</tt>. These
238
274
  # will be added to all created paragraphs.
@@ -292,7 +328,7 @@ module ActionView
292
328
  # <table>
293
329
  # <% @items.each do |item| %>
294
330
  # <tr class="<%= cycle("odd", "even") -%>">
295
- # <td>item</td>
331
+ # <td><%= item %></td>
296
332
  # </tr>
297
333
  # <% end %>
298
334
  # </table>
@@ -317,7 +353,7 @@ module ActionView
317
353
  # <% end %>
318
354
  def cycle(first_value, *values)
319
355
  options = values.extract_options!
320
- name = options.fetch(:name, 'default')
356
+ name = options.fetch(:name, "default")
321
357
 
322
358
  values.unshift(*first_value)
323
359
 
@@ -386,22 +422,21 @@ module ActionView
386
422
  def to_s
387
423
  value = @values[@index].to_s
388
424
  @index = next_index
389
- return value
425
+ value
390
426
  end
391
427
 
392
428
  private
429
+ def next_index
430
+ step_index(1)
431
+ end
393
432
 
394
- def next_index
395
- step_index(1)
396
- end
397
-
398
- def previous_index
399
- step_index(-1)
400
- end
433
+ def previous_index
434
+ step_index(-1)
435
+ end
401
436
 
402
- def step_index(n)
403
- (@index + n) % @values.size
404
- end
437
+ def step_index(n)
438
+ (@index + n) % @values.size
439
+ end
405
440
  end
406
441
 
407
442
  private
@@ -410,7 +445,7 @@ module ActionView
410
445
  # uses an instance variable of ActionView::Base.
411
446
  def get_cycle(name)
412
447
  @_cycles = Hash.new unless defined?(@_cycles)
413
- return @_cycles[name]
448
+ @_cycles[name]
414
449
  end
415
450
 
416
451
  def set_cycle(name, cycle_object)