actionview 6.0.0.beta1 → 6.1.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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +273 -119
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  5. data/lib/action_view/base.rb +81 -15
  6. data/lib/action_view/cache_expiry.rb +52 -0
  7. data/lib/action_view/context.rb +0 -5
  8. data/lib/action_view/dependency_tracker.rb +10 -4
  9. data/lib/action_view/digestor.rb +11 -19
  10. data/lib/action_view/flows.rb +0 -1
  11. data/lib/action_view/gem_version.rb +3 -3
  12. data/lib/action_view/helpers/active_model_helper.rb +0 -1
  13. data/lib/action_view/helpers/asset_tag_helper.rb +62 -22
  14. data/lib/action_view/helpers/asset_url_helper.rb +6 -4
  15. data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
  16. data/lib/action_view/helpers/cache_helper.rb +16 -23
  17. data/lib/action_view/helpers/csp_helper.rb +4 -2
  18. data/lib/action_view/helpers/date_helper.rb +5 -6
  19. data/lib/action_view/helpers/form_helper.rb +70 -34
  20. data/lib/action_view/helpers/form_options_helper.rb +10 -18
  21. data/lib/action_view/helpers/form_tag_helper.rb +12 -9
  22. data/lib/action_view/helpers/javascript_helper.rb +7 -5
  23. data/lib/action_view/helpers/number_helper.rb +9 -8
  24. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  25. data/lib/action_view/helpers/rendering_helper.rb +17 -7
  26. data/lib/action_view/helpers/sanitize_helper.rb +10 -16
  27. data/lib/action_view/helpers/tag_helper.rb +94 -19
  28. data/lib/action_view/helpers/tags/base.rb +10 -7
  29. data/lib/action_view/helpers/tags/check_box.rb +0 -1
  30. data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -1
  31. data/lib/action_view/helpers/tags/collection_helpers.rb +0 -1
  32. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -1
  33. data/lib/action_view/helpers/tags/color_field.rb +0 -1
  34. data/lib/action_view/helpers/tags/date_field.rb +1 -2
  35. data/lib/action_view/helpers/tags/date_select.rb +2 -3
  36. data/lib/action_view/helpers/tags/datetime_field.rb +0 -1
  37. data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -2
  38. data/lib/action_view/helpers/tags/label.rb +4 -1
  39. data/lib/action_view/helpers/tags/month_field.rb +1 -2
  40. data/lib/action_view/helpers/tags/radio_button.rb +0 -1
  41. data/lib/action_view/helpers/tags/select.rb +1 -2
  42. data/lib/action_view/helpers/tags/text_field.rb +0 -1
  43. data/lib/action_view/helpers/tags/time_field.rb +1 -2
  44. data/lib/action_view/helpers/tags/week_field.rb +1 -2
  45. data/lib/action_view/helpers/text_helper.rb +2 -3
  46. data/lib/action_view/helpers/translation_helper.rb +98 -51
  47. data/lib/action_view/helpers/url_helper.rb +124 -16
  48. data/lib/action_view/layouts.rb +8 -10
  49. data/lib/action_view/log_subscriber.rb +26 -11
  50. data/lib/action_view/lookup_context.rb +59 -31
  51. data/lib/action_view/path_set.rb +3 -12
  52. data/lib/action_view/railtie.rb +39 -41
  53. data/lib/action_view/record_identifier.rb +0 -1
  54. data/lib/action_view/renderer/abstract_renderer.rb +142 -11
  55. data/lib/action_view/renderer/collection_renderer.rb +196 -0
  56. data/lib/action_view/renderer/object_renderer.rb +34 -0
  57. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +35 -29
  58. data/lib/action_view/renderer/partial_renderer.rb +21 -273
  59. data/lib/action_view/renderer/renderer.rb +59 -4
  60. data/lib/action_view/renderer/streaming_template_renderer.rb +9 -7
  61. data/lib/action_view/renderer/template_renderer.rb +35 -27
  62. data/lib/action_view/rendering.rb +49 -29
  63. data/lib/action_view/routing_url_for.rb +1 -1
  64. data/lib/action_view/template/error.rb +30 -15
  65. data/lib/action_view/template/handlers/builder.rb +2 -2
  66. data/lib/action_view/template/handlers/erb/erubi.rb +15 -9
  67. data/lib/action_view/template/handlers/erb.rb +14 -19
  68. data/lib/action_view/template/handlers/html.rb +1 -1
  69. data/lib/action_view/template/handlers/raw.rb +2 -2
  70. data/lib/action_view/template/handlers.rb +1 -1
  71. data/lib/action_view/template/html.rb +5 -6
  72. data/lib/action_view/template/inline.rb +22 -0
  73. data/lib/action_view/template/raw_file.rb +25 -0
  74. data/lib/action_view/template/renderable.rb +24 -0
  75. data/lib/action_view/template/resolver.rb +141 -140
  76. data/lib/action_view/template/sources/file.rb +17 -0
  77. data/lib/action_view/template/sources.rb +13 -0
  78. data/lib/action_view/template/text.rb +2 -3
  79. data/lib/action_view/template.rb +49 -75
  80. data/lib/action_view/test_case.rb +20 -28
  81. data/lib/action_view/testing/resolvers.rb +18 -27
  82. data/lib/action_view/unbound_template.rb +31 -0
  83. data/lib/action_view/view_paths.rb +59 -38
  84. data/lib/action_view.rb +7 -2
  85. data/lib/assets/compiled/rails-ujs.js +25 -16
  86. metadata +30 -18
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "action_view/helpers/tag_helper"
4
- require "active_support/core_ext/string/access"
5
- require "i18n/exceptions"
4
+ require "active_support/core_ext/symbol/starts_ends_with"
6
5
 
