actionview 4.2.11 → 5.0.7

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 (68) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +304 -184
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/action_view.rb +1 -1
  6. data/lib/action_view/base.rb +14 -2
  7. data/lib/action_view/dependency_tracker.rb +51 -18
  8. data/lib/action_view/digestor.rb +83 -81
  9. data/lib/action_view/flows.rb +4 -5
  10. data/lib/action_view/gem_version.rb +3 -3
  11. data/lib/action_view/helpers/asset_tag_helper.rb +15 -5
  12. data/lib/action_view/helpers/asset_url_helper.rb +51 -12
  13. data/lib/action_view/helpers/atom_feed_helper.rb +6 -5
  14. data/lib/action_view/helpers/cache_helper.rb +62 -21
  15. data/lib/action_view/helpers/capture_helper.rb +5 -4
  16. data/lib/action_view/helpers/controller_helper.rb +11 -2
  17. data/lib/action_view/helpers/date_helper.rb +59 -13
  18. data/lib/action_view/helpers/debug_helper.rb +1 -1
  19. data/lib/action_view/helpers/form_helper.rb +74 -72
  20. data/lib/action_view/helpers/form_options_helper.rb +79 -39
  21. data/lib/action_view/helpers/form_tag_helper.rb +74 -44
  22. data/lib/action_view/helpers/javascript_helper.rb +4 -4
  23. data/lib/action_view/helpers/number_helper.rb +28 -13
  24. data/lib/action_view/helpers/output_safety_helper.rb +32 -2
  25. data/lib/action_view/helpers/record_tag_helper.rb +12 -99
  26. data/lib/action_view/helpers/rendering_helper.rb +2 -2
  27. data/lib/action_view/helpers/sanitize_helper.rb +1 -2
  28. data/lib/action_view/helpers/tag_helper.rb +19 -11
  29. data/lib/action_view/helpers/tags/base.rb +45 -29
  30. data/lib/action_view/helpers/tags/collection_check_boxes.rb +4 -28
  31. data/lib/action_view/helpers/tags/collection_helpers.rb +32 -0
  32. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +1 -9
  33. data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
  34. data/lib/action_view/helpers/tags/label.rb +1 -1
  35. data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
  36. data/lib/action_view/helpers/tags/search_field.rb +12 -9
  37. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  38. data/lib/action_view/helpers/tags/translator.rb +1 -1
  39. data/lib/action_view/helpers/text_helper.rb +27 -11
  40. data/lib/action_view/helpers/translation_helper.rb +56 -26
  41. data/lib/action_view/helpers/url_helper.rb +108 -79
  42. data/lib/action_view/layouts.rb +11 -10
  43. data/lib/action_view/log_subscriber.rb +35 -1
  44. data/lib/action_view/lookup_context.rb +69 -48
  45. data/lib/action_view/model_naming.rb +1 -1
  46. data/lib/action_view/path_set.rb +9 -0
  47. data/lib/action_view/railtie.rb +18 -3
  48. data/lib/action_view/record_identifier.rb +45 -19
  49. data/lib/action_view/renderer/abstract_renderer.rb +7 -3
  50. data/lib/action_view/renderer/partial_renderer.rb +38 -37
  51. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +49 -0
  52. data/lib/action_view/renderer/renderer.rb +2 -6
  53. data/lib/action_view/renderer/streaming_template_renderer.rb +1 -1
  54. data/lib/action_view/renderer/template_renderer.rb +11 -10
  55. data/lib/action_view/rendering.rb +15 -7
  56. data/lib/action_view/routing_url_for.rb +18 -6
  57. data/lib/action_view/tasks/{dependencies.rake → cache_digests.rake} +2 -2
  58. data/lib/action_view/template.rb +36 -12
  59. data/lib/action_view/template/error.rb +20 -9
  60. data/lib/action_view/template/handlers.rb +6 -4
  61. data/lib/action_view/template/handlers/html.rb +9 -0
  62. data/lib/action_view/template/handlers/raw.rb +1 -3
  63. data/lib/action_view/template/resolver.rb +49 -42
  64. data/lib/action_view/template/types.rb +14 -16
  65. data/lib/action_view/test_case.rb +15 -9
  66. data/lib/action_view/testing/resolvers.rb +1 -2
  67. data/lib/action_view/view_paths.rb +6 -24
  68. metadata +16 -20
