actionpack 2.1.2 → 2.2.2
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 +223 -7
- data/README +6 -12
- data/Rakefile +11 -11
- data/lib/action_controller.rb +9 -9
- data/lib/action_controller/assertions/response_assertions.rb +29 -78
- data/lib/action_controller/assertions/routing_assertions.rb +33 -33
- data/lib/action_controller/assertions/selector_assertions.rb +9 -5
- data/lib/action_controller/base.rb +227 -161
- data/lib/action_controller/benchmarking.rb +37 -24
- data/lib/action_controller/caching/actions.rb +53 -21
- data/lib/action_controller/caching/fragments.rb +10 -36
- data/lib/action_controller/caching/sweeping.rb +3 -3
- data/lib/action_controller/cgi_ext/session.rb +2 -22
- data/lib/action_controller/cgi_process.rb +8 -46
- data/lib/action_controller/components.rb +4 -1
- data/lib/action_controller/cookies.rb +10 -0
- data/lib/action_controller/dispatcher.rb +49 -15
- data/lib/action_controller/filters.rb +48 -10
- data/lib/action_controller/headers.rb +16 -14
- data/lib/action_controller/helpers.rb +2 -2
- data/lib/action_controller/http_authentication.rb +1 -1
- data/lib/action_controller/integration.rb +57 -60
- data/lib/action_controller/layout.rb +27 -53
- data/lib/action_controller/mime_responds.rb +5 -1
- data/lib/action_controller/mime_type.rb +64 -42
- data/lib/action_controller/mime_types.rb +2 -1
- data/lib/action_controller/performance_test.rb +16 -0
- data/lib/action_controller/polymorphic_routes.rb +16 -9
- data/lib/action_controller/rack_process.rb +303 -0
- data/lib/action_controller/request.rb +205 -97
- data/lib/action_controller/request_forgery_protection.rb +2 -2
- data/lib/action_controller/request_profiler.rb +0 -0
- data/lib/action_controller/rescue.rb +20 -115
- data/lib/action_controller/resources.rb +186 -83
- data/lib/action_controller/response.rb +140 -26
- data/lib/action_controller/routing.rb +28 -30
- data/lib/action_controller/routing/builder.rb +45 -54
- data/lib/action_controller/routing/optimisations.rb +31 -21
- data/lib/action_controller/routing/recognition_optimisation.rb +33 -27
- data/lib/action_controller/routing/route.rb +162 -147
- data/lib/action_controller/routing/route_set.rb +8 -7
- data/lib/action_controller/routing/routing_ext.rb +4 -1
- data/lib/action_controller/routing/segments.rb +50 -21
- data/lib/action_controller/session/cookie_store.rb +3 -2
- data/lib/action_controller/session/drb_server.rb +7 -7
- data/lib/action_controller/session_management.rb +6 -2
- data/lib/action_controller/streaming.rb +15 -8
- data/lib/action_controller/templates/rescues/diagnostics.erb +2 -2
- data/lib/action_controller/templates/rescues/template_error.erb +2 -2
- data/lib/action_controller/test_case.rb +66 -2
- data/lib/action_controller/test_process.rb +71 -66
- data/lib/action_controller/translation.rb +13 -0
- data/lib/action_controller/url_rewriter.rb +90 -13
- data/lib/action_controller/vendor/html-scanner/html/node.rb +9 -2
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +1 -1
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +2 -2
- data/lib/action_controller/verification.rb +2 -2
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_view.rb +19 -11
- data/lib/action_view/base.rb +184 -150
- data/lib/action_view/helpers.rb +38 -0
- data/lib/action_view/helpers/active_record_helper.rb +56 -27
- data/lib/action_view/helpers/asset_tag_helper.rb +356 -153
- data/lib/action_view/helpers/atom_feed_helper.rb +74 -19
- data/lib/action_view/helpers/benchmark_helper.rb +3 -3
- data/lib/action_view/helpers/cache_helper.rb +1 -2
- data/lib/action_view/helpers/capture_helper.rb +19 -44
- data/lib/action_view/helpers/date_helper.rb +486 -296
- data/lib/action_view/helpers/debug_helper.rb +20 -13
- data/lib/action_view/helpers/form_helper.rb +71 -30
- data/lib/action_view/helpers/form_options_helper.rb +15 -85
- data/lib/action_view/helpers/form_tag_helper.rb +61 -38
- data/lib/action_view/helpers/javascript_helper.rb +80 -89
- data/lib/action_view/helpers/number_helper.rb +179 -74
- data/lib/action_view/helpers/prototype_helper.rb +216 -201
- data/lib/action_view/helpers/record_tag_helper.rb +4 -5
- data/lib/action_view/helpers/sanitize_helper.rb +65 -33
- data/lib/action_view/helpers/scriptaculous_helper.rb +2 -2
- data/lib/action_view/helpers/tag_helper.rb +39 -22
- data/lib/action_view/helpers/text_helper.rb +212 -118
- data/lib/action_view/helpers/translation_helper.rb +21 -0
- data/lib/action_view/helpers/url_helper.rb +100 -58
- data/lib/action_view/inline_template.rb +13 -14
- data/lib/action_view/locale/en.yml +91 -0
- data/lib/action_view/partials.rb +100 -55
- data/lib/action_view/paths.rb +125 -0
- data/lib/action_view/renderable.rb +102 -0
- data/lib/action_view/renderable_partial.rb +48 -0
- data/lib/action_view/template.rb +90 -101
- data/lib/action_view/template_error.rb +11 -21
- data/lib/action_view/template_handler.rb +8 -28
- data/lib/action_view/template_handlers.rb +45 -0
- data/lib/action_view/template_handlers/builder.rb +5 -15
- data/lib/action_view/template_handlers/erb.rb +9 -6
- data/lib/action_view/template_handlers/rjs.rb +2 -17
- data/lib/action_view/test_case.rb +7 -4
- data/test/abstract_unit.rb +4 -1
- data/test/active_record_unit.rb +28 -30
- data/test/activerecord/render_partial_with_record_identification_test.rb +25 -12
- data/test/controller/action_pack_assertions_test.rb +8 -37
- data/test/controller/addresses_render_test.rb +0 -3
- data/test/controller/assert_select_test.rb +51 -24
- data/test/controller/base_test.rb +4 -4
- data/test/controller/caching_test.rb +136 -66
- data/test/controller/capture_test.rb +1 -21
- data/test/controller/cgi_test.rb +157 -10
- data/test/controller/components_test.rb +41 -25
- data/test/controller/content_type_test.rb +49 -17
- data/test/controller/cookie_test.rb +1 -1
- data/test/controller/deprecation/deprecated_base_methods_test.rb +0 -3
- data/test/controller/dispatcher_test.rb +9 -1
- data/test/controller/filter_params_test.rb +2 -2
- data/test/controller/filters_test.rb +13 -13
- data/test/controller/html-scanner/cdata_node_test.rb +15 -0
- data/test/controller/html-scanner/node_test.rb +21 -0
- data/test/controller/html-scanner/sanitizer_test.rb +14 -0
- data/test/controller/integration_test.rb +167 -6
- data/test/controller/layout_test.rb +11 -68
- data/test/controller/logging_test.rb +46 -0
- data/test/controller/mime_responds_test.rb +61 -59
- data/test/controller/mime_type_test.rb +6 -6
- data/test/controller/polymorphic_routes_test.rb +37 -2
- data/test/controller/rack_test.rb +323 -0
- data/test/controller/redirect_test.rb +72 -71
- data/test/controller/render_test.rb +1120 -108
- data/test/controller/request_forgery_protection_test.rb +66 -52
- data/test/controller/request_test.rb +103 -146
- data/test/controller/rescue_test.rb +20 -24
- data/test/controller/resources_test.rb +408 -25
- data/test/controller/routing_test.rb +1774 -1774
- data/test/controller/send_file_test.rb +0 -4
- data/test/controller/session/cookie_store_test.rb +53 -1
- data/test/controller/test_test.rb +15 -37
- data/test/controller/translation_test.rb +26 -0
- data/test/controller/url_rewriter_test.rb +27 -28
- data/test/controller/view_paths_test.rb +48 -47
- data/test/fixtures/_top_level_partial.html.erb +1 -0
- data/test/fixtures/_top_level_partial_only.erb +1 -0
- data/test/fixtures/developers/_developer.erb +1 -0
- data/test/fixtures/fun/games/_game.erb +1 -0
- data/test/fixtures/fun/serious/games/_game.erb +1 -0
- data/test/fixtures/functional_caching/formatted_fragment_cached.html.erb +3 -0
- data/test/fixtures/functional_caching/formatted_fragment_cached.js.rjs +6 -0
- data/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder +5 -0
- data/test/fixtures/functional_caching/inline_fragment_cached.html.erb +2 -0
- data/test/fixtures/layouts/_column.html.erb +2 -0
- data/test/fixtures/projects/_project.erb +1 -0
- data/test/fixtures/public/javascripts/subdir/subdir.js +1 -0
- data/test/fixtures/public/stylesheets/subdir/subdir.css +1 -0
- data/test/fixtures/replies/_reply.erb +1 -0
- data/test/fixtures/test/_counter.html.erb +1 -0
- data/test/fixtures/test/_customer.erb +1 -1
- data/test/fixtures/test/_customer_with_var.erb +1 -0
- data/test/fixtures/test/_layout_for_block_with_args.html.erb +3 -0
- data/test/fixtures/test/_local_inspector.html.erb +1 -0
- data/test/fixtures/test/_partial_with_only_html_version.html.erb +1 -0
- data/test/fixtures/test/hello.builder +1 -1
- data/test/fixtures/test/hyphen-ated.erb +1 -0
- data/test/fixtures/test/implicit_content_type.atom.builder +2 -0
- data/test/fixtures/test/nested_layout.erb +3 -0
- data/test/fixtures/test/non_erb_block_content_for.builder +1 -1
- data/test/fixtures/test/sub_template_raise.html.erb +1 -0
- data/test/fixtures/test/template.erb +1 -0
- data/test/fixtures/test/using_layout_around_block_with_args.html.erb +1 -0
- data/test/template/active_record_helper_i18n_test.rb +46 -0
- data/test/template/active_record_helper_test.rb +24 -24
- data/test/template/asset_tag_helper_test.rb +161 -29
- data/test/template/atom_feed_helper_test.rb +114 -5
- data/test/template/compiled_templates_test.rb +59 -0
- data/test/template/date_helper_i18n_test.rb +113 -0
- data/test/template/date_helper_test.rb +403 -109
- data/test/template/form_helper_test.rb +213 -154
- data/test/template/form_options_helper_test.rb +249 -897
- data/test/template/form_tag_helper_test.rb +80 -32
- data/test/template/javascript_helper_test.rb +17 -18
- data/test/template/number_helper_i18n_test.rb +54 -0
- data/test/template/number_helper_test.rb +43 -13
- data/test/template/prototype_helper_test.rb +101 -84
- data/test/template/record_tag_helper_test.rb +24 -20
- data/test/template/render_test.rb +193 -0
- data/test/template/sanitize_helper_test.rb +3 -3
- data/test/template/tag_helper_test.rb +34 -14
- data/test/template/text_helper_test.rb +83 -9
- data/test/template/translation_helper_test.rb +28 -0
- data/test/template/url_helper_test.rb +55 -18
- metadata +57 -18
- data/lib/action_view/helpers/javascripts/controls.js +0 -963
- data/lib/action_view/helpers/javascripts/dragdrop.js +0 -972
- data/lib/action_view/helpers/javascripts/effects.js +0 -1120
- data/lib/action_view/helpers/javascripts/prototype.js +0 -4225
- data/lib/action_view/partial_template.rb +0 -70
- data/lib/action_view/template_finder.rb +0 -177
- data/lib/action_view/template_handlers/compilable.rb +0 -128
- data/test/controller/custom_handler_test.rb +0 -45
- data/test/controller/new_render_test.rb +0 -945
- data/test/fixtures/test/block_content_for.erb +0 -2
- data/test/fixtures/test/erb_content_for.erb +0 -2
- data/test/template/deprecated_erb_variable_test.rb +0 -9
- data/test/template/template_finder_test.rb +0 -73
- data/test/template/template_object_test.rb +0 -95
@@ -0,0 +1,38 @@
|
|
1
|
+
Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file|
|
2
|
+
next unless file =~ /^([a-z][a-z_]*_helper).rb$/
|
3
|
+
require "action_view/helpers/#{$1}"
|
4
|
+
end
|
5
|
+
|
6
|
+
module ActionView #:nodoc:
|
7
|
+
module Helpers #:nodoc:
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
include SanitizeHelper::ClassMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
include ActiveRecordHelper
|
17
|
+
include AssetTagHelper
|
18
|
+
include AtomFeedHelper
|
19
|
+
include BenchmarkHelper
|
20
|
+
include CacheHelper
|
21
|
+
include CaptureHelper
|
22
|
+
include DateHelper
|
23
|
+
include DebugHelper
|
24
|
+
include FormHelper
|
25
|
+
include FormOptionsHelper
|
26
|
+
include FormTagHelper
|
27
|
+
include NumberHelper
|
28
|
+
include PrototypeHelper
|
29
|
+
include RecordIdentificationHelper
|
30
|
+
include RecordTagHelper
|
31
|
+
include SanitizeHelper
|
32
|
+
include ScriptaculousHelper
|
33
|
+
include TagHelper
|
34
|
+
include TextHelper
|
35
|
+
include TranslationHelper
|
36
|
+
include UrlHelper
|
37
|
+
end
|
38
|
+
end
|
@@ -25,7 +25,7 @@ module ActionView
|
|
25
25
|
# Returns an entire form with all needed input tags for a specified Active Record object. For example, if <tt>@post</tt>
|
26
26
|
# has attributes named +title+ of type +VARCHAR+ and +body+ of type +TEXT+ then
|
27
27
|
#
|
28
|
-
# form("post")
|
28
|
+
# form("post")
|
29
29
|
#
|
30
30
|
# would yield a form like the following (modulus formatting):
|
31
31
|
#
|
@@ -90,23 +90,41 @@ module ActionView
|
|
90
90
|
end
|
91
91
|
|
92
92
|
# Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
|
93
|
-
# This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a
|
94
|
-
# (to properly explain the error), and a
|
95
|
-
#
|
93
|
+
# This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a <tt>:prepend_text</tt>
|
94
|
+
# and/or <tt>:append_text</tt> (to properly explain the error), and a <tt>:css_class</tt> to style it
|
95
|
+
# accordingly. +object+ should either be the name of an instance variable or the actual object. The method can be
|
96
|
+
# passed in either as a string or a symbol.
|
97
|
+
# As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute:
|
96
98
|
#
|
97
99
|
# <%= error_message_on "post", "title" %>
|
98
100
|
# # => <div class="formError">can't be empty</div>
|
99
101
|
#
|
100
|
-
# <%= error_message_on @post,
|
102
|
+
# <%= error_message_on @post, :title %>
|
101
103
|
# # => <div class="formError">can't be empty</div>
|
102
104
|
#
|
103
|
-
# <%= error_message_on "post", "title",
|
104
|
-
#
|
105
|
-
|
105
|
+
# <%= error_message_on "post", "title",
|
106
|
+
# :prepend_text => "Title simply ",
|
107
|
+
# :append_text => " (or it won't work).",
|
108
|
+
# :css_class => "inputError" %>
|
109
|
+
def error_message_on(object, method, *args)
|
110
|
+
options = args.extract_options!
|
111
|
+
unless args.empty?
|
112
|
+
ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate ' +
|
113
|
+
'prepend_text, append_text, and css_class arguments', caller)
|
114
|
+
|
115
|
+
options[:prepend_text] = args[0] || ''
|
116
|
+
options[:append_text] = args[1] || ''
|
117
|
+
options[:css_class] = args[2] || 'formError'
|
118
|
+
end
|
119
|
+
options.reverse_merge!(:prepend_text => '', :append_text => '', :css_class => 'formError')
|
120
|
+
|
106
121
|
if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
|
107
122
|
(errors = obj.errors.on(method))
|
108
|
-
content_tag("div",
|
109
|
-
|
123
|
+
content_tag("div",
|
124
|
+
"#{options[:prepend_text]}#{errors.is_a?(Array) ? errors.first : errors}#{options[:append_text]}",
|
125
|
+
:class => options[:css_class]
|
126
|
+
)
|
127
|
+
else
|
110
128
|
''
|
111
129
|
end
|
112
130
|
end
|
@@ -133,7 +151,7 @@ module ActionView
|
|
133
151
|
#
|
134
152
|
# To specify the display for one object, you simply provide its name as a parameter.
|
135
153
|
# For example, for the <tt>@user</tt> model:
|
136
|
-
#
|
154
|
+
#
|
137
155
|
# error_messages_for 'user'
|
138
156
|
#
|
139
157
|
# To specify more than one object, you simply list them; optionally, you can add an extra <tt>:object_name</tt> parameter, which
|
@@ -141,7 +159,7 @@ module ActionView
|
|
141
159
|
#
|
142
160
|
# error_messages_for 'user_common', 'user', :object_name => 'user'
|
143
161
|
#
|
144
|
-
# If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt>
|
162
|
+
# If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> parameter which gives the actual
|
145
163
|
# object (or array of objects to use):
|
146
164
|
#
|
147
165
|
# error_messages_for 'user', :object => @question.user
|
@@ -151,12 +169,14 @@ module ActionView
|
|
151
169
|
# instance yourself and set it up. View the source of this method to see how easy it is.
|
152
170
|
def error_messages_for(*params)
|
153
171
|
options = params.extract_options!.symbolize_keys
|
172
|
+
|
154
173
|
if object = options.delete(:object)
|
155
174
|
objects = [object].flatten
|
156
175
|
else
|
157
176
|
objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact
|
158
177
|
end
|
159
|
-
|
178
|
+
|
179
|
+
count = objects.inject(0) {|sum, object| sum + object.errors.count }
|
160
180
|
unless count.zero?
|
161
181
|
html = {}
|
162
182
|
[:id, :class].each do |key|
|
@@ -168,16 +188,25 @@ module ActionView
|
|
168
188
|
end
|
169
189
|
end
|
170
190
|
options[:object_name] ||= params.first
|
171
|
-
options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message)
|
172
|
-
options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message)
|
173
|
-
error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
|
174
191
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
192
|
+
I18n.with_options :locale => options[:locale], :scope => [:activerecord, :errors, :template] do |locale|
|
193
|
+
header_message = if options.include?(:header_message)
|
194
|
+
options[:header_message]
|
195
|
+
else
|
196
|
+
object_name = options[:object_name].to_s.gsub('_', ' ')
|
197
|
+
object_name = I18n.t(object_name, :default => object_name, :scope => [:activerecord, :models], :count => 1)
|
198
|
+
locale.t :header, :count => count, :model => object_name
|
199
|
+
end
|
200
|
+
message = options.include?(:message) ? options[:message] : locale.t(:body)
|
201
|
+
error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join
|
179
202
|
|
180
|
-
|
203
|
+
contents = ''
|
204
|
+
contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
|
205
|
+
contents << content_tag(:p, message) unless message.blank?
|
206
|
+
contents << content_tag(:ul, error_messages)
|
207
|
+
|
208
|
+
content_tag(:div, contents, html)
|
209
|
+
end
|
181
210
|
else
|
182
211
|
''
|
183
212
|
end
|
@@ -217,7 +246,7 @@ module ActionView
|
|
217
246
|
|
218
247
|
alias_method :tag_without_error_wrapping, :tag
|
219
248
|
def tag(name, options)
|
220
|
-
if object.respond_to?(
|
249
|
+
if object.respond_to?(:errors) && object.errors.respond_to?(:on)
|
221
250
|
error_wrapping(tag_without_error_wrapping(name, options), object.errors.on(@method_name))
|
222
251
|
else
|
223
252
|
tag_without_error_wrapping(name, options)
|
@@ -226,7 +255,7 @@ module ActionView
|
|
226
255
|
|
227
256
|
alias_method :content_tag_without_error_wrapping, :content_tag
|
228
257
|
def content_tag(name, value, options)
|
229
|
-
if object.respond_to?(
|
258
|
+
if object.respond_to?(:errors) && object.errors.respond_to?(:on)
|
230
259
|
error_wrapping(content_tag_without_error_wrapping(name, value, options), object.errors.on(@method_name))
|
231
260
|
else
|
232
261
|
content_tag_without_error_wrapping(name, value, options)
|
@@ -235,7 +264,7 @@ module ActionView
|
|
235
264
|
|
236
265
|
alias_method :to_date_select_tag_without_error_wrapping, :to_date_select_tag
|
237
266
|
def to_date_select_tag(options = {}, html_options = {})
|
238
|
-
if object.respond_to?(
|
267
|
+
if object.respond_to?(:errors) && object.errors.respond_to?(:on)
|
239
268
|
error_wrapping(to_date_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name))
|
240
269
|
else
|
241
270
|
to_date_select_tag_without_error_wrapping(options, html_options)
|
@@ -244,7 +273,7 @@ module ActionView
|
|
244
273
|
|
245
274
|
alias_method :to_datetime_select_tag_without_error_wrapping, :to_datetime_select_tag
|
246
275
|
def to_datetime_select_tag(options = {}, html_options = {})
|
247
|
-
if object.respond_to?(
|
276
|
+
if object.respond_to?(:errors) && object.errors.respond_to?(:on)
|
248
277
|
error_wrapping(to_datetime_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name))
|
249
278
|
else
|
250
279
|
to_datetime_select_tag_without_error_wrapping(options, html_options)
|
@@ -253,7 +282,7 @@ module ActionView
|
|
253
282
|
|
254
283
|
alias_method :to_time_select_tag_without_error_wrapping, :to_time_select_tag
|
255
284
|
def to_time_select_tag(options = {}, html_options = {})
|
256
|
-
if object.respond_to?(
|
285
|
+
if object.respond_to?(:errors) && object.errors.respond_to?(:on)
|
257
286
|
error_wrapping(to_time_select_tag_without_error_wrapping(options, html_options), object.errors.on(@method_name))
|
258
287
|
else
|
259
288
|
to_time_select_tag_without_error_wrapping(options, html_options)
|
@@ -269,7 +298,7 @@ module ActionView
|
|
269
298
|
end
|
270
299
|
|
271
300
|
def column_type
|
272
|
-
object.send(
|
301
|
+
object.send(:column_for_attribute, @method_name).type
|
273
302
|
end
|
274
303
|
end
|
275
304
|
end
|
@@ -5,12 +5,12 @@ require 'action_view/helpers/tag_helper'
|
|
5
5
|
module ActionView
|
6
6
|
module Helpers #:nodoc:
|
7
7
|
# This module provides methods for generating HTML that links views to assets such
|
8
|
-
# as images, javascripts, stylesheets, and feeds. These methods do not verify
|
9
|
-
# the assets exist before linking to them.
|
8
|
+
# as images, javascripts, stylesheets, and feeds. These methods do not verify
|
9
|
+
# the assets exist before linking to them.
|
10
10
|
#
|
11
11
|
# === Using asset hosts
|
12
12
|
# By default, Rails links to these assets on the current host in the public
|
13
|
-
# folder, but you can direct Rails to link to assets from a dedicated assets server by
|
13
|
+
# folder, but you can direct Rails to link to assets from a dedicated assets server by
|
14
14
|
# setting ActionController::Base.asset_host in your <tt>config/environment.rb</tt>. For example,
|
15
15
|
# let's say your asset host is <tt>assets.example.com</tt>.
|
16
16
|
#
|
@@ -22,16 +22,16 @@ module ActionView
|
|
22
22
|
#
|
23
23
|
# This is useful since browsers typically open at most two connections to a single host,
|
24
24
|
# which means your assets often wait in single file for their turn to load. You can
|
25
|
-
# alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
|
25
|
+
# alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
|
26
26
|
# to automatically distribute asset requests among four hosts (e.g., "assets0.example.com" through "assets3.example.com")
|
27
|
-
# so browsers will open eight connections rather than two.
|
27
|
+
# so browsers will open eight connections rather than two.
|
28
28
|
#
|
29
29
|
# image_tag("rails.png")
|
30
30
|
# => <img src="http://assets0.example.com/images/rails.png" alt="Rails" />
|
31
31
|
# stylesheet_link_tag("application")
|
32
32
|
# => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
|
33
33
|
#
|
34
|
-
# To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
|
34
|
+
# To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
|
35
35
|
# the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from
|
36
36
|
# your ISP.
|
37
37
|
#
|
@@ -86,7 +86,7 @@ module ActionView
|
|
86
86
|
# asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp,
|
87
87
|
# which then updates the URL as the timestamp is part of that, which in turn busts the cache).
|
88
88
|
#
|
89
|
-
# It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
|
89
|
+
# It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
|
90
90
|
# advantage of this feature. Here's an example for Apache:
|
91
91
|
#
|
92
92
|
# # Asset Expiration
|
@@ -95,16 +95,17 @@ module ActionView
|
|
95
95
|
# ExpiresDefault "access plus 1 year"
|
96
96
|
# </FilesMatch>
|
97
97
|
#
|
98
|
-
# Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
|
98
|
+
# Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
|
99
99
|
# have their clocks synchronized. If one of them drift out of sync, you'll see different timestamps at random and the cache won't
|
100
100
|
# work. Which means that the browser will request the same assets over and over again even thought they didn't change. You can use
|
101
|
-
# something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
|
101
|
+
# something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
|
102
102
|
# requested over and over).
|
103
103
|
module AssetTagHelper
|
104
104
|
ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public"
|
105
105
|
JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
|
106
106
|
STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets"
|
107
|
-
|
107
|
+
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
|
108
|
+
|
108
109
|
# Returns a link tag that browsers and news readers can use to auto-detect
|
109
110
|
# an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
|
110
111
|
# <tt>:atom</tt>. Control the link options in url_for format using the
|
@@ -150,14 +151,10 @@ module ActionView
|
|
150
151
|
# javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr.js
|
151
152
|
# javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
|
152
153
|
def javascript_path(source)
|
153
|
-
|
154
|
+
JavaScriptTag.new(self, @controller, source).public_path
|
154
155
|
end
|
155
156
|
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
|
156
157
|
|
157
|
-
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
|
158
|
-
@@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
|
159
|
-
@@stylesheet_expansions = {}
|
160
|
-
|
161
158
|
# Returns an html script tag for each of the +sources+ provided. You
|
162
159
|
# can pass in the filename (.js extension is optional) of javascript files
|
163
160
|
# that exist in your public/javascripts directory for inclusion into the
|
@@ -193,7 +190,7 @@ module ActionView
|
|
193
190
|
#
|
194
191
|
# * = The application.js file is only referenced if it exists
|
195
192
|
#
|
196
|
-
# Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
|
193
|
+
# Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
|
197
194
|
# (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method.
|
198
195
|
#
|
199
196
|
# You can also include all javascripts in the javascripts directory using <tt>:all</tt> as the source:
|
@@ -209,12 +206,16 @@ module ActionView
|
|
209
206
|
# Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to
|
210
207
|
# all subsequently included files.
|
211
208
|
#
|
209
|
+
# If you want Rails to search in all the subdirectories under javascripts, you should explicitly set <tt>:recursive</tt>:
|
210
|
+
#
|
211
|
+
# javascript_include_tag :all, :recursive => true
|
212
|
+
#
|
212
213
|
# == Caching multiple javascripts into one
|
213
214
|
#
|
214
215
|
# You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
|
215
216
|
# compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
|
216
217
|
# is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
|
217
|
-
# environment).
|
218
|
+
# environment).
|
218
219
|
#
|
219
220
|
# ==== Examples
|
220
221
|
# javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
|
@@ -235,18 +236,27 @@ module ActionView
|
|
235
236
|
#
|
236
237
|
# javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is true =>
|
237
238
|
# <script type="text/javascript" src="/javascripts/shop.js"></script>
|
239
|
+
#
|
240
|
+
# The <tt>:recursive</tt> option is also available for caching:
|
241
|
+
#
|
242
|
+
# javascript_include_tag :all, :cache => true, :recursive => true
|
238
243
|
def javascript_include_tag(*sources)
|
239
244
|
options = sources.extract_options!.stringify_keys
|
240
245
|
cache = options.delete("cache")
|
246
|
+
recursive = options.delete("recursive")
|
241
247
|
|
242
248
|
if ActionController::Base.perform_caching && cache
|
243
249
|
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
|
244
250
|
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
|
245
251
|
|
246
|
-
|
252
|
+
unless File.exists?(joined_javascript_path)
|
253
|
+
JavaScriptSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_javascript_path)
|
254
|
+
end
|
247
255
|
javascript_src_tag(joined_javascript_name, options)
|
248
256
|
else
|
249
|
-
|
257
|
+
JavaScriptSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
|
258
|
+
javascript_src_tag(source, options)
|
259
|
+
}.join("\n")
|
250
260
|
end
|
251
261
|
end
|
252
262
|
|
@@ -262,7 +272,7 @@ module ActionView
|
|
262
272
|
# <script type="text/javascript" src="/javascripts/body.js"></script>
|
263
273
|
# <script type="text/javascript" src="/javascripts/tail.js"></script>
|
264
274
|
def self.register_javascript_expansion(expansions)
|
265
|
-
|
275
|
+
JavaScriptSources.expansions.merge!(expansions)
|
266
276
|
end
|
267
277
|
|
268
278
|
# Register one or more stylesheet files to be included when <tt>symbol</tt>
|
@@ -277,7 +287,7 @@ module ActionView
|
|
277
287
|
# <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
|
278
288
|
# <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
|
279
289
|
def self.register_stylesheet_expansion(expansions)
|
280
|
-
|
290
|
+
StylesheetSources.expansions.merge!(expansions)
|
281
291
|
end
|
282
292
|
|
283
293
|
# Register one or more additional JavaScript files to be included when
|
@@ -285,11 +295,11 @@ module ActionView
|
|
285
295
|
# typically intended to be called from plugin initialization to register additional
|
286
296
|
# .js files that the plugin installed in <tt>public/javascripts</tt>.
|
287
297
|
def self.register_javascript_include_default(*sources)
|
288
|
-
|
298
|
+
JavaScriptSources.expansions[:defaults].concat(sources)
|
289
299
|
end
|
290
300
|
|
291
301
|
def self.reset_javascript_include_default #:nodoc:
|
292
|
-
|
302
|
+
JavaScriptSources.expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
|
293
303
|
end
|
294
304
|
|
295
305
|
# Computes the path to a stylesheet asset in the public stylesheets directory.
|
@@ -304,7 +314,7 @@ module ActionView
|
|
304
314
|
# stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style.css
|
305
315
|
# stylesheet_path "http://www.railsapplication.com/css/style.js" # => http://www.railsapplication.com/css/style.css
|
306
316
|
def stylesheet_path(source)
|
307
|
-
|
317
|
+
StylesheetTag.new(self, @controller, source).public_path
|
308
318
|
end
|
309
319
|
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
|
310
320
|
|
@@ -332,13 +342,17 @@ module ActionView
|
|
332
342
|
# <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />
|
333
343
|
# <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />
|
334
344
|
#
|
335
|
-
# You can also include all styles in the
|
345
|
+
# You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source:
|
336
346
|
#
|
337
347
|
# stylesheet_link_tag :all # =>
|
338
348
|
# <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
|
339
349
|
# <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
|
340
350
|
# <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
|
341
351
|
#
|
352
|
+
# If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>:
|
353
|
+
#
|
354
|
+
# stylesheet_link_tag :all, :recursive => true
|
355
|
+
#
|
342
356
|
# == Caching multiple stylesheets into one
|
343
357
|
#
|
344
358
|
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
|
@@ -362,18 +376,27 @@ module ActionView
|
|
362
376
|
#
|
363
377
|
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true =>
|
364
378
|
# <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
|
379
|
+
#
|
380
|
+
# The <tt>:recursive</tt> option is also available for caching:
|
381
|
+
#
|
382
|
+
# stylesheet_link_tag :all, :cache => true, :recursive => true
|
365
383
|
def stylesheet_link_tag(*sources)
|
366
384
|
options = sources.extract_options!.stringify_keys
|
367
385
|
cache = options.delete("cache")
|
386
|
+
recursive = options.delete("recursive")
|
368
387
|
|
369
388
|
if ActionController::Base.perform_caching && cache
|
370
389
|
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
|
371
390
|
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
|
372
391
|
|
373
|
-
|
392
|
+
unless File.exists?(joined_stylesheet_path)
|
393
|
+
StylesheetSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_stylesheet_path)
|
394
|
+
end
|
374
395
|
stylesheet_tag(joined_stylesheet_name, options)
|
375
396
|
else
|
376
|
-
|
397
|
+
StylesheetSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
|
398
|
+
stylesheet_tag(source, options)
|
399
|
+
}.join("\n")
|
377
400
|
end
|
378
401
|
end
|
379
402
|
|
@@ -388,7 +411,7 @@ module ActionView
|
|
388
411
|
# image_path("/icons/edit.png") # => /icons/edit.png
|
389
412
|
# image_path("http://www.railsapplication.com/img/edit.png") # => http://www.railsapplication.com/img/edit.png
|
390
413
|
def image_path(source)
|
391
|
-
|
414
|
+
ImageTag.new(self, @controller, source).public_path
|
392
415
|
end
|
393
416
|
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
|
394
417
|
|
@@ -421,9 +444,9 @@ module ActionView
|
|
421
444
|
# <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
|
422
445
|
# image_tag("/icons/icon.gif", :class => "menu_icon") # =>
|
423
446
|
# <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
|
424
|
-
# image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
|
447
|
+
# image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
|
425
448
|
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
|
426
|
-
# image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
|
449
|
+
# image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
|
427
450
|
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
|
428
451
|
def image_tag(source, options = {})
|
429
452
|
options.symbolize_keys!
|
@@ -436,178 +459,358 @@ module ActionView
|
|
436
459
|
end
|
437
460
|
|
438
461
|
if mouseover = options.delete(:mouseover)
|
439
|
-
options[:onmouseover]
|
440
|
-
options[:onmouseout]
|
462
|
+
options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
|
463
|
+
options[:onmouseout] = "this.src='#{image_path(options[:src])}'"
|
441
464
|
end
|
442
465
|
|
443
466
|
tag("img", options)
|
444
467
|
end
|
445
468
|
|
446
469
|
private
|
447
|
-
def
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
470
|
+
def javascript_src_tag(source, options)
|
471
|
+
content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
|
472
|
+
end
|
473
|
+
|
474
|
+
def stylesheet_tag(source, options)
|
475
|
+
tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
|
476
|
+
end
|
477
|
+
|
478
|
+
module ImageAsset
|
479
|
+
DIRECTORY = 'images'.freeze
|
480
|
+
|
481
|
+
def directory
|
482
|
+
DIRECTORY
|
483
|
+
end
|
484
|
+
|
485
|
+
def extension
|
486
|
+
nil
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
module JavaScriptAsset
|
491
|
+
DIRECTORY = 'javascripts'.freeze
|
492
|
+
EXTENSION = 'js'.freeze
|
493
|
+
|
494
|
+
def public_directory
|
495
|
+
JAVASCRIPTS_DIR
|
496
|
+
end
|
497
|
+
|
498
|
+
def directory
|
499
|
+
DIRECTORY
|
500
|
+
end
|
501
|
+
|
502
|
+
def extension
|
503
|
+
EXTENSION
|
454
504
|
end
|
455
505
|
end
|
456
506
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
507
|
+
module StylesheetAsset
|
508
|
+
DIRECTORY = 'stylesheets'.freeze
|
509
|
+
EXTENSION = 'css'.freeze
|
510
|
+
|
511
|
+
def public_directory
|
512
|
+
STYLESHEETS_DIR
|
513
|
+
end
|
514
|
+
|
515
|
+
def directory
|
516
|
+
DIRECTORY
|
517
|
+
end
|
518
|
+
|
519
|
+
def extension
|
520
|
+
EXTENSION
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
class AssetTag
|
525
|
+
extend ActiveSupport::Memoizable
|
526
|
+
|
527
|
+
Cache = {}
|
528
|
+
CacheGuard = Mutex.new
|
529
|
+
|
530
|
+
ProtocolRegexp = %r{^[-a-z]+://}.freeze
|
531
|
+
|
532
|
+
def initialize(template, controller, source, include_host = true)
|
533
|
+
# NOTE: The template arg is temporarily needed for a legacy plugin
|
534
|
+
# hook that is expected to call rewrite_asset_path on the
|
535
|
+
# template. This should eventually be removed.
|
536
|
+
@template = template
|
537
|
+
@controller = controller
|
538
|
+
@source = source
|
539
|
+
@include_host = include_host
|
540
|
+
@cache_key = if controller.respond_to?(:request)
|
541
|
+
[self.class.name,controller.request.protocol,
|
542
|
+
ActionController::Base.asset_host,
|
543
|
+
ActionController::Base.relative_url_root,
|
544
|
+
source, include_host]
|
470
545
|
else
|
471
|
-
[
|
472
|
-
dir, source, ext, include_host ].join
|
546
|
+
[self.class.name,ActionController::Base.asset_host, source, include_host]
|
473
547
|
end
|
548
|
+
end
|
549
|
+
|
550
|
+
def public_path
|
551
|
+
compute_public_path(@source)
|
552
|
+
end
|
553
|
+
memoize :public_path
|
554
|
+
|
555
|
+
def asset_file_path
|
556
|
+
File.join(ASSETS_DIR, public_path.split('?').first)
|
557
|
+
end
|
558
|
+
memoize :asset_file_path
|
559
|
+
|
560
|
+
def contents
|
561
|
+
File.read(asset_file_path)
|
562
|
+
end
|
563
|
+
|
564
|
+
def mtime
|
565
|
+
File.mtime(asset_file_path)
|
566
|
+
end
|
474
567
|
|
475
|
-
|
476
|
-
|
477
|
-
|
568
|
+
private
|
569
|
+
def request
|
570
|
+
@controller.request
|
571
|
+
end
|
478
572
|
|
479
|
-
|
573
|
+
def request?
|
574
|
+
@controller.respond_to?(:request)
|
575
|
+
end
|
576
|
+
|
577
|
+
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
|
578
|
+
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
|
579
|
+
# roots. Rewrite the asset path for cache-busting asset ids. Include
|
580
|
+
# asset host, if configured, with the correct request protocol.
|
581
|
+
def compute_public_path(source)
|
582
|
+
if source =~ ProtocolRegexp
|
583
|
+
source += ".#{extension}" if missing_extension?(source)
|
584
|
+
source = prepend_asset_host(source)
|
480
585
|
source
|
481
586
|
else
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
source = "
|
587
|
+
CacheGuard.synchronize do
|
588
|
+
Cache[@cache_key] ||= begin
|
589
|
+
source += ".#{extension}" if missing_extension?(source) || file_exists_with_extension?(source)
|
590
|
+
source = "/#{directory}/#{source}" unless source[0] == ?/
|
591
|
+
source = rewrite_asset_path(source)
|
592
|
+
source = prepend_relative_url_root(source)
|
593
|
+
source = prepend_asset_host(source)
|
594
|
+
source
|
486
595
|
end
|
487
596
|
end
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
def missing_extension?(source)
|
601
|
+
extension && File.extname(source).blank?
|
602
|
+
end
|
603
|
+
|
604
|
+
def file_exists_with_extension?(source)
|
605
|
+
extension && File.exist?(File.join(ASSETS_DIR, directory, "#{source}.#{extension}"))
|
606
|
+
end
|
607
|
+
|
608
|
+
def prepend_relative_url_root(source)
|
609
|
+
relative_url_root = ActionController::Base.relative_url_root
|
610
|
+
if request? && @include_host && source !~ %r{^#{relative_url_root}/}
|
611
|
+
"#{relative_url_root}#{source}"
|
612
|
+
else
|
613
|
+
source
|
614
|
+
end
|
615
|
+
end
|
488
616
|
|
489
|
-
|
617
|
+
def prepend_asset_host(source)
|
618
|
+
if @include_host && source !~ ProtocolRegexp
|
619
|
+
host = compute_asset_host(source)
|
620
|
+
if request? && !host.blank? && host !~ ProtocolRegexp
|
621
|
+
host = "#{request.protocol}#{host}"
|
622
|
+
end
|
623
|
+
"#{host}#{source}"
|
624
|
+
else
|
625
|
+
source
|
490
626
|
end
|
491
627
|
end
|
492
628
|
|
493
|
-
|
629
|
+
# Pick an asset host for this source. Returns +nil+ if no host is set,
|
630
|
+
# the host if no wildcard is set, the host interpolated with the
|
631
|
+
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
|
632
|
+
# or the value returned from invoking the proc if it's a proc.
|
633
|
+
def compute_asset_host(source)
|
634
|
+
if host = ActionController::Base.asset_host
|
635
|
+
if host.is_a?(Proc)
|
636
|
+
case host.arity
|
637
|
+
when 2
|
638
|
+
host.call(source, request)
|
639
|
+
else
|
640
|
+
host.call(source)
|
641
|
+
end
|
642
|
+
else
|
643
|
+
(host =~ /%d/) ? host % (source.hash % 4) : host
|
644
|
+
end
|
645
|
+
end
|
646
|
+
end
|
494
647
|
|
495
|
-
|
496
|
-
|
648
|
+
# Use the RAILS_ASSET_ID environment variable or the source's
|
649
|
+
# modification time as its cache-busting asset id.
|
650
|
+
def rails_asset_id(source)
|
651
|
+
if asset_id = ENV["RAILS_ASSET_ID"]
|
652
|
+
asset_id
|
653
|
+
else
|
654
|
+
path = File.join(ASSETS_DIR, source)
|
497
655
|
|
498
|
-
|
499
|
-
|
656
|
+
if File.exist?(path)
|
657
|
+
File.mtime(path).to_i.to_s
|
658
|
+
else
|
659
|
+
''
|
660
|
+
end
|
661
|
+
end
|
500
662
|
end
|
501
663
|
|
502
|
-
|
503
|
-
|
504
|
-
source
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
# Pick an asset host for this source. Returns +nil+ if no host is set,
|
509
|
-
# the host if no wildcard is set, the host interpolated with the
|
510
|
-
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
|
511
|
-
# or the value returned from invoking the proc if it's a proc.
|
512
|
-
def compute_asset_host(source)
|
513
|
-
if host = ActionController::Base.asset_host
|
514
|
-
if host.is_a?(Proc)
|
515
|
-
case host.arity
|
516
|
-
when 2
|
517
|
-
host.call(source, @controller.request)
|
664
|
+
# Break out the asset path rewrite in case plugins wish to put the asset id
|
665
|
+
# someplace other than the query string.
|
666
|
+
def rewrite_asset_path(source)
|
667
|
+
if @template.respond_to?(:rewrite_asset_path)
|
668
|
+
# DEPRECATE: This way to override rewrite_asset_path
|
669
|
+
@template.send(:rewrite_asset_path, source)
|
518
670
|
else
|
519
|
-
|
671
|
+
asset_id = rails_asset_id(source)
|
672
|
+
if asset_id.blank?
|
673
|
+
source
|
674
|
+
else
|
675
|
+
"#{source}?#{asset_id}"
|
676
|
+
end
|
520
677
|
end
|
521
|
-
else
|
522
|
-
(host =~ /%d/) ? host % (source.hash % 4) : host
|
523
678
|
end
|
524
|
-
end
|
525
679
|
end
|
526
680
|
|
527
|
-
|
528
|
-
|
529
|
-
def rails_asset_id(source)
|
530
|
-
if asset_id = ENV["RAILS_ASSET_ID"]
|
531
|
-
asset_id
|
532
|
-
else
|
533
|
-
path = File.join(ASSETS_DIR, source)
|
534
|
-
|
535
|
-
if File.exist?(path)
|
536
|
-
File.mtime(path).to_i.to_s
|
537
|
-
else
|
538
|
-
''
|
539
|
-
end
|
540
|
-
end
|
681
|
+
class ImageTag < AssetTag
|
682
|
+
include ImageAsset
|
541
683
|
end
|
542
684
|
|
543
|
-
|
544
|
-
|
545
|
-
def rewrite_asset_path(source)
|
546
|
-
asset_id = rails_asset_id(source)
|
547
|
-
if asset_id.blank?
|
548
|
-
source
|
549
|
-
else
|
550
|
-
source + "?#{asset_id}"
|
551
|
-
end
|
685
|
+
class JavaScriptTag < AssetTag
|
686
|
+
include JavaScriptAsset
|
552
687
|
end
|
553
688
|
|
554
|
-
|
555
|
-
|
689
|
+
class StylesheetTag < AssetTag
|
690
|
+
include StylesheetAsset
|
556
691
|
end
|
557
692
|
|
558
|
-
|
559
|
-
|
560
|
-
end
|
693
|
+
class AssetCollection
|
694
|
+
extend ActiveSupport::Memoizable
|
561
695
|
|
562
|
-
|
563
|
-
|
564
|
-
end
|
696
|
+
Cache = {}
|
697
|
+
CacheGuard = Mutex.new
|
565
698
|
|
566
|
-
|
567
|
-
|
568
|
-
|
699
|
+
def self.create(template, controller, sources, recursive)
|
700
|
+
CacheGuard.synchronize do
|
701
|
+
key = [self, sources, recursive]
|
702
|
+
Cache[key] ||= new(template, controller, sources, recursive).freeze
|
703
|
+
end
|
704
|
+
end
|
569
705
|
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
end.flatten
|
578
|
-
expanded_sources << "application" if sources.include?(:defaults) && file_exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
|
579
|
-
expanded_sources
|
706
|
+
def initialize(template, controller, sources, recursive)
|
707
|
+
# NOTE: The template arg is temporarily needed for a legacy plugin
|
708
|
+
# hook. See NOTE under AssetTag#initialize for more details
|
709
|
+
@template = template
|
710
|
+
@controller = controller
|
711
|
+
@sources = sources
|
712
|
+
@recursive = recursive
|
580
713
|
end
|
581
|
-
end
|
582
714
|
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
determine_source(source, @@stylesheet_expansions)
|
589
|
-
end.flatten
|
715
|
+
def write_asset_file_contents(joined_asset_path)
|
716
|
+
FileUtils.mkdir_p(File.dirname(joined_asset_path))
|
717
|
+
File.open(joined_asset_path, "w+") { |cache| cache.write(joined_contents) }
|
718
|
+
mt = latest_mtime
|
719
|
+
File.utime(mt, mt, joined_asset_path)
|
590
720
|
end
|
721
|
+
|
722
|
+
private
|
723
|
+
def determine_source(source, collection)
|
724
|
+
case source
|
725
|
+
when Symbol
|
726
|
+
collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
|
727
|
+
else
|
728
|
+
source
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
def validate_sources!
|
733
|
+
@sources.collect { |source| determine_source(source, self.class.expansions) }.flatten
|
734
|
+
end
|
735
|
+
|
736
|
+
def all_asset_files
|
737
|
+
path = [public_directory, ('**' if @recursive), "*.#{extension}"].compact
|
738
|
+
Dir[File.join(*path)].collect { |file|
|
739
|
+
file[-(file.size - public_directory.size - 1)..-1].sub(/\.\w+$/, '')
|
740
|
+
}.sort
|
741
|
+
end
|
742
|
+
|
743
|
+
def tag_sources
|
744
|
+
expand_sources.collect { |source| tag_class.new(@template, @controller, source, false) }
|
745
|
+
end
|
746
|
+
|
747
|
+
def joined_contents
|
748
|
+
tag_sources.collect { |source| source.contents }.join("\n\n")
|
749
|
+
end
|
750
|
+
|
751
|
+
# Set mtime to the latest of the combined files to allow for
|
752
|
+
# consistent ETag without a shared filesystem.
|
753
|
+
def latest_mtime
|
754
|
+
tag_sources.map { |source| source.mtime }.max
|
755
|
+
end
|
591
756
|
end
|
592
757
|
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
758
|
+
class JavaScriptSources < AssetCollection
|
759
|
+
include JavaScriptAsset
|
760
|
+
|
761
|
+
EXPANSIONS = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
|
762
|
+
|
763
|
+
def self.expansions
|
764
|
+
EXPANSIONS
|
599
765
|
end
|
600
|
-
end
|
601
766
|
|
602
|
-
|
603
|
-
|
767
|
+
APPLICATION_JS = "application".freeze
|
768
|
+
APPLICATION_FILE = "application.js".freeze
|
769
|
+
|
770
|
+
def expand_sources
|
771
|
+
if @sources.include?(:all)
|
772
|
+
assets = all_asset_files
|
773
|
+
((defaults.dup & assets) + assets).uniq!
|
774
|
+
else
|
775
|
+
expanded_sources = validate_sources!
|
776
|
+
expanded_sources << APPLICATION_JS if include_application?
|
777
|
+
expanded_sources
|
778
|
+
end
|
779
|
+
end
|
780
|
+
memoize :expand_sources
|
781
|
+
|
782
|
+
private
|
783
|
+
def tag_class
|
784
|
+
JavaScriptTag
|
785
|
+
end
|
786
|
+
|
787
|
+
def defaults
|
788
|
+
determine_source(:defaults, self.class.expansions)
|
789
|
+
end
|
790
|
+
|
791
|
+
def include_application?
|
792
|
+
@sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, APPLICATION_FILE))
|
793
|
+
end
|
604
794
|
end
|
605
795
|
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
796
|
+
class StylesheetSources < AssetCollection
|
797
|
+
include StylesheetAsset
|
798
|
+
|
799
|
+
EXPANSIONS = {}
|
800
|
+
|
801
|
+
def self.expansions
|
802
|
+
EXPANSIONS
|
610
803
|
end
|
804
|
+
|
805
|
+
def expand_sources
|
806
|
+
@sources.first == :all ? all_asset_files : validate_sources!
|
807
|
+
end
|
808
|
+
memoize :expand_sources
|
809
|
+
|
810
|
+
private
|
811
|
+
def tag_class
|
812
|
+
StylesheetTag
|
813
|
+
end
|
611
814
|
end
|
612
815
|
end
|
613
816
|
end
|