7
6
  module ActionView
8
7
  # = Action View Translation Helpers
@@ -57,78 +56,87 @@ module ActionView
57
56
  # that include HTML tags so that you know what kind of output to expect
58
57
  # when you call translate in a template and translators know which keys
59
58
  # they can provide HTML values for.
60
- def translate(key, options = {})
61
- options = options.dup
62
- if options.has_key?(:default)
63
- remaining_defaults = Array(options.delete(:default)).compact
64
- options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol)
65
- end
59
+ #
60
+ # To access the translated text along with the fully resolved
61
+ # translation key, <tt>translate</tt> accepts a block:
62
+ #
63
+ # <%= translate(".relative_key") do |translation, resolved_key| %>
64
+ # <span title="<%= resolved_key %>"><%= translation %></span>
65
+ # <% end %>
66
+ #
67
+ # This enables annotate translated text to be aware of the scope it was
68
+ # resolved against.
69
+ #
70
+ def translate(key, **options)
71
+ return key.map { |k| translate(k, **options) } if key.is_a?(Array)
72
+ key = key&.to_s unless key.is_a?(Symbol)
66
73
 
67
- # If the user has explicitly decided to NOT raise errors, pass that option to I18n.
68
- # Otherwise, tell I18n to raise an exception, which we rescue further in this method.
69
- # Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
70
- if options[:raise] == false
71
- raise_error = false
72
- i18n_raise = false
73
- else
74
- raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations
75
- i18n_raise = true
74
+ alternatives = if options.key?(:default)
75
+ options[:default].is_a?(Array) ? options.delete(:default).compact : [options.delete(:default)]
76
76
  end
77
77
 
78
- if html_safe_translation_key?(key)
79
- html_safe_options = options.dup
80
- options.except(*I18n::RESERVED_KEYS).each do |name, value|
81
- unless name == :count && value.is_a?(Numeric)
82
- html_safe_options[name] = ERB::Util.html_escape(value.to_s)
83
- end
78
+ options[:raise] = true if options[:raise].nil? && ActionView::Base.raise_on_missing_translations
79
+ default = MISSING_TRANSLATION
80
+
81
+ translation = while key || alternatives.present?
82
+ if alternatives.blank? && !options[:raise].nil?
83
+ default = NO_DEFAULT # let I18n handle missing translation
84
84
  end
