actionpack 3.0.0.beta3 → 3.0.0.beta4
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- data/CHANGELOG +19 -0
- data/lib/abstract_controller.rb +1 -1
- data/lib/abstract_controller/asset_paths.rb +9 -0
- data/lib/abstract_controller/base.rb +5 -13
- data/lib/abstract_controller/callbacks.rb +1 -1
- data/lib/abstract_controller/helpers.rb +0 -1
- data/lib/abstract_controller/layouts.rb +3 -3
- data/lib/abstract_controller/logger.rb +1 -1
- data/lib/abstract_controller/rendering.rb +1 -0
- data/lib/action_controller/base.rb +5 -1
- data/lib/action_controller/caching.rb +2 -3
- data/lib/action_controller/caching/actions.rb +1 -1
- data/lib/action_controller/caching/fragments.rb +1 -1
- data/lib/action_controller/caching/pages.rb +8 -8
- data/lib/action_controller/caching/sweeping.rb +1 -0
- data/lib/action_controller/deprecated/base.rb +10 -36
- data/lib/action_controller/metal.rb +45 -3
- data/lib/action_controller/metal/compatibility.rb +2 -2
- data/lib/action_controller/metal/helpers.rb +3 -3
- data/lib/action_controller/metal/http_authentication.rb +158 -0
- data/lib/action_controller/metal/instrumentation.rb +5 -5
- data/lib/action_controller/metal/rack_delegation.rb +4 -4
- data/lib/action_controller/metal/renderers.rb +3 -3
- data/lib/action_controller/metal/request_forgery_protection.rb +45 -74
- data/lib/action_controller/metal/responder.rb +1 -1
- data/lib/action_controller/metal/url_for.rb +8 -0
- data/lib/action_controller/railtie.rb +26 -39
- data/lib/action_controller/test_case.rb +147 -135
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -0
- data/lib/action_dispatch.rb +0 -1
- data/lib/action_dispatch/http/parameters.rb +2 -1
- data/lib/action_dispatch/http/request.rb +19 -7
- data/lib/action_dispatch/http/response.rb +3 -33
- data/lib/action_dispatch/middleware/cookies.rb +44 -10
- data/lib/action_dispatch/middleware/flash.rb +11 -1
- data/lib/action_dispatch/middleware/params_parser.rb +3 -1
- data/lib/action_dispatch/middleware/session/abstract_store.rb +47 -83
- data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -165
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +2 -2
- data/lib/action_dispatch/middleware/show_exceptions.rb +18 -12
- data/lib/action_dispatch/middleware/stack.rb +17 -67
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +1 -1
- data/lib/action_dispatch/railtie.rb +0 -2
- data/lib/action_dispatch/routing/deprecated_mapper.rb +1 -0
- data/lib/action_dispatch/routing/mapper.rb +89 -23
- data/lib/action_dispatch/routing/route_set.rb +22 -16
- data/lib/action_dispatch/routing/url_for.rb +1 -1
- data/lib/action_dispatch/testing/assertions/routing.rb +1 -0
- data/lib/action_dispatch/testing/assertions/selector.rb +11 -7
- data/lib/action_dispatch/testing/test_process.rb +3 -2
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_view.rb +5 -1
- data/lib/action_view/base.rb +10 -4
- data/lib/action_view/helpers/active_model_helper.rb +1 -8
- data/lib/action_view/helpers/asset_tag_helper.rb +7 -4
- data/lib/action_view/helpers/cache_helper.rb +14 -14
- data/lib/action_view/helpers/capture_helper.rb +25 -6
- data/lib/action_view/helpers/date_helper.rb +33 -44
- data/lib/action_view/helpers/form_helper.rb +47 -27
- data/lib/action_view/helpers/form_options_helper.rb +26 -3
- data/lib/action_view/helpers/form_tag_helper.rb +8 -4
- data/lib/action_view/helpers/number_helper.rb +5 -2
- data/lib/action_view/helpers/prototype_helper.rb +1 -1
- data/lib/action_view/helpers/tag_helper.rb +1 -1
- data/lib/action_view/helpers/text_helper.rb +55 -46
- data/lib/action_view/helpers/translation_helper.rb +19 -8
- data/lib/action_view/helpers/url_helper.rb +2 -4
- data/lib/action_view/locale/en.yml +14 -14
- data/lib/action_view/lookup_context.rb +52 -22
- data/lib/action_view/paths.rb +1 -0
- data/lib/action_view/render/layouts.rb +3 -12
- data/lib/action_view/render/partials.rb +21 -10
- data/lib/action_view/render/rendering.rb +1 -1
- data/lib/action_view/template.rb +172 -26
- data/lib/action_view/template/error.rb +25 -27
- data/lib/action_view/template/handlers.rb +1 -1
- data/lib/action_view/template/handlers/erb.rb +92 -45
- data/lib/action_view/template/resolver.rb +4 -1
- data/lib/action_view/test_case.rb +105 -72
- data/lib/action_view/testing/resolvers.rb +43 -0
- metadata +62 -20
- data/lib/abstract_controller/assigns.rb +0 -21
- data/lib/action_dispatch/middleware/cascade.rb +0 -29
@@ -3,17 +3,26 @@ require 'action_view/helpers/tag_helper'
|
|
3
3
|
module ActionView
|
4
4
|
module Helpers
|
5
5
|
module TranslationHelper
|
6
|
-
# Delegates to I18n#translate but also performs
|
6
|
+
# Delegates to I18n#translate but also performs three additional functions. First, it'll catch MissingTranslationData exceptions
|
7
7
|
# and turn them into inline spans that contains the missing key, such that you can see in a view what is missing where.
|
8
8
|
#
|
9
9
|
# Second, it'll scope the key by the current partial if the key starts with a period. So if you call translate(".foo") from the
|
10
10
|
# people/index.html.erb template, you'll actually be calling I18n.translate("people.index.foo"). This makes it less repetitive
|
11
11
|
# to translate many keys within the same partials and gives you a simple framework for scoping them consistently. If you don't
|
12
12
|
# prepend the key with a period, nothing is converted.
|
13
|
+
#
|
14
|
+
# Third, it’ll mark the translation as safe HTML if the key has the suffix "_html" or the last element of the key is the word
|
15
|
+
# "html". For example, calling translate("footer_html") or translate("footer.html") will return a safe HTML string that won’t
|
16
|
+
# be escaped by other HTML helper methods. This naming convention helps to identify translations that include HTML tags so that
|
17
|
+
# you know what kind of output to expect when you call translate in a template.
|
18
|
+
|
13
19
|
def translate(key, options = {})
|
14
|
-
options
|
15
|
-
|
16
|
-
|
20
|
+
translation = I18n.translate(scope_key_by_partial(key), options.merge!(:raise => true))
|
21
|
+
if html_safe_translation_key?(key) && translation.respond_to?(:html_safe)
|
22
|
+
translation.html_safe
|
23
|
+
else
|
24
|
+
translation
|
25
|
+
end
|
17
26
|
rescue I18n::MissingTranslationData => e
|
18
27
|
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
|
19
28
|
content_tag('span', keys.join(', '), :class => 'translation_missing')
|
@@ -27,12 +36,10 @@ module ActionView
|
|
27
36
|
alias :l :localize
|
28
37
|
|
29
38
|
private
|
30
|
-
|
31
39
|
def scope_key_by_partial(key)
|
32
|
-
|
33
|
-
if strkey.first == "."
|
40
|
+
if key.to_s.first == "."
|
34
41
|
if @_virtual_path
|
35
|
-
@_virtual_path.gsub(%r{/_?}, ".") +
|
42
|
+
@_virtual_path.gsub(%r{/_?}, ".") + key.to_s
|
36
43
|
else
|
37
44
|
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
|
38
45
|
end
|
@@ -40,6 +47,10 @@ module ActionView
|
|
40
47
|
key
|
41
48
|
end
|
42
49
|
end
|
50
|
+
|
51
|
+
def html_safe_translation_key?(key)
|
52
|
+
key.to_s =~ /(\b|_|\.)html$/
|
53
|
+
end
|
43
54
|
end
|
44
55
|
end
|
45
56
|
end
|
@@ -504,7 +504,7 @@ module ActionView
|
|
504
504
|
"document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
|
505
505
|
string << sprintf("%%%x", c)
|
506
506
|
end
|
507
|
-
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>"
|
507
|
+
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe
|
508
508
|
elsif encode == "hex"
|
509
509
|
email_address_encoded = ''
|
510
510
|
email_address_obfuscated.each_byte do |c|
|
@@ -596,10 +596,8 @@ module ActionView
|
|
596
596
|
html_options = {} if html_options.nil?
|
597
597
|
html_options = html_options.stringify_keys
|
598
598
|
|
599
|
-
if (options.is_a?(Hash) && options.key?('remote')) || (html_options.is_a?(Hash) && html_options.key?('remote'))
|
599
|
+
if (options.is_a?(Hash) && options.key?('remote') && options.delete('remote')) || (html_options.is_a?(Hash) && html_options.key?('remote') && html_options.delete('remote'))
|
600
600
|
html_options['data-remote'] = 'true'
|
601
|
-
options.delete('remote') if options.is_a?(Hash)
|
602
|
-
html_options.delete('remote') if html_options.is_a?(Hash)
|
603
601
|
end
|
604
602
|
|
605
603
|
confirm = html_options.delete("confirm")
|
@@ -102,37 +102,37 @@
|
|
102
102
|
half_a_minute: "half a minute"
|
103
103
|
less_than_x_seconds:
|
104
104
|
one: "less than 1 second"
|
105
|
-
other: "less than {
|
105
|
+
other: "less than %{count} seconds"
|
106
106
|
x_seconds:
|
107
107
|
one: "1 second"
|
108
|
-
other: "{
|
108
|
+
other: "%{count} seconds"
|
109
109
|
less_than_x_minutes:
|
110
110
|
one: "less than a minute"
|
111
|
-
other: "less than {
|
111
|
+
other: "less than %{count} minutes"
|
112
112
|
x_minutes:
|
113
113
|
one: "1 minute"
|
114
|
-
other: "{
|
114
|
+
other: "%{count} minutes"
|
115
115
|
about_x_hours:
|
116
116
|
one: "about 1 hour"
|
117
|
-
other: "about {
|
117
|
+
other: "about %{count} hours"
|
118
118
|
x_days:
|
119
119
|
one: "1 day"
|
120
|
-
other: "{
|
120
|
+
other: "%{count} days"
|
121
121
|
about_x_months:
|
122
122
|
one: "about 1 month"
|
123
|
-
other: "about {
|
123
|
+
other: "about %{count} months"
|
124
124
|
x_months:
|
125
125
|
one: "1 month"
|
126
|
-
other: "{
|
126
|
+
other: "%{count} months"
|
127
127
|
about_x_years:
|
128
128
|
one: "about 1 year"
|
129
|
-
other: "about {
|
129
|
+
other: "about %{count} years"
|
130
130
|
over_x_years:
|
131
131
|
one: "over 1 year"
|
132
|
-
other: "over {
|
132
|
+
other: "over %{count} years"
|
133
133
|
almost_x_years:
|
134
134
|
one: "almost 1 year"
|
135
|
-
other: "almost {
|
135
|
+
other: "almost %{count} years"
|
136
136
|
prompts:
|
137
137
|
year: "Year"
|
138
138
|
month: "Month"
|
@@ -148,7 +148,7 @@
|
|
148
148
|
|
149
149
|
# Default translation keys for submit FormHelper
|
150
150
|
submit:
|
151
|
-
create: 'Create {
|
152
|
-
update: 'Update {
|
153
|
-
submit: 'Save {
|
151
|
+
create: 'Create %{model}'
|
152
|
+
update: 'Update %{model}'
|
153
|
+
submit: 'Save %{model}'
|
154
154
|
|
@@ -13,8 +13,13 @@ module ActionView
|
|
13
13
|
mattr_accessor :registered_details
|
14
14
|
self.registered_details = []
|
15
15
|
|
16
|
+
mattr_accessor :registered_detail_setters
|
17
|
+
self.registered_detail_setters = []
|
18
|
+
|
16
19
|
def self.register_detail(name, options = {}, &block)
|
17
20
|
self.registered_details << name
|
21
|
+
self.registered_detail_setters << [name, "#{name}="]
|
22
|
+
|
18
23
|
Accessors.send :define_method, :"_#{name}_defaults", &block
|
19
24
|
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
|
20
25
|
def #{name}
|
@@ -23,12 +28,7 @@ module ActionView
|
|
23
28
|
|
24
29
|
def #{name}=(value)
|
25
30
|
value = Array.wrap(value.presence || _#{name}_defaults)
|
26
|
-
|
27
|
-
if value != @details[:#{name}]
|
28
|
-
@details_key = nil
|
29
|
-
@details = @details.dup if @details.frozen?
|
30
|
-
@details[:#{name}] = value.freeze
|
31
|
-
end
|
31
|
+
_set_detail(:#{name}, value) if value != @details[:#{name}]
|
32
32
|
end
|
33
33
|
METHOD
|
34
34
|
end
|
@@ -59,8 +59,11 @@ module ActionView
|
|
59
59
|
def initialize(view_paths, details = {})
|
60
60
|
@details, @details_key = { :handlers => default_handlers }, nil
|
61
61
|
@frozen_formats, @skip_default_locale = false, false
|
62
|
+
|
62
63
|
self.view_paths = view_paths
|
63
|
-
self.
|
64
|
+
self.registered_detail_setters.each do |key, setter|
|
65
|
+
send(setter, details[key])
|
66
|
+
end
|
64
67
|
end
|
65
68
|
|
66
69
|
module ViewPaths
|
@@ -116,11 +119,11 @@ module ActionView
|
|
116
119
|
end
|
117
120
|
|
118
121
|
def default_handlers #:nodoc:
|
119
|
-
|
122
|
+
@@default_handlers ||= Template::Handlers.extensions
|
120
123
|
end
|
121
124
|
|
122
125
|
def handlers_regexp #:nodoc:
|
123
|
-
|
126
|
+
@@handlers_regexp ||= /\.(?:#{default_handlers.join('|')})$/
|
124
127
|
end
|
125
128
|
end
|
126
129
|
|
@@ -141,10 +144,13 @@ module ActionView
|
|
141
144
|
end
|
142
145
|
|
143
146
|
# Overload formats= to reject [:"*/*"] values.
|
144
|
-
def formats=(
|
145
|
-
|
146
|
-
|
147
|
-
|
147
|
+
def formats=(values)
|
148
|
+
if values && values.size == 1
|
149
|
+
value = values.first
|
150
|
+
values = nil if value == :"*/*"
|
151
|
+
values << :html if value == :js
|
152
|
+
end
|
153
|
+
super(values)
|
148
154
|
end
|
149
155
|
|
150
156
|
# Do not use the default locale on template lookup.
|
@@ -170,24 +176,48 @@ module ActionView
|
|
170
176
|
super(@skip_default_locale ? I18n.locale : _locale_defaults)
|
171
177
|
end
|
172
178
|
|
179
|
+
# A method which only uses the first format in the formats array for layout lookup.
|
180
|
+
# This method plays straight with instance variables for performance reasons.
|
181
|
+
def with_layout_format
|
182
|
+
if formats.size == 1
|
183
|
+
yield
|
184
|
+
else
|
185
|
+
old_formats = formats
|
186
|
+
_set_detail(:formats, formats[0,1])
|
187
|
+
|
188
|
+
begin
|
189
|
+
yield
|
190
|
+
ensure
|
191
|
+
_set_detail(:formats, old_formats)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
173
196
|
# Update the details keys by merging the given hash into the current
|
174
197
|
# details hash. If a block is given, the details are modified just during
|
175
198
|
# the execution of the block and reverted to the previous value after.
|
176
|
-
def update_details(new_details
|
199
|
+
def update_details(new_details)
|
177
200
|
old_details = @details.dup
|
178
201
|
|
179
|
-
|
180
|
-
send(
|
202
|
+
registered_detail_setters.each do |key, setter|
|
203
|
+
send(setter, new_details[key]) if new_details.key?(key)
|
181
204
|
end
|
182
205
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
end
|
206
|
+
begin
|
207
|
+
yield
|
208
|
+
ensure
|
209
|
+
@details_key = nil
|
210
|
+
@details = old_details
|
189
211
|
end
|
190
212
|
end
|
213
|
+
|
214
|
+
protected
|
215
|
+
|
216
|
+
def _set_detail(key, value)
|
217
|
+
@details_key = nil
|
218
|
+
@details = @details.dup if @details.frozen?
|
219
|
+
@details[key] = value.freeze
|
220
|
+
end
|
191
221
|
end
|
192
222
|
|
193
223
|
include Accessors
|
data/lib/action_view/paths.rb
CHANGED
@@ -57,15 +57,11 @@ module ActionView
|
|
57
57
|
# This is the method which actually finds the layout using details in the lookup
|
58
58
|
# context object. If no layout is found, it checkes if at least a layout with
|
59
59
|
# the given name exists across all details before raising the error.
|
60
|
-
#
|
61
|
-
# If self.formats contains several formats, just the first one is considered in
|
62
|
-
# the layout lookup.
|
63
60
|
def find_layout(layout)
|
64
61
|
begin
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
update_details(:formats => self.formats.first){ _find_layout(layout) }
|
62
|
+
with_layout_format do
|
63
|
+
layout =~ /^\// ?
|
64
|
+
with_fallbacks { find_template(layout) } : find_template(layout)
|
69
65
|
end
|
70
66
|
rescue ActionView::MissingTemplate => e
|
71
67
|
update_details(:formats => nil) do
|
@@ -74,11 +70,6 @@ module ActionView
|
|
74
70
|
end
|
75
71
|
end
|
76
72
|
|
77
|
-
def _find_layout(layout) #:nodoc:
|
78
|
-
layout =~ /^\// ?
|
79
|
-
with_fallbacks { find_template(layout) } : find_template(layout)
|
80
|
-
end
|
81
|
-
|
82
73
|
# Contains the logic that actually renders the layout.
|
83
74
|
def _render_layout(layout, locals, &block) #:nodoc:
|
84
75
|
layout.render(self, locals){ |*name| _layout_for(*name, &block) }
|
@@ -211,12 +211,12 @@ module ActionView
|
|
211
211
|
identifier = ((@template = find_template) ? @template.identifier : @path)
|
212
212
|
|
213
213
|
if @collection
|
214
|
-
ActiveSupport::Notifications.instrument("action_view
|
214
|
+
ActiveSupport::Notifications.instrument("render_collection.action_view",
|
215
215
|
:identifier => identifier || "collection", :count => @collection.size) do
|
216
216
|
render_collection
|
217
217
|
end
|
218
218
|
else
|
219
|
-
content = ActiveSupport::Notifications.instrument("action_view
|
219
|
+
content = ActiveSupport::Notifications.instrument("render_partial.action_view",
|
220
220
|
:identifier => identifier) do
|
221
221
|
render_partial
|
222
222
|
end
|
@@ -241,15 +241,21 @@ module ActionView
|
|
241
241
|
end
|
242
242
|
|
243
243
|
def collection_with_template(template = @template)
|
244
|
-
segments, locals,
|
244
|
+
segments, locals, template = [], @locals, @template
|
245
245
|
|
246
|
-
|
247
|
-
|
246
|
+
if @options[:as]
|
247
|
+
as = @options[:as]
|
248
|
+
counter = "#{as}_counter".to_sym
|
249
|
+
else
|
250
|
+
as = template.variable_name
|
251
|
+
counter = template.counter_name
|
252
|
+
end
|
253
|
+
|
254
|
+
locals[counter] = -1
|
248
255
|
|
249
256
|
@collection.each do |object|
|
250
|
-
locals[
|
257
|
+
locals[counter] += 1
|
251
258
|
locals[as] = object
|
252
|
-
|
253
259
|
segments << template.render(@view, locals)
|
254
260
|
end
|
255
261
|
|
@@ -257,13 +263,18 @@ module ActionView
|
|
257
263
|
end
|
258
264
|
|
259
265
|
def collection_without_template(collection_paths = @collection_paths)
|
260
|
-
segments, locals
|
261
|
-
index, template
|
266
|
+
segments, locals = [], @locals
|
267
|
+
index, template = -1, nil
|
268
|
+
|
269
|
+
if @options[:as]
|
270
|
+
as = @options[:as]
|
271
|
+
counter = "#{as}_counter"
|
272
|
+
end
|
262
273
|
|
263
274
|
@collection.each_with_index do |object, i|
|
264
275
|
template = find_template(collection_paths[i])
|
265
|
-
locals[template.counter_name] = (index += 1)
|
266
276
|
locals[as || template.variable_name] = object
|
277
|
+
locals[counter || template.counter_name] = (index += 1)
|
267
278
|
|
268
279
|
segments << template.render(@view, locals)
|
269
280
|
end
|
@@ -52,7 +52,7 @@ module ActionView
|
|
52
52
|
locals = options[:locals] || {}
|
53
53
|
layout = find_layout(layout) if layout
|
54
54
|
|
55
|
-
ActiveSupport::Notifications.instrument("action_view
|
55
|
+
ActiveSupport::Notifications.instrument("render_template.action_view",
|
56
56
|
:identifier => template.identifier, :layout => layout.try(:virtual_path)) do
|
57
57
|
|
58
58
|
content = template.render(self, locals) { |*name| _layout_for(*name) }
|
data/lib/action_view/template.rb
CHANGED
@@ -1,12 +1,92 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# This is so that templates compiled in this file are UTF-8
|
3
1
|
require 'active_support/core_ext/array/wrap'
|
4
2
|
require 'active_support/core_ext/object/blank'
|
3
|
+
require 'active_support/core_ext/kernel/singleton_class'
|
5
4
|
|
6
5
|
module ActionView
|
7
6
|
class Template
|
8
7
|
extend ActiveSupport::Autoload
|
9
8
|
|
9
|
+
# === Encodings in ActionView::Template
|
10
|
+
#
|
11
|
+
# ActionView::Template is one of a few sources of potential
|
12
|
+
# encoding issues in Rails. This is because the source for
|
13
|
+
# templates are usually read from disk, and Ruby (like most
|
14
|
+
# encoding-aware programming languages) assumes that the
|
15
|
+
# String retrieved through File IO is encoded in the
|
16
|
+
# <tt>default_external</tt> encoding. In Rails, the default
|
17
|
+
# <tt>default_external</tt> encoding is UTF-8.
|
18
|
+
#
|
19
|
+
# As a result, if a user saves their template as ISO-8859-1
|
20
|
+
# (for instance, using a non-Unicode-aware text editor),
|
21
|
+
# and uses characters outside of the ASCII range, their
|
22
|
+
# users will see diamonds with question marks in them in
|
23
|
+
# the browser.
|
24
|
+
#
|
25
|
+
# For the rest of this documentation, when we say "UTF-8",
|
26
|
+
# we mean "UTF-8 or whatever the default_internal encoding
|
27
|
+
# is set to". By default, it will be UTF-8.
|
28
|
+
#
|
29
|
+
# To mitigate this problem, we use a few strategies:
|
30
|
+
# 1. If the source is not valid UTF-8, we raise an exception
|
31
|
+
# when the template is compiled to alert the user
|
32
|
+
# to the problem.
|
33
|
+
# 2. The user can specify the encoding using Ruby-style
|
34
|
+
# encoding comments in any template engine. If such
|
35
|
+
# a comment is supplied, Rails will apply that encoding
|
36
|
+
# to the resulting compiled source returned by the
|
37
|
+
# template handler.
|
38
|
+
# 3. In all cases, we transcode the resulting String to
|
39
|
+
# the UTF-8.
|
40
|
+
#
|
41
|
+
# This means that other parts of Rails can always assume
|
42
|
+
# that templates are encoded in UTF-8, even if the original
|
43
|
+
# source of the template was not UTF-8.
|
44
|
+
#
|
45
|
+
# From a user's perspective, the easiest thing to do is
|
46
|
+
# to save your templates as UTF-8. If you do this, you
|
47
|
+
# do not need to do anything else for things to "just work".
|
48
|
+
#
|
49
|
+
# === Instructions for template handlers
|
50
|
+
#
|
51
|
+
# The easiest thing for you to do is to simply ignore
|
52
|
+
# encodings. Rails will hand you the template source
|
53
|
+
# as the default_internal (generally UTF-8), raising
|
54
|
+
# an exception for the user before sending the template
|
55
|
+
# to you if it could not determine the original encoding.
|
56
|
+
#
|
57
|
+
# For the greatest simplicity, you can support only
|
58
|
+
# UTF-8 as the <tt>default_internal</tt>. This means
|
59
|
+
# that from the perspective of your handler, the
|
60
|
+
# entire pipeline is just UTF-8.
|
61
|
+
#
|
62
|
+
# === Advanced: Handlers with alternate metadata sources
|
63
|
+
#
|
64
|
+
# If you want to provide an alternate mechanism for
|
65
|
+
# specifying encodings (like ERB does via <%# encoding: ... %>),
|
66
|
+
# you may indicate that you will handle encodings yourself
|
67
|
+
# by implementing <tt>self.handles_encoding?</tt>
|
68
|
+
# on your handler.
|
69
|
+
#
|
70
|
+
# If you do, Rails will not try to encode the String
|
71
|
+
# into the default_internal, passing you the unaltered
|
72
|
+
# bytes tagged with the assumed encoding (from
|
73
|
+
# default_external).
|
74
|
+
#
|
75
|
+
# In this case, make sure you return a String from
|
76
|
+
# your handler encoded in the default_internal. Since
|
77
|
+
# you are handling out-of-band metadata, you are
|
78
|
+
# also responsible for alerting the user to any
|
79
|
+
# problems with converting the user's data to
|
80
|
+
# the default_internal.
|
81
|
+
#
|
82
|
+
# To do so, simply raise the raise WrongEncodingError
|
83
|
+
# as follows:
|
84
|
+
#
|
85
|
+
# raise WrongEncodingError.new(
|
86
|
+
# problematic_string,
|
87
|
+
# expected_encoding
|
88
|
+
# )
|
89
|
+
|
10
90
|
eager_autoload do
|
11
91
|
autoload :Error
|
12
92
|
autoload :Handler
|
@@ -16,20 +96,22 @@ module ActionView
|
|
16
96
|
|
17
97
|
extend Template::Handlers
|
18
98
|
|
19
|
-
attr_reader :source, :identifier, :handler, :virtual_path, :formats
|
99
|
+
attr_reader :source, :identifier, :handler, :virtual_path, :formats,
|
100
|
+
:original_encoding
|
20
101
|
|
21
|
-
Finalizer = proc do |method_name|
|
102
|
+
Finalizer = proc do |method_name, mod|
|
22
103
|
proc do
|
23
|
-
|
104
|
+
mod.module_eval do
|
24
105
|
remove_possible_method method_name
|
25
106
|
end
|
26
107
|
end
|
27
108
|
end
|
28
109
|
|
29
110
|
def initialize(source, identifier, handler, details)
|
30
|
-
@source
|
31
|
-
@identifier
|
32
|
-
@handler
|
111
|
+
@source = source
|
112
|
+
@identifier = identifier
|
113
|
+
@handler = handler
|
114
|
+
@original_encoding = nil
|
33
115
|
|
34
116
|
@virtual_path = details[:virtual_path]
|
35
117
|
@method_names = {}
|
@@ -41,8 +123,14 @@ module ActionView
|
|
41
123
|
def render(view, locals, &block)
|
42
124
|
# Notice that we use a bang in this instrumentation because you don't want to
|
43
125
|
# consume this in production. This is only slow if it's being listened to.
|
44
|
-
ActiveSupport::Notifications.instrument("action_view
|
45
|
-
|
126
|
+
ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do
|
127
|
+
if view.is_a?(ActionView::CompiledTemplates)
|
128
|
+
mod = ActionView::CompiledTemplates
|
129
|
+
else
|
130
|
+
mod = view.singleton_class
|
131
|
+
end
|
132
|
+
|
133
|
+
method_name = compile(locals, view, mod)
|
46
134
|
view.send(method_name, locals, &block)
|
47
135
|
end
|
48
136
|
rescue Exception => e
|
@@ -50,7 +138,7 @@ module ActionView
|
|
50
138
|
e.sub_template_of(self)
|
51
139
|
raise e
|
52
140
|
else
|
53
|
-
raise Template::Error.new(self, view.assigns, e)
|
141
|
+
raise Template::Error.new(self, view.respond_to?(:assigns) ? view.assigns : {}, e)
|
54
142
|
end
|
55
143
|
end
|
56
144
|
|
@@ -75,37 +163,95 @@ module ActionView
|
|
75
163
|
end
|
76
164
|
|
77
165
|
private
|
78
|
-
|
166
|
+
# Among other things, this method is responsible for properly setting
|
167
|
+
# the encoding of the source. Until this point, we assume that the
|
168
|
+
# source is BINARY data. If no additional information is supplied,
|
169
|
+
# we assume the encoding is the same as Encoding.default_external.
|
170
|
+
#
|
171
|
+
# The user can also specify the encoding via a comment on the first
|
172
|
+
# line of the template (# encoding: NAME-OF-ENCODING). This will work
|
173
|
+
# with any template engine, as we process out the encoding comment
|
174
|
+
# before passing the source on to the template engine, leaving a
|
175
|
+
# blank line in its stead.
|
176
|
+
#
|
177
|
+
# If the template engine handles encodings, we send the encoded
|
178
|
+
# String to the engine without further processing. This allows
|
179
|
+
# the template engine to support additional mechanisms for
|
180
|
+
# specifying the encoding. For instance, ERB supports <%# encoding: %>
|
181
|
+
#
|
182
|
+
# Otherwise, after we figure out the correct encoding, we then
|
183
|
+
# encode the source into Encoding.default_internal. In general,
|
184
|
+
# this means that templates will be UTF-8 inside of Rails,
|
185
|
+
# regardless of the original source encoding.
|
186
|
+
def compile(locals, view, mod)
|
79
187
|
method_name = build_method_name(locals)
|
80
188
|
return method_name if view.respond_to?(method_name)
|
81
189
|
|
82
190
|
locals_code = locals.keys.map! { |key| "#{key} = local_assigns[:#{key}];" }.join
|
83
191
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
192
|
+
if source.encoding_aware?
|
193
|
+
# Look for # encoding: *. If we find one, we'll encode the
|
194
|
+
# String in that encoding, otherwise, we'll use the
|
195
|
+
# default external encoding.
|
196
|
+
if source.sub!(/\A#{ENCODING_FLAG}/, '')
|
197
|
+
encoding = magic_encoding = $1
|
198
|
+
else
|
199
|
+
encoding = Encoding.default_external
|
200
|
+
end
|
201
|
+
|
202
|
+
# Tag the source with the default external encoding
|
203
|
+
# or the encoding specified in the file
|
204
|
+
source.force_encoding(encoding)
|
205
|
+
|
206
|
+
# If the user didn't specify an encoding, and the handler
|
207
|
+
# handles encodings, we simply pass the String as is to
|
208
|
+
# the handler (with the default_external tag)
|
209
|
+
if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
|
210
|
+
source
|
211
|
+
# Otherwise, if the String is valid in the encoding,
|
212
|
+
# encode immediately to default_internal. This means
|
213
|
+
# that if a handler doesn't handle encodings, it will
|
214
|
+
# always get Strings in the default_internal
|
215
|
+
elsif source.valid_encoding?
|
216
|
+
source.encode!
|
217
|
+
# Otherwise, since the String is invalid in the encoding
|
218
|
+
# specified, raise an exception
|
219
|
+
else
|
220
|
+
raise WrongEncodingError.new(source, encoding)
|
221
|
+
end
|
89
222
|
end
|
90
223
|
|
224
|
+
code = @handler.call(self)
|
225
|
+
|
226
|
+
# Make sure that the resulting String to be evalled is in the
|
227
|
+
# encoding of the code
|
91
228
|
source = <<-end_src
|
92
229
|
def #{method_name}(local_assigns)
|
93
|
-
_old_virtual_path, @_virtual_path = @_virtual_path, #{@virtual_path.inspect};_old_output_buffer = output_buffer;#{locals_code};#{code}
|
230
|
+
_old_virtual_path, @_virtual_path = @_virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code}
|
94
231
|
ensure
|
95
|
-
@_virtual_path,
|
232
|
+
@_virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
|
96
233
|
end
|
97
234
|
end_src
|
98
235
|
|
99
|
-
if
|
100
|
-
source
|
101
|
-
|
102
|
-
|
103
|
-
|
236
|
+
if source.encoding_aware?
|
237
|
+
# Make sure the source is in the encoding of the returned code
|
238
|
+
source.force_encoding(code.encoding)
|
239
|
+
|
240
|
+
# In case we get back a String from a handler that is not in
|
241
|
+
# BINARY or the default_internal, encode it to the default_internal
|
242
|
+
source.encode!
|
243
|
+
|
244
|
+
# Now, validate that the source we got back from the template
|
245
|
+
# handler is valid in the default_internal. This is for handlers
|
246
|
+
# that handle encoding but screw up
|
247
|
+
unless source.valid_encoding?
|
248
|
+
raise WrongEncodingError.new(@source, Encoding.default_internal)
|
249
|
+
end
|
104
250
|
end
|
105
251
|
|
106
252
|
begin
|
107
|
-
|
108
|
-
ObjectSpace.define_finalizer(self, Finalizer[method_name])
|
253
|
+
mod.module_eval(source, identifier, 0)
|
254
|
+
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
|
109
255
|
|
110
256
|
method_name
|
111
257
|
rescue Exception => e # errors from template code
|