actionview 6.0.0

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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +271 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/lib/action_view.rb +98 -0
  6. data/lib/action_view/base.rb +312 -0
  7. data/lib/action_view/buffers.rb +67 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +32 -0
  10. data/lib/action_view/dependency_tracker.rb +175 -0
  11. data/lib/action_view/digestor.rb +126 -0
  12. data/lib/action_view/flows.rb +76 -0
  13. data/lib/action_view/gem_version.rb +17 -0
  14. data/lib/action_view/helpers.rb +66 -0
  15. data/lib/action_view/helpers/active_model_helper.rb +55 -0
  16. data/lib/action_view/helpers/asset_tag_helper.rb +488 -0
  17. data/lib/action_view/helpers/asset_url_helper.rb +470 -0
  18. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  19. data/lib/action_view/helpers/cache_helper.rb +271 -0
  20. data/lib/action_view/helpers/capture_helper.rb +216 -0
  21. data/lib/action_view/helpers/controller_helper.rb +36 -0
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  24. data/lib/action_view/helpers/date_helper.rb +1200 -0
  25. data/lib/action_view/helpers/debug_helper.rb +36 -0
  26. data/lib/action_view/helpers/form_helper.rb +2569 -0
  27. data/lib/action_view/helpers/form_options_helper.rb +896 -0
  28. data/lib/action_view/helpers/form_tag_helper.rb +920 -0
  29. data/lib/action_view/helpers/javascript_helper.rb +95 -0
  30. data/lib/action_view/helpers/number_helper.rb +456 -0
  31. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  32. data/lib/action_view/helpers/rendering_helper.rb +101 -0
  33. data/lib/action_view/helpers/sanitize_helper.rb +171 -0
  34. data/lib/action_view/helpers/tag_helper.rb +314 -0
  35. data/lib/action_view/helpers/tags.rb +44 -0
  36. data/lib/action_view/helpers/tags/base.rb +196 -0
  37. data/lib/action_view/helpers/tags/check_box.rb +66 -0
  38. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  39. data/lib/action_view/helpers/tags/collection_check_boxes.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_helpers.rb +119 -0
  41. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  42. data/lib/action_view/helpers/tags/collection_select.rb +30 -0
  43. data/lib/action_view/helpers/tags/color_field.rb +27 -0
  44. data/lib/action_view/helpers/tags/date_field.rb +15 -0
  45. data/lib/action_view/helpers/tags/date_select.rb +74 -0
  46. data/lib/action_view/helpers/tags/datetime_field.rb +32 -0
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +21 -0
  48. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  49. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  50. data/lib/action_view/helpers/tags/file_field.rb +10 -0
  51. data/lib/action_view/helpers/tags/grouped_collection_select.rb +31 -0
  52. data/lib/action_view/helpers/tags/hidden_field.rb +10 -0
  53. data/lib/action_view/helpers/tags/label.rb +81 -0
  54. data/lib/action_view/helpers/tags/month_field.rb +15 -0
  55. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  56. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  58. data/lib/action_view/helpers/tags/radio_button.rb +33 -0
  59. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  60. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  61. data/lib/action_view/helpers/tags/select.rb +43 -0
  62. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  63. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  64. data/lib/action_view/helpers/tags/text_field.rb +34 -0
  65. data/lib/action_view/helpers/tags/time_field.rb +15 -0
  66. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  67. data/lib/action_view/helpers/tags/time_zone_select.rb +22 -0
  68. data/lib/action_view/helpers/tags/translator.rb +39 -0
  69. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  70. data/lib/action_view/helpers/tags/week_field.rb +15 -0
  71. data/lib/action_view/helpers/text_helper.rb +486 -0
  72. data/lib/action_view/helpers/translation_helper.rb +145 -0
  73. data/lib/action_view/helpers/url_helper.rb +676 -0
  74. data/lib/action_view/layouts.rb +433 -0
  75. data/lib/action_view/locale/en.yml +56 -0
  76. data/lib/action_view/log_subscriber.rb +96 -0
  77. data/lib/action_view/lookup_context.rb +316 -0
  78. data/lib/action_view/model_naming.rb +14 -0
  79. data/lib/action_view/path_set.rb +95 -0
  80. data/lib/action_view/railtie.rb +105 -0
  81. data/lib/action_view/record_identifier.rb +112 -0
  82. data/lib/action_view/renderer/abstract_renderer.rb +108 -0
  83. data/lib/action_view/renderer/partial_renderer.rb +563 -0
  84. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  85. data/lib/action_view/renderer/renderer.rb +68 -0
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
  87. data/lib/action_view/renderer/template_renderer.rb +108 -0
  88. data/lib/action_view/rendering.rb +171 -0
  89. data/lib/action_view/routing_url_for.rb +146 -0
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template.rb +393 -0
  92. data/lib/action_view/template/error.rb +161 -0
  93. data/lib/action_view/template/handlers.rb +92 -0
  94. data/lib/action_view/template/handlers/builder.rb +25 -0
  95. data/lib/action_view/template/handlers/erb.rb +84 -0
  96. data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
  97. data/lib/action_view/template/handlers/html.rb +11 -0
  98. data/lib/action_view/template/handlers/raw.rb +11 -0
  99. data/lib/action_view/template/html.rb +43 -0
  100. data/lib/action_view/template/inline.rb +22 -0
  101. data/lib/action_view/template/raw_file.rb +28 -0
  102. data/lib/action_view/template/resolver.rb +394 -0
  103. data/lib/action_view/template/sources.rb +13 -0
  104. data/lib/action_view/template/sources/file.rb +17 -0
  105. data/lib/action_view/template/text.rb +35 -0
  106. data/lib/action_view/template/types.rb +57 -0
  107. data/lib/action_view/test_case.rb +300 -0
  108. data/lib/action_view/testing/resolvers.rb +67 -0
  109. data/lib/action_view/unbound_template.rb +32 -0
  110. data/lib/action_view/version.rb +10 -0
  111. data/lib/action_view/view_paths.rb +129 -0
  112. data/lib/assets/compiled/rails-ujs.js +746 -0
  113. metadata +260 -0
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/tag_helper"
4
+ require "active_support/core_ext/string/access"
5
+ require "i18n/exceptions"
6
+
7
+ module ActionView
8
+ # = Action View Translation Helpers
9
+ module Helpers #:nodoc:
10
+ module TranslationHelper
11
+ extend ActiveSupport::Concern
12
+
13
+ include TagHelper
14
+
15
+ included do
16
+ mattr_accessor :debug_missing_translation, default: true
17
+ end
18
+
19
+ # Delegates to <tt>I18n#translate</tt> but also performs three additional
20
+ # functions.
21
+ #
22
+ # First, it will ensure that any thrown +MissingTranslation+ messages will
23
+ # be rendered as inline spans that:
24
+ #
25
+ # * Have a <tt>translation-missing</tt> class applied
26
+ # * Contain the missing key as the value of the +title+ attribute
27
+ # * Have a titleized version of the last key segment as text
28
+ #
29
+ # For example, the value returned for the missing translation key
30
+ # <tt>"blog.post.title"</tt> will be:
31
+ #
32
+ # <span
33
+ # class="translation_missing"
34
+ # title="translation missing: en.blog.post.title">Title</span>
35
+ #
36
+ # This allows for views to display rather reasonable strings while still
37
+ # giving developers a way to find missing translations.
38
+ #
39
+ # If you would prefer missing translations to raise an error, you can
40
+ # opt out of span-wrapping behavior globally by setting
41
+ # <tt>ActionView::Base.raise_on_missing_translations = true</tt> or
42
+ # individually by passing <tt>raise: true</tt> as an option to
43
+ # <tt>translate</tt>.
44
+ #
45
+ # Second, if the key starts with a period <tt>translate</tt> will scope
46
+ # the key by the current partial. Calling <tt>translate(".foo")</tt> from
47
+ # the <tt>people/index.html.erb</tt> template is equivalent to calling
48
+ # <tt>translate("people.index.foo")</tt>. This makes it less
49
+ # repetitive to translate many keys within the same partial and provides
50
+ # a convention to scope keys consistently.
51
+ #
52
+ # Third, the translation will be marked as <tt>html_safe</tt> if the key
53
+ # has the suffix "_html" or the last element of the key is "html". Calling
54
+ # <tt>translate("footer_html")</tt> or <tt>translate("footer.html")</tt>
55
+ # will return an HTML safe string that won't be escaped by other HTML
56
+ # helper methods. This naming convention helps to identify translations
57
+ # that include HTML tags so that you know what kind of output to expect
58
+ # when you call translate in a template and translators know which keys
59
+ # 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.wrap(options.delete(:default)).compact
64
+ options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol)
65
+ end
66
+
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
76
+ end
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
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 }
88
+ else
89
+ translation.respond_to?(:html_safe) ? translation.html_safe : translation
90
+ 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(", ")
106
+ end
107
+
108
+ return title unless ActionView::Base.debug_missing_translation
109
+
110
+ content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
111
+ end
112
+ end
113
+ alias :t :translate
114
+
115
+ # Delegates to <tt>I18n.localize</tt> with no additional functionality.
116
+ #
117
+ # See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
118
+ # for more information.
119
+ def localize(*args)
120
+ I18n.localize(*args)
121
+ end
122
+ alias :l :localize
123
+
124
+ private
125
+ def scope_key_by_partial(key)
126
+ stringified_key = key.to_s
127
+ if stringified_key.first == "."
128
+ if @virtual_path
129
+ @_scope_key_by_partial_cache ||= {}
130
+ @_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
131
+ "#{@_scope_key_by_partial_cache[@virtual_path]}#{stringified_key}"
132
+ else
133
+ raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
134
+ end
135
+ else
136
+ key
137
+ end
138
+ end
139
+
140
+ def html_safe_translation_key?(key)
141
+ /(?:_|\b)html\z/.match?(key.to_s)
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,676 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view/helpers/javascript_helper"
4
+ require "active_support/core_ext/array/access"
5
+ require "active_support/core_ext/hash/keys"
6
+ require "active_support/core_ext/string/output_safety"
7
+
8
+ module ActionView
9
+ # = Action View URL Helpers
10
+ module Helpers #:nodoc:
11
+ # Provides a set of methods for making links and getting URLs that
12
+ # depend on the routing subsystem (see ActionDispatch::Routing).
13
+ # This allows you to use the same format for links in views
14
+ # and controllers.
15
+ module UrlHelper
16
+ # This helper may be included in any class that includes the
17
+ # URL helpers of a routes (routes.url_helpers). Some methods
18
+ # provided here will only work in the context of a request
19
+ # (link_to_unless_current, for instance), which must be provided
20
+ # as a method called #request on the context.
21
+ BUTTON_TAG_METHOD_VERBS = %w{patch put delete}
22
+ extend ActiveSupport::Concern
23
+
24
+ include TagHelper
25
+
26
+ module ClassMethods
27
+ def _url_for_modules
28
+ ActionView::RoutingUrlFor
29
+ end
30
+ end
31
+
32
+ # Basic implementation of url_for to allow use helpers without routes existence
33
+ def url_for(options = nil) # :nodoc:
34
+ case options
35
+ when String
36
+ options
37
+ when :back
38
+ _back_url
39
+ else
40
+ raise ArgumentError, "arguments passed to url_for can't be handled. Please require " \
41
+ "routes or provide your own implementation"
42
+ end
43
+ end
44
+
45
+ def _back_url # :nodoc:
46
+ _filtered_referrer || "javascript:history.back()"
47
+ end
48
+ protected :_back_url
49
+
50
+ def _filtered_referrer # :nodoc:
51
+ if controller.respond_to?(:request)
52
+ referrer = controller.request.env["HTTP_REFERER"]
53
+ if referrer && URI(referrer).scheme != "javascript"
54
+ referrer
55
+ end
56
+ end
57
+ rescue URI::InvalidURIError
58
+ end
59
+ protected :_filtered_referrer
60
+
61
+ # Creates an anchor element of the given +name+ using a URL created by the set of +options+.
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
65
+ # of an options hash will generate a link to the referrer (a JavaScript back link
66
+ # will be used in place of a referrer if none exists). If +nil+ is passed as the name
67
+ # the value of the link itself will become the name.
68
+ #
69
+ # ==== Signatures
70
+ #
71
+ # link_to(body, url, html_options = {})
72
+ # # url is a String; you can use URL helpers like
73
+ # # posts_path
74
+ #
75
+ # link_to(body, url_options = {}, html_options = {})
76
+ # # url_options, except :method, is passed to url_for
77
+ #
78
+ # link_to(options = {}, html_options = {}) do
79
+ # # name
80
+ # end
81
+ #
82
+ # link_to(url, html_options = {}) do
83
+ # # name
84
+ # end
85
+ #
86
+ # ==== Options
87
+ # * <tt>:data</tt> - This option can be used to add custom data attributes.
88
+ # * <tt>method: symbol of HTTP verb</tt> - This modifier will dynamically
89
+ # create an HTML form and immediately submit the form for processing using
90
+ # the HTTP verb specified. Useful for having links perform a POST operation
91
+ # in dangerous actions like deleting a record (which search bots can follow
92
+ # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
93
+ # Note that if the user has JavaScript disabled, the request will fall back
94
+ # to using GET. If <tt>href: '#'</tt> is used and the user has JavaScript
95
+ # disabled clicking the link will have no effect. If you are relying on the
96
+ # POST behavior, you should check for it in your controller's action by using
97
+ # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>patch?</tt>, or <tt>put?</tt>.
98
+ # * <tt>remote: true</tt> - This will allow the unobtrusive JavaScript
99
+ # driver to make an Ajax request to the URL in question instead of following
100
+ # the link. The drivers each provide mechanisms for listening for the
101
+ # completion of the Ajax request and performing JavaScript operations once
102
+ # they're complete
103
+ #
104
+ # ==== Data attributes
105
+ #
106
+ # * <tt>confirm: 'question?'</tt> - This will allow the unobtrusive JavaScript
107
+ # driver to prompt with the question specified (in this case, the
108
+ # resulting text would be <tt>question?</tt>. If the user accepts, the
109
+ # link is processed normally, otherwise no action is taken.
110
+ # * <tt>:disable_with</tt> - Value of this parameter will be used as the
111
+ # name for a disabled version of the link. This feature is provided by
112
+ # the unobtrusive JavaScript driver.
113
+ #
114
+ # ==== Examples
115
+ # Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments
116
+ # and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base
117
+ # your application on resources and use
118
+ #
119
+ # link_to "Profile", profile_path(@profile)
120
+ # # => <a href="/profiles/1">Profile</a>
121
+ #
122
+ # or the even pithier
123
+ #
124
+ # link_to "Profile", @profile
125
+ # # => <a href="/profiles/1">Profile</a>
126
+ #
127
+ # in place of the older more verbose, non-resource-oriented
128
+ #
129
+ # link_to "Profile", controller: "profiles", action: "show", id: @profile
130
+ # # => <a href="/profiles/show/1">Profile</a>
131
+ #
132
+ # Similarly,
133
+ #
134
+ # link_to "Profiles", profiles_path
135
+ # # => <a href="/profiles">Profiles</a>
136
+ #
137
+ # is better than
138
+ #
139
+ # link_to "Profiles", controller: "profiles"
140
+ # # => <a href="/profiles">Profiles</a>
141
+ #
142
+ # When name is +nil+ the href is presented instead
143
+ #
144
+ # link_to nil, "http://example.com"
145
+ # # => <a href="http://www.example.com">http://www.example.com</a>
146
+ #
147
+ # You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
148
+ #
149
+ # <%= link_to(@profile) do %>
150
+ # <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
151
+ # <% end %>
152
+ # # => <a href="/profiles/1">
153
+ # <strong>David</strong> -- <span>Check it out!</span>
154
+ # </a>
155
+ #
156
+ # Classes and ids for CSS are easy to produce:
157
+ #
158
+ # link_to "Articles", articles_path, id: "news", class: "article"
159
+ # # => <a href="/articles" class="article" id="news">Articles</a>
160
+ #
161
+ # Be careful when using the older argument style, as an extra literal hash is needed:
162
+ #
163
+ # link_to "Articles", { controller: "articles" }, id: "news", class: "article"
164
+ # # => <a href="/articles" class="article" id="news">Articles</a>
165
+ #
166
+ # Leaving the hash off gives the wrong link:
167
+ #
168
+ # link_to "WRONG!", controller: "articles", id: "news", class: "article"
169
+ # # => <a href="/articles/index/news?class=article">WRONG!</a>
170
+ #
171
+ # +link_to+ can also produce links with anchors or query strings:
172
+ #
173
+ # link_to "Comment wall", profile_path(@profile, anchor: "wall")
174
+ # # => <a href="/profiles/1#wall">Comment wall</a>
175
+ #
176
+ # link_to "Ruby on Rails search", controller: "searches", query: "ruby on rails"
177
+ # # => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>
178
+ #
179
+ # link_to "Nonsense search", searches_path(foo: "bar", baz: "quux")
180
+ # # => <a href="/searches?foo=bar&amp;baz=quux">Nonsense search</a>
181
+ #
182
+ # The only option specific to +link_to+ (<tt>:method</tt>) is used as follows:
183
+ #
184
+ # link_to("Destroy", "http://www.example.com", method: :delete)
185
+ # # => <a href='http://www.example.com' rel="nofollow" data-method="delete">Destroy</a>
186
+ #
187
+ # You can also use custom data attributes using the <tt>:data</tt> option:
188
+ #
189
+ # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
190
+ # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a>
191
+ #
192
+ # Also you can set any link attributes such as <tt>target</tt>, <tt>rel</tt>, <tt>type</tt>:
193
+ #
194
+ # link_to "External link", "http://www.rubyonrails.org/", target: "_blank", rel: "nofollow"
195
+ # # => <a href="http://www.rubyonrails.org/" target="_blank" rel="nofollow">External link</a>
196
+ def link_to(name = nil, options = nil, html_options = nil, &block)
197
+ html_options, options, name = options, name, block if block_given?
198
+ options ||= {}
199
+
200
+ html_options = convert_options_to_data_attributes(options, html_options)
201
+
202
+ url = url_for(options)
203
+ html_options["href"] ||= url
204
+
205
+ content_tag("a", name || url, html_options, &block)
206
+ end
207
+
208
+ # Generates a form containing a single button that submits to the URL created
209
+ # by the set of +options+. This is the safest method to ensure links that
210
+ # cause changes to your data are not triggered by search bots or accelerators.
211
+ # If the HTML button does not work with your layout, you can also consider
212
+ # using the +link_to+ method with the <tt>:method</tt> modifier as described in
213
+ # the +link_to+ documentation.
214
+ #
215
+ # By default, the generated form element has a class name of <tt>button_to</tt>
216
+ # to allow styling of the form itself and its children. This can be changed
217
+ # using the <tt>:form_class</tt> modifier within +html_options+. You can control
218
+ # the form submission and input element behavior using +html_options+.
219
+ # This method accepts the <tt>:method</tt> modifier described in the +link_to+ documentation.
220
+ # If no <tt>:method</tt> modifier is given, it will default to performing a POST operation.
221
+ # You can also disable the button by passing <tt>disabled: true</tt> in +html_options+.
222
+ # If you are using RESTful routes, you can pass the <tt>:method</tt>
223
+ # to change the HTTP verb used to submit the form.
224
+ #
225
+ # ==== Options
226
+ # The +options+ hash accepts the same options as +url_for+.
227
+ #
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>,
230
+ # <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
231
+ # * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
232
+ # * <tt>:data</tt> - This option can be used to add custom data attributes.
233
+ # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
234
+ # submit behavior. By default this behavior is an ajax submit.
235
+ # * <tt>:form</tt> - This hash will be form attributes
236
+ # * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
237
+ # be placed
238
+ # * <tt>:params</tt> - Hash of parameters to be rendered as hidden fields within the form.
239
+ #
240
+ # ==== Data attributes
241
+ #
242
+ # * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
243
+ # prompt with the question specified. If the user accepts, the link is
244
+ # processed normally, otherwise no action is taken.
245
+ # * <tt>:disable_with</tt> - Value of this parameter will be
246
+ # used as the value for a disabled version of the submit
247
+ # button when the form is submitted. This feature is provided
248
+ # by the unobtrusive JavaScript driver.
249
+ #
250
+ # ==== Examples
251
+ # <%= button_to "New", action: "new" %>
252
+ # # => "<form method="post" action="/controller/new" class="button_to">
253
+ # # <input value="New" type="submit" />
254
+ # # </form>"
255
+ #
256
+ # <%= button_to "New", new_article_path %>
257
+ # # => "<form method="post" action="/articles/new" class="button_to">
258
+ # # <input value="New" type="submit" />
259
+ # # </form>"
260
+ #
261
+ # <%= button_to [:make_happy, @user] do %>
262
+ # Make happy <strong><%= @user.name %></strong>
263
+ # <% end %>
264
+ # # => "<form method="post" action="/users/1/make_happy" class="button_to">
265
+ # # <button type="submit">
266
+ # # Make happy <strong><%= @user.name %></strong>
267
+ # # </button>
268
+ # # </form>"
269
+ #
270
+ # <%= button_to "New", { action: "new" }, form_class: "new-thing" %>
271
+ # # => "<form method="post" action="/controller/new" class="new-thing">
272
+ # # <input value="New" type="submit" />
273
+ # # </form>"
274
+ #
275
+ #
276
+ # <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %>
277
+ # # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
278
+ # # <input value="Create" type="submit" />
279
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
280
+ # # </form>"
281
+ #
282
+ #
283
+ # <%= button_to "Delete Image", { action: "delete", id: @image.id },
284
+ # method: :delete, data: { confirm: "Are you sure?" } %>
285
+ # # => "<form method="post" action="/images/delete/1" class="button_to">
286
+ # # <input type="hidden" name="_method" value="delete" />
287
+ # # <input data-confirm='Are you sure?' value="Delete Image" type="submit" />
288
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
289
+ # # </form>"
290
+ #
291
+ #
292
+ # <%= button_to('Destroy', 'http://www.example.com',
293
+ # method: "delete", remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %>
294
+ # # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
295
+ # # <input name='_method' value='delete' type='hidden' />
296
+ # # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
297
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
298
+ # # </form>"
299
+ # #
300
+ def button_to(name = nil, options = nil, html_options = nil, &block)
301
+ html_options, options = options, name if block_given?
302
+ options ||= {}
303
+ html_options ||= {}
304
+ html_options = html_options.stringify_keys
305
+
306
+ url = options.is_a?(String) ? options : url_for(options)
307
+ remote = html_options.delete("remote")
308
+ params = html_options.delete("params")
309
+
310
+ method = html_options.delete("method").to_s
311
+ method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".html_safe
312
+
313
+ form_method = method == "get" ? "get" : "post"
314
+ form_options = html_options.delete("form") || {}
315
+ form_options[:class] ||= html_options.delete("form_class") || "button_to"
316
+ form_options[:method] = form_method
317
+ form_options[:action] = url
318
+ form_options[:'data-remote'] = true if remote
319
+
320
+ request_token_tag = if form_method == "post"
321
+ request_method = method.empty? ? "post" : method
322
+ token_tag(nil, form_options: { action: url, method: request_method })
323
+ else
324
+ ""
325
+ end
326
+
327
+ html_options = convert_options_to_data_attributes(options, html_options)
328
+ html_options["type"] = "submit"
329
+
330
+ button = if block_given?
331
+ content_tag("button", html_options, &block)
332
+ else
333
+ html_options["value"] = name || url
334
+ tag("input", html_options)
335
+ end
336
+
337
+ inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
338
+ if params
339
+ to_form_params(params).each do |param|
340
+ inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value])
341
+ end
342
+ end
343
+ content_tag("form", inner_tags, form_options)
344
+ end
345
+
346
+ # Creates a link tag of the given +name+ using a URL created by the set of
347
+ # +options+ unless the current request URI is the same as the links, in
348
+ # which case only the name is returned (or the given block is yielded, if
349
+ # one exists). You can give +link_to_unless_current+ a block which will
350
+ # specialize the default behavior (e.g., show a "Start Here" link rather
351
+ # than the link's text).
352
+ #
353
+ # ==== Examples
354
+ # Let's say you have a navigation menu...
355
+ #
356
+ # <ul id="navbar">
357
+ # <li><%= link_to_unless_current("Home", { action: "index" }) %></li>
358
+ # <li><%= link_to_unless_current("About Us", { action: "about" }) %></li>
359
+ # </ul>
360
+ #
361
+ # If in the "about" action, it will render...
362
+ #
363
+ # <ul id="navbar">
364
+ # <li><a href="/controller/index">Home</a></li>
365
+ # <li>About Us</li>
366
+ # </ul>
367
+ #
368
+ # ...but if in the "index" action, it will render:
369
+ #
370
+ # <ul id="navbar">
371
+ # <li>Home</li>
372
+ # <li><a href="/controller/about">About Us</a></li>
373
+ # </ul>
374
+ #
375
+ # The implicit block given to +link_to_unless_current+ is evaluated if the current
376
+ # action is the action given. So, if we had a comments page and wanted to render a
377
+ # "Go Back" link instead of a link to the comments page, we could do something like this...
378
+ #
379
+ # <%=
380
+ # link_to_unless_current("Comment", { controller: "comments", action: "new" }) do
381
+ # link_to("Go back", { controller: "posts", action: "index" })
382
+ # end
383
+ # %>
384
+ def link_to_unless_current(name, options = {}, html_options = {}, &block)
385
+ link_to_unless current_page?(options), name, options, html_options, &block
386
+ end
387
+
388
+ # Creates a link tag of the given +name+ using a URL created by the set of
389
+ # +options+ unless +condition+ is true, in which case only the name is
390
+ # returned. To specialize the default behavior (i.e., show a login link rather
391
+ # than just the plaintext link text), you can pass a block that
392
+ # accepts the name or the full argument list for +link_to_unless+.
393
+ #
394
+ # ==== Examples
395
+ # <%= link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) %>
396
+ # # If the user is logged in...
397
+ # # => <a href="/controller/reply/">Reply</a>
398
+ #
399
+ # <%=
400
+ # link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) do |name|
401
+ # link_to(name, { controller: "accounts", action: "signup" })
402
+ # end
403
+ # %>
404
+ # # If the user is logged in...
405
+ # # => <a href="/controller/reply/">Reply</a>
406
+ # # If not...
407
+ # # => <a href="/accounts/signup">Reply</a>
408
+ def link_to_unless(condition, name, options = {}, html_options = {}, &block)
409
+ link_to_if !condition, name, options, html_options, &block
410
+ end
411
+
412
+ # Creates a link tag of the given +name+ using a URL created by the set of
413
+ # +options+ if +condition+ is true, otherwise only the name is
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+).
417
+ #
418
+ # ==== Examples
419
+ # <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %>
420
+ # # If the user isn't logged in...
421
+ # # => <a href="/sessions/new/">Login</a>
422
+ #
423
+ # <%=
424
+ # link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) do
425
+ # link_to(@current_user.login, { controller: "accounts", action: "show", id: @current_user })
426
+ # end
427
+ # %>
428
+ # # If the user isn't logged in...
429
+ # # => <a href="/sessions/new/">Login</a>
430
+ # # If they are logged in...
431
+ # # => <a href="/accounts/show/3">my_username</a>
432
+ def link_to_if(condition, name, options = {}, html_options = {}, &block)
433
+ if condition
434
+ link_to(name, options, html_options)
435
+ else
436
+ if block_given?
437
+ block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
438
+ else
439
+ ERB::Util.html_escape(name)
440
+ end
441
+ end
442
+ end
443
+
444
+ # Creates a mailto link tag to the specified +email_address+, which is
445
+ # also used as the name of the link unless +name+ is specified. Additional
446
+ # HTML attributes for the link can be passed in +html_options+.
447
+ #
448
+ # +mail_to+ has several methods for customizing the email itself by
449
+ # passing special keys to +html_options+.
450
+ #
451
+ # ==== Options
452
+ # * <tt>:subject</tt> - Preset the subject line of the email.
453
+ # * <tt>:body</tt> - Preset the body of the email.
454
+ # * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
455
+ # * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
456
+ # * <tt>:reply_to</tt> - Preset the Reply-To field of the email.
457
+ #
458
+ # ==== Obfuscation
459
+ # Prior to Rails 4.0, +mail_to+ provided options for encoding the address
460
+ # in order to hinder email harvesters. To take advantage of these options,
461
+ # install the +actionview-encoded_mail_to+ gem.
462
+ #
463
+ # ==== Examples
464
+ # mail_to "me@domain.com"
465
+ # # => <a href="mailto:me@domain.com">me@domain.com</a>
466
+ #
467
+ # mail_to "me@domain.com", "My email"
468
+ # # => <a href="mailto:me@domain.com">My email</a>
469
+ #
470
+ # mail_to "me@domain.com", "My email", cc: "ccaddress@domain.com",
471
+ # subject: "This is an example email"
472
+ # # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
473
+ #
474
+ # You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
475
+ #
476
+ # <%= mail_to "me@domain.com" do %>
477
+ # <strong>Email me:</strong> <span>me@domain.com</span>
478
+ # <% end %>
479
+ # # => <a href="mailto:me@domain.com">
480
+ # <strong>Email me:</strong> <span>me@domain.com</span>
481
+ # </a>
482
+ def mail_to(email_address, name = nil, html_options = {}, &block)
483
+ html_options, name = name, nil if block_given?
484
+ html_options = (html_options || {}).stringify_keys
485
+
486
+ extras = %w{ cc bcc body subject reply_to }.map! { |item|
487
+ option = html_options.delete(item).presence || next
488
+ "#{item.dasherize}=#{ERB::Util.url_encode(option)}"
489
+ }.compact
490
+ extras = extras.empty? ? "" : "?" + extras.join("&")
491
+
492
+ encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
493
+ html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
494
+
495
+ content_tag("a", name || email_address, html_options, &block)
496
+ end
497
+
498
+ # True if the current request URI was generated by the given +options+.
499
+ #
500
+ # ==== Examples
501
+ # Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc&page=1</tt> action.
502
+ #
503
+ # current_page?(action: 'process')
504
+ # # => false
505
+ #
506
+ # current_page?(action: 'checkout')
507
+ # # => true
508
+ #
509
+ # current_page?(controller: 'library', action: 'checkout')
510
+ # # => false
511
+ #
512
+ # current_page?(controller: 'shop', action: 'checkout')
513
+ # # => true
514
+ #
515
+ # current_page?(controller: 'shop', action: 'checkout', order: 'asc')
516
+ # # => false
517
+ #
518
+ # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1')
519
+ # # => true
520
+ #
521
+ # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2')
522
+ # # => false
523
+ #
524
+ # current_page?('http://www.example.com/shop/checkout')
525
+ # # => true
526
+ #
527
+ # current_page?('http://www.example.com/shop/checkout', check_parameters: true)
528
+ # # => false
529
+ #
530
+ # current_page?('/shop/checkout')
531
+ # # => true
532
+ #
533
+ # current_page?('http://www.example.com/shop/checkout?order=desc&page=1')
534
+ # # => true
535
+ #
536
+ # Let's say we're in the <tt>http://www.example.com/products</tt> action with method POST in case of invalid product.
537
+ #
538
+ # current_page?(controller: 'product', action: 'index')
539
+ # # => false
540
+ #
541
+ # We can also pass in the symbol arguments instead of strings.
542
+ #
543
+ def current_page?(options, check_parameters: false)
544
+ unless request
545
+ raise "You cannot use helpers that need to determine the current " \
546
+ "page unless your view context provides a Request object " \
547
+ "in a #request method"
548
+ end
549
+
550
+ return false unless request.get? || request.head?
551
+
552
+ check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters)
553
+ url_string = URI.parser.unescape(url_for(options)).force_encoding(Encoding::BINARY)
554
+
555
+ # We ignore any extra parameters in the request_uri if the
556
+ # submitted URL doesn't have any either. This lets the function
557
+ # work with things like ?order=asc
558
+ # the behaviour can be disabled with check_parameters: true
559
+ request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
560
+ request_uri = URI.parser.unescape(request_uri).force_encoding(Encoding::BINARY)
561
+
562
+ if url_string.start_with?("/") && url_string != "/"
563
+ url_string.chomp!("/")
564
+ request_uri.chomp!("/")
565
+ end
566
+
567
+ if %r{^\w+://}.match?(url_string)
568
+ url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
569
+ else
570
+ url_string == request_uri
571
+ end
572
+ end
573
+
574
+ private
575
+ def convert_options_to_data_attributes(options, html_options)
576
+ if html_options
577
+ html_options = html_options.stringify_keys
578
+ html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options)
579
+
580
+ method = html_options.delete("method")
581
+
582
+ add_method_to_attributes!(html_options, method) if method
583
+
584
+ html_options
585
+ else
586
+ link_to_remote_options?(options) ? { "data-remote" => "true" } : {}
587
+ end
588
+ end
589
+
590
+ def link_to_remote_options?(options)
591
+ if options.is_a?(Hash)
592
+ options.delete("remote") || options.delete(:remote)
593
+ end
594
+ end
595
+
596
+ def add_method_to_attributes!(html_options, method)
597
+ if method_not_get_method?(method) && html_options["rel"] !~ /nofollow/
598
+ if html_options["rel"].blank?
599
+ html_options["rel"] = "nofollow"
600
+ else
601
+ html_options["rel"] = "#{html_options["rel"]} nofollow"
602
+ end
603
+ end
604
+ html_options["data-method"] = method
605
+ end
606
+
607
+ STRINGIFIED_COMMON_METHODS = {
608
+ get: "get",
609
+ delete: "delete",
610
+ patch: "patch",
611
+ post: "post",
612
+ put: "put",
613
+ }.freeze
614
+
615
+ def method_not_get_method?(method)
616
+ return false unless method
617
+ (STRINGIFIED_COMMON_METHODS[method] || method.to_s.downcase) != "get"
618
+ end
619
+
620
+ def token_tag(token = nil, form_options: {})
621
+ if token != false && defined?(protect_against_forgery?) && protect_against_forgery?
622
+ token ||= form_authenticity_token(form_options: form_options)
623
+ tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
624
+ else
625
+ ""
626
+ end
627
+ end
628
+
629
+ def method_tag(method)
630
+ tag("input", type: "hidden", name: "_method", value: method.to_s)
631
+ end
632
+
633
+ # Returns an array of hashes each containing :name and :value keys
634
+ # suitable for use as the names and values of form input fields:
635
+ #
636
+ # to_form_params(name: 'David', nationality: 'Danish')
637
+ # # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}]
638
+ #
639
+ # to_form_params(country: { name: 'Denmark' })
640
+ # # => [{name: 'country[name]', value: 'Denmark'}]
641
+ #
642
+ # to_form_params(countries: ['Denmark', 'Sweden']})
643
+ # # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}]
644
+ #
645
+ # An optional namespace can be passed to enclose key names:
646
+ #
647
+ # to_form_params({ name: 'Denmark' }, 'country')
648
+ # # => [{name: 'country[name]', value: 'Denmark'}]
649
+ def to_form_params(attribute, namespace = nil)
650
+ attribute = if attribute.respond_to?(:permitted?)
651
+ attribute.to_h
652
+ else
653
+ attribute
654
+ end
655
+
656
+ params = []
657
+ case attribute
658
+ when Hash
659
+ attribute.each do |key, value|
660
+ prefix = namespace ? "#{namespace}[#{key}]" : key
661
+ params.push(*to_form_params(value, prefix))
662
+ end
663
+ when Array
664
+ array_prefix = "#{namespace}[]"
665
+ attribute.each do |value|
666
+ params.push(*to_form_params(value, array_prefix))
667
+ end
668
+ else
669
+ params << { name: namespace.to_s, value: attribute.to_param }
670
+ end
671
+
672
+ params.sort_by { |pair| pair[:name] }
673
+ end
674
+ end
675
+ end
676
+ end