85
- translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
86
- if translation.respond_to?(:map)
87
- translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
85
+
86
+ key = scope_key_by_partial(key)
87
+
88
+ if html_safe_translation_key?(key)
89
+ html_safe_options ||= html_escape_translation_options(options)
90
+ translated = I18n.translate(key, **html_safe_options, default: default)
91
+ break html_safe_translation(translated) unless translated.equal?(MISSING_TRANSLATION)
88
92
  else
89
- translation.respond_to?(:html_safe) ? translation.html_safe : translation
93
+ translated = I18n.translate(key, **options, default: default)
94
+ break translated unless translated.equal?(MISSING_TRANSLATION)
90
95
  end
91
- else
92
- I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
93
- end
94
- rescue I18n::MissingTranslationData => e
95
- if remaining_defaults.present?
96
- translate remaining_defaults.shift, options.merge(default: remaining_defaults)
97
- else
98
- raise e if raise_error
99
-
100
- keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
101
- title = +"translation missing: #{keys.join('.')}"
102
-
103
- interpolations = options.except(:default, :scope)
104
- if interpolations.any?
105
- title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(", ")
96
+
97
+ if alternatives.present? && !alternatives.first.is_a?(Symbol)
98
+ break alternatives.first && I18n.translate(**options, default: alternatives)
106
99
  end
107
100
 
108
- return title unless ActionView::Base.debug_missing_translation
101
+ first_key ||= key
102
+ key = alternatives&.shift
103
+ end
109
104
 
110
- content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
105
+ if key.nil? && !first_key.nil?
106
+ translation = missing_translation(first_key, options)
107
+ key = first_key
111
108
  end
109
+
110
+ block_given? ? yield(translation, key) : translation
112
111
  end
113
112
  alias :t :translate
114
113
 
115
114
  # Delegates to <tt>I18n.localize</tt> with no additional functionality.
116
115
  #
117
- # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
116
+ # See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
118
117
  # for more information.
119
- def localize(*args)
120
- I18n.localize(*args)
118
+ def localize(object, **options)
119
+ I18n.localize(object, **options)
121
120
  end
122
121
  alias :l :localize
123
122
 
124
123
  private
124
+ MISSING_TRANSLATION = Object.new
125
+ private_constant :MISSING_TRANSLATION
126
+
127
+ NO_DEFAULT = [].freeze
128
+ private_constant :NO_DEFAULT
129
+
130
+ def self.i18n_option?(name)
131
+ (@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name)
132
+ end
133
+
125
134
  def scope_key_by_partial(key)
126
- stringified_key = key.to_s
127
- if stringified_key.first == "."
135
+ if key&.start_with?(".")
128
136
  if @virtual_path
129
137
  @_scope_key_by_partial_cache ||= {}
130
138
  @_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
131
- "#{@_scope_key_by_partial_cache[@virtual_path]}#{stringified_key}"
139
+ "#{@_scope_key_by_partial_cache[@virtual_path]}#{key}"
132
140
  else
133
141
  raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
134
142
  end
@@ -137,8 +145,47 @@ module ActionView
137
145
  end
138
146
  end
139
147
 
148
+ def html_escape_translation_options(options)
149
+ return options if options.empty?
150
+ html_safe_options = options.dup
151
+
152
+ options.each do |name, value|
153
+ unless TranslationHelper.i18n_option?(name) || (name == :count && value.is_a?(Numeric))
154
+ html_safe_options[name] = ERB::Util.html_escape(value.to_s)
155
+ end
156
+ end
157
+
158
+ html_safe_options
159
+ end
160
+
140
161
  def html_safe_translation_key?(key)
141
- /(\b|_|\.)html$/.match?(key.to_s)
162
+ /(?:_|\b)html\z/.match?(key)
163
+ end
164
+
165
+ def html_safe_translation(translation)
166
+ if translation.respond_to?(:map)
167
+ translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
168
+ else
169
+ translation.respond_to?(:html_safe) ? translation.html_safe : translation
170
+ end
171
+ end
172
+
173
+ def missing_translation(key, options)
174
+ keys = I18n.normalize_keys(options[:locale] || I18n.locale, key, options[:scope])
175
+
176
+ title = +"translation missing: #{keys.join(".")}"
177
+
178
+ options.each do |name, value|
179
+ unless name == :scope
180
+ title << ", " << name.to_s << ": " << ERB::Util.html_escape(value)
181
+ end
182
+ end
183
+
184
+ if ActionView::Base.debug_missing_translation
185
+ content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
186
+ else
187
+ title
188
+ end
142
189
  end
