actionview 6.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +271 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +40 -0
  5. data/lib/action_view.rb +98 -0
  6. data/lib/action_view/base.rb +312 -0
  7. data/lib/action_view/buffers.rb +67 -0
  8. data/lib/action_view/cache_expiry.rb +54 -0
  9. data/lib/action_view/context.rb +32 -0
  10. data/lib/action_view/dependency_tracker.rb +175 -0
  11. data/lib/action_view/digestor.rb +126 -0
  12. data/lib/action_view/flows.rb +76 -0
  13. data/lib/action_view/gem_version.rb +17 -0
  14. data/lib/action_view/helpers.rb +66 -0
  15. data/lib/action_view/helpers/active_model_helper.rb +55 -0
  16. data/lib/action_view/helpers/asset_tag_helper.rb +488 -0
  17. data/lib/action_view/helpers/asset_url_helper.rb +470 -0
  18. data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
  19. data/lib/action_view/helpers/cache_helper.rb +271 -0
  20. data/lib/action_view/helpers/capture_helper.rb +216 -0
  21. data/lib/action_view/helpers/controller_helper.rb +36 -0
  22. data/lib/action_view/helpers/csp_helper.rb +26 -0
  23. data/lib/action_view/helpers/csrf_helper.rb +35 -0
  24. data/lib/action_view/helpers/date_helper.rb +1200 -0
  25. data/lib/action_view/helpers/debug_helper.rb +36 -0
  26. data/lib/action_view/helpers/form_helper.rb +2569 -0
  27. data/lib/action_view/helpers/form_options_helper.rb +896 -0
  28. data/lib/action_view/helpers/form_tag_helper.rb +920 -0
  29. data/lib/action_view/helpers/javascript_helper.rb +95 -0
  30. data/lib/action_view/helpers/number_helper.rb +456 -0
  31. data/lib/action_view/helpers/output_safety_helper.rb +70 -0
  32. data/lib/action_view/helpers/rendering_helper.rb +101 -0
  33. data/lib/action_view/helpers/sanitize_helper.rb +171 -0
  34. data/lib/action_view/helpers/tag_helper.rb +314 -0
  35. data/lib/action_view/helpers/tags.rb +44 -0
  36. data/lib/action_view/helpers/tags/base.rb +196 -0
  37. data/lib/action_view/helpers/tags/check_box.rb +66 -0
  38. data/lib/action_view/helpers/tags/checkable.rb +18 -0
  39. data/lib/action_view/helpers/tags/collection_check_boxes.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_helpers.rb +119 -0
  41. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
  42. data/lib/action_view/helpers/tags/collection_select.rb +30 -0
  43. data/lib/action_view/helpers/tags/color_field.rb +27 -0
  44. data/lib/action_view/helpers/tags/date_field.rb +15 -0
  45. data/lib/action_view/helpers/tags/date_select.rb +74 -0
  46. data/lib/action_view/helpers/tags/datetime_field.rb +32 -0
  47. data/lib/action_view/helpers/tags/datetime_local_field.rb +21 -0
  48. data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
  49. data/lib/action_view/helpers/tags/email_field.rb +10 -0
  50. data/lib/action_view/helpers/tags/file_field.rb +10 -0
  51. data/lib/action_view/helpers/tags/grouped_collection_select.rb +31 -0
  52. data/lib/action_view/helpers/tags/hidden_field.rb +10 -0
  53. data/lib/action_view/helpers/tags/label.rb +81 -0
  54. data/lib/action_view/helpers/tags/month_field.rb +15 -0
  55. data/lib/action_view/helpers/tags/number_field.rb +20 -0
  56. data/lib/action_view/helpers/tags/password_field.rb +14 -0
  57. data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
  58. data/lib/action_view/helpers/tags/radio_button.rb +33 -0
  59. data/lib/action_view/helpers/tags/range_field.rb +10 -0
  60. data/lib/action_view/helpers/tags/search_field.rb +27 -0
  61. data/lib/action_view/helpers/tags/select.rb +43 -0
  62. data/lib/action_view/helpers/tags/tel_field.rb +10 -0
  63. data/lib/action_view/helpers/tags/text_area.rb +24 -0
  64. data/lib/action_view/helpers/tags/text_field.rb +34 -0
  65. data/lib/action_view/helpers/tags/time_field.rb +15 -0
  66. data/lib/action_view/helpers/tags/time_select.rb +10 -0
  67. data/lib/action_view/helpers/tags/time_zone_select.rb +22 -0
  68. data/lib/action_view/helpers/tags/translator.rb +39 -0
  69. data/lib/action_view/helpers/tags/url_field.rb +10 -0
  70. data/lib/action_view/helpers/tags/week_field.rb +15 -0
  71. data/lib/action_view/helpers/text_helper.rb +486 -0
  72. data/lib/action_view/helpers/translation_helper.rb +145 -0
  73. data/lib/action_view/helpers/url_helper.rb +676 -0
  74. data/lib/action_view/layouts.rb +433 -0
  75. data/lib/action_view/locale/en.yml +56 -0
  76. data/lib/action_view/log_subscriber.rb +96 -0
  77. data/lib/action_view/lookup_context.rb +316 -0
  78. data/lib/action_view/model_naming.rb +14 -0
  79. data/lib/action_view/path_set.rb +95 -0
  80. data/lib/action_view/railtie.rb +105 -0
  81. data/lib/action_view/record_identifier.rb +112 -0
  82. data/lib/action_view/renderer/abstract_renderer.rb +108 -0
  83. data/lib/action_view/renderer/partial_renderer.rb +563 -0
  84. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
  85. data/lib/action_view/renderer/renderer.rb +68 -0
  86. data/lib/action_view/renderer/streaming_template_renderer.rb +105 -0
  87. data/lib/action_view/renderer/template_renderer.rb +108 -0
  88. data/lib/action_view/rendering.rb +171 -0
  89. data/lib/action_view/routing_url_for.rb +146 -0
  90. data/lib/action_view/tasks/cache_digests.rake +25 -0
  91. data/lib/action_view/template.rb +393 -0
  92. data/lib/action_view/template/error.rb +161 -0
  93. data/lib/action_view/template/handlers.rb +92 -0
  94. data/lib/action_view/template/handlers/builder.rb +25 -0
  95. data/lib/action_view/template/handlers/erb.rb +84 -0
  96. data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
  97. data/lib/action_view/template/handlers/html.rb +11 -0
  98. data/lib/action_view/template/handlers/raw.rb +11 -0
  99. data/lib/action_view/template/html.rb +43 -0
  100. data/lib/action_view/template/inline.rb +22 -0
  101. data/lib/action_view/template/raw_file.rb +28 -0
  102. data/lib/action_view/template/resolver.rb +394 -0
  103. data/lib/action_view/template/sources.rb +13 -0
  104. data/lib/action_view/template/sources/file.rb +17 -0
  105. data/lib/action_view/template/text.rb +35 -0
  106. data/lib/action_view/template/types.rb +57 -0
  107. data/lib/action_view/test_case.rb +300 -0
  108. data/lib/action_view/testing/resolvers.rb +67 -0
  109. data/lib/action_view/unbound_template.rb +32 -0
  110. data/lib/action_view/version.rb +10 -0
  111. data/lib/action_view/view_paths.rb +129 -0
  112. data/lib/assets/compiled/rails-ujs.js +746 -0
  113. metadata +260 -0
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/routing/polymorphic_routes"
4
+
5
+ module ActionView
6
+ module RoutingUrlFor
7
+ # Returns the URL for the set of +options+ provided. This takes the
8
+ # same options as +url_for+ in Action Controller (see the
9
+ # documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
10
+ # <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action"
11
+ # instead of the fully qualified URL like "http://example.com/controller/action".
12
+ #
13
+ # ==== Options
14
+ # * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path.
15
+ # * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified).
16
+ # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this
17
+ # is currently not recommended since it breaks caching.
18
+ # * <tt>:host</tt> - Overrides the default (current) host if provided.
19
+ # * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
20
+ # * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
21
+ # * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
22
+ #
23
+ # ==== Relying on named routes
24
+ #
25
+ # Passing a record (like an Active Record) instead of a hash as the options parameter will
26
+ # trigger the named route for that record. The lookup will happen on the name of the class. So passing a
27
+ # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as
28
+ # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route).
29
+ #
30
+ # ==== Implicit Controller Namespacing
31
+ #
32
+ # Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one.
33
+ #
34
+ # ==== Examples
35
+ # <%= url_for(action: 'index') %>
36
+ # # => /blogs/
37
+ #
38
+ # <%= url_for(action: 'find', controller: 'books') %>
39
+ # # => /books/find
40
+ #
41
+ # <%= url_for(action: 'login', controller: 'members', only_path: false, protocol: 'https') %>
42
+ # # => https://www.example.com/members/login/
43
+ #
44
+ # <%= url_for(action: 'play', anchor: 'player') %>
45
+ # # => /messages/play/#player
46
+ #
47
+ # <%= url_for(action: 'jump', anchor: 'tax&ship') %>
48
+ # # => /testing/jump/#tax&ship
49
+ #
50
+ # <%= url_for(Workshop.new) %>
51
+ # # relies on Workshop answering a persisted? call (and in this case returning false)
52
+ # # => /workshops
53
+ #
54
+ # <%= url_for(@workshop) %>
55
+ # # calls @workshop.to_param which by default returns the id
56
+ # # => /workshops/5
57
+ #
58
+ # # to_param can be re-defined in a model to provide different URL names:
59
+ # # => /workshops/1-workshop-name
60
+ #
61
+ # <%= url_for("http://www.example.com") %>
62
+ # # => http://www.example.com
63
+ #
64
+ # <%= url_for(:back) %>
65
+ # # if request.env["HTTP_REFERER"] is set to "http://www.example.com"
66
+ # # => http://www.example.com
67
+ #
68
+ # <%= url_for(:back) %>
69
+ # # if request.env["HTTP_REFERER"] is not set or is blank
70
+ # # => javascript:history.back()
71
+ #
72
+ # <%= url_for(action: 'index', controller: 'users') %>
73
+ # # Assuming an "admin" namespace
74
+ # # => /admin/users
75
+ #
76
+ # <%= url_for(action: 'index', controller: '/users') %>
77
+ # # Specify absolute path with beginning slash
78
+ # # => /users
79
+ def url_for(options = nil)
80
+ case options
81
+ when String
82
+ options
83
+ when nil
84
+ super(only_path: _generate_paths_by_default)
85
+ when Hash
86
+ options = options.symbolize_keys
87
+ ensure_only_path_option(options)
88
+
89
+ super(options)
90
+ when ActionController::Parameters
91
+ ensure_only_path_option(options)
92
+
93
+ super(options)
94
+ when :back
95
+ _back_url
96
+ when Array
97
+ components = options.dup
98
+ options = components.extract_options!
99
+ ensure_only_path_option(options)
100
+
101
+ if options[:only_path]
102
+ polymorphic_path(components, options)
103
+ else
104
+ polymorphic_url(components, options)
105
+ end
106
+ else
107
+ method = _generate_paths_by_default ? :path : :url
108
+ builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method)
109
+
110
+ case options
111
+ when Symbol
112
+ builder.handle_string_call(self, options)
113
+ when Class
114
+ builder.handle_class_call(self, options)
115
+ else
116
+ builder.handle_model_call(self, options)
117
+ end
118
+ end
119
+ end
120
+
121
+ def url_options #:nodoc:
122
+ return super unless controller.respond_to?(:url_options)
123
+ controller.url_options
124
+ end
125
+
126
+ private
127
+ def _routes_context
128
+ controller
129
+ end
130
+
131
+ def optimize_routes_generation?
132
+ controller.respond_to?(:optimize_routes_generation?, true) ?
133
+ controller.optimize_routes_generation? : super
134
+ end
135
+
136
+ def _generate_paths_by_default
137
+ true
138
+ end
139
+
140
+ def ensure_only_path_option(options)
141
+ unless options.key?(:only_path)
142
+ options[:only_path] = _generate_paths_by_default unless options[:host]
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :cache_digests do
4
+ desc "Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)"
5
+ task nested_dependencies: :environment do
6
+ abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present?
7
+ puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:to_dep_map)
8
+ end
9
+
10
+ desc "Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)"
11
+ task dependencies: :environment do
12
+ abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present?
13
+ puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:name)
14
+ end
15
+
16
+ class CacheDigests
17
+ def self.template_name
18
+ ENV["TEMPLATE"].split(".", 2).first
19
+ end
20
+
21
+ def self.finder
22
+ ApplicationController.new.lookup_context
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,393 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/try"
4
+ require "active_support/core_ext/kernel/singleton_class"
5
+ require "active_support/deprecation"
6
+ require "thread"
7
+ require "delegate"
8
+
9
+ module ActionView
10
+ # = Action View Template
11
+ class Template
12
+ extend ActiveSupport::Autoload
13
+
14
+ def self.finalize_compiled_template_methods
15
+ ActiveSupport::Deprecation.warn "ActionView::Template.finalize_compiled_template_methods is deprecated and has no effect"
16
+ end
17
+
18
+ def self.finalize_compiled_template_methods=(_)
19
+ ActiveSupport::Deprecation.warn "ActionView::Template.finalize_compiled_template_methods= is deprecated and has no effect"
20
+ end
21
+
22
+ # === Encodings in ActionView::Template
23
+ #
24
+ # ActionView::Template is one of a few sources of potential
25
+ # encoding issues in Rails. This is because the source for
26
+ # templates are usually read from disk, and Ruby (like most
27
+ # encoding-aware programming languages) assumes that the
28
+ # String retrieved through File IO is encoded in the
29
+ # <tt>default_external</tt> encoding. In Rails, the default
30
+ # <tt>default_external</tt> encoding is UTF-8.
31
+ #
32
+ # As a result, if a user saves their template as ISO-8859-1
33
+ # (for instance, using a non-Unicode-aware text editor),
34
+ # and uses characters outside of the ASCII range, their
35
+ # users will see diamonds with question marks in them in
36
+ # the browser.
37
+ #
38
+ # For the rest of this documentation, when we say "UTF-8",
39
+ # we mean "UTF-8 or whatever the default_internal encoding
40
+ # is set to". By default, it will be UTF-8.
41
+ #
42
+ # To mitigate this problem, we use a few strategies:
43
+ # 1. If the source is not valid UTF-8, we raise an exception
44
+ # when the template is compiled to alert the user
45
+ # to the problem.
46
+ # 2. The user can specify the encoding using Ruby-style
47
+ # encoding comments in any template engine. If such
48
+ # a comment is supplied, Rails will apply that encoding
49
+ # to the resulting compiled source returned by the
50
+ # template handler.
51
+ # 3. In all cases, we transcode the resulting String to
52
+ # the UTF-8.
53
+ #
54
+ # This means that other parts of Rails can always assume
55
+ # that templates are encoded in UTF-8, even if the original
56
+ # source of the template was not UTF-8.
57
+ #
58
+ # From a user's perspective, the easiest thing to do is
59
+ # to save your templates as UTF-8. If you do this, you
60
+ # do not need to do anything else for things to "just work".
61
+ #
62
+ # === Instructions for template handlers
63
+ #
64
+ # The easiest thing for you to do is to simply ignore
65
+ # encodings. Rails will hand you the template source
66
+ # as the default_internal (generally UTF-8), raising
67
+ # an exception for the user before sending the template
68
+ # to you if it could not determine the original encoding.
69
+ #
70
+ # For the greatest simplicity, you can support only
71
+ # UTF-8 as the <tt>default_internal</tt>. This means
72
+ # that from the perspective of your handler, the
73
+ # entire pipeline is just UTF-8.
74
+ #
75
+ # === Advanced: Handlers with alternate metadata sources
76
+ #
77
+ # If you want to provide an alternate mechanism for
78
+ # specifying encodings (like ERB does via <%# encoding: ... %>),
79
+ # you may indicate that you will handle encodings yourself
80
+ # by implementing <tt>handles_encoding?</tt> on your handler.
81
+ #
82
+ # If you do, Rails will not try to encode the String
83
+ # into the default_internal, passing you the unaltered
84
+ # bytes tagged with the assumed encoding (from
85
+ # default_external).
86
+ #
87
+ # In this case, make sure you return a String from
88
+ # your handler encoded in the default_internal. Since
89
+ # you are handling out-of-band metadata, you are
90
+ # also responsible for alerting the user to any
91
+ # problems with converting the user's data to
92
+ # the <tt>default_internal</tt>.
93
+ #
94
+ # To do so, simply raise +WrongEncodingError+ as follows:
95
+ #
96
+ # raise WrongEncodingError.new(
97
+ # problematic_string,
98
+ # expected_encoding
99
+ # )
100
+
101
+ ##
102
+ # :method: local_assigns
103
+ #
104
+ # Returns a hash with the defined local variables.
105
+ #
106
+ # Given this sub template rendering:
107
+ #
108
+ # <%= render "shared/header", { headline: "Welcome", person: person } %>
109
+ #
110
+ # You can use +local_assigns+ in the sub templates to access the local variables:
111
+ #
112
+ # local_assigns[:headline] # => "Welcome"
113
+
114
+ eager_autoload do
115
+ autoload :Error
116
+ autoload :RawFile
117
+ autoload :Handlers
118
+ autoload :HTML
119
+ autoload :Inline
120
+ autoload :Sources
121
+ autoload :Text
122
+ autoload :Types
123
+ end
124
+
125
+ extend Template::Handlers
126
+
127
+ attr_reader :identifier, :handler, :original_encoding, :updated_at
128
+ attr_reader :variable, :format, :variant, :locals, :virtual_path
129
+
130
+ def initialize(source, identifier, handler, format: nil, variant: nil, locals: nil, virtual_path: nil, updated_at: nil)
131
+ unless locals
132
+ ActiveSupport::Deprecation.warn "ActionView::Template#initialize requires a locals parameter"
133
+ locals = []
134
+ end
135
+
136
+ @source = source
137
+ @identifier = identifier
138
+ @handler = handler
139
+ @compiled = false
140
+ @locals = locals
141
+ @virtual_path = virtual_path
142
+
143
+ @variable = if @virtual_path
144
+ base = @virtual_path[-1] == "/" ? "" : ::File.basename(@virtual_path)
145
+ base =~ /\A_?(.*?)(?:\.\w+)*\z/
146
+ $1.to_sym
147
+ end
148
+
149
+ if updated_at
150
+ ActiveSupport::Deprecation.warn "ActionView::Template#updated_at is deprecated"
151
+ @updated_at = updated_at
152
+ else
153
+ @updated_at = Time.now
154
+ end
155
+ @format = format
156
+ @variant = variant
157
+ @compile_mutex = Mutex.new
158
+ end
159
+
160
+ deprecate :original_encoding
161
+ deprecate :updated_at
162
+ deprecate def virtual_path=(_); end
163
+ deprecate def locals=(_); end
164
+ deprecate def formats=(_); end
165
+ deprecate def formats; Array(format); end
166
+ deprecate def variants=(_); end
167
+ deprecate def variants; [variant]; end
168
+ deprecate def refresh(_); self; end
169
+
170
+ # Returns whether the underlying handler supports streaming. If so,
171
+ # a streaming buffer *may* be passed when it starts rendering.
172
+ def supports_streaming?
173
+ handler.respond_to?(:supports_streaming?) && handler.supports_streaming?
174
+ end
175
+
176
+ # Render a template. If the template was not compiled yet, it is done
177
+ # exactly before rendering.
178
+ #
179
+ # This method is instrumented as "!render_template.action_view". Notice that
180
+ # we use a bang in this instrumentation because you don't want to
181
+ # consume this in production. This is only slow if it's being listened to.
182
+ def render(view, locals, buffer = ActionView::OutputBuffer.new, &block)
183
+ instrument_render_template do
184
+ compile!(view)
185
+ view._run(method_name, self, locals, buffer, &block)
186
+ end
187
+ rescue => e
188
+ handle_render_error(view, e)
189
+ end
190
+
191
+ def type
192
+ @type ||= Types[format]
193
+ end
194
+
195
+ def short_identifier
196
+ @short_identifier ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "") : identifier
197
+ end
198
+
199
+ def inspect
200
+ "#<#{self.class.name} #{short_identifier} locals=#{@locals.inspect}>"
201
+ end
202
+
203
+ def source
204
+ @source.to_s
205
+ end
206
+
207
+ # This method is responsible for properly setting the encoding of the
208
+ # source. Until this point, we assume that the source is BINARY data.
209
+ # If no additional information is supplied, we assume the encoding is
210
+ # the same as <tt>Encoding.default_external</tt>.
211
+ #
212
+ # The user can also specify the encoding via a comment on the first
213
+ # line of the template (# encoding: NAME-OF-ENCODING). This will work
214
+ # with any template engine, as we process out the encoding comment
215
+ # before passing the source on to the template engine, leaving a
216
+ # blank line in its stead.
217
+ def encode!
218
+ source = self.source
219
+
220
+ return source unless source.encoding == Encoding::BINARY
221
+
222
+ # Look for # encoding: *. If we find one, we'll encode the
223
+ # String in that encoding, otherwise, we'll use the
224
+ # default external encoding.
225
+ if source.sub!(/\A#{ENCODING_FLAG}/, "")
226
+ encoding = magic_encoding = $1
227
+ else
228
+ encoding = Encoding.default_external
229
+ end
230
+
231
+ # Tag the source with the default external encoding
232
+ # or the encoding specified in the file
233
+ source.force_encoding(encoding)
234
+
235
+ # If the user didn't specify an encoding, and the handler
236
+ # handles encodings, we simply pass the String as is to
237
+ # the handler (with the default_external tag)
238
+ if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
239
+ source
240
+ # Otherwise, if the String is valid in the encoding,
241
+ # encode immediately to default_internal. This means
242
+ # that if a handler doesn't handle encodings, it will
243
+ # always get Strings in the default_internal
244
+ elsif source.valid_encoding?
245
+ source.encode!
246
+ # Otherwise, since the String is invalid in the encoding
247
+ # specified, raise an exception
248
+ else
249
+ raise WrongEncodingError.new(source, encoding)
250
+ end
251
+ end
252
+
253
+
254
+ # Exceptions are marshalled when using the parallel test runner with DRb, so we need
255
+ # to ensure that references to the template object can be marshalled as well. This means forgoing
256
+ # the marshalling of the compiler mutex and instantiating that again on unmarshalling.
257
+ def marshal_dump # :nodoc:
258
+ [ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant ]
259
+ end
260
+
261
+ def marshal_load(array) # :nodoc:
262
+ @source, @identifier, @handler, @compiled, @locals, @virtual_path, @updated_at, @format, @variant = *array
263
+ @compile_mutex = Mutex.new
264
+ end
265
+
266
+ private
267
+
268
+ # Compile a template. This method ensures a template is compiled
269
+ # just once and removes the source after it is compiled.
270
+ def compile!(view)
271
+ return if @compiled
272
+
273
+ # Templates can be used concurrently in threaded environments
274
+ # so compilation and any instance variable modification must
275
+ # be synchronized
276
+ @compile_mutex.synchronize do
277
+ # Any thread holding this lock will be compiling the template needed
278
+ # by the threads waiting. So re-check the @compiled flag to avoid
279
+ # re-compilation
280
+ return if @compiled
281
+
282
+ mod = view.compiled_method_container
283
+
284
+ instrument("!compile_template") do
285
+ compile(mod)
286
+ end
287
+
288
+ @compiled = true
289
+ end
290
+ end
291
+
292
+ class LegacyTemplate < DelegateClass(Template) # :nodoc:
293
+ attr_reader :source
294
+
295
+ def initialize(template, source)
296
+ super(template)
297
+ @source = source
298
+ end
299
+ end
300
+
301
+ # Among other things, this method is responsible for properly setting
302
+ # the encoding of the compiled template.
303
+ #
304
+ # If the template engine handles encodings, we send the encoded
305
+ # String to the engine without further processing. This allows
306
+ # the template engine to support additional mechanisms for
307
+ # specifying the encoding. For instance, ERB supports <%# encoding: %>
308
+ #
309
+ # Otherwise, after we figure out the correct encoding, we then
310
+ # encode the source into <tt>Encoding.default_internal</tt>.
311
+ # In general, this means that templates will be UTF-8 inside of Rails,
312
+ # regardless of the original source encoding.
313
+ def compile(mod)
314
+ source = encode!
315
+ code = @handler.call(self, source)
316
+
317
+ # Make sure that the resulting String to be eval'd is in the
318
+ # encoding of the code
319
+ original_source = source
320
+ source = +<<-end_src
321
+ def #{method_name}(local_assigns, output_buffer)
322
+ @virtual_path = #{@virtual_path.inspect};#{locals_code};#{code}
323
+ end
324
+ end_src
325
+
326
+ # Make sure the source is in the encoding of the returned code
327
+ source.force_encoding(code.encoding)
328
+
329
+ # In case we get back a String from a handler that is not in
330
+ # BINARY or the default_internal, encode it to the default_internal
331
+ source.encode!
332
+
333
+ # Now, validate that the source we got back from the template
334
+ # handler is valid in the default_internal. This is for handlers
335
+ # that handle encoding but screw up
336
+ unless source.valid_encoding?
337
+ raise WrongEncodingError.new(source, Encoding.default_internal)
338
+ end
339
+
340
+ begin
341
+ mod.module_eval(source, identifier, 0)
342
+ rescue SyntaxError
343
+ # Account for when code in the template is not syntactically valid; e.g. if we're using
344
+ # ERB and the user writes <%= foo( %>, attempting to call a helper `foo` and interpolate
345
+ # the result into the template, but missing an end parenthesis.
346
+ raise SyntaxErrorInTemplate.new(self, original_source)
347
+ end
348
+ end
349
+
350
+ def handle_render_error(view, e)
351
+ if e.is_a?(Template::Error)
352
+ e.sub_template_of(self)
353
+ raise e
354
+ else
355
+ raise Template::Error.new(self)
356
+ end
357
+ end
358
+
359
+ def locals_code
360
+ # Only locals with valid variable names get set directly. Others will
361
+ # still be available in local_assigns.
362
+ locals = @locals - Module::RUBY_RESERVED_KEYWORDS
363
+ locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
364
+
365
+ # Assign for the same variable is to suppress unused variable warning
366
+ locals.each_with_object(+"") { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
367
+ end
368
+
369
+ def method_name
370
+ @method_name ||= begin
371
+ m = +"_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
372
+ m.tr!("-", "_")
373
+ m
374
+ end
375
+ end
376
+
377
+ def identifier_method_name
378
+ short_identifier.tr("^a-z_", "_")
379
+ end
380
+
381
+ def instrument(action, &block) # :doc:
382
+ ActiveSupport::Notifications.instrument("#{action}.action_view", instrument_payload, &block)
383
+ end
384
+
385
+ def instrument_render_template(&block)
386
+ ActiveSupport::Notifications.instrument("!render_template.action_view", instrument_payload, &block)
387
+ end
388
+
389
+ def instrument_payload
390
+ { virtual_path: @virtual_path, identifier: @identifier }
391
+ end
392
+ end
393
+ end