Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +196 -67
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -2
  5. data/lib/action_view.rb +3 -2
  6. data/lib/action_view/base.rb +107 -10
  7. data/lib/action_view/buffers.rb +15 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +5 -9
  10. data/lib/action_view/digestor.rb +12 -20
  11. data/lib/action_view/gem_version.rb +3 -3
  12. data/lib/action_view/helpers.rb +0 -2
  13. data/lib/action_view/helpers/asset_tag_helper.rb +7 -30
  14. data/lib/action_view/helpers/asset_url_helper.rb +4 -3
  15. data/lib/action_view/helpers/cache_helper.rb +18 -10
  16. data/lib/action_view/helpers/capture_helper.rb +4 -0
  17. data/lib/action_view/helpers/csp_helper.rb +4 -2
  18. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  19. data/lib/action_view/helpers/date_helper.rb +69 -25
  20. data/lib/action_view/helpers/form_helper.rb +240 -8
  21. data/lib/action_view/helpers/form_options_helper.rb +27 -18
  22. data/lib/action_view/helpers/form_tag_helper.rb +12 -9
  23. data/lib/action_view/helpers/javascript_helper.rb +9 -8
  24. data/lib/action_view/helpers/number_helper.rb +5 -0
  25. data/lib/action_view/helpers/output_safety_helper.rb +1 -1
  26. data/lib/action_view/helpers/record_tag_helper.rb +0 -23
  27. data/lib/action_view/helpers/rendering_helper.rb +6 -4
  28. data/lib/action_view/helpers/sanitize_helper.rb +12 -18
  29. data/lib/action_view/helpers/tag_helper.rb +7 -6
  30. data/lib/action_view/helpers/tags/base.rb +9 -5
  31. data/lib/action_view/helpers/tags/color_field.rb +1 -1
  32. data/lib/action_view/helpers/tags/translator.rb +1 -6
  33. data/lib/action_view/helpers/text_helper.rb +3 -3
  34. data/lib/action_view/helpers/translation_helper.rb +16 -12
  35. data/lib/action_view/helpers/url_helper.rb +15 -15
  36. data/lib/action_view/layouts.rb +5 -5
  37. data/lib/action_view/log_subscriber.rb +6 -6
  38. data/lib/action_view/lookup_context.rb +73 -31
  39. data/lib/action_view/path_set.rb +5 -10
  40. data/lib/action_view/railtie.rb +24 -1
  41. data/lib/action_view/record_identifier.rb +2 -2
  42. data/lib/action_view/renderer/abstract_renderer.rb +56 -3
  43. data/lib/action_view/renderer/partial_renderer.rb +66 -55
  44. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +62 -16
  45. data/lib/action_view/renderer/renderer.rb +16 -4
  46. data/lib/action_view/renderer/streaming_template_renderer.rb +5 -5
  47. data/lib/action_view/renderer/template_renderer.rb +24 -18
  48. data/lib/action_view/rendering.rb +51 -31
  49. data/lib/action_view/routing_url_for.rb +12 -11
  50. data/lib/action_view/template.rb +102 -70
  51. data/lib/action_view/template/error.rb +21 -1
  52. data/lib/action_view/template/handlers.rb +27 -1
  53. data/lib/action_view/template/handlers/builder.rb +2 -2
  54. data/lib/action_view/template/handlers/erb.rb +17 -7
  55. data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
  56. data/lib/action_view/template/handlers/html.rb +1 -1
  57. data/lib/action_view/template/handlers/raw.rb +2 -2
  58. data/lib/action_view/template/html.rb +14 -5
  59. data/lib/action_view/template/inline.rb +22 -0
  60. data/lib/action_view/template/raw_file.rb +28 -0
  61. data/lib/action_view/template/resolver.rb +136 -133
  62. data/lib/action_view/template/sources.rb +13 -0
  63. data/lib/action_view/template/sources/file.rb +17 -0
  64. data/lib/action_view/template/text.rb +5 -3
  65. data/lib/action_view/test_case.rb +1 -1
  66. data/lib/action_view/testing/resolvers.rb +33 -20
  67. data/lib/action_view/unbound_template.rb +32 -0
  68. data/lib/action_view/view_paths.rb +25 -1
  69. data/lib/assets/compiled/rails-ujs.js +32 -6
  70. metadata +22 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
