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.

Files changed (83) hide show
  1. data/CHANGELOG +19 -0
  2. data/lib/abstract_controller.rb +1 -1
  3. data/lib/abstract_controller/asset_paths.rb +9 -0
  4. data/lib/abstract_controller/base.rb +5 -13
  5. data/lib/abstract_controller/callbacks.rb +1 -1
  6. data/lib/abstract_controller/helpers.rb +0 -1
  7. data/lib/abstract_controller/layouts.rb +3 -3
  8. data/lib/abstract_controller/logger.rb +1 -1
  9. data/lib/abstract_controller/rendering.rb +1 -0
  10. data/lib/action_controller/base.rb +5 -1
  11. data/lib/action_controller/caching.rb +2 -3
  12. data/lib/action_controller/caching/actions.rb +1 -1
  13. data/lib/action_controller/caching/fragments.rb +1 -1
  14. data/lib/action_controller/caching/pages.rb +8 -8
  15. data/lib/action_controller/caching/sweeping.rb +1 -0
  16. data/lib/action_controller/deprecated/base.rb +10 -36
  17. data/lib/action_controller/metal.rb +45 -3
  18. data/lib/action_controller/metal/compatibility.rb +2 -2
  19. data/lib/action_controller/metal/helpers.rb +3 -3
  20. data/lib/action_controller/metal/http_authentication.rb +158 -0
  21. data/lib/action_controller/metal/instrumentation.rb +5 -5
  22. data/lib/action_controller/metal/rack_delegation.rb +4 -4
  23. data/lib/action_controller/metal/renderers.rb +3 -3
  24. data/lib/action_controller/metal/request_forgery_protection.rb +45 -74
  25. data/lib/action_controller/metal/responder.rb +1 -1
  26. data/lib/action_controller/metal/url_for.rb +8 -0
  27. data/lib/action_controller/railtie.rb +26 -39
  28. data/lib/action_controller/test_case.rb +147 -135
  29. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -0
  30. data/lib/action_dispatch.rb +0 -1
  31. data/lib/action_dispatch/http/parameters.rb +2 -1
  32. data/lib/action_dispatch/http/request.rb +19 -7
  33. data/lib/action_dispatch/http/response.rb +3 -33
  34. data/lib/action_dispatch/middleware/cookies.rb +44 -10
  35. data/lib/action_dispatch/middleware/flash.rb +11 -1
  36. data/lib/action_dispatch/middleware/params_parser.rb +3 -1
  37. data/lib/action_dispatch/middleware/session/abstract_store.rb +47 -83
  38. data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -165
  39. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +2 -2
  40. data/lib/action_dispatch/middleware/show_exceptions.rb +18 -12
  41. data/lib/action_dispatch/middleware/stack.rb +17 -67
  42. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +1 -1
  43. data/lib/action_dispatch/railtie.rb +0 -2
  44. data/lib/action_dispatch/routing/deprecated_mapper.rb +1 -0
  45. data/lib/action_dispatch/routing/mapper.rb +89 -23
  46. data/lib/action_dispatch/routing/route_set.rb +22 -16
  47. data/lib/action_dispatch/routing/url_for.rb +1 -1
  48. data/lib/action_dispatch/testing/assertions/routing.rb +1 -0
  49. data/lib/action_dispatch/testing/assertions/selector.rb +11 -7
  50. data/lib/action_dispatch/testing/test_process.rb +3 -2
  51. data/lib/action_pack/version.rb +1 -1
  52. data/lib/action_view.rb +5 -1
  53. data/lib/action_view/base.rb +10 -4
  54. data/lib/action_view/helpers/active_model_helper.rb +1 -8
  55. data/lib/action_view/helpers/asset_tag_helper.rb +7 -4
  56. data/lib/action_view/helpers/cache_helper.rb +14 -14
  57. data/lib/action_view/helpers/capture_helper.rb +25 -6
  58. data/lib/action_view/helpers/date_helper.rb +33 -44
  59. data/lib/action_view/helpers/form_helper.rb +47 -27
  60. data/lib/action_view/helpers/form_options_helper.rb +26 -3
  61. data/lib/action_view/helpers/form_tag_helper.rb +8 -4
  62. data/lib/action_view/helpers/number_helper.rb +5 -2
  63. data/lib/action_view/helpers/prototype_helper.rb +1 -1
  64. data/lib/action_view/helpers/tag_helper.rb +1 -1
  65. data/lib/action_view/helpers/text_helper.rb +55 -46
  66. data/lib/action_view/helpers/translation_helper.rb +19 -8
  67. data/lib/action_view/helpers/url_helper.rb +2 -4
  68. data/lib/action_view/locale/en.yml +14 -14
  69. data/lib/action_view/lookup_context.rb +52 -22
  70. data/lib/action_view/paths.rb +1 -0
  71. data/lib/action_view/render/layouts.rb +3 -12
  72. data/lib/action_view/render/partials.rb +21 -10
  73. data/lib/action_view/render/rendering.rb +1 -1
  74. data/lib/action_view/template.rb +172 -26
  75. data/lib/action_view/template/error.rb +25 -27
  76. data/lib/action_view/template/handlers.rb +1 -1
  77. data/lib/action_view/template/handlers/erb.rb +92 -45
  78. data/lib/action_view/template/resolver.rb +4 -1
  79. data/lib/action_view/test_case.rb +105 -72
  80. data/lib/action_view/testing/resolvers.rb +43 -0
  81. metadata +62 -20
  82. data/lib/abstract_controller/assigns.rb +0 -21
  83. 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 two additional functions. First, it'll catch MissingTranslationData exceptions
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[:raise] = true
15
- translation = I18n.translate(scope_key_by_partial(key), options)
16
- (translation.respond_to?(:join) ? translation.join : translation).html_safe
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
- strkey = key.respond_to?(:join) ? key.join : key.to_s
33
- if strkey.first == "."
40
+ if key.to_s.first == "."
34
41
  if @_virtual_path