@@ -14,7 +14,7 @@ module ActionView
14
14
  private
15
15
 
16
16
  def format_date(value)
17
- value.try(:strftime, "%Y-%m-%dT%T.%L%z")
17
+ raise NotImplementedError
18
18
  end
19
19
 
20
20
  def datetime_value(value)
@@ -17,7 +17,7 @@ module ActionView
17
17
  method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name
18
18
 
19
19
  content ||= Translator
20
- .new(object, @object_name, method_and_value, "helpers.label")
20
+ .new(object, @object_name, method_and_value, scope: "helpers.label")
21
21
  .translate
22
22
  content ||= @method_name.humanize
23
23
 
@@ -10,7 +10,7 @@ module ActionView
10
10
  method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}"
11
11
 
12
12
  placeholder ||= Tags::Translator
13
- .new(object, @object_name, method_and_value, "helpers.placeholder")
13
+ .new(object, @object_name, method_and_value, scope: "helpers.placeholder")
14
14
  .translate
15
15
  placeholder ||= @method_name.humanize
16
16
  @options[:placeholder] = placeholder
@@ -3,18 +3,21 @@ module ActionView
3
3
  module Tags # :nodoc:
4
4
  class SearchField < TextField # :nodoc:
5
5
  def render
6
- super do |options|
7
- if options["autosave"]
8
- if options["autosave"] == true
9
- options["autosave"] = request.host.split(".").reverse.join(".")
10
- end
11
- options["results"] ||= 10
12
- end
6
+ options = @options.stringify_keys
13
7
 
14
- if options["onsearch"]
15
- options["incremental"] = true unless options.has_key?("incremental")
8
+ if options["autosave"]
9
+ if options["autosave"] == true
10
+ options["autosave"] = request.host.split(".").reverse.join(".")
16
11
  end
12
+ options["results"] ||= 10
13
+ end
14
+
15
+ if options["onsearch"]
16
+ options["incremental"] = true unless options.has_key?("incremental")
17
17
  end
18
+
19
+ @options = options
20
+ super
18
21
  end
19
22
  end
20
23
  end
@@ -11,7 +11,6 @@ module ActionView
11
11
  options["size"] = options["maxlength"] unless options.key?("size")
12
12
  options["type"] ||= field_type
13
13
  options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file"
14
- yield options if block_given?
15
14
  add_default_name_and_id(options)
16
15
  tag("input", options)
17
16
  end
@@ -2,7 +2,7 @@ module ActionView
2
2
  module Helpers
3
3
  module Tags # :nodoc:
4
4
  class Translator # :nodoc:
5
- def initialize(object, object_name, method_and_value, scope)
5
+ def initialize(object, object_name, method_and_value, scope:)
6
6
  @object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
7
7
  @method_and_value = method_and_value
8
8
  @scope = scope
@@ -103,7 +103,9 @@ module ActionView
103
103
  # Highlights one or more +phrases+ everywhere in +text+ by inserting it into
104
104
  # a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
105
105
  # as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
106
- # '<mark>\1</mark>') or passing a block that receives each matched term.
106
+ # '<mark>\1</mark>') or passing a block that receives each matched term. By default +text+
107
+ # is sanitized to prevent possible XSS attacks. If the input is trustworthy, passing false
108
+ # for <tt>:sanitize</tt> will turn sanitizing off.
107
109
  #
108
110
  # highlight('You searched for: rails', 'rails')
109
111
  # # => You searched for: <mark>rails</mark>
@@ -122,6 +124,9 @@ module ActionView
122
124
  #
123
125
  # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
124
126
  # # => You searched for: <a href="search?q=rails">rails</a>
127
+ #
128
+ # highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
129
+ # # => "<a>ruby</a> on <mark>rails</mark>"
125
130
  def highlight(text, phrases, options = {})
126
131
  text = sanitize(text) if options.fetch(:sanitize, true)
127
132
 
@@ -199,7 +204,12 @@ module ActionView
199
204
 
200
205
  # Attempts to pluralize the +singular+ word unless +count+ is 1. If
201
206
  # +plural+ is supplied, it will use that when count is > 1, otherwise