---
2
2
SHA256:
3
- metadata.gz: afb542680a6bdea85297d58343a24da1754f7893189439ead02c8b566de24d27
4
- data.tar.gz: 53438d080b42df0f88a29b45f9f2a4dfb2afa1129dbe366a612bf47c25645fea
3
+ metadata.gz: '09307c25b4aca33f07c78f0eb2b2e1dcb83316272272340495d9bdb71200b66c'
4
+ data.tar.gz: c721b0700848cd657d418b9369ce575abfc12205e764cbdd04aa4f23320b80be
5
5
SHA512:
6
- metadata.gz: 985967fad7c02691fca261d82a25bf1b409071c5ee422127bec7bde426b03a8bac3024736861149c8aa4a80de0240e393c4e98b18f52c2041bbe16223d409f59
7
- data.tar.gz: b5965f912d6ef4a64764370c17adc1f24bdc960fb8a4e549930d54f2d708dde433a1e9495f35d5fbbc66c3e5bee57d66a3b42107fada1bd708d1201cb657d04e
6
+ metadata.gz: acd68ab05735381289baf92c2895b30dc73b8e25e36ea9b5496104e66d46d630e352785750518404da5c71cade47b8987effe98fcc7e35d6d108dfba51bb2b4f
7
+ data.tar.gz: d6e8f0d10ff2d4048c7ad3469584153f6faf284853b7f1afd191dbd3b3b469295855655c5ac59b986c52a894a7c774d11f0a09e747a492ad6d3b0e5edfb0bf27
data/CHANGELOG.md CHANGED
@@ -1,4 +1,98 @@
1
- ## Rails 5.2.3 (March 27, 2019) ##
1
+ ## Rails 6.0.0 (August 16, 2019) ##
2
+
3
+ * ActionView::Helpers::SanitizeHelper: support rails-html-sanitizer 1.1.0.
4
+
5
+ *Juanito Fatas*
6
+
7
+
8
+ ## Rails 6.0.0.rc2 (July 22, 2019) ##
9
+
10
+ * Fix `select_tag` so that it doesn't change `options` when `include_blank` is present.
11
+
12
+ *Younes SERRAJ*
13
+
14
+
15
+ ## Rails 6.0.0.rc1 (April 24, 2019) ##
16
+
17
+ * Fix partial caching skips same item issue
18
+
19
+ If we render cached collection partials with repeated items, those repeated items
20
+ will get skipped. For example, if you have 5 identical items in your collection, Rails
21
+ only renders the first one when `cached` is set to true. But it should render all
22
+ 5 items instead.
23
+
24
+ Fixes #35114.
25
+
26
+ *Stan Lo*
27
+
28
+ * Only clear ActionView cache in development on file changes
29
+
30
+ To speed up development mode, view caches are only cleared when files in
31
+ the view paths have changed. Applications which have implemented custom
32
+ `ActionView::Resolver` subclasses may need to add their own cache clearing.
33
+
34
+ *John Hawthorn*
35
+
36
+ * Fix `ActionView::FixtureResolver` so that it handles template variants correctly.
37
+
38
+ *Edward Rudd*
39
+
40
+
41
+ ## Rails 6.0.0.beta3 (March 11, 2019) ##
42
+
43
+ * Only accept formats from registered mime types
44
+
45
+ A lack of filtering on mime types could allow an attacker to read
46
+ arbitrary files on the target server or to perform a denial of service
47
+ attack.
48
+
49
+ Fixes CVE-2019-5418
50
+ Fixes CVE-2019-5419
51
+
52
+ *John Hawthorn*, *Eileen M. Uchitelle*, *Aaron Patterson*
53
+
54
+
55
+ ## Rails 6.0.0.beta2 (February 25, 2019) ##
56
+
57
+ * `ActionView::Template.finalize_compiled_template_methods` is deprecated with
58
+ no replacement.
59
+
60
+ *tenderlove*
61
+
62
+ * `config.action_view.finalize_compiled_template_methods` is deprecated with
63
+ no replacement.
64
+
65
+ *tenderlove*
66
+
67
+ * Ensure unique DOM IDs for collection inputs with float values.
68
+
69
+ Fixes #34974.
70
+
71
+ *Mark Edmondson*
72
+
73
+
74
+ ## Rails 6.0.0.beta1 (January 18, 2019) ##
75
+
76
+ * [Rename npm package](https://github.com/rails/rails/pull/34905) from
77
+ [`rails-ujs`](https://www.npmjs.com/package/rails-ujs) to
78
+ [`@rails/ujs`](https://www.npmjs.com/package/@rails/ujs).
79
+
80
+ *Javan Makhmali*
81
+
82
+ * Remove deprecated `image_alt` helper.
83
+
84
+ *Rafael Mendonça França*
85
+
86
+ * Fix the need of `#protect_against_forgery?` method defined in
87
+ `ActionView::Base` subclasses. This prevents the use of forms and buttons.
88
+
89
+ *Genadi Samokovarov*
90
+
91
+ * Fix UJS permanently showing disabled text in a[data-remote][data-disable-with] elements within forms.
92
+
93
+ Fixes #33889.
94
+
95
+ *Wolfgang Hobmaier*
2
96
3
97
* Prevent non-primary mouse keys from triggering Rails UJS click handlers.
4
98
Firefox fires click events even if the click was triggered by non-primary mouse keys such as right- or scroll-wheel-clicks.
@@ -8,32 +102,54 @@
8
102
<%= link_to 'Remote', remote_path, class: 'remote', remote: true, data: { type: :json } %>
9
103
```
10
104
11
- Fixes #34541
105
+ Fixes #34541.
12
106
13
107
*Wolfgang Hobmaier*
14
108
109
+ * Prevent `ActionView::TextHelper#word_wrap` from unexpectedly stripping white space from the _left_ side of lines.
15
110
16
- ## Rails 5.2.2.1 (March 11, 2019) ##
111
+ For example, given input like this:
17
112
18
- * No changes.
113
+ ```
114
+ This is a paragraph with an initial indent,
115
+ followed by additional lines that are not indented,
116
+ and finally terminated with a blockquote:
117
+ "A pithy saying"
118
+ ```
19
119
120
+ Calling `word_wrap` should not trim the indents on the first and last lines.
20
121
21
- ## Rails 5.2.2 (December 04, 2018) ##
122
+ Fixes #34487.
22
123
23
- * No changes.
124
+ *Lyle Mullican*
24
125
126
+ * Add allocations to template rendering instrumentation.
25
127
26
- ## Rails 5.2.1.1 (November 27, 2018) ##
128
+ Adds the allocations for template and partial rendering to the server output on render.
27
129
28
- * No changes.
130
+ ```
131
+ Rendered posts/_form.html.erb (Duration: 7.1ms | Allocations: 6004)
132
+ Rendered posts/new.html.erb within layouts/application (Duration: 8.3ms | Allocations: 6654)
133
+ Completed 200 OK in 858ms (Views: 848.4ms | ActiveRecord: 0.4ms | Allocations: 1539564)
134
+ ```
135
+
136
+ *Eileen M. Uchitelle*, *Aaron Patterson*
137
+
138
+ * Respect the `only_path` option passed to `url_for` when the options are passed in as an array
139
+
140
+ Fixes #33237.
141
+
142
+ *Joel Ambass*
29
143
144
+ * Deprecate calling private model methods from view helpers.
30
145
31
- ## Rails 5.2.1 (August 07, 2018) ##
146
+ For example, in methods like `options_from_collection_for_select`
147
+ and `collection_select` it is possible to call private methods from
148
+ the objects used.
32
149
33
- * Fix leak of `skip_default_ids` and `allow_method_names_outside_object` options
150
+ Fixes #33546.
34
- to HTML attributes.
35
151
36
- *Yurii Cherniavskyi*
152
+ *Ana María Martínez Gómez*
37
153
38
154
* Fix issue with `button_to`'s `to_form_params`
39
155
@@ -46,97 +162,110 @@
46
162
47
163
*Georgi Georgiev*
48
164
49
- * Fix JavaScript views rendering does not work with Firefox when using
165
+ * Mark arrays of translations as trusted safe by using the `_html` suffix.
50
- Content Security Policy.
51
-
52
- Fixes #32577.
53
-
54
- *Yuji Yaginuma*
55
-
56
- * Add the `nonce: true` option for `javascript_include_tag` helper to
57
- support automatic nonce generation for Content Security Policy.
58
- Works the same way as `javascript_tag nonce: true` does.
59
166
60
- *Yaroslav Markin*
167
+ Example:
61
168
169
+ en:
170
+ foo_html:
171
+ - "One"
172
+ - "<strong>Two</strong>"
173
+ - "Three &#128075; &#128578;"
62
174
63
- ## Rails 5.2.0 (April 09, 2018) ##
175
+ *Juan Broullon*
64
176
65
- * Pass the `:skip_pipeline` option in `image_submit_tag` when calling `path_to_image`.
177
+ * Add `year_format` option to date_select tag. This option makes it possible to customize year
178
+ names. Lambda should be passed to use this option.
66
179
67
- Fixes #32248.
180
+ Example:
68
181
69
- *Andrew White*
182
+ date_select('user_birthday', '', start_year: 1998, end_year: 2000, year_format: ->year { "Heisei #{year - 1988}" })
70
183
71
- * Allow the use of callable objects as group methods for grouped selects.
184
+ The HTML produced:
72
185
73
- Until now, the `option_groups_from_collection_for_select` method was only able to
74
- handle method names as `group_method` and `group_label_method` parameters,
75
- it is now able to receive procs and other callable objects too.
186
+ <select id="user_birthday__1i" name="user_birthday[(1i)]">
187
+ <option value="1998">Heisei 10</option>
188
+ <option value="1999">Heisei 11</option>
189
+ <option value="2000">Heisei 12</option>
190
+ </select>
191
+ /* The rest is omitted */
76
192
77
- *Jérémie Bonal*
193
+ *Koki Ryu*
78
194
79
- * Add `preload_link_tag` helper.
195
+ * Fix JavaScript views rendering does not work with Firefox when using
196
+ Content Security Policy.
80
197
81
- This helper that allows to the browser to initiate early fetch of resources
198
+ Fixes #32577.
82
- (different to the specified in `javascript_include_tag` and `stylesheet_link_tag`).
83
- Additionally, this sends Early Hints if supported by browser.
84
199
85
- *Guillermo Iguaran*
200
+ *Yuji Yaginuma*
86
201
87
- * Change `form_with` to generates ids by default.
202
+ * Add the `nonce: true` option for `javascript_include_tag` helper to
203
+ support automatic nonce generation for Content Security Policy.
204
+ Works the same way as `javascript_tag nonce: true` does.
88
205
89
- When `form_with` was introduced we disabled the automatic generation of ids
206
+ *Yaroslav Markin*
90
- that was enabled in `form_for`. This usually is not an good idea since labels don't work
91
- when the input doesn't have an id and it made harder to test with Capybara.
92
207
93
- You can still disable the automatic generation of ids setting `config.action_view.form_with_generates_ids`
208
+ * Remove `ActionView::Helpers::RecordTagHelper`.
94
- to `false.`
95
209
96
- *Nick Pezza*
210
+ *Yoshiyuki Hirano*
97
211
98
- * Fix issues with `field_error_proc` wrapping `optgroup` and select divider `option`.
212
+ * Disable `ActionView::Template` finalizers in test environment.
99
213
100
- Fixes #31088
214
+ Template finalization can be expensive in large view test suites.
215
+ Add a configuration option,
216
+ `action_view.finalize_compiled_template_methods`, and turn it off in
217
+ the test environment.
101
218
102
- *Matthias Neumayr*
219
+ *Simon Coffey*
103
220
104
- * Remove deprecated Erubis ERB handler.
221
+ * Extract the `confirm` call in its own, overridable method in `rails_ujs`.
105
222
106
- *Rafael Mendonça França*
223
+ Example:
107
224
108
- * Remove default `alt` text generation.
225
+ Rails.confirm = function(message, element) {
226
+ return (my_bootstrap_modal_confirm(message));
227
+ }
109
228
110
- Fixes #30096
229
+ *Mathieu Mahé*
111
230
112
- *Cameron Cundiff*
231
+ * Enable select tag helper to mark `prompt` option as `selected` and/or `disabled` for `required`
232
+ field.
113
233
114
- * Add `srcset` option to `image_tag` helper.
234
+ Example:
115
235
116
- *Roberto Miranda*
236
+ select :post,
237
+ :category,
238
+ ["lifestyle", "programming", "spiritual"],
239
+ { selected: "", disabled: "", prompt: "Choose one" },
240
+ { required: true }
117
241
118
- * Fix issues with scopes and engine on `current_page?` method.
242
+ Placeholder option would be selected and disabled.
119
243
120
- Fixes #29401.
244
+ The HTML produced:
121
245
122
- *Nikita Savrov*
246
+ <select required="required" name="post[category]" id="post_category">
247
+ <option disabled="disabled" selected="selected" value="">Choose one</option>
248
+ <option value="lifestyle">lifestyle</option>
249
+ <option value="programming">programming</option>
250
+ <option value="spiritual">spiritual</option></select>
123
251
124
- * Generate field ids in `collection_check_boxes` and `collection_radio_buttons`.
252
+ *Sergey Prikhodko*
125
253
126
- This makes sure that the labels are linked up with the fields.
254
+ * Don't enforce UTF-8 by default.
127
255
128
- Fixes #29014.
256
+ With the disabling of TLS 1.0 by most major websites, continuing to run
257
+ IE8 or lower becomes increasingly difficult so default to not enforcing
258
+ UTF-8 encoding as it's not relevant to other browsers.
129
259
130
- *Yuji Yaginuma*
260
+ *Andrew White*
131
261
132
- * Add `:json` type to `auto_discovery_link_tag` to support [JSON Feeds](https://jsonfeed.org/version/1).
262
+ * Change translation key of `submit_tag` from `module_name_class_name` to `module_name/class_name`.
133
263
134
- *Mike Gunderloy*
264
+ *Rui Onodera*
135
265
136
- * Update `distance_of_time_in_words` helper to display better error messages
266
+ * Rails 6 requires Ruby 2.5.0 or newer.
137
- for bad input.
138
267
139
- *Jay Hayes*
268
+ *Jeremy Daer*, *Kasper Timm Hansen*
140
269
141
270
142
- Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionview/CHANGELOG.md) for previous changes.
271
+ Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actionview/CHANGELOG.md) for previous changes.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2018 David Heinemeier Hansson
1
+ Copyright (c) 2004-2019 David Heinemeier Hansson
2
2
3
3
Permission is hereby granted, free of charge, to any person obtaining
4
4
a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -5,6 +5,8 @@ view helpers that assist when building HTML forms, Atom feeds and more.
5
5
Template formats that Action View handles are ERB (embedded Ruby, typically
6
6
used to inline short Ruby snippets inside HTML), and XML Builder.
7
7
8
+ You can read more about Action View in the {Action View Overview}[https://edgeguides.rubyonrails.org/action_view_overview.html] guide.
9
+
8
10
== Download and installation
9
11
10
12
The latest version of Action View can be installed with RubyGems:
@@ -13,7 +15,7 @@ The latest version of Action View can be installed with RubyGems:
13
15
14
16
Source code can be downloaded as part of the Rails project on GitHub:
15
17
16
- * https://github.com/rails/rails/tree/5-2-stable/actionview
18
+ * https://github.com/rails/rails/tree/master/actionview
17
19
18
20
19
21
== License
@@ -27,7 +29,7 @@ Action View is released under the MIT license:
27
29
28
30
API documentation is at
29
31
30
- * http://api.rubyonrails.org
32
+ * https://api.rubyonrails.org
31
33
32
34
Bug reports for the Ruby on Rails project can be filed here:
33
35
data/lib/action_view.rb CHANGED
@@ -1,7 +1,7 @@
1
1
# frozen_string_literal: true
2
2
3
3
#--
4
- # Copyright (c) 2004-2018 David Heinemeier Hansson
4
+ # Copyright (c) 2004-2019 David Heinemeier Hansson
5
5
#
6
6
# Permission is hereby granted, free of charge, to any person obtaining
7
7
# a copy of this software and associated documentation files (the
@@ -35,7 +35,6 @@ module ActionView
35
35
eager_autoload do
36
36
autoload :Base
37
37
autoload :Context
38
- autoload :CompiledTemplates, "action_view/context"
39
38
autoload :Digestor
40
39
autoload :Helpers
41
40
autoload :LookupContext
@@ -45,6 +44,7 @@ module ActionView
45
44
autoload :Rendering
46
45
autoload :RoutingUrlFor
47
46
autoload :Template
47
+ autoload :UnboundTemplate
48
48
autoload :ViewPaths
49
49
50
50
autoload_under "renderer" do
@@ -81,6 +81,7 @@ module ActionView
81
81
end
82
82
end
83
83
84
+ autoload :CacheExpiry
84
85
autoload :TestCase
85
86
86
87
def self.eager_load!
data/lib/action_view/base.rb CHANGED
@@ -3,6 +3,7 @@
3
3
require "active_support/core_ext/module/attr_internal"
4
4
require "active_support/core_ext/module/attribute_accessors"
5
5
require "active_support/ordered_options"
6
+ require "active_support/deprecation"
6
7
require "action_view/log_subscriber"
7
8
require "action_view/helpers"
8
9
require "action_view/context"
@@ -179,37 +180,133 @@ module ActionView #:nodoc:
179
180
def xss_safe? #:nodoc:
180
181
true
181
182
end
183
+
184
+ def with_empty_template_cache # :nodoc:
185
+ subclass = Class.new(self) {
186
+ # We can't implement these as self.class because subclasses will
187
+ # share the same template cache as superclasses, so "changed?" won't work
188
+ # correctly.
189
+ define_method(:compiled_method_container) { subclass }
190
+ define_singleton_method(:compiled_method_container) { subclass }
191
+ }
192
+ end
193
+
194
+ def changed?(other) # :nodoc:
195
+ compiled_method_container != other.compiled_method_container
196
+ end
182
197
end
183
198
184
- attr_accessor :view_renderer
199
+ attr_reader :view_renderer, :lookup_context
185
200
attr_internal :config, :assigns
186
201
187
- delegate :lookup_context, to: :view_renderer
188
202
delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context
189
203
190
204
def assign(new_assigns) # :nodoc:
191
205
@_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
192
206
end
193
207
194
- def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc:
208
+ # :stopdoc:
209
+
210
+ def self.build_lookup_context(context)
211
+ case context
212
+ when ActionView::Renderer
213
+ context.lookup_context
214
+ when Array
215
+ ActionView::LookupContext.new(context)
216
+ when ActionView::PathSet
217
+ ActionView::LookupContext.new(context)
218
+ when nil
219
+ ActionView::LookupContext.new([])
220
+ else
221
+ raise NotImplementedError, context.class.name
222
+ end
223
+ end
224
+
225
+ def self.empty
226
+ with_view_paths([])
227
+ end
228
+
229
+ def self.with_view_paths(view_paths, assigns = {}, controller = nil)
230
+ with_context ActionView::LookupContext.new(view_paths), assigns, controller
231
+ end
232
+
233
+ def self.with_context(context, assigns = {}, controller = nil)
234
+ new context, assigns, controller
235
+ end
236
+
237
+ NULL = Object.new
238
+
239
+ # :startdoc:
240
+
241
+ def initialize(lookup_context = nil, assigns = {}, controller = nil, formats = NULL) #:nodoc:
195
242
@_config = ActiveSupport::InheritableOptions.new
196
243
197
- if context.is_a?(ActionView::Renderer)
198
- @view_renderer = context
244
+ unless formats == NULL
245
+ ActiveSupport::Deprecation.warn <<~eowarn.squish
246
+ Passing formats to ActionView::Base.new is deprecated
247
+ eowarn
248
+ end
249
+
250
+ case lookup_context
251
+ when ActionView::LookupContext
252
+ @lookup_context = lookup_context
199
253
else
200
- lookup_context = context.is_a?(ActionView::LookupContext) ?
201
- context : ActionView::LookupContext.new(context)
202
- lookup_context.formats = formats if formats
203
- lookup_context.prefixes = controller._prefixes if controller
204
- @view_renderer = ActionView::Renderer.new(lookup_context)
254
+ ActiveSupport::Deprecation.warn <<~eowarn.squish
255
+ ActionView::Base instances should be constructed with a lookup context,
256
+ assignments, and a controller.
257
+ eowarn
258
+ @lookup_context = self.class.build_lookup_context(lookup_context)
205
259
end
206
260
261
+ @view_renderer = ActionView::Renderer.new @lookup_context
262
+ @current_template = nil
263
+
207
264
@cache_hit = {}
208
265
assign(assigns)
209
266
assign_controller(controller)
210
267
_prepare_context
211
268
end
212
269
270
+ def _run(method, template, locals, buffer, &block)
271
+ _old_output_buffer, _old_virtual_path, _old_template = @output_buffer, @virtual_path, @current_template
272
+ @current_template = template
273
+ @output_buffer = buffer
274
+ send(method, locals, buffer, &block)
275
+ ensure
276
+ @output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
277
+ end
278
+
279
+ def compiled_method_container
280
+ if self.class == ActionView::Base
281
+ ActiveSupport::Deprecation.warn <<~eowarn.squish
282
+ ActionView::Base instances must implement `compiled_method_container`
283
+ or use the class method `with_empty_template_cache` for constructing
284
+ an ActionView::Base instances that has an empty cache.
285
+ eowarn
286
+ end
287
+
288
+ self.class
289
+ end
290
+
291
+ def in_rendering_context(options)
292
+ old_view_renderer = @view_renderer
293
+ old_lookup_context = @lookup_context
294
+
295
+ if !lookup_context.html_fallback_for_js && options[:formats]
296
+ formats = Array(options[:formats])
297
+ if formats == [:js]
298
+ formats << :html
299
+ end
300
+ @lookup_context = lookup_context.with_prepended_formats(formats)
301
+ @view_renderer = ActionView::Renderer.new @lookup_context
302
+ end
303
+
304
+ yield @view_renderer
305
+ ensure
306
+ @view_renderer = old_view_renderer
307
+ @lookup_context = old_lookup_context
308
+ end
309
+
213
310
ActiveSupport.run_load_hooks(:action_view, self)
214
311
end
215
312
end
data/lib/action_view/buffers.rb CHANGED
@@ -3,6 +3,21 @@
3
3
require "active_support/core_ext/string/output_safety"
4
4
5
5
module ActionView
6
+ # Used as a buffer for views
7
+ #
8
+ # The main difference between this and ActiveSupport::SafeBuffer
9
+ # is for the methods `<<` and `safe_expr_append=` the inputs are
10
+ # checked for nil before they are assigned and `to_s` is called on
11
+ # the input. For example:
12
+ #
13
+ # obuf = ActionView::OutputBuffer.new "hello"
14
+ # obuf << 5
15
+ # puts obuf # => "hello5"
16
+ #
17
+ # sbuf = ActiveSupport::SafeBuffer.new "hello"
18
+ # sbuf << 5
19
+ # puts sbuf # => "hello\u0005"
20
+ #
6
21
class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
7
22
def initialize(*)
8
23
super
data/lib/action_view/cache_expiry.rb ADDED
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionView
4
+ class CacheExpiry
5
+ class Executor
6
+ def initialize(watcher:)
7
+ @cache_expiry = CacheExpiry.new(watcher: watcher)
8
+ end
9
+
10
+ def before(target)
11
+ @cache_expiry.clear_cache_if_necessary
12
+ end
13
+ end
14
+
15
+ def initialize(watcher:)
16
+ @watched_dirs = nil
17
+ @watcher_class = watcher
18
+ @watcher = nil
19
+ @mutex = Mutex.new
20
+ end
21
+
22
+ def clear_cache_if_necessary
23
+ @mutex.synchronize do
24
+ watched_dirs = dirs_to_watch
25
+ return if watched_dirs.empty?
26
+
27
+ if watched_dirs != @watched_dirs
28
+ @watched_dirs = watched_dirs
29
+ @watcher = @watcher_class.new([], watched_dirs) do
30
+ clear_cache
31
+ end
32
+ @watcher.execute
33
+ else
34
+ @watcher.execute_if_updated
35
+ end
36
+ end
37
+ end
38
+
39
+ def clear_cache
40
+ ActionView::LookupContext::DetailsKey.clear
41
+ end
42
+
43
+ private
44
+
45
+ def dirs_to_watch
46
+ fs_paths = all_view_paths.grep(FileSystemResolver)
47
+ fs_paths.map(&:path).sort.uniq
48
+ end
49
+
50
+ def all_view_paths
51
+ ActionView::ViewPaths.all_view_paths.flat_map(&:paths)
52
+ end
53
+ end
54
+ end
data/lib/action_view/context.rb CHANGED
@@ -1,21 +1,17 @@
1
1
# frozen_string_literal: true
2
2
3
3
module ActionView
4
- module CompiledTemplates #:nodoc:
5
- # holds compiled template code
6
- end
7
-
8
4
# = Action View Context
9
5
#
10
6
# Action View contexts are supplied to Action Controller to render a template.
11
7
# The default Action View context is ActionView::Base.
12
8
#
13
- # In order to work with ActionController, a Context must just include this module.
14
- # The initialization of the variables used by the context (@output_buffer, @view_flow,
15
- # and @virtual_path) is responsibility of the object that includes this module
16
- # (although you can call _prepare_context defined below).
9
+ # In order to work with Action Controller, a Context must just include this
10
+ # module. The initialization of the variables used by the context
11
+ # (@output_buffer, @view_flow, and @virtual_path) is responsibility of the
12
+ # object that includes this module (although you can call _prepare_context
13
+ # defined below).
17
14
module Context
18
- include CompiledTemplates
19
15
attr_accessor :output_buffer, :view_flow
20
16
21
17
# Prepares the context by setting the appropriate instance variables.
data/lib/action_view/digestor.rb CHANGED
@@ -1,28 +1,24 @@
1
1
# frozen_string_literal: true
2
2
3
- require "concurrent/map"
4
3
require "action_view/dependency_tracker"
5
- require "monitor"
6
4
7
5
module ActionView
8
6
class Digestor
9
7
@@digest_mutex = Mutex.new
10
8
11
- module PerExecutionDigestCacheExpiry
12
- def self.before(target)
13
- ActionView::LookupContext::DetailsKey.clear
14
- end
15
- end
16
-
17
9
class << self
18
10
# Supported options:
19
11
#
20
- # * <tt>name</tt> - Template name
21
- # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
22
- # * <tt>dependencies</tt> - An array of dependent views
23
- def digest(name:, finder:, dependencies: [])
24
- dependencies ||= []
25
- cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".")
12
+ # * <tt>name</tt> - Template name
13
+ # * <tt>format</tt> - Template format
14
+ # * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
15
+ # * <tt>dependencies</tt> - An array of dependent views
16
+ def digest(name:, format: nil, finder:, dependencies: nil)
17
+ if dependencies.nil? || dependencies.empty?
18
+ cache_key = "#{name}.#{format}"
19
+ else
20
+ cache_key = [ name, format, dependencies ].flatten.compact.join(".")
21
+ end
26
22
27
23
# this is a correctly done double-checked locking idiom
28
24
# (Concurrent::Map's lookups have volatile semantics)
@@ -32,7 +28,7 @@ module ActionView
32
28
root = tree(name, finder, partial)
33
29
dependencies.each do |injected_dep|
34
30
root.children << Injected.new(injected_dep, nil, nil)
35
- end
31
+ end if dependencies
36
32
finder.digest_cache[cache_key] = root.digest(finder)
37
33
end
38
34
end
@@ -47,8 +43,6 @@ module ActionView
47
43
logical_name = name.gsub(%r|/_|, "/")
48
44
49
45
if template = find_template(finder, logical_name, [], partial, [])
50
- finder.rendered_format ||= template.formats.first
51
-
52
46
if node = seen[template.identifier] # handle cycles in the tree
53
47
node
54
48
else
@@ -72,9 +66,7 @@ module ActionView
72
66
private
73
67
def find_template(finder, name, prefixes, partial, keys)
74
68
finder.disable_cache do
75
- format = finder.rendered_format
69
+ finder.find_all(name, prefixes, partial, keys).first
76
- result = finder.find_all(name, prefixes, partial, keys, formats: [format]).first if format
77
- result || finder.find_all(name, prefixes, partial, keys).first
78
70
end
79
71
end
80
72
end
data/lib/action_view/gem_version.rb CHANGED
@@ -7,9 +7,9 @@ module ActionView
7
7
end
8
8
9
9
module VERSION
10
- MAJOR = 5
11
- MINOR = 2
12
- TINY = 3
10
+ MAJOR = 6
11
+ MINOR = 0
12
+ TINY = 0
13
13
PRE = nil
14
14
15
15
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
data/lib/action_view/helpers.rb CHANGED
@@ -23,7 +23,6 @@ module ActionView #:nodoc:
23
23
autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
24
24
autoload :NumberHelper
25
25
autoload :OutputSafetyHelper
26
- autoload :RecordTagHelper
27
26
autoload :RenderingHelper
28
27
autoload :SanitizeHelper
29
28
autoload :TagHelper
@@ -57,7 +56,6 @@ module ActionView #:nodoc:
57
56
include JavaScriptHelper
58
57
include NumberHelper
59
58
include OutputSafetyHelper
60
- include RecordTagHelper
61
59
include RenderingHelper
62
60
include SanitizeHelper
63
61
include TagHelper
data/lib/action_view/helpers/asset_tag_helper.rb CHANGED
@@ -55,7 +55,7 @@ module ActionView
55
55
# that path.
56
56
# * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
57
57
# when it is set to true.
58
- # * <tt>:nonce<tt> - When set to true, adds an automatic nonce value if
58
+ # * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if
59
59
# you have Content Security Policy enabled.
60
60
#
61
61
# ==== Examples
@@ -98,7 +98,7 @@ module ActionView
98
98
if tag_options["nonce"] == true
99
99
tag_options["nonce"] = content_security_policy_nonce
100
100
end
101
- content_tag("script".freeze, "", tag_options)
101
+ content_tag("script", "", tag_options)
102
102
}.join("\n").html_safe
103
103
104
104
request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) && request
@@ -329,14 +329,14 @@ module ActionView
329
329
# image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw")
330
330
# # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw">
331
331
#
332
- # Active Storage (images that are uploaded by the users of your app):
332
+ # Active Storage blobs (images that are uploaded by the users of your app):
333
333
#
334
334
# image_tag(user.avatar)
335
335
# # => <img src="/rails/active_storage/blobs/.../tiger.jpg" />
336
- # image_tag(user.avatar.variant(resize: "100x100"))
337
- # # => <img src="/rails/active_storage/variants/.../tiger.jpg" />
338
- # image_tag(user.avatar.variant(resize: "100x100"), size: '100')
339
- # # => <img width="100" height="100" src="/rails/active_storage/variants/.../tiger.jpg" />
336
+ # image_tag(user.avatar.variant(resize_to_limit: [100, 100]))
337
+ # # => <img src="/rails/active_storage/representations/.../tiger.jpg" />
338
+ # image_tag(user.avatar.variant(resize_to_limit: [100, 100]), size: '100')
339
+ # # => <img width="100" height="100" src="/rails/active_storage/representations/.../tiger.jpg" />
340
340
def image_tag(source, options = {})
341
341
options = options.symbolize_keys
342
342
check_for_image_tag_errors(options)
@@ -355,29 +355,6 @@ module ActionView
355
355
tag("img", options)
356
356
end
357
357
358
- # Returns a string suitable for an HTML image tag alt attribute.
359
- # The +src+ argument is meant to be an image file path.
360
- # The method removes the basename of the file path and the digest,
361
- # if any. It also removes hyphens and underscores from file names and
362
- # replaces them with spaces, returning a space-separated, titleized
363
- # string.
364
- #
365
- # ==== Examples
366
- #
367
- # image_alt('rails.png')
368
- # # => Rails
369
- #
370
- # image_alt('hyphenated-file-name.png')
371
- # # => Hyphenated file name
372
- #
373
- # image_alt('underscored_file_name.png')
374
- # # => Underscored file name
375
- def image_alt(src)
376
- ActiveSupport::Deprecation.warn("image_alt is deprecated and will be removed from Rails 6.0. You must explicitly set alt text on images.")
377
-
378
- File.basename(src, ".*".freeze).sub(/-[[:xdigit:]]{32,64}\z/, "".freeze).tr("-_".freeze, " ".freeze).capitalize
379
- end
380
-
381
358
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
382
359
# a single video tag will be returned. If +sources+ is an array, a video
383
360
# tag with nested source tags for each source will be returned. The
data/lib/action_view/helpers/asset_url_helper.rb CHANGED
@@ -98,8 +98,9 @@ module ActionView
98
98
# have SSL certificates for each of the asset hosts this technique allows you
99
99
# to avoid warnings in the client about mixed media.
100
100
# Note that the +request+ parameter might not be supplied, e.g. when the assets
101
- # are precompiled via a Rake task. Make sure to use a +Proc+ instead of a lambda,
102
- # since a +Proc+ allows missing parameters and sets them to +nil+.
101
+ # are precompiled with the command `rails assets:precompile`. Make sure to use a
102
+ # +Proc+ instead of a lambda, since a +Proc+ allows missing parameters and sets them
103
+ # to +nil+.
103
104
#
104
105
# config.action_controller.asset_host = Proc.new { |source, request|
105
106
# if request && request.ssl?
@@ -187,7 +188,7 @@ module ActionView
187
188
return "" if source.blank?
188
189
return source if URI_REGEXP.match?(source)
189
190
190
- tail, source = source[/([\?#].+)#x2F;], source.sub(/([\?#].+)#x2F;, "".freeze)
191
+ tail, source = source[/([\?#].+)#x2F;], source.sub(/([\?#].+)#x2F;, "")
191
192
192
193
if extname = compute_asset_extname(source, options)
193
194
source = "#{source}#{extname}"
data/lib/action_view/helpers/cache_helper.rb CHANGED
@@ -201,34 +201,42 @@ module ActionView
201
201
end
202
202
203
203
# This helper returns the name of a cache key for a given fragment cache
204
- # call. By supplying +skip_digest:+ true to cache, the digestion of cache
204
+ # call. By supplying <tt>skip_digest: true</tt> to cache, the digestion of cache
205
205
# fragments can be manually bypassed. This is useful when cache fragments
206
206
# cannot be manually expired unless you know the exact key which is the
207
207
# case when using memcached.
208
208
#
209
209
# The digest will be generated using +virtual_path:+ if it is provided.
210
210
#
211
- def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil)
211
+ def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil, digest_path: nil)
212
212
if skip_digest
213
213
name
214
214
else
215
- fragment_name_with_digest(name, virtual_path)
215
+ fragment_name_with_digest(name, virtual_path, digest_path)
216
+ end
217
+ end
218
+
219
+ def digest_path_from_template(template) # :nodoc:
220
+ digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies)
221
+
222
+ if digest.present?
223
+ "#{template.virtual_path}:#{digest}"
224
+ else
225
+ template.virtual_path
216
226
end
217
227
end
218
228
219
229
private
220
230
221
- def fragment_name_with_digest(name, virtual_path)
231
+ def fragment_name_with_digest(name, virtual_path, digest_path)
222
232
virtual_path ||= @virtual_path
223
233
224
- if virtual_path
234
+ if virtual_path || digest_path
225
235
name = controller.url_for(name).split("://").last if name.is_a?(Hash)
226
236
227
- if digest = Digestor.digest(name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies).presence
228
- [ "#{virtual_path}:#{digest}", name ]
229
- else
237
+ digest_path ||= digest_path_from_template(@current_template)
238
+
239
+ [ digest_path, name ]
230
- [ virtual_path, name ]
231
- end
232
240
else
233
241
name
234
242
end
data/lib/action_view/helpers/capture_helper.rb CHANGED
@@ -36,6 +36,10 @@ module ActionView
36
36
# </body>
37
37
# </html>
38
38
#
39
+ # The return of capture is the string generated by the block. For Example:
40
+ #
41
+ # @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500"
42
+ #
39
43
def capture(*args)
40
44
value = nil
41
45
buffer = with_output_buffer { value = yield(*args) }
data/lib/action_view/helpers/csp_helper.rb CHANGED
@@ -14,9 +14,11 @@ module ActionView
14
14
# This is used by the Rails UJS helper to create dynamically
15
15
# loaded inline <script> elements.
16
16
#
17
- def csp_meta_tag
17
+ def csp_meta_tag(**options)
18
18
if content_security_policy?
19
- tag("meta", name: "csp-nonce", content: content_security_policy_nonce)
19
+ options[:name] = "csp-nonce"
20
+ options[:content] = content_security_policy_nonce
21
+ tag("meta", options)
20
22
end
21
23
end
22
24
end
data/lib/action_view/helpers/csrf_helper.rb CHANGED
@@ -20,7 +20,7 @@ module ActionView
20
20
# "X-CSRF-Token" HTTP header. If you are using rails-ujs this happens automatically.
21
21
#
22
22
def csrf_meta_tags
23
- if protect_against_forgery?
23
+ if defined?(protect_against_forgery?) && protect_against_forgery?
24
24
[
25
25
tag("meta", name: "csrf-param", content: request_forgery_protection_token),
26
26
tag("meta", name: "csrf-token", content: form_authenticity_token)
data/lib/action_view/helpers/date_helper.rb CHANGED
@@ -205,6 +205,7 @@ module ActionView
205
205
# * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if
206
206
# you are creating new record. While editing existing record, <tt>:end_year</tt> defaults to
207
207
# the current selected year plus 5.
208
+ # * <tt>:year_format</tt> - Set format of years for year select. Lambda should be passed.
208
209
# * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
209
210
# as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
210
211
# first of the given month in order to not create invalid dates like 31 February.
@@ -275,6 +276,9 @@ module ActionView
275
276
# # Generates a date select with custom prompts.
276
277
# date_select("article", "written_on", prompt: { day: 'Select day', month: 'Select month', year: 'Select year' })
277
278
#
279
+ # # Generates a date select with custom year format.
280
+ # date_select("article", "written_on", year_format: ->(year) { "Heisei #{year - 1988}" })
281
+ #
278
282
# The selects are prepared for multi-parameter assignment to an Active Record object.
279
283
#
280
284
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
@@ -302,15 +306,15 @@ module ActionView
302
306
# time_select("article", "start_time", include_seconds: true)
303
307
#
304
308
# # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30, and 45.
305
- # time_select 'game', 'game_time', {minute_step: 15}
309
+ # time_select 'game', 'game_time', { minute_step: 15 }
306
310
#
307
311
# # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
308
- # time_select("article", "written_on", prompt: {hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds'})
309
- # time_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours
312
+ # time_select("article", "written_on", prompt: { hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds' })
313
+ # time_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours
310
314
# time_select("article", "written_on", prompt: true) # generic prompts for all
311
315
#
312
316
# # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
313
- # time_select 'game', 'game_time', {ampm: true}
317
+ # time_select 'game', 'game_time', { ampm: true }
314
318
#
315
319
# The selects are prepared for multi-parameter assignment to an Active Record object.
316
320
#
@@ -346,8 +350,8 @@ module ActionView
346
350
# datetime_select("article", "written_on", discard_type: true)
347
351
#
348
352
# # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
349
- # datetime_select("article", "written_on", prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
350
- # datetime_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours
353
+ # datetime_select("article", "written_on", prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
354
+ # datetime_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours
351
355
# datetime_select("article", "written_on", prompt: true) # generic prompts for all
352
356
#
353
357
# The selects are prepared for multi-parameter assignment to an Active Record object.
@@ -397,8 +401,8 @@ module ActionView
397
401
# select_datetime(my_date_time, prefix: 'payday')
398
402
#
399
403
# # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
400
- # select_datetime(my_date_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
401
- # select_datetime(my_date_time, prompt: {hour: true}) # generic prompt for hours
404
+ # select_datetime(my_date_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
405
+ # select_datetime(my_date_time, prompt: { hour: true }) # generic prompt for hours
402
406
# select_datetime(my_date_time, prompt: true) # generic prompts for all
403
407
def select_datetime(datetime = Time.current, options = {}, html_options = {})
404
408
DateTimeSelector.new(datetime, options, html_options).select_datetime
@@ -436,8 +440,8 @@ module ActionView
436
440
# select_date(my_date, prefix: 'payday')
437
441
#
438
442
# # Generates a date select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
439
- # select_date(my_date, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
440
- # select_date(my_date, prompt: {hour: true}) # generic prompt for hours
443
+ # select_date(my_date, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
444
+ # select_date(my_date, prompt: { hour: true }) # generic prompt for hours
441
445
# select_date(my_date, prompt: true) # generic prompts for all
442
446
def select_date(date = Date.current, options = {}, html_options = {})
443
447
DateTimeSelector.new(date, options, html_options).select_date
@@ -476,8 +480,8 @@ module ActionView
476
480
# select_time(my_time, start_hour: 2, end_hour: 14)
477
481
#
478
482
# # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts.
479
- # select_time(my_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
480
- # select_time(my_time, prompt: {hour: true}) # generic prompt for hours
483
+ # select_time(my_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
484
+ # select_time(my_time, prompt: { hour: true }) # generic prompt for hours
481
485
# select_time(my_time, prompt: true) # generic prompts for all
482
486
def select_time(datetime = Time.current, options = {}, html_options = {})
483
487
DateTimeSelector.new(datetime, options, html_options).select_time
@@ -668,8 +672,6 @@ module ActionView
668
672
# <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
669
673
# time_tag Date.yesterday, 'Yesterday' # =>
670
674
# <time datetime="2010-11-03">Yesterday</time>
671
- # time_tag Date.today, pubdate: true # =>
672
- # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
673
675
# time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # =>
674
676
# <time datetime="2010-W44">November 04, 2010</time>
675
677
#
@@ -681,9 +683,8 @@ module ActionView
681
683
options = args.extract_options!
682
684
format = options.delete(:format) || :long
683
685
content = args.first || I18n.l(date_or_time, format: format)
684
- datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601
685
686
686
- content_tag("time".freeze, content, options.reverse_merge(datetime: datetime), &block)
687
+ content_tag("time", content, options.reverse_merge(datetime: date_or_time.iso8601), &block)
687
688
end
688
689
689
690
private
@@ -702,7 +703,7 @@ module ActionView
702
703
class DateTimeSelector #:nodoc:
703
704
include ActionView::Helpers::TagHelper
704
705
705
- DEFAULT_PREFIX = "date".freeze
706
+ DEFAULT_PREFIX = "date"
706
707
POSITION = {
707
708
year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6
708
709
}.freeze
@@ -823,7 +824,7 @@ module ActionView
823
824
1.upto(12) do |month_number|
824
825
options = { value: month_number }
825
826
options[:selected] = "selected" if month == month_number
826
- month_options << content_tag("option".freeze, month_name(month_number), options) + "\n"
827
+ month_options << content_tag("option", month_name(month_number), options) + "\n"
827
828
end
828
829
build_select(:month, month_options.join)
829
830
end
@@ -851,7 +852,7 @@ module ActionView
851
852
raise ArgumentError, "There are too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter."
852
853
end
853
854
854
- build_options_and_select(:year, val, options)
855
+ build_select(:year, build_year_options(val, options))
855
856
end
856
857
end
857
858
@@ -934,6 +935,21 @@ module ActionView
934
935
end
935
936
end
936
937
938
+ # Looks up year names by number.
939
+ #
940
+ # year_name(1998) # => 1998
941
+ #
942
+ # If the <tt>:year_format</tt> option is passed:
943
+ #
944
+ # year_name(1998) # => "Heisei 10"
945
+ def year_name(number)
946
+ if year_format_lambda = @options[:year_format]
947
+ year_format_lambda.call(number)
948
+ else
949
+ number
950
+ end
951
+ end
952
+
937
953
def date_order
938
954
@date_order ||= @options[:order] || translated_date_order
939
955
end
@@ -990,7 +1006,35 @@ module ActionView
990
1006
tag_options[:selected] = "selected" if selected == i
991
1007
text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
992
1008
text = options[:ampm] ? AMPM_TRANSLATION[i] : text
993
- select_options << content_tag("option".freeze, text, tag_options)
1009
+ select_options << content_tag("option", text, tag_options)
1010
+ end
1011
+
1012
+ (select_options.join("\n") + "\n").html_safe
1013
+ end
1014
+
1015
+ # Build select option HTML for year.
1016
+ # If <tt>year_format</tt> option is not passed
1017
+ # build_year_options(1998, start: 1998, end: 2000)
1018
+ # => "<option value="1998" selected="selected">1998</option>
1019
+ # <option value="1999">1999</option>
1020
+ # <option value="2000">2000</option>"
1021
+ #
1022
+ # If <tt>year_format</tt> option is passed
1023
+ # build_year_options(1998, start: 1998, end: 2000, year_format: ->year { "Heisei #{ year - 1988 }" })
1024
+ # => "<option value="1998" selected="selected">Heisei 10</option>
1025
+ # <option value="1999">Heisei 11</option>
1026
+ # <option value="2000">Heisei 12</option>"
1027
+ def build_year_options(selected, options = {})
1028
+ start = options.delete(:start)
1029
+ stop = options.delete(:end)
1030
+ step = options.delete(:step)
1031
+
1032
+ select_options = []
1033
+ start.step(stop, step) do |value|
1034
+ tag_options = { value: value }
1035
+ tag_options[:selected] = "selected" if selected == value
1036
+ text = year_name(value)
1037
+ select_options << content_tag("option", text, tag_options)
994
1038
end
995
1039
996
1040
(select_options.join("\n") + "\n").html_safe
@@ -1009,12 +1053,12 @@ module ActionView
1009
1053
select_options[:disabled] = "disabled" if @options[:disabled]
1010
1054
select_options[:class] = css_class_attribute(type, select_options[:class], @options[:with_css_classes]) if @options[:with_css_classes]
1011
1055
1012
- select_html = "\n".dup
1013
- select_html << content_tag("option".freeze, "", value: "") + "\n" if @options[:include_blank]
1056
+ select_html = +"\n"
1057
+ select_html << content_tag("option", "", value: "") + "\n" if @options[:include_blank]
1014
1058
select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
1015
1059
select_html << select_options_as_html
1016
1060
1017
- (content_tag("select".freeze, select_html.html_safe, select_options) + "\n").html_safe
1061
+ (content_tag("select", select_html.html_safe, select_options) + "\n").html_safe
1018
1062
end
1019
1063
1020
1064
# Builds the css class value for the select element
@@ -1047,7 +1091,7 @@ module ActionView
1047
1091
I18n.translate(:"datetime.prompts.#{type}", locale: @options[:locale])
1048
1092
end
1049
1093
1050
- prompt ? content_tag("option".freeze, prompt, value: "") : ""
1094
+ prompt ? content_tag("option", prompt, value: "") : ""
1051
1095
end
1052
1096
1053
1097
# Builds hidden input tag for date part and value.
@@ -1091,7 +1135,7 @@ module ActionView
1091
1135
# Given an ordering of datetime components, create the selection HTML
1092
1136
# and join them with their appropriate separators.
1093
1137
def build_selects_from_types(order)
1094
- select = "".dup
1138
+ select = +""
1095
1139
first_visible = order.find { |type| !@options[:"discard_#{type}"] }
1096
1140
order.reverse_each do |type|
1097
1141
separator = separator(type) unless type == first_visible # don't add before first visible field
data/lib/action_view/helpers/form_helper.rb CHANGED
@@ -590,6 +590,9 @@ module ActionView
590
590
# Skipped if a <tt>:url</tt> is passed.
591
591
# * <tt>:scope</tt> - The scope to prefix input field names with and
592
592
# thereby how the submitted parameters are grouped in controllers.
593
+ # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
594
+ # id attributes on form elements. The namespace attribute will be prefixed
595
+ # with underscore on the generated HTML id.
593
596
# * <tt>:model</tt> - A model object to infer the <tt>:url</tt> and
594
597
# <tt>:scope</tt> by, plus fill out input field values.
595
598
# So if a +title+ attribute is set to "Ahoy!" then a +title+ input
@@ -610,8 +613,8 @@ module ActionView
610
613
# unnecessary unless you support browsers without JavaScript.
611
614
# * <tt>:local</tt> - By default form submits are remote and unobtrusive XHRs.
612
615
# Disable remote submits with <tt>local: true</tt>.
613
- # * <tt>:skip_enforcing_utf8</tt> - By default a hidden field named +utf8+
614
- # is output to enforce UTF-8 submits. Set to true to skip the field.
616
+ # * <tt>:skip_enforcing_utf8</tt> - If set to true, a hidden input with name
617
+ # utf8 is not output.
615
618
# * <tt>:builder</tt> - Override the object used to build the form.
616
619
# * <tt>:id</tt> - Optional HTML id attribute.
617
620
# * <tt>:class</tt> - Optional HTML class attribute.
@@ -736,7 +739,7 @@ module ActionView
736
739
# def labelled_form_with(**options, &block)
737
740
# form_with(**options.merge(builder: LabellingFormBuilder), &block)
738
741
# end
739
- def form_with(model: nil, scope: nil, url: nil, format: nil, **options)
742
+ def form_with(model: nil, scope: nil, url: nil, format: nil, **options, &block)
740
743
options[:allow_method_names_outside_object] = true
741
744
options[:skip_default_ids] = !form_with_generates_ids
742
745
@@ -749,7 +752,7 @@ module ActionView
749
752
750
753
if block_given?
751
754
builder = instantiate_builder(scope, model, options)
752
- output = capture(builder, &Proc.new)
755
+ output = capture(builder, &block)
753
756
options[:multipart] ||= builder.multipart?
754
757
755
758
html_options = html_options_for_form_with(url, model, options)
@@ -1127,6 +1130,9 @@ module ActionView
1127
1130
# text_field(:post, :title, class: "create_input")
1128
1131
# # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
1129
1132
#
1133
+ # text_field(:post, :title, maxlength: 30, class: "title_input")
1134
+ # # => <input type="text" id="post_title" name="post[title]" maxlength="30" size="30" value="#{@post.title}" class="title_input" />
1135
+ #
1130
1136
# text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
1131
1137
# # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/>
1132
1138
#
@@ -1519,10 +1525,10 @@ module ActionView
1519
1525
1520
1526
private
1521
1527
def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: !form_with_generates_remote_forms,
1522
- skip_enforcing_utf8: false, **options)
1528
+ skip_enforcing_utf8: nil, **options)
1523
1529
html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html)
1524
1530
html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted?
1525
- html_options[:enforce_utf8] = !skip_enforcing_utf8
1531
+ html_options[:enforce_utf8] = !skip_enforcing_utf8 unless skip_enforcing_utf8.nil?
1526
1532
1527
1533
html_options[:enctype] = "multipart/form-data" if html_options.delete(:multipart)
1528
1534
@@ -1674,6 +1680,227 @@ module ActionView
1674
1680
@index = options[:index] || options[:child_index]
1675
1681
end
1676
1682
1683
+ ##
1684
+ # :method: text_field
1685
+ #
1686
+ # :call-seq: text_field(method, options = {})
1687
+ #
1688
+ # Wraps ActionView::Helpers::FormHelper#text_field for form builders:
1689
+ #
1690
+ # <%= form_with model: @user do |f| %>
1691
+ # <%= f.text_field :name %>
1692
+ # <% end %>
1693
+ #
1694
+ # Please refer to the documentation of the base helper for details.
1695
+
1696
+ ##
1697
+ # :method: password_field
1698
+ #
1699
+ # :call-seq: password_field(method, options = {})
1700
+ #
1701
+ # Wraps ActionView::Helpers::FormHelper#password_field for form builders:
1702
+ #
1703
+ # <%= form_with model: @user do |f| %>
1704
+ # <%= f.password_field :password %>
1705
+ # <% end %>
1706
+ #
1707
+ # Please refer to the documentation of the base helper for details.
1708
+
1709
+ ##
1710
+ # :method: text_area
1711
+ #
1712
+ # :call-seq: text_area(method, options = {})
1713
+ #
1714
+ # Wraps ActionView::Helpers::FormHelper#text_area for form builders:
1715
+ #
1716
+ # <%= form_with model: @user do |f| %>
1717
+ # <%= f.text_area :detail %>
1718
+ # <% end %>
1719
+ #
1720
+ # Please refer to the documentation of the base helper for details.
1721
+
1722
+ ##
1723
+ # :method: color_field
1724
+ #
1725
+ # :call-seq: color_field(method, options = {})
1726
+ #
1727
+ # Wraps ActionView::Helpers::FormHelper#color_field for form builders:
1728
+ #
1729
+ # <%= form_with model: @user do |f| %>
1730
+ # <%= f.color_field :favorite_color %>
1731
+ # <% end %>
1732
+ #
1733
+ # Please refer to the documentation of the base helper for details.
1734
+
1735
+ ##
1736
+ # :method: search_field
1737
+ #
1738
+ # :call-seq: search_field(method, options = {})
1739
+ #
1740
+ # Wraps ActionView::Helpers::FormHelper#search_field for form builders:
1741
+ #
1742
+ # <%= form_with model: @user do |f| %>
1743
+ # <%= f.search_field :name %>
1744
+ # <% end %>
1745
+ #
1746
+ # Please refer to the documentation of the base helper for details.
1747
+
1748
+ ##
1749
+ # :method: telephone_field
1750
+ #
1751
+ # :call-seq: telephone_field(method, options = {})
1752
+ #
1753
+ # Wraps ActionView::Helpers::FormHelper#telephone_field for form builders:
1754
+ #
1755
+ # <%= form_with model: @user do |f| %>
1756
+ # <%= f.telephone_field :phone %>
1757
+ # <% end %>
1758
+ #
1759
+ # Please refer to the documentation of the base helper for details.
1760
+
1761
+ ##
1762
+ # :method: phone_field
1763
+ #
1764
+ # :call-seq: phone_field(method, options = {})
1765
+ #
1766
+ # Wraps ActionView::Helpers::FormHelper#phone_field for form builders:
1767
+ #
1768
+ # <%= form_with model: @user do |f| %>
1769
+ # <%= f.phone_field :phone %>
1770
+ # <% end %>
1771
+ #
1772
+ # Please refer to the documentation of the base helper for details.
1773
+
1774
+ ##
1775
+ # :method: date_field
1776
+ #
1777
+ # :call-seq: date_field(method, options = {})
1778
+ #
1779
+ # Wraps ActionView::Helpers::FormHelper#date_field for form builders:
1780
+ #
1781
+ # <%= form_with model: @user do |f| %>
1782
+ # <%= f.date_field :born_on %>
1783
+ # <% end %>
1784
+ #
1785
+ # Please refer to the documentation of the base helper for details.
1786
+
1787
+ ##
1788
+ # :method: time_field
1789
+ #
1790
+ # :call-seq: time_field(method, options = {})
1791
+ #
1792
+ # Wraps ActionView::Helpers::FormHelper#time_field for form builders:
1793
+ #
1794
+ # <%= form_with model: @user do |f| %>
1795
+ # <%= f.time_field :borned_at %>
1796
+ # <% end %>
1797
+ #
1798
+ # Please refer to the documentation of the base helper for details.
1799
+
1800
+ ##
1801
+ # :method: datetime_field
1802
+ #
1803
+ # :call-seq: datetime_field(method, options = {})
1804
+ #
1805
+ # Wraps ActionView::Helpers::FormHelper#datetime_field for form builders:
1806
+ #
1807
+ # <%= form_with model: @user do |f| %>
1808
+ # <%= f.datetime_field :graduation_day %>
1809
+ # <% end %>
1810
+ #
1811
+ # Please refer to the documentation of the base helper for details.
1812
+
1813
+ ##
1814
+ # :method: datetime_local_field
1815
+ #
1816
+ # :call-seq: datetime_local_field(method, options = {})
1817
+ #
1818
+ # Wraps ActionView::Helpers::FormHelper#datetime_local_field for form builders:
1819
+ #
1820
+ # <%= form_with model: @user do |f| %>
1821
+ # <%= f.datetime_local_field :graduation_day %>
1822
+ # <% end %>
1823
+ #
1824
+ # Please refer to the documentation of the base helper for details.
1825
+
1826
+ ##
1827
+ # :method: month_field
1828
+ #
1829
+ # :call-seq: month_field(method, options = {})
1830
+ #
1831
+ # Wraps ActionView::Helpers::FormHelper#month_field for form builders:
1832
+ #
1833
+ # <%= form_with model: @user do |f| %>
1834
+ # <%= f.month_field :birthday_month %>
1835
+ # <% end %>
1836
+ #
1837
+ # Please refer to the documentation of the base helper for details.
1838
+
1839
+ ##
1840
+ # :method: week_field
1841
+ #
1842
+ # :call-seq: week_field(method, options = {})
1843
+ #
1844
+ # Wraps ActionView::Helpers::FormHelper#week_field for form builders:
1845
+ #
1846
+ # <%= form_with model: @user do |f| %>
1847
+ # <%= f.week_field :birthday_week %>
1848
+ # <% end %>
1849
+ #
1850
+ # Please refer to the documentation of the base helper for details.
1851
+
1852
+ ##
1853
+ # :method: url_field
1854
+ #
1855
+ # :call-seq: url_field(method, options = {})
1856
+ #
1857
+ # Wraps ActionView::Helpers::FormHelper#url_field for form builders:
1858
+ #
1859
+ # <%= form_with model: @user do |f| %>
1860
+ # <%= f.url_field :homepage %>
1861
+ # <% end %>
1862
+ #
1863
+ # Please refer to the documentation of the base helper for details.
1864
+
1865
+ ##
1866
+ # :method: email_field
1867
+ #
1868
+ # :call-seq: email_field(method, options = {})
1869
+ #
1870
+ # Wraps ActionView::Helpers::FormHelper#email_field for form builders:
1871
+ #
1872
+ # <%= form_with model: @user do |f| %>
1873
+ # <%= f.email_field :address %>
1874
+ # <% end %>
1875
+ #
1876
+ # Please refer to the documentation of the base helper for details.
1877
+
1878
+ ##
1879
+ # :method: number_field
1880
+ #
1881
+ # :call-seq: number_field(method, options = {})
1882
+ #
1883
+ # Wraps ActionView::Helpers::FormHelper#number_field for form builders:
1884
+ #
1885
+ # <%= form_with model: @user do |f| %>
1886
+ # <%= f.number_field :age %>
1887
+ # <% end %>
1888
+ #
1889
+ # Please refer to the documentation of the base helper for details.
1890
+
1891
+ ##
1892
+ # :method: range_field
1893
+ #
1894
+ # :call-seq: range_field(method, options = {})
1895
+ #
1896
+ # Wraps ActionView::Helpers::FormHelper#range_field for form builders:
1897
+ #
1898
+ # <%= form_with model: @user do |f| %>
1899
+ # <%= f.range_field :age %>
1900
+ # <% end %>
1901
+ #
1902
+ # Please refer to the documentation of the base helper for details.
1903
+
1677
1904
(field_helpers - [:label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field]).each do |selector|
1678
1905
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1679
1906
def #{selector}(method, options = {}) # def text_field(method, options = {})
@@ -2247,7 +2474,7 @@ module ActionView
2247
2474
@template.button_tag(value, options, &block)
2248
2475
end
2249
2476
2250
- def emitted_hidden_id?
2477
+ def emitted_hidden_id? # :nodoc:
2251
2478
@emitted_hidden_id ||= nil
2252
2479
end
2253
2480
@@ -2267,7 +2494,12 @@ module ActionView
2267
2494
end
2268
2495
2269
2496
defaults = []
2270
- defaults << :"helpers.submit.#{object_name}.#{key}"
2497
+ # Object is a model and it is not overwritten by as and scope option.
2498
+ if object.respond_to?(:model_name) && object_name.to_s == model.downcase
2499
+ defaults << :"helpers.submit.#{object.model_name.i18n_key}.#{key}"
2500
+ else
2501
+ defaults << :"helpers.submit.#{object_name}.#{key}"
2502
+ end
2271
2503
defaults << :"helpers.submit.#{key}"
2272
2504
defaults << "#{key.to_s.humanize} #{model}"
2273
2505
data/lib/action_view/helpers/form_options_helper.rb CHANGED
@@ -16,7 +16,7 @@ module ActionView
16
16
#
17
17
# * <tt>:include_blank</tt> - set to true or a prompt string if the first option element of the select element is a blank. Useful if there is not a default value required for the select element.
18
18
#
19
- # select("post", "category", Post::CATEGORIES, {include_blank: true})
19
+ # select("post", "category", Post::CATEGORIES, { include_blank: true })
20
20
#
21
21
# could become:
22
22
#
@@ -30,7 +30,7 @@ module ActionView
30
30
#
31
31
# Example with <tt>@post.person_id => 2</tt>:
32
32
#
33
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {include_blank: 'None'})
33
+ # select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: 'None' })
34
34
#
35
35
# could become:
36
36
#
@@ -43,7 +43,7 @@ module ActionView
43
43
#
44
44
# * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
45
45
#
46
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, {prompt: 'Select Person'})
46
+ # select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { prompt: 'Select Person' })
47
47
#
48
48
# could become:
49
49
#
@@ -69,7 +69,7 @@ module ActionView
69
69
#
70
70
# * <tt>:disabled</tt> - can be a single value or an array of values that will be disabled options in the final output.
71
71
#
72
- # select("post", "category", Post::CATEGORIES, {disabled: 'restricted'})
72
+ # select("post", "category", Post::CATEGORIES, { disabled: 'restricted' })
73
73
#
74
74
# could become:
75
75
#
@@ -82,7 +82,7 @@ module ActionView
82
82
#
83
83
# When used with the <tt>collection_select</tt> helper, <tt>:disabled</tt> can also be a Proc that identifies those options that should be disabled.
84
84
#
85
- # collection_select(:post, :category_id, Category.all, :id, :name, {disabled: -> (category) { category.archived? }})
85
+ # collection_select(:post, :category_id, Category.all, :id, :name, { disabled: -> (category) { category.archived? } })
86
86
#
87
87
# If the categories "2008 stuff" and "Christmas" return true when the method <tt>archived?</tt> is called, this would return:
88
88
# <select name="post[category_id]" id="post_category_id">
@@ -107,7 +107,7 @@ module ActionView
107
107
#
108
108
# For example:
109
109
#
110
- # select("post", "person_id", Person.all.collect {|p| [ p.name, p.id ] }, { include_blank: true })
110
+ # select("post", "person_id", Person.all.collect { |p| [ p.name, p.id ] }, { include_blank: true })
111
111
#
112
112
# would become:
113
113
#
@@ -323,12 +323,12 @@ module ActionView
323
323
#
324
324
# You can optionally provide HTML attributes as the last element of the array.
325
325
#
326
- # options_for_select([ "Denmark", ["USA", {class: 'bold'}], "Sweden" ], ["USA", "Sweden"])
326
+ # options_for_select([ "Denmark", ["USA", { class: 'bold' }], "Sweden" ], ["USA", "Sweden"])
327
327
# # => <option value="Denmark">Denmark</option>
328
328
# # => <option value="USA" class="bold" selected="selected">USA</option>
329
329
# # => <option value="Sweden" selected="selected">Sweden</option>
330
330
#
331
- # options_for_select([["Dollar", "quot;, {class: "bold"}], ["Kroner", "DKK", {onclick: "alert('HI');"}]])
331
+ # options_for_select([["Dollar", "quot;, { class: "bold" }], ["Kroner", "DKK", { onclick: "alert('HI');" }]])
332
332
# # => <option value="quot; class="bold">Dollar</option>
333
333
# # => <option value="DKK" onclick="alert('HI');">Kroner</option>
334
334
#
@@ -463,7 +463,7 @@ module ActionView
463
463
option_tags = options_from_collection_for_select(
464
464
value_for_collection(group, group_method), option_key_method, option_value_method, selected_key)
465
465
466
- content_tag("optgroup".freeze, option_tags, label: value_for_collection(group, group_label_method))
466
+ content_tag("optgroup", option_tags, label: value_for_collection(group, group_label_method))
467
467
end.join.html_safe
468
468
end
469
469
@@ -535,7 +535,7 @@ module ActionView
535
535
body = "".html_safe
536
536
537
537
if prompt
538
- body.safe_concat content_tag("option".freeze, prompt_text(prompt), value: "")
538
+ body.safe_concat content_tag("option", prompt_text(prompt), value: "")
539
539
end
540
540
541
541
grouped_options.each do |container|
@@ -548,7 +548,7 @@ module ActionView
548
548
end
549
549
550
550
html_attributes = { label: label }.merge!(html_attributes)
551
- body.safe_concat content_tag("optgroup".freeze, options_for_select(container, selected_key), html_attributes)
551
+ body.safe_concat content_tag("optgroup", options_for_select(container, selected_key), html_attributes)
552
552
end
553
553
554
554
body
@@ -566,9 +566,10 @@ module ActionView
566
566
# an ActiveSupport::TimeZone.
567
567
#
568
568
# By default, +model+ is the ActiveSupport::TimeZone constant (which can
569
- # be obtained in Active Record as a value object). The only requirement
570
- # is that the +model+ parameter be an object that responds to +all+, and
571
- # returns an array of objects that represent time zones.
569
+ # be obtained in Active Record as a value object). The +model+ parameter
570
+ # must respond to +all+ and return an array of objects that represent time
571
+ # zones; each object must respond to +name+. If a Regexp is given it will
572
+ # attempt to match the zones using the <code>=~<code> operator.
572
573
#
573
574
# NOTE: Only the option tags are returned, you have to wrap this call in
574
575
# a regular HTML select tag.
@@ -584,7 +585,7 @@ module ActionView
584
585
end
585
586
586
587
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
587
- zone_options.safe_concat content_tag("option".freeze, "-------------", value: "", disabled: true)
588
+ zone_options.safe_concat content_tag("option", "-------------", value: "", disabled: true)
588
589
zone_options.safe_concat "\n"
589
590
590
591
zones = zones - priority_zones
@@ -654,7 +655,7 @@ module ActionView
654
655
#
655
656
# ==== Gotcha
656
657
#
657
- # The HTML specification says when nothing is select on a collection of radio buttons
658
+ # The HTML specification says when nothing is selected on a collection of radio buttons
658
659
# web browsers do not send any value to server.
659
660
# Unfortunately this introduces a gotcha:
660
661
# if a +User+ model has a +category_id+ field and in the form no category is selected, no +category_id+ parameter is sent. So,
@@ -794,7 +795,7 @@ module ActionView
794
795
def extract_values_from_collection(collection, value_method, selected)
795
796
if selected.is_a?(Proc)
796
797
collection.map do |element|
797
- element.send(value_method) if selected.call(element)
798
+ public_or_deprecated_send(element, value_method) if selected.call(element)
798
799
end.compact
799
800
else
800
801
selected
@@ -802,7 +803,15 @@ module ActionView
802
803
end
803
804
804
805
def value_for_collection(item, value)
805
- value.respond_to?(:call) ? value.call(item) : item.send(value)
806
+ value.respond_to?(:call) ? value.call(item) : public_or_deprecated_send(item, value)
807
+ end
808
+
809
+ def public_or_deprecated_send(item, value)
810
+ item.public_send(value)
811
+ rescue NoMethodError
812
+ raise unless item.respond_to?(value, true) && !item.respond_to?(value)
813
+ ActiveSupport::Deprecation.warn "Using private methods from view helpers is deprecated (calling private #{item.class}##{value})"
814
+ item.send(value)
806
815
end
807
816
808
817
def prompt_text(prompt)
data/lib/action_view/helpers/form_tag_helper.rb CHANGED
@@ -22,7 +22,9 @@ module ActionView
22
22
mattr_accessor :embed_authenticity_token_in_remote_forms
23
23
self.embed_authenticity_token_in_remote_forms = nil
24
24
25
- # Starts a form tag that points the action to a url configured with <tt>url_for_options</tt> just like
25
+ mattr_accessor :default_enforce_utf8, default: true
26
+
27
+ # Starts a form tag that points the action to a URL configured with <tt>url_for_options</tt> just like
26
28
# ActionController::Base#url_for. The method for the form defaults to POST.
27
29
#
28
30
# ==== Options
@@ -135,7 +137,8 @@ module ActionView
135
137
html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
136
138
137
139
if options.include?(:include_blank)
138
- include_blank = options.delete(:include_blank)
140
+ include_blank = options[:include_blank]
141
+ options = options.except(:include_blank)
139
142
options_for_blank_options_tag = { value: "" }
140
143
141
144
if include_blank == true
@@ -144,15 +147,15 @@ module ActionView
144
147
end
145
148
146
149
if include_blank
147
- option_tags = content_tag("option".freeze, include_blank, options_for_blank_options_tag).safe_concat(option_tags)
150
+ option_tags = content_tag("option", include_blank, options_for_blank_options_tag).safe_concat(option_tags)
148
151
end
149
152
end
150
153
151
154
if prompt = options.delete(:prompt)
152
- option_tags = content_tag("option".freeze, prompt, value: "").safe_concat(option_tags)
155
+ option_tags = content_tag("option", prompt, value: "").safe_concat(option_tags)
153
156
end
154
157
155
- content_tag "select".freeze, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
158
+ content_tag "select", option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
156
159
end
157
160
158
161
# Creates a standard text field; use these text fields to input smaller chunks of text like a username
@@ -387,8 +390,8 @@ module ActionView
387
390
# * Any other key creates standard HTML options for the tag.
388
391
#
389
392
# ==== Examples
390
- # radio_button_tag 'gender', 'male'
391
- # # => <input id="gender_male" name="gender" type="radio" value="male" />
393
+ # radio_button_tag 'favorite_color', 'maroon'
394
+ # # => <input id="favorite_color_maroon" name="favorite_color" type="radio" value="maroon" />
392
395
#
393
396
# radio_button_tag 'receive_updates', 'no', true
394
397
# # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" />
@@ -575,7 +578,7 @@ module ActionView
575
578
# # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
576
579
def field_set_tag(legend = nil, options = nil, &block)
577
580
output = tag(:fieldset, options, true)
578
- output.safe_concat(content_tag("legend".freeze, legend)) unless legend.blank?
581
+ output.safe_concat(content_tag("legend", legend)) unless legend.blank?
579
582
output.concat(capture(&block)) if block_given?
580
583
output.safe_concat("</fieldset>")
581
584
end
@@ -867,7 +870,7 @@ module ActionView
867
870
})
868
871
end
869
872
870
- if html_options.delete("enforce_utf8") { true }
873
+ if html_options.delete("enforce_utf8") { default_enforce_utf8 }
871
874
utf8_enforcer_tag + method_tag
872
875
else
873
876
method_tag
data/lib/action_view/helpers/javascript_helper.rb CHANGED
@@ -15,8 +15,8 @@ module ActionView
15
15
"'" => "\\'"
16
16
}
17
17
18
- JS_ESCAPE_MAP["\342\200\250".dup.force_encoding(Encoding::UTF_8).encode!] = "&#x2028;"
19
- JS_ESCAPE_MAP["\342\200\251".dup.force_encoding(Encoding::UTF_8).encode!] = "&#x2029;"
18
+ JS_ESCAPE_MAP[(+"\342\200\250").force_encoding(Encoding::UTF_8).encode!] = "&#x2028;"
19
+ JS_ESCAPE_MAP[(+"\342\200\251").force_encoding(Encoding::UTF_8).encode!] = "&#x2029;"
20
20
21
21
# Escapes carriage returns and single and double quotes for JavaScript segments.
22
22
#
@@ -25,12 +25,13 @@ module ActionView
25
25
#
26
26
# $('some_element').replaceWith('<%= j render 'some/element_template' %>');
27
27
def escape_javascript(javascript)
28
- if javascript
29
- result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) { |match| JS_ESCAPE_MAP[match] }
30
- javascript.html_safe? ? result.html_safe : result
28
+ javascript = javascript.to_s
29
+ if javascript.empty?
30
+ result = ""
31
31
else
32
- ""
32
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) { |match| JS_ESCAPE_MAP[match] }
33
33
end
34
+ javascript.html_safe? ? result.html_safe : result
34
35
end
35
36
36
37
alias_method :j, :escape_javascript
@@ -65,7 +66,7 @@ module ActionView
65
66
# <% end -%>
66
67
#
67
68
# If you have a content security policy enabled then you can add an automatic
68
- # nonce value by passing +nonce: true+ as part of +html_options+. Example:
69
+ # nonce value by passing <tt>nonce: true</tt> as part of +html_options+. Example:
69
70
#
70
71
# <%= javascript_tag nonce: true do -%>
71
72
# alert('All is good')
@@ -83,7 +84,7 @@ module ActionView
83
84
html_options[:nonce] = content_security_policy_nonce
84
85
end
85
86
86
- content_tag("script".freeze, javascript_cdata_section(content), html_options)
87
+ content_tag("script", javascript_cdata_section(content), html_options)
87
88
end
88
89
89
90
def javascript_cdata_section(content) #:nodoc:
data/lib/action_view/helpers/number_helper.rb CHANGED
@@ -100,6 +100,9 @@ module ActionView
100
100
# absolute value of the number.
101
101
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
102
102
# the argument is invalid.
103
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
104
+ # insignificant zeros after the decimal separator (defaults to
105
+ # +false+).
103
106
#
104
107
# ==== Examples
105
108
#
@@ -117,6 +120,8 @@ module ActionView
117
120
# # => R$1234567890,50
118
121
# number_to_currency(1234567890.50, unit: "Rquot;, separator: ",", delimiter: "", format: "%n %u")
119
122
# # => 1234567890,50 R$
123
+ # number_to_currency(1234567890.50, strip_insignificant_zeros: true)
124
+ # # => "$1,234,567,890.5"
120
125
def number_to_currency(number, options = {})
121
126
delegate_number_helper_method(:number_to_currency, number, options)
122
127
end
data/lib/action_view/helpers/output_safety_helper.rb CHANGED
@@ -38,7 +38,7 @@ module ActionView #:nodoc:
38
38
39
39
# Converts the array to a comma-separated sentence where the last element is
40
40
# joined by the connector word. This is the html_safe-aware version of
41
- # ActiveSupport's {Array#to_sentence}[http://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
41
+ # ActiveSupport's {Array#to_sentence}[https://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
42
42
#
43
43
def to_sentence(array, options = {})
44
44
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
data/lib/action_view/helpers/record_tag_helper.rb DELETED
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActionView
4
- module Helpers #:nodoc:
5
- module RecordTagHelper
6
- def div_for(*) # :nodoc:
7
- raise NoMethodError, "The `div_for` method has been removed from " \
8
- "Rails. To continue using it, add the `record_tag_helper` gem to " \
9
- "your Gemfile:\n" \
10
- " gem 'record_tag_helper', '~> 1.0'\n" \
11
- "Consult the Rails upgrade guide for details."
12
- end
13
-
14
- def content_tag_for(*) # :nodoc:
15
- raise NoMethodError, "The `content_tag_for` method has been removed from " \
16
- "Rails. To continue using it, add the `record_tag_helper` gem to " \
17
- "your Gemfile:\n" \
18
- " gem 'record_tag_helper', '~> 1.0'\n" \
19
- "Consult the Rails upgrade guide for details."
20
- end
21
- end
22
- end
23
- end
data/lib/action_view/helpers/rendering_helper.rb CHANGED
@@ -27,10 +27,12 @@ module ActionView
27
27
def render(options = {}, locals = {}, &block)
28
28
case options
29
29
when Hash
30
- if block_given?
31
- view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
32
- else
33
- view_renderer.render(self, options)
30
+ in_rendering_context(options) do |renderer|
31
+ if block_given?
32
+ view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
33
+ else
34
+ view_renderer.render(self, options)
35
+ end
34
36
end
35
37
else
36
38
view_renderer.render_partial(self, partial: options, locals: locals, &block)
data/lib/action_view/helpers/sanitize_helper.rb CHANGED
@@ -1,6 +1,5 @@
1
1
# frozen_string_literal: true
2
2
3
- require "active_support/core_ext/object/try"
4
3
require "rails-html-sanitizer"
5
4
6
5
module ActionView
@@ -10,14 +9,14 @@ module ActionView
10
9
# These helper methods extend Action View making them callable within your template files.
11
10
module SanitizeHelper
12
11
extend ActiveSupport::Concern
13
- # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted.
12
+ # Sanitizes HTML input, stripping all but known-safe tags and attributes.
14
13
#
15
14
# It also strips href/src attributes with unsafe protocols like
16
15
# <tt>javascript:</tt>, while also protecting against attempts to use Unicode,
17
16
# ASCII, and hex character references to work around these protocol filters.
18
17
# All special characters will be escaped.
19
18
#
20
- # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML
19
+ # The default sanitizer is Rails::Html::SafeListSanitizer. See {Rails HTML
21
20
# Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information.
22
21
#
23
22
# Custom sanitization rules can also be provided.
@@ -40,7 +39,7 @@ module ActionView
40
39
#
41
40
# <%= sanitize @comment.body %>
42
41
#
43
- # Providing custom whitelisted tags and attributes:
42
+ # Providing custom lists of permitted tags and attributes:
44
43
#
45
44
# <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %>
46
45
#
@@ -80,12 +79,12 @@ module ActionView
80
79
# config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a']
81
80
# config.action_view.sanitized_allowed_attributes = ['href', 'title']
82
81
def sanitize(html, options = {})
83
- self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe)
82
+ self.class.safe_list_sanitizer.sanitize(html, options)&.html_safe
84
83
end
85
84
86
85
# Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute.
87
86
def sanitize_css(style)
88
- self.class.white_list_sanitizer.sanitize_css(style)
87
+ self.class.safe_list_sanitizer.sanitize_css(style)
89
88
end
90
89
91
90
# Strips all HTML tags from +html+, including comments and special characters.
@@ -123,20 +122,18 @@ module ActionView
123
122
end
124
123
125
124
module ClassMethods #:nodoc:
126
- attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer
125
+ attr_writer :full_sanitizer, :link_sanitizer, :safe_list_sanitizer
127
126
128
- # Vendors the full, link and white list sanitizers.
129
- # Provided strictly for compatibility and can be removed in Rails 5.1.
130
127
def sanitizer_vendor
131
128
Rails::Html::Sanitizer
132
129
end
133
130
134
131
def sanitized_allowed_tags
135
- sanitizer_vendor.white_list_sanitizer.allowed_tags
132
+ safe_list_sanitizer.allowed_tags
136
133
end
137
134
138
135
def sanitized_allowed_attributes
139
- sanitizer_vendor.white_list_sanitizer.allowed_attributes
136
+ safe_list_sanitizer.allowed_attributes
140
137
end
141
138
142
139
# Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
@@ -145,7 +142,6 @@ module ActionView
145
142
# class Application < Rails::Application
146
143
# config.action_view.full_sanitizer = MySpecialSanitizer.new
147
144
# end
148
- #
149
145
def full_sanitizer
150
146
@full_sanitizer ||= sanitizer_vendor.full_sanitizer.new
151
147
end
@@ -156,20 +152,18 @@ module ActionView
156
152
# class Application < Rails::Application
157
153
# config.action_view.link_sanitizer = MySpecialSanitizer.new
158
154
# end
159
- #
160
155
def link_sanitizer
161
156
@link_sanitizer ||= sanitizer_vendor.link_sanitizer.new
162
157
end
163
158
164
- # Gets the Rails::Html::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
159
+ # Gets the Rails::Html::SafeListSanitizer instance used by sanitize and +sanitize_css+.
165
160
# Replace with any object that responds to +sanitize+.
166
161
#
167
162
# class Application < Rails::Application
168
- # config.action_view.white_list_sanitizer = MySpecialSanitizer.new
163
+ # config.action_view.safe_list_sanitizer = MySpecialSanitizer.new
169
164
# end
170
- #
171
- def white_list_sanitizer
165
+ def safe_list_sanitizer
166
+ @safe_list_sanitizer ||= sanitizer_vendor.safe_list_sanitizer.new
172
- @white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new
173
167
end
174
168
end
175
169
end
data/lib/action_view/helpers/tag_helper.rb CHANGED
@@ -58,7 +58,7 @@ module ActionView
58
58
59
59
def tag_options(options, escape = true)
60
60
return if options.blank?
61
- output = "".dup
61
+ output = +""
62
62
sep = " "
63
63
options.each_pair do |key, value|
64
64
if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
@@ -86,11 +86,12 @@ module ActionView
86
86
87
87
def tag_option(key, value, escape)
88
88
if value.is_a?(Array)
89
- value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze)
89
+ value = escape ? safe_join(value, " ") : value.join(" ")
90
90
else
91
- value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
91
+ value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s.dup
92
92
end
93
- %(#{key}="#{value.gsub('"'.freeze, '&quot;'.freeze)}")
93
+ value.gsub!('"', "&quot;")
94
+ %(#{key}="#{value}")
94
95
end
95
96
96
97
private
@@ -227,10 +228,10 @@ module ActionView
227
228
# tag("img", src: "open & shut.png")
228
229
# # => <img src="open &amp; shut.png" />
229
230
#
230
- # tag("img", {src: "open &amp; shut.png"}, false, false)
231
+ # tag("img", { src: "open &amp; shut.png" }, false, false)
231
232
# # => <img src="open &amp; shut.png" />
232
233
#
233
- # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
234
+ # tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
234
235
# # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
235
236
def tag(name = nil, options = nil, open = false, escape = true)
236
237
if name.nil?
data/lib/action_view/helpers/tags/base.rb CHANGED
@@ -109,11 +109,11 @@ module ActionView
109
109
# a little duplication to construct less strings
110
110
case
111
111
when @object_name.empty?
112
- "#{sanitized_method_name}#{"[]" if multiple}"
112
+ "#{sanitized_method_name}#{multiple ? "[]" : ""}"
113
113
when index
114
- "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
114
+ "#{@object_name}[#{index}][#{sanitized_method_name}]#{multiple ? "[]" : ""}"
115
115
else
116
- "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
116
+ "#{@object_name}[#{sanitized_method_name}]#{multiple ? "[]" : ""}"
117
117
end
118
118
end
119
119
@@ -138,7 +138,7 @@ module ActionView
138
138
end
139
139
140
140
def sanitized_value(value)
141
- value.to_s.gsub(/\s/, "_").gsub(/[^-[[:word:]]]/, "").mb_chars.downcase.to_s
141
+ value.to_s.gsub(/[\s\.]/, "_").gsub(/[^-[[:word:]]]/, "").downcase
142
142
end
143
143
144
144
def select_content_tag(option_tags, options, html_options)
@@ -170,7 +170,11 @@ module ActionView
170
170
option_tags = tag_builder.content_tag_string("option", options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, value: "") + "\n" + option_tags
171
171
end
172
172
if value.blank? && options[:prompt]
173
- option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), value: "") + "\n" + option_tags
173
+ tag_options = { value: "" }.tap do |prompt_opts|
174
+ prompt_opts[:disabled] = true if options[:disabled] == ""
175
+ prompt_opts[:selected] = true if options[:selected] == ""
176
+ end
177
+ option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
174
178
end
175
179
option_tags
176
180
end
data/lib/action_view/helpers/tags/color_field.rb CHANGED
@@ -15,7 +15,7 @@ module ActionView
15
15
16
16
def validate_color_string(string)
17
17
regex = /#[0-9a-fA-F]{6}/
18
- if regex.match(string)
18
+ if regex.match?(string)
19
19
string.downcase
20
20
else
21
21
"#000000"
data/lib/action_view/helpers/tags/translator.rb CHANGED
@@ -16,13 +16,8 @@ module ActionView
16
16
translated_attribute || human_attribute_name
17
17
end
18
18
19
- # TODO Change this to private once we've dropped Ruby 2.2 support.
20
- # Workaround for Ruby 2.2 "private attribute?" warning.
21
- protected
22
-
23
- attr_reader :object_name, :method_and_value, :scope, :model
24
-
25
19
private
20
+ attr_reader :object_name, :method_and_value, :scope, :model
26
21
27
22
def i18n_default
28
23
if model
data/lib/action_view/helpers/text_helper.rb CHANGED
@@ -188,7 +188,7 @@ module ActionView
188
188
189
189
unless separator.empty?
190
190
text.split(separator).each do |value|
191
- if value.match(regex)
191
+ if value.match?(regex)
192
192
phrase = value
193
193
break
194
194
end
@@ -228,7 +228,7 @@ module ActionView
228
228
# pluralize(2, 'Person', locale: :de)
229
229
# # => 2 Personen
230
230
def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
231
- word = if (count == 1 || count.to_s =~ /^1(\.0+)?#x2F;)
231
+ word = if count == 1 || count.to_s =~ /^1(\.0+)?#x2F;
232
232
singular
233
233
else
234
234
plural || singular.pluralize(locale)
@@ -259,7 +259,7 @@ module ActionView
259
259
# # => Once\r\nupon\r\na\r\ntime
260
260
def word_wrap(text, line_width: 80, break_sequence: "\n")
261
261
text.split("\n").collect! do |line|
262
- line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : line
262
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").rstrip : line
263
263
end * break_sequence
264
264
end
265
265
data/lib/action_view/helpers/translation_helper.rb CHANGED
@@ -59,11 +59,9 @@ module ActionView
59
59
# they can provide HTML values for.
60
60
def translate(key, options = {})
61
61
options = options.dup
62
- has_default = options.has_key?(:default)
63
- remaining_defaults = Array(options.delete(:default)).compact
64
-
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
- if has_default && !remaining_defaults.first.kind_of?(Symbol)
66
- options[:default] = remaining_defaults
67
65
end
68
66
69
67
# If the user has explicitly decided to NOT raise errors, pass that option to I18n.
@@ -85,8 +83,11 @@ module ActionView
85
83
end
86
84
end
87
85
translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
88
-
89
- translation.respond_to?(:html_safe) ? translation.html_safe : translation
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
90
91
else
91
92
I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
92
93
end
@@ -97,7 +98,7 @@ module ActionView
97
98
raise e if raise_error
98
99
99
100
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
100
- title = "translation missing: #{keys.join('.')}".dup
101
+ title = +"translation missing: #{keys.join('.')}"
101
102
102
103
interpolations = options.except(:default, :scope)
103
104
if interpolations.any?
@@ -113,7 +114,7 @@ module ActionView
113
114
114
115
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
115
116
#
116
- # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
117
+ # See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
117
118
# for more information.
118
119
def localize(*args)
119
120
I18n.localize(*args)
@@ -122,9 +123,12 @@ module ActionView
122
123
123
124
private
124
125
def scope_key_by_partial(key)
125
- if key.to_s.first == "."
126
+ stringified_key = key.to_s
127
+ if stringified_key.first == "."
126
128
if @virtual_path
127
- @virtual_path.gsub(%r{/_?}, ".") + key.to_s
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}"
128
132
else
129
133
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
130
134
end
@@ -134,7 +138,7 @@ module ActionView
134
138
end
135
139
136
140
def html_safe_translation_key?(key)
137
- /(\b|_|\.)html#x2F;.match?(key.to_s)
141
+ /(?:_|\b)html\z/.match?(key.to_s)
138
142
end
139
143
end
140
144
end
data/lib/action_view/helpers/url_helper.rb CHANGED
@@ -200,9 +200,9 @@ module ActionView
200
200
html_options = convert_options_to_data_attributes(options, html_options)
201
201
202
202
url = url_for(options)
203
- html_options["href".freeze] ||= url
203
+ html_options["href"] ||= url
204
204
205
- content_tag("a".freeze, name || url, html_options, &block)
205
+ content_tag("a", name || url, html_options, &block)
206
206
end
207
207
208
208
# Generates a form containing a single button that submits to the URL created
@@ -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>"
@@ -308,7 +308,7 @@ module ActionView
308
308
params = html_options.delete("params")
309
309
310
310
method = html_options.delete("method").to_s
311
- method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".freeze.html_safe
311
+ method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".html_safe
312
312
313
313
form_method = method == "get" ? "get" : "post"
314
314
form_options = html_options.delete("form") || {}
@@ -321,7 +321,7 @@ module ActionView
321
321
request_method = method.empty? ? "post" : method
322
322
token_tag(nil, form_options: { action: url, method: request_method })
323
323
else
324
- "".freeze
324
+ ""
325
325
end
326
326
327
327
html_options = convert_options_to_data_attributes(options, html_options)
@@ -487,12 +487,12 @@ module ActionView
487
487
option = html_options.delete(item).presence || next
488
488
"#{item.dasherize}=#{ERB::Util.url_encode(option)}"
489
489
}.compact
490
- extras = extras.empty? ? "".freeze : "?" + extras.join("&")
490
+ extras = extras.empty? ? "" : "?" + extras.join("&")
491
491
492
492
encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
493
493
html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
494
494
495
- content_tag("a".freeze, name || email_address, html_options, &block)
495
+ content_tag("a", name || email_address, html_options, &block)
496
496
end
497
497
498
498
# True if the current request URI was generated by the given +options+.
@@ -553,7 +553,7 @@ module ActionView
553
553
url_string = URI.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
@@ -575,21 +575,21 @@ module ActionView
575
575
def convert_options_to_data_attributes(options, html_options)
576
576
if html_options
577
577
html_options = html_options.stringify_keys
578
- html_options["data-remote"] = "true".freeze if link_to_remote_options?(options) || link_to_remote_options?(html_options)
578
+ html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options)
579
579
580
- method = html_options.delete("method".freeze)
580
+ method = html_options.delete("method")
581
581
582
582
add_method_to_attributes!(html_options, method) if method
583
583
584
584
html_options
585
585
else
586
- link_to_remote_options?(options) ? { "data-remote" => "true".freeze } : {}
586
+ link_to_remote_options?(options) ? { "data-remote" => "true" } : {}
587
587
end
588
588
end
589
589
590
590
def link_to_remote_options?(options)
591
591
if options.is_a?(Hash)
592
- options.delete("remote".freeze) || options.delete(:remote)
592
+ options.delete("remote") || options.delete(:remote)
593
593
end
594
594
end
595
595
@@ -618,11 +618,11 @@ module ActionView
618
618
end
619
619
620
620
def token_tag(token = nil, form_options: {})
621
- if token != false && protect_against_forgery?
621
+ if token != false && defined?(protect_against_forgery?) && protect_against_forgery?
622
622
token ||= form_authenticity_token(form_options: form_options)
623
623
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
624
624
else
625
- "".freeze
625
+ ""
626
626
end
627
627
end
628
628
@@ -636,7 +636,7 @@ module ActionView
636
636
# to_form_params(name: 'David', nationality: 'Danish')
637
637
# # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}]
638
638
#
639
- # to_form_params(country: {name: 'Denmark'})
639
+ # to_form_params(country: { name: 'Denmark' })
640
640
# # => [{name: 'country[name]', value: 'Denmark'}]
641
641
#
642
642
# to_form_params(countries: ['Denmark', 'Sweden']})
data/lib/action_view/layouts.rb CHANGED
@@ -322,7 +322,7 @@ module ActionView
322
322
end
323
323
324
324
class_eval <<-RUBY, __FILE__, __LINE__ + 1
325
- def _layout(formats)
325
+ def _layout(lookup_context, formats)
326
326
if _conditional_layout?
327
327
#{layout_definition}
328
328
else
@@ -388,8 +388,8 @@ module ActionView
388
388
case name
389
389
when String then _normalize_layout(name)
390
390
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) }
391
+ when true then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, true) }
392
+ when :default then Proc.new { |lookup_context, formats| _default_layout(lookup_context, formats, false) }
393
393
when false, nil then nil
394
394
else
395
395
raise ArgumentError,
@@ -411,9 +411,9 @@ module ActionView
411
411
#
412
412
# ==== Returns
413
413
# * <tt>template</tt> - The template object for the default layout (or +nil+)
414
- def _default_layout(formats, require_layout = false)
414
+ def _default_layout(lookup_context, formats, require_layout = false)
415
415
begin
416
- value = _layout(formats) if action_has_layout?
416
+ value = _layout(lookup_context, formats) if action_has_layout?
417
417
rescue NameError => e
418
418
raise e, "Could not render layout: #{e.message}"
419
419
end
data/lib/action_view/log_subscriber.rb CHANGED
@@ -16,17 +16,17 @@ module ActionView
16
16
17
17
def render_template(event)
18
18
info do
19
- message = " Rendered #{from_rails_root(event.payload[:identifier])}".dup
19
+ message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
20
20
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
21
- message << " (#{event.duration.round(1)}ms)"
21
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
22
22
end
23
23
end
24
24
25
25
def render_partial(event)
26
26
info do
27
- message = " Rendered #{from_rails_root(event.payload[:identifier])}".dup
27
+ message = +" Rendered #{from_rails_root(event.payload[:identifier])}"
28
28
message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
29
- message << " (#{event.duration.round(1)}ms)"
29
+ message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
30
30
message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil?
31
31
message
32
32
end
@@ -37,7 +37,7 @@ module ActionView
37
37
38
38
info do
39
39
" Rendered collection of #{from_rails_root(identifier)}" \
40
- " #{render_count(event.payload)} (#{event.duration.round(1)}ms)"
40
+ " #{render_count(event.payload)} (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
41
41
end
42
42
end
43
43
@@ -85,7 +85,7 @@ module ActionView
85
85
86
86
def log_rendering_start(payload)
87
87
info do
88
- message = " Rendering #{from_rails_root(payload[:identifier])}".dup
88
+ message = +" Rendering #{from_rails_root(payload[:identifier])}"
89
89
message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
90
90
message
91
91
end
data/lib/action_view/lookup_context.rb CHANGED
@@ -3,6 +3,7 @@
3
3
require "concurrent/map"
4
4
require "active_support/core_ext/module/remove_method"
5
5
require "active_support/core_ext/module/attribute_accessors"
6
+ require "active_support/deprecation"
6
7
require "action_view/template/resolver"
7
8
8
9
module ActionView
@@ -15,6 +16,8 @@ module ActionView
15
16
# only once during the request, it speeds up all cache accesses.
16
17
class LookupContext #:nodoc:
17
18
attr_accessor :prefixes, :rendered_format
19
+ deprecate :rendered_format
20
+ deprecate :rendered_format=
18
21
19
22
mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances
20
23
@@ -24,7 +27,7 @@ module ActionView
24
27
registered_details << name
25
28
Accessors::DEFAULT_PROCS[name] = block
26
29
27
- Accessors.send :define_method, :"default_#{name}", &block
30
+ Accessors.define_method(:"default_#{name}", &block)
28
31
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
29
32
def #{name}
30
33
@details.fetch(:#{name}, [])
@@ -57,21 +60,36 @@ module ActionView
57
60
alias :eql? :equal?
58
61
59
62
@details_keys = Concurrent::Map.new
63
+ @digest_cache = Concurrent::Map.new
60
64
61
- def self.get(details)
65
+ def self.digest_cache(details)
66
+ @digest_cache[details_cache_key(details)] ||= Concurrent::Map.new
67
+ end
68
+
69
+ def self.details_cache_key(details)
62
70
if details[:formats]
63
71
details = details.dup
64
72
details[:formats] &= Template::Types.symbols
65
73
end
66
- @details_keys[details] ||= Concurrent::Map.new
74
+ @details_keys[details] ||= Object.new
67
75
end
68
76
69
77
def self.clear
78
+ ActionView::ViewPaths.all_view_paths.each do |path_set|
79
+ path_set.each(&:clear_cache)
80
+ end
81
+ ActionView::LookupContext.fallbacks.each(&:clear_cache)
82
+ @view_context_class = nil
70
83
@details_keys.clear
84
+ @digest_cache.clear
71
85
end
72
86
73
87
def self.digest_caches
74
- @details_keys.values
88
+ @digest_cache.values
89
+ end
90
+
91
+ def self.view_context_class(klass)
92
+ @view_context_class ||= klass.with_empty_template_cache
75
93
end
76
94
end
77
95
@@ -82,7 +100,7 @@ module ActionView
82
100
# Calculate the details key. Remove the handlers from calculation to improve performance
83
101
# since the user cannot modify it explicitly.
84
102
def details_key #:nodoc:
85
- @details_key ||= DetailsKey.get(@details) if @cache
103
+ @details_key ||= DetailsKey.details_cache_key(@details) if @cache
86
104
end
87
105
88
106
# Temporary skip passing the details_key forward.
@@ -96,7 +114,8 @@ module ActionView
96
114
private
97
115
98
116
def _set_detail(key, value) # :doc:
99
- @details = @details.dup if @details_key
117
+ @details = @details.dup if @digest_cache || @details_key
118
+ @digest_cache = nil
100
119
@details_key = nil
101
120
@details[key] = value
102
121
end
@@ -106,20 +125,13 @@ module ActionView
106
125
module ViewPaths
107
126
attr_reader :view_paths, :html_fallback_for_js
108
127
109
- # Whenever setting view paths, makes a copy so that we can manipulate them in
110
- # instance objects as we wish.
111
- def view_paths=(paths)
112
- @view_paths = ActionView::PathSet.new(Array(paths))
113
- end
114
-
115
128
def find(name, prefixes = [], partial = false, keys = [], options = {})
116
129
@view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options))
117
130
end
118
131
alias :find_template :find
119
132
120
- def find_file(name, prefixes = [], partial = false, keys = [], options = {})
121
- @view_paths.find_file(*args_for_lookup(name, prefixes, partial, keys, options))
133
+ alias :find_file :find
134
+ deprecate :find_file
122
- end
123
135
124
136
def find_all(name, prefixes = [], partial = false, keys = [], options = {})
125
137
@view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options))
@@ -138,19 +150,34 @@ module ActionView
138
150
# Adds fallbacks to the view paths. Useful in cases when you are rendering
139
151
# a :file.
140
152
def with_fallbacks
141
- added_resolvers = 0
142
- self.class.fallbacks.each do |resolver|
143
- next if view_paths.include?(resolver)
144
- view_paths.push(resolver)
145
- added_resolvers += 1
153
+ view_paths = build_view_paths((@view_paths.paths + self.class.fallbacks).uniq)
154
+
155
+ if block_given?
156
+ ActiveSupport::Deprecation.warn <<~eowarn.squish
157
+ Calling `with_fallbacks` with a block is deprecated. Call methods on
158
+ the lookup context returned by `with_fallbacks` instead.
159
+ eowarn
160
+
161
+ begin
162
+ _view_paths = @view_paths
163
+ @view_paths = view_paths
164
+ yield
165
+ ensure
166
+ @view_paths = _view_paths
167
+ end
168
+ else
169
+ ActionView::LookupContext.new(view_paths, @details, @prefixes)
146
170
end
147
- yield
148
- ensure
149
- added_resolvers.times { view_paths.pop }
150
171
end
151
172
152
173
private
153
174
175
+ # Whenever setting view paths, makes a copy so that we can manipulate them in
176
+ # instance objects as we wish.
177
+ def build_view_paths(paths)
178
+ ActionView::PathSet.new(Array(paths))
179
+ end
180
+
154
181
def args_for_lookup(name, prefixes, partial, keys, details_options)
155
182
name, prefixes = normalize_name(name, prefixes)
156
183
details, details_key = detail_args_for(details_options)
@@ -163,7 +190,7 @@ module ActionView
163
190
user_details = @details.merge(options)
164
191
165
192
if @cache
166
- details_key = DetailsKey.get(user_details)
193
+ details_key = DetailsKey.details_cache_key(user_details)
167
194
else
168
195
details_key = nil
169
196
end
@@ -190,7 +217,7 @@ module ActionView
190
217
end
191
218
192
219
if @cache
193
- [details, DetailsKey.get(details)]
220
+ [details, DetailsKey.details_cache_key(details)]
194
221
else
195
222
[details, nil]
196
223
end
@@ -202,13 +229,13 @@ module ActionView
202
229
# name instead of the prefix.
203
230
def normalize_name(name, prefixes)
204
231
prefixes = prefixes.presence
205
- parts = name.to_s.split("/".freeze)
232
+ parts = name.to_s.split("/")
206
233
parts.shift if parts.first.empty?
207
234
name = parts.pop
208
235
209
236
return name, prefixes || [""] if parts.empty?
210
237
211
- parts = parts.join("/".freeze)
238
+ parts = parts.join("/")
212
239
prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
213
240
214
241
return name, prefixes
@@ -221,16 +248,23 @@ module ActionView
221
248
222
249
def initialize(view_paths, details = {}, prefixes = [])
223
250
@details_key = nil
251
+ @digest_cache = nil
224
252
@cache = true
225
253
@prefixes = prefixes
226
- @rendered_format = nil
227
254
228
255
@details = initialize_details({}, details)
229
- self.view_paths = view_paths
256
+ @view_paths = build_view_paths(view_paths)
230
257
end
231
258
232
259
def digest_cache
233
- details_key
260
+ @digest_cache ||= DetailsKey.digest_cache(@details)
261
+ end
262
+
263
+ def with_prepended_formats(formats)
264
+ details = @details.dup
265
+ details[:formats] = formats
266
+
267
+ self.class.new(@view_paths, details, @prefixes)
234
268
end
235
269
236
270
def initialize_details(target, details)
@@ -245,7 +279,15 @@ module ActionView
245
279
# add :html as fallback to :js.
246
280
def formats=(values)
247
281
if values
248
- values.concat(default_formats) if values.delete "*/*".freeze
282
+ values = values.dup
283
+ values.concat(default_formats) if values.delete "*/*"
284
+ values.uniq!
285
+
286
+ invalid_values = (values - Template::Types.symbols)
287
+ unless invalid_values.empty?
288
+ raise ArgumentError, "Invalid formats: #{invalid_values.map(&:inspect).join(", ")}"
289
+ end
290
+
249
291
if values == [:js]
250
292
values << :html
251
293
@html_fallback_for_js = true
data/lib/action_view/path_set.rb CHANGED
@@ -48,12 +48,11 @@ module ActionView #:nodoc:
48
48
find_all(*args).first || raise(MissingTemplate.new(self, *args))
49
49
end
50
50
51
- def find_file(path, prefixes = [], *args)
52
- _find_all(path, prefixes, args, true).first || raise(MissingTemplate.new(self, path, prefixes, *args))
51
+ alias :find_file :find
52
+ deprecate :find_file
53
- end
54
53
55
54
def find_all(path, prefixes = [], *args)
56
- _find_all path, prefixes, args, false
55
+ _find_all path, prefixes, args
57
56
end
58
57
59
58
def exists?(path, prefixes, *args)
@@ -71,15 +70,11 @@ module ActionView #:nodoc:
71
70
72
71
private
73
72
74
- def _find_all(path, prefixes, args, outside_app)
73
+ def _find_all(path, prefixes, args)
75
74
prefixes = [prefixes] if String === prefixes
76
75
prefixes.each do |prefix|
77
76
paths.each do |resolver|
78
- if outside_app
77
+ templates = resolver.find_all(path, prefix, *args)
79
- templates = resolver.find_all_anywhere(path, prefix, *args)
80
- else
81
- templates = resolver.find_all(path, prefix, *args)
82
- end
83
78
return templates unless templates.empty?
84
79
end
85
80
end
data/lib/action_view/railtie.rb CHANGED
@@ -6,9 +6,13 @@ require "rails"
6
6
module ActionView
7
7
# = Action View Railtie
8
8
class Railtie < Rails::Engine # :nodoc:
9
+ NULL_OPTION = Object.new
10
+
9
11
config.action_view = ActiveSupport::OrderedOptions.new
10
12
config.action_view.embed_authenticity_token_in_remote_forms = nil
11
13
config.action_view.debug_missing_translation = true
14
+ config.action_view.default_enforce_utf8 = nil
15
+ config.action_view.finalize_compiled_template_methods = NULL_OPTION
12
16
13
17
config.eager_load_namespaces << ActionView
14
18
@@ -35,6 +39,25 @@ module ActionView
35
39
end
36
40
end
37
41
42
+ initializer "action_view.default_enforce_utf8" do |app|
43
+ ActiveSupport.on_load(:action_view) do
44
+ default_enforce_utf8 = app.config.action_view.delete(:default_enforce_utf8)
45
+ unless default_enforce_utf8.nil?
46
+ ActionView::Helpers::FormTagHelper.default_enforce_utf8 = default_enforce_utf8
47
+ end
48
+ end
49
+ end
50
+
51
+ initializer "action_view.finalize_compiled_template_methods" do |app|
52
+ ActiveSupport.on_load(:action_view) do
53
+ option = app.config.action_view.delete(:finalize_compiled_template_methods)
54
+
55
+ if option != NULL_OPTION
56
+ ActiveSupport::Deprecation.warn "action_view.finalize_compiled_template_methods is deprecated and has no effect"
57
+ end
58
+ end
59
+ end
60
+
38
61
initializer "action_view.logger" do
39
62
ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
40
63
end
@@ -58,7 +81,7 @@ module ActionView
58
81
initializer "action_view.per_request_digest_cache" do |app|
59
82
ActiveSupport.on_load(:action_view) do
60
83
unless ActionView::Resolver.caching?
61
- app.executor.to_run ActionView::Digestor::PerExecutionDigestCacheExpiry
84
+ app.executor.to_run ActionView::CacheExpiry::Executor.new(watcher: app.config.file_watcher)
62
85
end
63
86
end
64
87
end
data/lib/action_view/record_identifier.rb CHANGED
@@ -59,8 +59,8 @@ module ActionView
59
59
60
60
include ModelNaming
61
61
62
- JOIN = "_".freeze
63
- NEW = "new".freeze
62
+ JOIN = "_"
63