35
- @_virtual_path.gsub(%r{/_?}, ".") + strkey
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 {{count}} seconds"
105
+ other: "less than %{count} seconds"
106
106
  x_seconds:
107
107
  one: "1 second"
108
- other: "{{count}} seconds"
108
+ other: "%{count} seconds"
109
109
  less_than_x_minutes:
110
110
  one: "less than a minute"
111
- other: "less than {{count}} minutes"
111
+ other: "less than %{count} minutes"
112
112
  x_minutes:
113
113
  one: "1 minute"
114
- other: "{{count}} minutes"
114
+ other: "%{count} minutes"
115
115
  about_x_hours:
116
116
  one: "about 1 hour"
117
- other: "about {{count}} hours"
117
+ other: "about %{count} hours"
118
118
  x_days:
119
119
  one: "1 day"
120
- other: "{{count}} days"
120
+ other: "%{count} days"
121
121
  about_x_months:
122
122
  one: "about 1 month"
123
- other: "about {{count}} months"
123
+ other: "about %{count} months"
124
124
  x_months:
125
125
  one: "1 month"
126
- other: "{{count}} months"
126
+ other: "%{count} months"
127
127
  about_x_years:
128
128
  one: "about 1 year"
129
- other: "about {{count}} years"
129
+ other: "about %{count} years"
130
130
  over_x_years:
131
131
  one: "over 1 year"
132
- other: "over {{count}} years"
132
+ other: "over %{count} years"
133
133
  almost_x_years:
134
134
  one: "almost 1 year"
135
- other: "almost {{count}} years"
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 {{model}}'
152
- update: 'Update {{model}}'
153
- submit: 'Save {{model}}'
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.update_details(details, true)
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
- @default_handlers ||= Template::Handlers.extensions
122
+ @@default_handlers ||= Template::Handlers.extensions
120
123
  end
121
124
 
122
125
  def handlers_regexp #:nodoc:
123
- @handlers_regexp ||= /\.(?:#{default_handlers.join('|')})$/
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=(value)
145
- value = nil if value == [:"*/*"]
146
- value << :html if value == [:js]
147
- super(value)
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, force=false)
199
+ def update_details(new_details)
177
200
  old_details = @details.dup
178
201
 
179
- registered_details.each do |key|
180
- send(:"#{key}=", new_details[key]) if force || new_details.key?(key)
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
- if block_given?
184
- begin
185
- yield
186
- ensure
187
- @details = old_details
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
@@ -31,6 +31,7 @@ module ActionView #:nodoc:
31
31
 
32
32
  def typecast!
33
33
  each_with_index do |path, i|
34
+ path = path.to_s if path.is_a?(Pathname)
34
35
  next unless path.is_a?(String)
35
36
  self[i] = FileSystemResolver.new(path)
36
37
  end
@@ -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
- if formats.size == 1
66
- _find_layout(layout)
67
- else
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.render_collection",
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.render_partial",
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, as, template = [], @locals, @options[:as] || @template.variable_name, @template
244
+ segments, locals, template = [], @locals, @template
245
245
 
246
- counter_name = template.counter_name
247
- locals[counter_name] = -1
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[counter_name] += 1
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, as = [], @locals, @options[:as]
261
- index, template = -1, nil
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.render_template",
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) }
@@ -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
- ActionView::CompiledTemplates.module_eval do
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 = source
31
- @identifier = identifier
32
- @handler = 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.render_template!", :virtual_path => @virtual_path) do
45
- method_name = compile(locals, view)
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
- def compile(locals, view)
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
- code = @handler.call(self)
85
- if code.sub!(/\A(#.*coding.*)\n/, '')
86
- encoding_comment = $1
87
- elsif defined?(Encoding) && Encoding.respond_to?(:default_external)
88
- encoding_comment = "#coding:#{Encoding.default_external}"
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, self.output_buffer = _old_virtual_path, _old_output_buffer
232
+ @_virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer
96
233
  end
97
234
  end_src
98
235
 
99
- if encoding_comment
100
- source = "#{encoding_comment}\n#{source}"
101
- line = -1
102
- else
103
- line = 0
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
- ActionView::CompiledTemplates.module_eval(source, identifier, line)
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