202
- # it will use the Inflector to determine the plural form.
207
+ # it will use the Inflector to determine the plural form for the given locale,
208
+ # which defaults to I18n.locale
209
+ #
210
+ # The word will be pluralized using rules defined for the locale
211
+ # (you must define your own inflection rules for languages other than English).
212
+ # See ActiveSupport::Inflector.pluralize
203
213
  #
204
214
  # pluralize(1, 'person')
205
215
  # # => 1 person
@@ -207,16 +217,19 @@ module ActionView
207
217
  # pluralize(2, 'person')
208
218
  # # => 2 people
209
219
  #
210
- # pluralize(3, 'person', 'users')
220
+ # pluralize(3, 'person', plural: 'users')
211
221
  # # => 3 users
212
222
  #
213
223
  # pluralize(0, 'person')
214
224
  # # => 0 people
215
- def pluralize(count, singular, plural = nil)
225
+ #
226
+ # pluralize(2, 'Person', locale: :de)
227
+ # # => 2 Personen
228
+ def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
216
229
  word = if (count == 1 || count =~ /^1(\.0+)?$/)
217
230
  singular
218
231
  else
219
- plural || singular.pluralize
232
+ plural || singular.pluralize(locale)
220
233
  end
221
234
 
222
235
  "#{count || 0} #{word}"
@@ -237,12 +250,15 @@ module ActionView
237
250
  #
238
251
  # word_wrap('Once upon a time', line_width: 1)
239
252
  # # => Once\nupon\na\ntime
240
- def word_wrap(text, options = {})
241
- line_width = options.fetch(:line_width, 80)
242
-
253
+ #
254
+ # You can also specify a custom +break_sequence+ ("\n" by default)
255
+ #
256
+ # word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n")
257
+ # # => Once\r\nupon\r\na\r\ntime
258
+ def word_wrap(text, line_width: 80, break_sequence: "\n")
243
259
  text.split("\n").collect! do |line|
244
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
245
- end * "\n"
260
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : line
261
+ end * break_sequence
246
262
  end
247
263
 
248
264
  # Returns +text+ transformed into HTML using simple formatting rules.
@@ -309,7 +325,7 @@ module ActionView
309
325
  # <table>
310
326
  # <% @items.each do |item| %>
311
327
  # <tr class="<%= cycle("odd", "even") -%>">
312
- # <td>item</td>
328
+ # <td><%= item %></td>
313
329
  # </tr>
314
330
  # <% end %>
315
331
  # </table>
@@ -6,35 +6,56 @@ module ActionView
6
6
  # = Action View Translation Helpers
7
7
  module Helpers
8
8
  module TranslationHelper
9
+ extend ActiveSupport::Concern
10
+
9
11
  include TagHelper
10
- # Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
12
+
13
+ included do
14
+ mattr_accessor :debug_missing_translation
15
+ self.debug_missing_translation = true
16
+ end
17
+
18
+ # Delegates to <tt>I18n#translate</tt> but also performs three additional
19
+ # functions.
20
+ #
21
+ # First, it will ensure that any thrown +MissingTranslation+ messages will
22
+ # be rendered as inline spans that:
23
+ #
24
+ # * Have a <tt>translation-missing</tt> class applied
25
+ # * Contain the missing key as the value of the +title+ attribute
26
+ # * Have a titleized version of the last key segment as text
11
27
  #
12
- # First, it will ensure that any thrown +MissingTranslation+ messages will be turned
13
- # into inline spans that:
28
+ # For example, the value returned for the missing translation key
29
+ # <tt>"blog.post.title"</tt> will be:
14
30
  #
15
- # * have a "translation-missing" class set,
16
- # * contain the missing key as a title attribute and
17
- # * a titleized version of the last key segment as a text.
31
+ # <span
32
+ # class="translation_missing"
33
+ # title="translation missing: en.blog.post.title">Title</span>
18
34
  #
19
- # E.g. the value returned for a missing translation key :"blog.post.title" will be
20
- # <span class="translation_missing" title="translation missing: en.blog.post.title">Title</span>.
21
- # This way your views will display rather reasonable strings but it will still
22
- # be easy to spot missing translations.
35
+ # This allows for views to display rather reasonable strings while still
36
+ # giving developers a way to find missing translations.
23
37
  #