143
190
  end
144
191
  end
@@ -45,7 +45,7 @@ module ActionView
45
45
  def _back_url # :nodoc:
46
46
  _filtered_referrer || "javascript:history.back()"
47
47
  end
48
- protected :_back_url
48
+ private :_back_url
49
49
 
50
50
  def _filtered_referrer # :nodoc:
51
51
  if controller.respond_to?(:request)
@@ -56,12 +56,12 @@ module ActionView
56
56
  end
57
57
  rescue URI::InvalidURIError
58
58
  end
59
- protected :_filtered_referrer
59
+ private :_filtered_referrer
60
60
 
61
61
  # Creates an anchor element of the given +name+ using a URL created by the set of +options+.
62
62
  # See the valid options in the documentation for +url_for+. It's also possible to
63
- # pass a String instead of an options hash, which generates an anchor element that uses the
64
- # value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead
63
+ # pass a \String instead of an options hash, which generates an anchor element that uses the
64
+ # value of the \String as the href for the link. Using a <tt>:back</tt> \Symbol instead
65
65
  # of an options hash will generate a link to the referrer (a JavaScript back link
66
66
  # will be used in place of a referrer if none exists). If +nil+ is passed as the name
67
67
  # the value of the link itself will become the name.
@@ -177,7 +177,7 @@ module ActionView
177
177
  # # => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>
178
178
  #
179
179
  # link_to "Nonsense search", searches_path(foo: "bar", baz: "quux")
180
- # # => <a href="/searches?foo=bar&amp;baz=quux">Nonsense search</a>
180
+ # # => <a href="/searches?foo=bar&baz=quux">Nonsense search</a>
181
181
  #
182
182
  # The only option specific to +link_to+ (<tt>:method</tt>) is used as follows:
183
183
  #
@@ -226,7 +226,7 @@ module ActionView
226
226
  # The +options+ hash accepts the same options as +url_for+.
227
227
  #
228
228
  # There are a few special +html_options+:
229
- # * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
229
+ # * <tt>:method</tt> - \Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
230
230
  # <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
231
231
  # * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
232
232
  # * <tt>:data</tt> - This option can be used to add custom data attributes.
@@ -235,7 +235,7 @@ module ActionView
235
235
  # * <tt>:form</tt> - This hash will be form attributes
236
236
  # * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
237
237
  # be placed
238
- # * <tt>:params</tt> - Hash of parameters to be rendered as hidden fields within the form.
238
+ # * <tt>:params</tt> - \Hash of parameters to be rendered as hidden fields within the form.
239
239
  #
240
240
  # ==== Data attributes
241
241
  #
@@ -253,7 +253,7 @@ module ActionView
253
253
  # # <input value="New" type="submit" />
254
254
  # # </form>"
255
255
  #
256
- # <%= button_to "New", new_articles_path %>
256
+ # <%= button_to "New", new_article_path %>
257
257
  # # => "<form method="post" action="/articles/new" class="button_to">
258
258
  # # <input value="New" type="submit" />
259
259
  # # </form>"
@@ -290,7 +290,7 @@ module ActionView
290
290
  #
291
291
  #
292
292
  # <%= button_to('Destroy', 'http://www.example.com',
293
- # method: "delete", remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %>
293
+ # method: :delete, remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %>
294
294
  # # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
295
295
  # # <input name='_method' value='delete' type='hidden' />
296
296
  # # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
@@ -412,8 +412,7 @@ module ActionView
412
412
  # Creates a link tag of the given +name+ using a URL created by the set of
413
413
  # +options+ if +condition+ is true, otherwise only the name is
414
414
  # returned. To specialize the default behavior, you can pass a block that