24
- # Second, it'll scope the key by the current partial if the key starts
25
- # with a period. So if you call <tt>translate(".foo")</tt> from the
26
- # <tt>people/index.html.erb</tt> template, you'll actually be calling
27
- # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
28
- # to translate many keys within the same partials and gives you a simple framework
29
- # for scoping them consistently. If you don't prepend the key with a period,
30
- # nothing is converted.
38
+ # If you would prefer missing translations to raise an error, you can
39
+ # opt out of span-wrapping behavior globally by setting
40
+ # <tt>ActionView::Base.raise_on_missing_translations = true</tt> or
41
+ # individually by passing <tt>raise: true</tt> as an option to
42
+ # <tt>translate</tt>.
31
43
  #
32
- # Third, it'll mark the translation as safe HTML if the key has the suffix
33
- # "_html" or the last element of the key is the word "html". For example,
34
- # calling translate("footer_html") or translate("footer.html") will return
35
- # a safe HTML string that won't be escaped by other HTML helper methods. This
36
- # naming convention helps to identify translations that include HTML tags so that
37
- # you know what kind of output to expect when you call translate in a template.
44
+ # Second, if the key starts with a period <tt>translate</tt> will scope
45
+ # the key by the current partial. Calling <tt>translate(".foo")</tt> from
46
+ # the <tt>people/index.html.erb</tt> template is equivalent to calling
47
+ # <tt>translate("people.index.foo")</tt>. This makes it less
48
+ # repetitive to translate many keys within the same partial and provides
49
+ # a convention to scope keys consistently.
50
+ #
51
+ # Third, the translation will be marked as <tt>html_safe</tt> if the key
52
+ # has the suffix "_html" or the last element of the key is "html". Calling
53
+ # <tt>translate("footer_html")</tt> or <tt>translate("footer.html")</tt>
54
+ # will return an HTML safe string that won't be escaped by other HTML
55
+ # helper methods. This naming convention helps to identify translations
56
+ # that include HTML tags so that you know what kind of output to expect
57
+ # when you call translate in a template and translators know which keys
58
+ # they can provide HTML values for.
38
59
  def translate(key, options = {})
39
60
  options = options.dup
40
61
  has_default = options.has_key?(:default)
@@ -47,11 +68,11 @@ module ActionView
47
68
  # If the user has explicitly decided to NOT raise errors, pass that option to I18n.
48
69
  # Otherwise, tell I18n to raise an exception, which we rescue further in this method.
49
70
  # Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
50
- if options[:raise] == false || (options.key?(:rescue_format) && options[:rescue_format].nil?)
71
+ if options[:raise] == false
51
72
  raise_error = false
52
73
  i18n_raise = false
53
74
  else
54
- raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations
75
+ raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations
55
76
  i18n_raise = true
56
77
  end
57
78
 
@@ -75,7 +96,16 @@ module ActionView
75
96
  raise e if raise_error
76
97
 
77
98
  keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
78
- content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
99
+ title = "translation missing: #{keys.join('.')}"
100
+
101
+ interpolations = options.except(:default, :scope)
102
+ if interpolations.any?
103
+ title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(', ')
104
+ end
105
+
106
+ return title unless ActionView::Base.debug_missing_translation
107
+
108
+ content_tag('span', keys.last.to_s.titleize, class: 'translation_missing', title: title)
79
109
  end
80
110
  end
81
111
  alias :t :translate
@@ -41,14 +41,24 @@ module ActionView
41
41
  end
42
42
 
43
43
  def _back_url # :nodoc:
44
- referrer = controller.respond_to?(:request) && controller.request.env["HTTP_REFERER"]
45
- referrer || 'javascript:history.back()'
44
+ _filtered_referrer || 'javascript:history.back()'
46
45
  end
47
46
  protected :_back_url
48
47
 
49
- # Creates a link tag of the given +name+ using a URL created by the set of +options+.
48
+ def _filtered_referrer # :nodoc:
49
+ if controller.respond_to?(:request)
50
+ referrer = controller.request.env["HTTP_REFERER"]
51
+ if referrer && URI(referrer).scheme != 'javascript'
52
+ referrer
53
+ end
54
+ end
55
+ rescue URI::InvalidURIError
56
+ end
57
+ protected :_filtered_referrer
58
+
59
+ # Creates an anchor element of the given +name+ using a URL created by the set of +options+.
50
60
  # See the valid options in the documentation for +url_for+. It's also possible to
51
- # pass a String instead of an options hash, which generates a link tag that uses the
61
+ # pass a String instead of an options hash, which generates an anchor element that uses the
52
62
  # value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead
53
63
  # of an options hash will generate a link to the referrer (a JavaScript back link
54
64
  # will be used in place of a referrer if none exists). If +nil+ is passed as the name
@@ -172,6 +182,11 @@ module ActionView
172
182
  #
173
183
  # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
174
184
  # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a>
185
+ #
186
+ # Also you can set any link attributes such as <tt>target</tt>, <tt>rel</tt>, <tt>type</tt>:
187
+ #
188
+ # link_to "External link", "http://www.rubyonrails.org/", target: "_blank", rel: "nofollow"
189
+ # # => <a href="http://www.rubyonrails.org/" target="_blank" rel="nofollow">External link</a>
175
190
  def link_to(name = nil, options = nil, html_options = nil, &block)
176
191
  html_options, options, name = options, name, block if block_given?
177
192
  options ||= {}
@@ -179,9 +194,9 @@ module ActionView
179
194
  html_options = convert_options_to_data_attributes(options, html_options)
180
195
 
181
196
  url = url_for(options)
182
- html_options['href'] ||= url
197
+ html_options["href".freeze] ||= url
183
198
 
184
- content_tag(:a, name || url, html_options, &block)
199
+ content_tag("a".freeze, name || url, html_options, &block)
185
200
  end
186
201
 
187
202
  # Generates a form containing a single button that submits to the URL created
@@ -280,24 +295,28 @@ module ActionView
280
295
  html_options, options = options, name if block_given?
281
296
  options ||= {}
282
297
  html_options ||= {}
283
-
284
298
  html_options = html_options.stringify_keys
285
- convert_boolean_attributes!(html_options, %w(disabled))
286
299
 
287
300
  url = options.is_a?(String) ? options : url_for(options)
288
301
  remote = html_options.delete('remote')
289
302
  params = html_options.delete('params')
290
303
 
291
304
  method = html_options.delete('method').to_s
292
- method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : ''.html_safe
305
+ method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : ''.freeze.html_safe
293
306
 
294
307
  form_method = method == 'get' ? 'get' : 'post'
295
308
  form_options = html_options.delete('form') || {}
296
309
  form_options[:class] ||= html_options.delete('form_class') || 'button_to'
297
- form_options.merge!(method: form_method, action: url)
298
- form_options.merge!("data-remote" => "true") if remote
310
+ form_options[:method] = form_method
311
+ form_options[:action] = url
312
+ form_options[:'data-remote'] = true if remote
299
313
 
300
- request_token_tag = form_method == 'post' ? token_tag : ''
314
+ request_token_tag = if form_method == 'post'
315
+ request_method = method.empty? ? 'post' : method
316
+ token_tag(nil, form_options: { action: url, method: request_method })
317
+ else
318
+ ''.freeze
319
+ end
301
320
 
302
321
  html_options = convert_options_to_data_attributes(options, html_options)
303
322
  html_options['type'] = 'submit'
@@ -311,8 +330,8 @@ module ActionView
311
330
 
312
331
  inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
313
332
  if params
314
- params.each do |param_name, value|
315
- inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param)
333
+ to_form_params(params).each do |param|
334
+ inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value])
316
335
  end
317
336
  end
318
337
  content_tag('form', inner_tags, form_options)
@@ -428,6 +447,7 @@ module ActionView
428
447
  # * <tt>:body</tt> - Preset the body of the email.
429
448
  # * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
430
449
  # * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
450
+ # * <tt>:reply_to</tt> - Preset the Reply-To field of the email.
431
451
  #
432
452
  # ==== Obfuscation
433
453
  # Prior to Rails 4.0, +mail_to+ provided options for encoding the address
@@ -457,72 +477,60 @@ module ActionView
457
477
  html_options, name = name, nil if block_given?
458
478
  html_options = (html_options || {}).stringify_keys
459
479
 