415
- # accepts the name or the full argument list for +link_to_unless+ (see the examples
416
- # in +link_to_unless+).
415
+ # accepts the name or the full argument list for +link_to_if+.
417
416
  #
418
417
  # ==== Examples
419
418
  # <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %>
@@ -540,7 +539,7 @@ module ActionView
540
539
  #
541
540
  # We can also pass in the symbol arguments instead of strings.
542
541
  #
543
- def current_page?(options, check_parameters: false)
542
+ def current_page?(options = nil, check_parameters: false, **options_as_kwargs)
544
543
  unless request
545
544
  raise "You cannot use helpers that need to determine the current " \
546
545
  "page unless your view context provides a Request object " \
@@ -549,15 +548,16 @@ module ActionView
549
548
 
550
549
  return false unless request.get? || request.head?
551
550
 
551
+ options ||= options_as_kwargs
552
552
  check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters)
553
- url_string = URI.parser.unescape(url_for(options)).force_encoding(Encoding::BINARY)
553
+ url_string = URI::DEFAULT_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY)
554
554
 
555
555
  # We ignore any extra parameters in the request_uri if the
556
- # submitted url doesn't have any either. This lets the function
556
+ # submitted URL doesn't have any either. This lets the function
557
557
  # work with things like ?order=asc
558
558
  # the behaviour can be disabled with check_parameters: true
559
559
  request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
560
- request_uri = URI.parser.unescape(request_uri).force_encoding(Encoding::BINARY)
560
+ request_uri = URI::DEFAULT_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)
561
561
 
562
562
  if url_string.start_with?("/") && url_string != "/"
563
563
  url_string.chomp!("/")
@@ -571,6 +571,114 @@ module ActionView
571
571
  end
572
572
  end
573
573
 