460
- extras = %w{ cc bcc body subject }.map! { |item|
461
- option = html_options.delete(item) || next
462
- "#{item}=#{Rack::Utils.escape_path(option)}"
480
+ extras = %w{ cc bcc body subject reply_to }.map! { |item|
481
+ option = html_options.delete(item).presence || next
482
+ "#{item.dasherize}=#{ERB::Util.url_encode(option)}"
463
483
  }.compact
464
- extras = extras.empty? ? '' : '?' + extras.join('&')
484
+ extras = extras.empty? ? ''.freeze : '?' + extras.join('&')
465
485
 
466
- encoded_email_address = ERB::Util.url_encode(email_address ? email_address.to_str : '').gsub("%40", "@")
486
+ encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
467
487
  html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
468
488
 
469
- content_tag(:a, name || email_address, html_options, &block)
489
+ content_tag("a".freeze, name || email_address, html_options, &block)
470
490
  end
471
491
 
472
492
  # True if the current request URI was generated by the given +options+.
473
493
  #
474
494
  # ==== Examples
475
- # Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc</tt> action.
495
+ # Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc&page=1</tt> action.
476
496
  #
477
497
  # current_page?(action: 'process')
478
498
  # # => false
479
499
  #
480
- # current_page?(controller: 'shop', action: 'checkout')
481
- # # => true
482
- #
483
- # current_page?(controller: 'shop', action: 'checkout', order: 'asc')
484
- # # => false
485
- #
486
500
  # current_page?(action: 'checkout')
487
501
  # # => true
488
502
  #
489
503
  # current_page?(controller: 'library', action: 'checkout')
490
504
  # # => false
491
505
  #
492
- # current_page?('http://www.example.com/shop/checkout')
493
- # # => true
494
- #
495
- # current_page?('/shop/checkout')
506
+ # current_page?(controller: 'shop', action: 'checkout')
496
507
  # # => true
497
508
  #
498
- # Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc&page=1</tt> action.
499
- #
500
- # current_page?(action: 'process')
509
+ # current_page?(controller: 'shop', action: 'checkout', order: 'asc')
501
510
  # # => false
502
511
  #
503
- # current_page?(controller: 'shop', action: 'checkout')
504
- # # => true
505
- #
506
512
  # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1')
507
513
  # # => true
508
514
  #
509
515
  # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2')
510
516
  # # => false
511
517
  #
512
- # current_page?(controller: 'shop', action: 'checkout', order: 'desc')
513
- # # => false
518
+ # current_page?('http://www.example.com/shop/checkout')
519
+ # # => true
514
520
  #
515
- # current_page?(action: 'checkout')
521
+ # current_page?('/shop/checkout')
516
522
  # # => true
517
523
  #
518
- # current_page?(controller: 'library', action: 'checkout')
519
- # # => false
524
+ # current_page?('http://www.example.com/shop/checkout?order=desc&page=1')
525
+ # # => true
520
526
  #
521
527
  # Let's say we're in the <tt>http://www.example.com/products</tt> action with method POST in case of invalid product.
522
528
  #
523
529
  # current_page?(controller: 'product', action: 'index')
524
530
  # # => false
525
531
  #
532
+ # We can also pass in the symbol arguments instead of strings.
533
+ #
526
534
  def current_page?(options)
527
535
  unless request
528
536
  raise "You cannot use helpers that need to determine the current " \
@@ -540,6 +548,8 @@ module ActionView
540
548
  request_uri = url_string.index("?") ? request.fullpath : request.path
541
549
  request_uri = URI.parser.unescape(request_uri).force_encoding(Encoding::BINARY)
542
550
 
551
+ url_string.chomp!("/") if url_string.start_with?("/") && url_string != "/"
552
+
543
553
  if url_string =~ /^\w+:\/\//
544
554
  url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
545
555
  else
@@ -551,70 +561,89 @@ module ActionView
551
561
  def convert_options_to_data_attributes(options, html_options)
552
562
  if html_options
553
563
  html_options = html_options.stringify_keys
554
- html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options)
564
+ html_options['data-remote'] = 'true'.freeze if link_to_remote_options?(options) || link_to_remote_options?(html_options)
555
565
 
556
- method = html_options.delete('method')
566
+ method = html_options.delete('method'.freeze)
557
567
 
558
568
  add_method_to_attributes!(html_options, method) if method
559
569
 
560
570
  html_options
561
571
  else
562
- link_to_remote_options?(options) ? {'data-remote' => 'true'} : {}
572
+ link_to_remote_options?(options) ? {'data-remote' => 'true'.freeze} : {}
563
573
  end
564
574
  end
565
575
 
566
576
  def link_to_remote_options?(options)
567
577
  if options.is_a?(Hash)