574
+ if RUBY_VERSION.start_with?("2.7")
575
+ using Module.new {
576
+ refine UrlHelper do
577
+ alias :_current_page? :current_page?
578
+ end
579
+ }
580
+
581
+ def current_page?(*args) # :nodoc:
582
+ options = args.pop
583
+ options.is_a?(Hash) ? _current_page?(*args, **options) : _current_page?(*args, options)
584
+ end
585
+ end
586
+
587
+ # Creates an SMS anchor link tag to the specified +phone_number+, which is
588
+ # also used as the name of the link unless +name+ is specified. Additional
589
+ # HTML attributes for the link can be passed in +html_options+.
590
+ #
591
+ # When clicked, an SMS message is prepopulated with the passed phone number
592
+ # and optional +body+ value.
593
+ #
594
+ # +sms_to+ has a +body+ option for customizing the SMS message itself by
595
+ # passing special keys to +html_options+.
596
+ #
597
+ # ==== Options
598
+ # * <tt>:body</tt> - Preset the body of the message.
599
+ #
600
+ # ==== Examples
601
+ # sms_to "5155555785"
602
+ # # => <a href="sms:5155555785;">5155555785</a>
603
+ #
604
+ # sms_to "5155555785", "Text me"
605
+ # # => <a href="sms:5155555785;">Text me</a>
606
+ #
607
+ # sms_to "5155555785", "Text me",
608
+ # body: "Hello Jim I have a question about your product."
609
+ # # => <a href="sms:5155555785;?body=Hello%20Jim%20I%20have%20a%20question%20about%20your%20product">Text me</a>
610
+ #
611
+ # You can use a block as well if your link target is hard to fit into the name parameter. \ERB example:
612
+ #
613
+ # <%= sms_to "5155555785" do %>
614
+ # <strong>Text me:</strong>
615
+ # <% end %>
616
+ # # => <a href="sms:5155555785;">
617
+ # <strong>Text me:</strong>
618
+ # </a>
619
+ def sms_to(phone_number, name = nil, html_options = {}, &block)
620
+ html_options, name = name, nil if block_given?
621
+ html_options = (html_options || {}).stringify_keys
622
+
623
+ extras = %w{ body }.map! { |item|
624
+ option = html_options.delete(item).presence || next
625
+ "#{item.dasherize}=#{ERB::Util.url_encode(option)}"
626
+ }.compact
627
+ extras = extras.empty? ? "" : "?&" + extras.join("&")
628
+
629
+ encoded_phone_number = ERB::Util.url_encode(phone_number)
630
+ html_options["href"] = "sms:#{encoded_phone_number};#{extras}"
631
+
632
+ content_tag("a", name || phone_number, html_options, &block)
633
+ end
634
+
635
+ # Creates a TEL anchor link tag to the specified +phone_number+, which is
636
+ # also used as the name of the link unless +name+ is specified. Additional
637
+ # HTML attributes for the link can be passed in +html_options+.
638
+ #
639
+ # When clicked, the default app to make calls is opened, and it
640
+ # is prepopulated with the passed phone number and optional
641
+ # +country_code+ value.
642
+ #
643
+ # +phone_to+ has an optional +country_code+ option which automatically adds the country
644
+ # code as well as the + sign in the phone numer that gets prepopulated,
645
+ # for example if +country_code: "01"+ +\+01+ will be prepended to the
646
+ # phone numer, by passing special keys to +html_options+.
647
+ #
648
+ # ==== Options
649
+ # * <tt>:country_code</tt> - Prepends the country code to the number
650
+ #
651
+ # ==== Examples
652
+ # phone_to "1234567890"
653
+ # # => <a href="tel:1234567890">1234567890</a>
654
+ #
655
+ # phone_to "1234567890", "Phone me"
656
+ # # => <a href="tel:134567890">Phone me</a>
657
+ #
658
+ # phone_to "1234567890", "Phone me", country_code: "01"
659
+ # # => <a href="tel:+015155555785">Phone me</a>
660
+ #
661
+ # You can use a block as well if your link target is hard to fit into the name parameter. \ERB example:
662
+ #
663
+ # <%= phone_to "1234567890" do %>
664
+ # <strong>Phone me:</strong>
665
+ # <% end %>
666
+ # # => <a href="tel:1234567890">
667
+ # <strong>Phone me:</strong>
668
+ # </a>
669
+ def phone_to(phone_number, name = nil, html_options = {}, &block)
670
+ html_options, name = name, nil if block_given?
671
+ html_options = (html_options || {}).stringify_keys
672
+
673
+ country_code = html_options.delete("country_code").presence
674
+ country_code = country_code.nil? ? "" : "+#{ERB::Util.url_encode(country_code)}"
675
+
676
+ encoded_phone_number = ERB::Util.url_encode(phone_number)
677
+ html_options["href"] = "tel:#{country_code}#{encoded_phone_number}"
678
+
679
+ content_tag("a", name || phone_number, html_options, &block)
680
+ end
681
+
574
682
  private
575
683
  def convert_options_to_data_attributes(options, html_options)
576
684
  if html_options
@@ -594,7 +702,7 @@ module ActionView
594
702
  end
595
703
 
596
704
  def add_method_to_attributes!(html_options, method)
597
- if method_not_get_method?(method) && html_options["rel"] !~ /nofollow/
705
+ if method_not_get_method?(method) && !html_options["rel"]&.match?(/nofollow/)
598
706
  if html_options["rel"].blank?
599
707
  html_options["rel"] = "nofollow"
600
708
  else
@@ -224,7 +224,6 @@ module ActionView
224
224
  # that if no layout conditions are used, this method is not used
225
225
  module LayoutConditions # :nodoc:
226
226
  private
227
-
228
227
  # Determines whether the current action has a layout definition by
229
228
  # checking the action name against the :only and :except conditions
230
229
  # set by the <tt>layout</tt> method.
@@ -307,7 +306,7 @@ module ActionView
307
306
  RUBY
308
307
  when Proc
309
308
  define_method :_layout_from_proc, &_layout
310
- protected :_layout_from_proc
309
+ private :_layout_from_proc
311
310
  <<-RUBY
312
311
  result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'})
313
312
  return #{default_behavior} if result.nil?
@@ -322,7 +321,8 @@ module ActionView
322
321
  end
323
322
 
324
323
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
325
- def _layout(formats)
324
+ # frozen_string_literal: true
325
+ def _layout(lookup_context, formats)
326
326
  if _conditional_layout?
327
327
  #{layout_definition}
328
328
  else
@@ -334,7 +334,6 @@ module ActionView
334
334
  end
335
335
 
336
336
  private
337
-
338
337
  # If no layout is supplied, look for a template named the return
339
338
  # value of this method.
340
339
  #
@@ -372,7 +371,6 @@ module ActionView
372
371
  end
373
372
 
374
373
  private
375
-
376
374
  def _conditional_layout?
377
375
  true
378
376
  end
@@ -388,8 +386,8 @@ module ActionView
388
386
  case name
389
387
  when String then _normalize_layout(name)
390
388
  when Proc then name
391
- when true then Proc.new { |formats| _default_layout(formats, true) }
392
- when :default then Proc.new { |formats| _default_layout(formats, false) }
389
+ when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) }
390
+ when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) }
393
391
  when false, nil then nil
394
392
  else
395
393
  raise ArgumentError,
@@ -398,7 +396,7 @@ module ActionView
398
396
  end
399
397
 
400
398
  def _normalize_layout(value)
401
- value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value
399
+ value.is_a?(String) && !value.match?(/\blayouts/) ? "layouts/#{value}" : value
402
400
  end
403
401
 
404
402
  # Returns the default layout for this controller.
@@ -411,9 +409,9 @@ module ActionView
411
409
  #
412
410
  # ==== Returns
413
411
  # * <tt>template</tt> - The template object for the default layout (or +nil+)
414
- def _default_layout(formats, require_layout = false)
412
+ def _default_layout(lookup_context, formats, require_layout = false)
415
413
  begin
416
- value = _layout(formats) if action_has_layout?
414
+ value = _layout(lookup_context, formats) if action_has_layout?
417
415
  rescue NameError => e
418
416
  raise e, "Could not render layout: #{e.message}"
419
417
  end
@@ -23,7 +23,7 @@ module ActionView
23
23
  end
24
24
 
25
25
  def render_partial(event)
26
- info do
26
+ debug do
27
27
  message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
28
28
  message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
29
29
  message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
@@ -32,19 +32,26 @@ module ActionView
32
32
  end
33
33
  end
34
34
 
35
+ def render_layout(event)
36
+ info do
37
+ message = +" Rendered layout #{from_rails_root(event.payload[:identifier])}"
38
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
39
+ end
40
+ end
41
+
35
42
  def render_collection(event)
36
43
  identifier = event.payload[:identifier] || "templates"
37
44
 
38
- info do
39
- " Rendered collection of #{from_rails_root(identifier)}" \
40
- " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
45
+ debug do
46
+ message = +" Rendered collection of #{from_rails_root(identifier)}"
47
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
48
+ message << " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
49
+ message
41
50
  end
42
51
  end
43
52
 
44
53
  def start(name, id, payload)
45
- if name == "render_template.action_view"
46
- log_rendering_start(payload)
47
- end
54
+ log_rendering_start(payload, name)
48
55
 
49
56
  super
50
57
  end
@@ -54,7 +61,6 @@ module ActionView
54
61
  end
55
62
 
56
63
  private
57
-
58
64
  EMPTY = ""
59
65
  def from_rails_root(string) # :doc:
60
66
  string = string.sub(rails_root, EMPTY)
@@ -83,9 +89,18 @@ module ActionView
83
89
  end
84
90
  end
85
91
 
86
- def log_rendering_start(payload)
87
- info do
88
- message = +" Rendering #{from_rails_root(payload[:identifier])}"
92
+ def log_rendering_start(payload, name)
93
+ debug do
94
+ qualifier =
95
+ if name == "render_template.action_view"
96
+ ""
97
+ elsif name == "render_layout.action_view"
98
+ "layout "
99
+ end
100
+
101
+ return unless qualifier
102
+
103
+ message = +" Rendering #{qualifier}#{from_rails_root(payload[:identifier])}"
89
104
  message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
90
105
  message
91
106
  end