568
- options.delete('remote') || options.delete(:remote)
578
+ options.delete('remote'.freeze) || options.delete(:remote)
569
579
  end
570
580
  end
571
581
 
572
582
  def add_method_to_attributes!(html_options, method)
573
- if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/
574
- html_options["rel"] = "#{html_options["rel"]} nofollow".lstrip
583
+ if method && method.to_s.downcase != "get".freeze && html_options["rel".freeze] !~ /nofollow/
584
+ html_options["rel".freeze] = "#{html_options["rel".freeze]} nofollow".lstrip
575
585
  end
576
- html_options["data-method"] = method
586
+ html_options["data-method".freeze] = method
577
587
  end
578
588
 
579
- # Processes the +html_options+ hash, converting the boolean
580
- # attributes from true/false form into the form required by
581
- # HTML/XHTML. (An attribute is considered to be boolean if
582
- # its name is listed in the given +bool_attrs+ array.)
583
- #
584
- # More specifically, for each boolean attribute in +html_options+
585
- # given as:
589
+ def token_tag(token=nil, form_options: {})
590
+ if token != false && protect_against_forgery?
591
+ token ||= form_authenticity_token(form_options: form_options)
592
+ tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
593
+ else
594
+ ''.freeze
595
+ end
596
+ end
597
+
598
+ def method_tag(method)
599
+ tag('input', type: 'hidden', name: '_method', value: method.to_s)
600
+ end
601
+
602
+ # Returns an array of hashes each containing :name and :value keys
603
+ # suitable for use as the names and values of form input fields:
586
604
  #
587
- # "attr" => bool_value
605
+ # to_form_params(name: 'David', nationality: 'Danish')
606
+ # # => [{name: :name, value: 'David'}, {name: 'nationality', value: 'Danish'}]
588
607
  #
589
- # if the associated +bool_value+ evaluates to true, it is
590
- # replaced with the attribute's name; otherwise the attribute is
591
- # removed from the +html_options+ hash. (See the XHTML 1.0 spec,
592
- # section 4.5 "Attribute Minimization" for more:
593
- # http://www.w3.org/TR/xhtml1/#h-4.5)
608
+ # to_form_params(country: {name: 'Denmark'})
609
+ # # => [{name: 'country[name]', value: 'Denmark'}]
594
610
  #
595
- # Returns the updated +html_options+ hash, which is also modified
596
- # in place.
611
+ # to_form_params(countries: ['Denmark', 'Sweden']})
612
+ # # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}]
597
613
  #
598
- # Example:
614
+ # An optional namespace can be passed to enclose key names:
599
615
  #
600
- # convert_boolean_attributes!( html_options,
601
- # %w( checked disabled readonly ) )
602
- def convert_boolean_attributes!(html_options, bool_attrs)
603
- bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) }
604
- html_options
605
- end
616
+ # to_form_params({ name: 'Denmark' }, 'country')
617
+ # # => [{name: 'country[name]', value: 'Denmark'}]
618
+ def to_form_params(attribute, namespace = nil) # :nodoc:
619
+ attribute = if attribute.respond_to?(:permitted?)
620
+ unless attribute.permitted?
621
+ raise ArgumentError, "Attempting to generate a buttom from non-sanitized request parameters!" \
622
+ " Whitelist and sanitize passed parameters to be secure."
623
+ end
624
+
625
+ attribute.to_h
626
+ else
627
+ attribute
628
+ end
606
629
 
607
- def token_tag(token=nil)
608
- if token != false && protect_against_forgery?
609
- token ||= form_authenticity_token
610
- tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
630
+ params = []
631
+ case attribute
632
+ when Hash
633
+ attribute.each do |key, value|
634
+ prefix = namespace ? "#{namespace}[#{key}]" : key
635
+ params.push(*to_form_params(value, prefix))
636
+ end
637
+ when Array
638
+ array_prefix = "#{namespace}[]"
639
+ attribute.each do |value|
640
+ params.push(*to_form_params(value, array_prefix))
641
+ end
611
642
  else
612
- ''
643
+ params << { name: namespace, value: attribute.to_param }
613
644
  end
614
- end
615
645
 
616
- def method_tag(method)
617
- tag('input', type: 'hidden', name: '_method', value: method.to_s)
646
+ params.sort_by { |pair| pair[:name] }
618
647
  end
619
648
  end
620
649
  end