omg-actionview 8.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +25 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +40 -0
- data/app/assets/javascripts/rails-ujs.esm.js +686 -0
- data/app/assets/javascripts/rails-ujs.js +630 -0
- data/lib/action_view/base.rb +316 -0
- data/lib/action_view/buffers.rb +165 -0
- data/lib/action_view/cache_expiry.rb +69 -0
- data/lib/action_view/context.rb +32 -0
- data/lib/action_view/dependency_tracker/erb_tracker.rb +159 -0
- data/lib/action_view/dependency_tracker/ruby_tracker.rb +43 -0
- data/lib/action_view/dependency_tracker/wildcard_resolver.rb +32 -0
- data/lib/action_view/dependency_tracker.rb +41 -0
- data/lib/action_view/deprecator.rb +7 -0
- data/lib/action_view/digestor.rb +130 -0
- data/lib/action_view/flows.rb +75 -0
- data/lib/action_view/gem_version.rb +17 -0
- data/lib/action_view/helpers/active_model_helper.rb +54 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +680 -0
- data/lib/action_view/helpers/asset_url_helper.rb +473 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +205 -0
- data/lib/action_view/helpers/cache_helper.rb +315 -0
- data/lib/action_view/helpers/capture_helper.rb +236 -0
- data/lib/action_view/helpers/content_exfiltration_prevention_helper.rb +70 -0
- data/lib/action_view/helpers/controller_helper.rb +42 -0
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +35 -0
- data/lib/action_view/helpers/date_helper.rb +1266 -0
- data/lib/action_view/helpers/debug_helper.rb +38 -0
- data/lib/action_view/helpers/form_helper.rb +2765 -0
- data/lib/action_view/helpers/form_options_helper.rb +927 -0
- data/lib/action_view/helpers/form_tag_helper.rb +1088 -0
- data/lib/action_view/helpers/javascript_helper.rb +96 -0
- data/lib/action_view/helpers/number_helper.rb +165 -0
- data/lib/action_view/helpers/output_safety_helper.rb +70 -0
- data/lib/action_view/helpers/rendering_helper.rb +218 -0
- data/lib/action_view/helpers/sanitize_helper.rb +201 -0
- data/lib/action_view/helpers/tag_helper.rb +621 -0
- data/lib/action_view/helpers/tags/base.rb +138 -0
- data/lib/action_view/helpers/tags/check_box.rb +65 -0
- data/lib/action_view/helpers/tags/checkable.rb +18 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +37 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +118 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +31 -0
- data/lib/action_view/helpers/tags/collection_select.rb +33 -0
- data/lib/action_view/helpers/tags/color_field.rb +26 -0
- data/lib/action_view/helpers/tags/date_field.rb +14 -0
- data/lib/action_view/helpers/tags/date_select.rb +75 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +39 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +29 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +10 -0
- data/lib/action_view/helpers/tags/email_field.rb +10 -0
- data/lib/action_view/helpers/tags/file_field.rb +26 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +34 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +14 -0
- data/lib/action_view/helpers/tags/label.rb +84 -0
- data/lib/action_view/helpers/tags/month_field.rb +14 -0
- data/lib/action_view/helpers/tags/number_field.rb +20 -0
- data/lib/action_view/helpers/tags/password_field.rb +14 -0
- data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
- data/lib/action_view/helpers/tags/radio_button.rb +32 -0
- data/lib/action_view/helpers/tags/range_field.rb +10 -0
- data/lib/action_view/helpers/tags/search_field.rb +27 -0
- data/lib/action_view/helpers/tags/select.rb +45 -0
- data/lib/action_view/helpers/tags/select_renderer.rb +56 -0
- data/lib/action_view/helpers/tags/tel_field.rb +10 -0
- data/lib/action_view/helpers/tags/text_area.rb +24 -0
- data/lib/action_view/helpers/tags/text_field.rb +33 -0
- data/lib/action_view/helpers/tags/time_field.rb +23 -0
- data/lib/action_view/helpers/tags/time_select.rb +10 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +25 -0
- data/lib/action_view/helpers/tags/translator.rb +39 -0
- data/lib/action_view/helpers/tags/url_field.rb +10 -0
- data/lib/action_view/helpers/tags/week_field.rb +14 -0
- data/lib/action_view/helpers/tags/weekday_select.rb +31 -0
- data/lib/action_view/helpers/tags.rb +47 -0
- data/lib/action_view/helpers/text_helper.rb +568 -0
- data/lib/action_view/helpers/translation_helper.rb +161 -0
- data/lib/action_view/helpers/url_helper.rb +812 -0
- data/lib/action_view/helpers.rb +68 -0
- data/lib/action_view/layouts.rb +434 -0
- data/lib/action_view/locale/en.yml +56 -0
- data/lib/action_view/log_subscriber.rb +132 -0
- data/lib/action_view/lookup_context.rb +299 -0
- data/lib/action_view/model_naming.rb +14 -0
- data/lib/action_view/path_registry.rb +57 -0
- data/lib/action_view/path_set.rb +84 -0
- data/lib/action_view/railtie.rb +132 -0
- data/lib/action_view/record_identifier.rb +118 -0
- data/lib/action_view/render_parser/prism_render_parser.rb +139 -0
- data/lib/action_view/render_parser/ripper_render_parser.rb +350 -0
- data/lib/action_view/render_parser.rb +40 -0
- data/lib/action_view/renderer/abstract_renderer.rb +186 -0
- data/lib/action_view/renderer/collection_renderer.rb +204 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +120 -0
- data/lib/action_view/renderer/partial_renderer.rb +267 -0
- data/lib/action_view/renderer/renderer.rb +107 -0
- data/lib/action_view/renderer/streaming_template_renderer.rb +107 -0
- data/lib/action_view/renderer/template_renderer.rb +115 -0
- data/lib/action_view/rendering.rb +190 -0
- data/lib/action_view/routing_url_for.rb +149 -0
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +264 -0
- data/lib/action_view/template/handlers/builder.rb +25 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +85 -0
- data/lib/action_view/template/handlers/erb.rb +157 -0
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/handlers.rb +66 -0
- data/lib/action_view/template/html.rb +33 -0
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +30 -0
- data/lib/action_view/template/resolver.rb +212 -0
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +32 -0
- data/lib/action_view/template/types.rb +50 -0
- data/lib/action_view/template.rb +580 -0
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +66 -0
- data/lib/action_view/test_case.rb +449 -0
- data/lib/action_view/testing/resolvers.rb +44 -0
- data/lib/action_view/unbound_template.rb +67 -0
- data/lib/action_view/version.rb +10 -0
- data/lib/action_view/view_paths.rb +117 -0
- data/lib/action_view.rb +104 -0
- metadata +275 -0
@@ -0,0 +1,473 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zlib"
|
4
|
+
|
5
|
+
module ActionView
|
6
|
+
module Helpers # :nodoc:
|
7
|
+
# = Action View Asset URL \Helpers
|
8
|
+
#
|
9
|
+
# This module provides methods for generating asset paths and
|
10
|
+
# URLs.
|
11
|
+
#
|
12
|
+
# image_path("rails.png")
|
13
|
+
# # => "/assets/rails.png"
|
14
|
+
#
|
15
|
+
# image_url("rails.png")
|
16
|
+
# # => "http://www.example.com/assets/rails.png"
|
17
|
+
#
|
18
|
+
# === Using asset hosts
|
19
|
+
#
|
20
|
+
# By default, \Rails links to these assets on the current host in the public
|
21
|
+
# folder, but you can direct \Rails to link to assets from a dedicated asset
|
22
|
+
# server by setting <tt>ActionController::Base.asset_host</tt> in the application
|
23
|
+
# configuration, typically in <tt>config/environments/production.rb</tt>.
|
24
|
+
# For example, you'd define <tt>assets.example.com</tt> to be your asset
|
25
|
+
# host this way, inside the <tt>configure</tt> block of your environment-specific
|
26
|
+
# configuration files or <tt>config/application.rb</tt>:
|
27
|
+
#
|
28
|
+
# config.action_controller.asset_host = "assets.example.com"
|
29
|
+
#
|
30
|
+
# Helpers take that into account:
|
31
|
+
#
|
32
|
+
# image_tag("rails.png")
|
33
|
+
# # => <img src="http://assets.example.com/assets/rails.png" />
|
34
|
+
# stylesheet_link_tag("application")
|
35
|
+
# # => <link href="http://assets.example.com/assets/application.css" rel="stylesheet" />
|
36
|
+
#
|
37
|
+
# Browsers open a limited number of simultaneous connections to a single
|
38
|
+
# host. The exact number varies by browser and version. This limit may cause
|
39
|
+
# some asset downloads to wait for previous assets to finish before they can
|
40
|
+
# begin. You can use the <tt>%d</tt> wildcard in the +asset_host+ to
|
41
|
+
# distribute the requests over four hosts. For example,
|
42
|
+
# <tt>assets%d.example.com</tt> will spread the asset requests over
|
43
|
+
# "assets0.example.com", ..., "assets3.example.com".
|
44
|
+
#
|
45
|
+
# image_tag("rails.png")
|
46
|
+
# # => <img src="http://assets0.example.com/assets/rails.png" />
|
47
|
+
# stylesheet_link_tag("application")
|
48
|
+
# # => <link href="http://assets2.example.com/assets/application.css" rel="stylesheet" />
|
49
|
+
#
|
50
|
+
# This may improve the asset loading performance of your application.
|
51
|
+
# It is also possible the combination of additional connection overhead
|
52
|
+
# (DNS, SSL) and the overall browser connection limits may result in this
|
53
|
+
# solution being slower. You should be sure to measure your actual
|
54
|
+
# performance across targeted browsers both before and after this change.
|
55
|
+
#
|
56
|
+
# To implement the corresponding hosts you can either set up four actual
|
57
|
+
# hosts or use wildcard DNS to CNAME the wildcard to a single asset host.
|
58
|
+
# You can read more about setting up your DNS CNAME records from your ISP.
|
59
|
+
#
|
60
|
+
# Note: This is purely a browser performance optimization and is not meant
|
61
|
+
# for server load balancing. See https://www.die.net/musings/page_load_time/
|
62
|
+
# for background and https://www.browserscope.org/?category=network for
|
63
|
+
# connection limit data.
|
64
|
+
#
|
65
|
+
# Alternatively, you can exert more control over the asset host by setting
|
66
|
+
# +asset_host+ to a proc like this:
|
67
|
+
#
|
68
|
+
# ActionController::Base.asset_host = Proc.new { |source|
|
69
|
+
# "http://assets#{OpenSSL::Digest::SHA256.hexdigest(source).to_i(16) % 2 + 1}.example.com"
|
70
|
+
# }
|
71
|
+
# image_tag("rails.png")
|
72
|
+
# # => <img src="http://assets1.example.com/assets/rails.png" />
|
73
|
+
# stylesheet_link_tag("application")
|
74
|
+
# # => <link href="http://assets2.example.com/assets/application.css" rel="stylesheet" />
|
75
|
+
#
|
76
|
+
# The example above generates "http://assets1.example.com" and
|
77
|
+
# "http://assets2.example.com". This option is useful for example if
|
78
|
+
# you need fewer/more than four hosts, custom host names, etc.
|
79
|
+
#
|
80
|
+
# As you see the proc takes a +source+ parameter. That's a string with the
|
81
|
+
# absolute path of the asset, for example "/assets/rails.png".
|
82
|
+
#
|
83
|
+
# ActionController::Base.asset_host = Proc.new { |source|
|
84
|
+
# if source.end_with?('.css')
|
85
|
+
# "http://stylesheets.example.com"
|
86
|
+
# else
|
87
|
+
# "http://assets.example.com"
|
88
|
+
# end
|
89
|
+
# }
|
90
|
+
# image_tag("rails.png")
|
91
|
+
# # => <img src="http://assets.example.com/assets/rails.png" />
|
92
|
+
# stylesheet_link_tag("application")
|
93
|
+
# # => <link href="http://stylesheets.example.com/assets/application.css" rel="stylesheet" />
|
94
|
+
#
|
95
|
+
# Alternatively you may ask for a second parameter +request+. That one is
|
96
|
+
# particularly useful for serving assets from an SSL-protected page. The
|
97
|
+
# example proc below disables asset hosting for HTTPS connections, while
|
98
|
+
# still sending assets for plain HTTP requests from asset hosts. If you don't
|
99
|
+
# have SSL certificates for each of the asset hosts this technique allows you
|
100
|
+
# to avoid warnings in the client about mixed media.
|
101
|
+
# Note that the +request+ parameter might not be supplied, e.g. when the assets
|
102
|
+
# are precompiled with the command <tt>bin/rails assets:precompile</tt>. Make sure to use a
|
103
|
+
# +Proc+ instead of a lambda, since a +Proc+ allows missing parameters and sets them
|
104
|
+
# to +nil+.
|
105
|
+
#
|
106
|
+
# config.action_controller.asset_host = Proc.new { |source, request|
|
107
|
+
# if request && request.ssl?
|
108
|
+
# "#{request.protocol}#{request.host_with_port}"
|
109
|
+
# else
|
110
|
+
# "#{request.protocol}assets.example.com"
|
111
|
+
# end
|
112
|
+
# }
|
113
|
+
#
|
114
|
+
# You can also implement a custom asset host object that responds to +call+
|
115
|
+
# and takes either one or two parameters just like the proc.
|
116
|
+
#
|
117
|
+
# config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
|
118
|
+
# "http://asset%d.example.com", "https://asset1.example.com"
|
119
|
+
# )
|
120
|
+
#
|
121
|
+
module AssetUrlHelper
|
122
|
+
URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i
|
123
|
+
|
124
|
+
# This is the entry point for all assets.
|
125
|
+
# When using an asset pipeline gem (e.g. propshaft or sprockets-rails), the
|
126
|
+
# behavior is "enhanced". You can bypass the asset pipeline by passing in
|
127
|
+
# <tt>skip_pipeline: true</tt> to the options.
|
128
|
+
#
|
129
|
+
# All other asset *_path helpers delegate through this method.
|
130
|
+
#
|
131
|
+
# === With the asset pipeline
|
132
|
+
#
|
133
|
+
# All options passed to +asset_path+ will be passed to +compute_asset_path+
|
134
|
+
# which is implemented by asset pipeline gems.
|
135
|
+
#
|
136
|
+
# asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js"
|
137
|
+
# asset_path('application.js', host: 'example.com') # => "//example.com/assets/application.js"
|
138
|
+
# asset_path("application.js", host: 'example.com', protocol: 'https') # => "https://example.com/assets/application.js"
|
139
|
+
#
|
140
|
+
# === Without the asset pipeline (<tt>skip_pipeline: true</tt>)
|
141
|
+
#
|
142
|
+
# Accepts a <tt>type</tt> option that can specify the asset's extension. No error
|
143
|
+
# checking is done to verify the source passed into +asset_path+ is valid
|
144
|
+
# and that the file exists on disk.
|
145
|
+
#
|
146
|
+
# asset_path("application.js", skip_pipeline: true) # => "application.js"
|
147
|
+
# asset_path("filedoesnotexist.png", skip_pipeline: true) # => "filedoesnotexist.png"
|
148
|
+
# asset_path("application", type: :javascript, skip_pipeline: true) # => "/javascripts/application.js"
|
149
|
+
# asset_path("application", type: :stylesheet, skip_pipeline: true) # => "/stylesheets/application.css"
|
150
|
+
#
|
151
|
+
# === Options applying to all assets
|
152
|
+
#
|
153
|
+
# Below lists scenarios that apply to +asset_path+ whether or not you're
|
154
|
+
# using the asset pipeline.
|
155
|
+
#
|
156
|
+
# - All fully qualified URLs are returned immediately. This bypasses the
|
157
|
+
# asset pipeline and all other behavior described.
|
158
|
+
#
|
159
|
+
# asset_path("http://www.example.com/js/xmlhr.js") # => "http://www.example.com/js/xmlhr.js"
|
160
|
+
#
|
161
|
+
# - All assets that begin with a forward slash are assumed to be full
|
162
|
+
# URLs and will not be expanded. This will bypass the asset pipeline.
|
163
|
+
#
|
164
|
+
# asset_path("/foo.png") # => "/foo.png"
|
165
|
+
#
|
166
|
+
# - All blank strings will be returned immediately. This bypasses the
|
167
|
+
# asset pipeline and all other behavior described.
|
168
|
+
#
|
169
|
+
# asset_path("") # => ""
|
170
|
+
#
|
171
|
+
# - If <tt>config.relative_url_root</tt> is specified, all assets will have that
|
172
|
+
# root prepended.
|
173
|
+
#
|
174
|
+
# Rails.application.config.relative_url_root = "bar"
|
175
|
+
# asset_path("foo.js", skip_pipeline: true) # => "bar/foo.js"
|
176
|
+
#
|
177
|
+
# - A different asset host can be specified via <tt>config.action_controller.asset_host</tt>
|
178
|
+
# this is commonly used in conjunction with a CDN.
|
179
|
+
#
|
180
|
+
# Rails.application.config.action_controller.asset_host = "assets.example.com"
|
181
|
+
# asset_path("foo.js", skip_pipeline: true) # => "http://assets.example.com/foo.js"
|
182
|
+
#
|
183
|
+
# - An extension name can be specified manually with <tt>extname</tt>.
|
184
|
+
#
|
185
|
+
# asset_path("foo", skip_pipeline: true, extname: ".js") # => "/foo.js"
|
186
|
+
# asset_path("foo.css", skip_pipeline: true, extname: ".js") # => "/foo.css.js"
|
187
|
+
def asset_path(source, options = {})
|
188
|
+
raise ArgumentError, "nil is not a valid asset source" if source.nil?
|
189
|
+
|
190
|
+
source = source.to_s
|
191
|
+
return "" if source.blank?
|
192
|
+
return source if URI_REGEXP.match?(source)
|
193
|
+
|
194
|
+
tail, source = source[/([?#].+)$/], source.sub(/([?#].+)$/, "")
|
195
|
+
|
196
|
+
if extname = compute_asset_extname(source, options)
|
197
|
+
source = "#{source}#{extname}"
|
198
|
+
end
|
199
|
+
|
200
|
+
unless source.start_with?(?/)
|
201
|
+
if options[:skip_pipeline]
|
202
|
+
source = public_compute_asset_path(source, options)
|
203
|
+
else
|
204
|
+
source = compute_asset_path(source, options)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
relative_url_root = defined?(config.relative_url_root) && config.relative_url_root
|
209
|
+
if relative_url_root
|
210
|
+
source = File.join(relative_url_root, source) unless source.start_with?("#{relative_url_root}/")
|
211
|
+
end
|
212
|
+
|
213
|
+
if host = compute_asset_host(source, options)
|
214
|
+
source = File.join(host, source)
|
215
|
+
end
|
216
|
+
|
217
|
+
"#{source}#{tail}"
|
218
|
+
end
|
219
|
+
alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route
|
220
|
+
|
221
|
+
# Computes the full URL to an asset in the public directory. This
|
222
|
+
# will use +asset_path+ internally, so most of their behaviors
|
223
|
+
# will be the same. If +:host+ options is set, it overwrites global
|
224
|
+
# +config.action_controller.asset_host+ setting.
|
225
|
+
#
|
226
|
+
# All other options provided are forwarded to +asset_path+ call.
|
227
|
+
#
|
228
|
+
# asset_url "application.js" # => http://example.com/assets/application.js
|
229
|
+
# asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js
|
230
|
+
#
|
231
|
+
def asset_url(source, options = {})
|
232
|
+
path_to_asset(source, options.merge(protocol: :request))
|
233
|
+
end
|
234
|
+
alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route
|
235
|
+
|
236
|
+
ASSET_EXTENSIONS = {
|
237
|
+
javascript: ".js",
|
238
|
+
stylesheet: ".css"
|
239
|
+
}
|
240
|
+
|
241
|
+
# Compute extname to append to asset path. Returns +nil+ if
|
242
|
+
# nothing should be added.
|
243
|
+
def compute_asset_extname(source, options = {})
|
244
|
+
return if options[:extname] == false
|
245
|
+
extname = options[:extname] || ASSET_EXTENSIONS[options[:type]]
|
246
|
+
if extname && File.extname(source) != extname
|
247
|
+
extname
|
248
|
+
else
|
249
|
+
nil
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Maps asset types to public directory.
|
254
|
+
ASSET_PUBLIC_DIRECTORIES = {
|
255
|
+
audio: "/audios",
|
256
|
+
font: "/fonts",
|
257
|
+
image: "/images",
|
258
|
+
javascript: "/javascripts",
|
259
|
+
stylesheet: "/stylesheets",
|
260
|
+
video: "/videos"
|
261
|
+
}
|
262
|
+
|
263
|
+
# Computes asset path to public directory. Plugins and
|
264
|
+
# extensions can override this method to point to custom assets
|
265
|
+
# or generate digested paths or query strings.
|
266
|
+
def compute_asset_path(source, options = {})
|
267
|
+
dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || ""
|
268
|
+
File.join(dir, source)
|
269
|
+
end
|
270
|
+
alias :public_compute_asset_path :compute_asset_path
|
271
|
+
|
272
|
+
# Pick an asset host for this source. Returns +nil+ if no host is set,
|
273
|
+
# the host if no wildcard is set, the host interpolated with the
|
274
|
+
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
|
275
|
+
# or the value returned from invoking call on an object responding to call
|
276
|
+
# (proc or otherwise).
|
277
|
+
def compute_asset_host(source = "", options = {})
|
278
|
+
request = self.request if respond_to?(:request)
|
279
|
+
host = options[:host]
|
280
|
+
host ||= config.asset_host if defined? config.asset_host
|
281
|
+
|
282
|
+
if host
|
283
|
+
if host.respond_to?(:call)
|
284
|
+
arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
|
285
|
+
args = [source]
|
286
|
+
args << request if request && (arity > 1 || arity < 0)
|
287
|
+
host = host.call(*args)
|
288
|
+
elsif host.include?("%d")
|
289
|
+
host = host % (Zlib.crc32(source) % 4)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
host ||= request.base_url if request && options[:protocol] == :request
|
294
|
+
return unless host
|
295
|
+
|
296
|
+
if URI_REGEXP.match?(host)
|
297
|
+
host
|
298
|
+
else
|
299
|
+
protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative)
|
300
|
+
case protocol
|
301
|
+
when :relative
|
302
|
+
"//#{host}"
|
303
|
+
when :request
|
304
|
+
"#{request.protocol}#{host}"
|
305
|
+
else
|
306
|
+
"#{protocol}://#{host}"
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Computes the path to a JavaScript asset in the public javascripts directory.
|
312
|
+
# If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
|
313
|
+
# Full paths from the document root will be passed through.
|
314
|
+
# Used internally by +javascript_include_tag+ to build the script path.
|
315
|
+
#
|
316
|
+
# javascript_path "xmlhr" # => /assets/xmlhr.js
|
317
|
+
# javascript_path "dir/xmlhr.js" # => /assets/dir/xmlhr.js
|
318
|
+
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
|
319
|
+
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
|
320
|
+
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
|
321
|
+
def javascript_path(source, options = {})
|
322
|
+
path_to_asset(source, { type: :javascript }.merge!(options))
|
323
|
+
end
|
324
|
+
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
|
325
|
+
|
326
|
+
# Computes the full URL to a JavaScript asset in the public javascripts directory.
|
327
|
+
# This will use +javascript_path+ internally, so most of their behaviors will be the same.
|
328
|
+
# Since +javascript_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
|
329
|
+
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
|
330
|
+
#
|
331
|
+
# javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/js/xmlhr.js
|
332
|
+
#
|
333
|
+
def javascript_url(source, options = {})
|
334
|
+
url_to_asset(source, { type: :javascript }.merge!(options))
|
335
|
+
end
|
336
|
+
alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
|
337
|
+
|
338
|
+
# Computes the path to a stylesheet asset in the public stylesheets directory.
|
339
|
+
# If the +source+ filename has no extension, .css will be appended (except for explicit URIs).
|
340
|
+
# Full paths from the document root will be passed through.
|
341
|
+
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
|
342
|
+
#
|
343
|
+
# stylesheet_path "style" # => /assets/style.css
|
344
|
+
# stylesheet_path "dir/style.css" # => /assets/dir/style.css
|
345
|
+
# stylesheet_path "/dir/style.css" # => /dir/style.css
|
346
|
+
# stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
|
347
|
+
# stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
|
348
|
+
def stylesheet_path(source, options = {})
|
349
|
+
path_to_asset(source, { type: :stylesheet }.merge!(options))
|
350
|
+
end
|
351
|
+
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
|
352
|
+
|
353
|
+
# Computes the full URL to a stylesheet asset in the public stylesheets directory.
|
354
|
+
# This will use +stylesheet_path+ internally, so most of their behaviors will be the same.
|
355
|
+
# Since +stylesheet_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
|
356
|
+
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
|
357
|
+
#
|
358
|
+
# stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/assets/css/style.css
|
359
|
+
#
|
360
|
+
def stylesheet_url(source, options = {})
|
361
|
+
url_to_asset(source, { type: :stylesheet }.merge!(options))
|
362
|
+
end
|
363
|
+
alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route
|
364
|
+
|
365
|
+
# Computes the path to an image asset.
|
366
|
+
# Full paths from the document root will be passed through.
|
367
|
+
# Used internally by +image_tag+ to build the image path:
|
368
|
+
#
|
369
|
+
# image_path("edit") # => "/assets/edit"
|
370
|
+
# image_path("edit.png") # => "/assets/edit.png"
|
371
|
+
# image_path("icons/edit.png") # => "/assets/icons/edit.png"
|
372
|
+
# image_path("/icons/edit.png") # => "/icons/edit.png"
|
373
|
+
# image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
|
374
|
+
#
|
375
|
+
# If you have images as application resources this method may conflict with their named routes.
|
376
|
+
# The alias +path_to_image+ is provided to avoid that. \Rails uses the alias internally, and
|
377
|
+
# plugin authors are encouraged to do so.
|
378
|
+
def image_path(source, options = {})
|
379
|
+
path_to_asset(source, { type: :image }.merge!(options))
|
380
|
+
end
|
381
|
+
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
|
382
|
+
|
383
|
+
# Computes the full URL to an image asset.
|
384
|
+
# This will use +image_path+ internally, so most of their behaviors will be the same.
|
385
|
+
# Since +image_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
|
386
|
+
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
|
387
|
+
#
|
388
|
+
# image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/assets/edit.png
|
389
|
+
#
|
390
|
+
def image_url(source, options = {})
|
391
|
+
url_to_asset(source, { type: :image }.merge!(options))
|
392
|
+
end
|
393
|
+
alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route
|
394
|
+
|
395
|
+
# Computes the path to a video asset in the public videos directory.
|
396
|
+
# Full paths from the document root will be passed through.
|
397
|
+
# Used internally by +video_tag+ to build the video path.
|
398
|
+
#
|
399
|
+
# video_path("hd") # => /videos/hd
|
400
|
+
# video_path("hd.avi") # => /videos/hd.avi
|
401
|
+
# video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
|
402
|
+
# video_path("/trailers/hd.avi") # => /trailers/hd.avi
|
403
|
+
# video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi
|
404
|
+
def video_path(source, options = {})
|
405
|
+
path_to_asset(source, { type: :video }.merge!(options))
|
406
|
+
end
|
407
|
+
alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
|
408
|
+
|
409
|
+
# Computes the full URL to a video asset in the public videos directory.
|
410
|
+
# This will use +video_path+ internally, so most of their behaviors will be the same.
|
411
|
+
# Since +video_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
|
412
|
+
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
|
413
|
+
#
|
414
|
+
# video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/videos/hd.avi
|
415
|
+
#
|
416
|
+
def video_url(source, options = {})
|
417
|
+
url_to_asset(source, { type: :video }.merge!(options))
|
418
|
+
end
|
419
|
+
alias_method :url_to_video, :video_url # aliased to avoid conflicts with a video_url named route
|
420
|
+
|
421
|
+
# Computes the path to an audio asset in the public audios directory.
|
422
|
+
# Full paths from the document root will be passed through.
|
423
|
+
# Used internally by +audio_tag+ to build the audio path.
|
424
|
+
#
|
425
|
+
# audio_path("horse") # => /audios/horse
|
426
|
+
# audio_path("horse.wav") # => /audios/horse.wav
|
427
|
+
# audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav
|
428
|
+
# audio_path("/sounds/horse.wav") # => /sounds/horse.wav
|
429
|
+
# audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav
|
430
|
+
def audio_path(source, options = {})
|
431
|
+
path_to_asset(source, { type: :audio }.merge!(options))
|
432
|
+
end
|
433
|
+
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
|
434
|
+
|
435
|
+
# Computes the full URL to an audio asset in the public audios directory.
|
436
|
+
# This will use +audio_path+ internally, so most of their behaviors will be the same.
|
437
|
+
# Since +audio_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
|
438
|
+
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
|
439
|
+
#
|
440
|
+
# audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/audios/horse.wav
|
441
|
+
#
|
442
|
+
def audio_url(source, options = {})
|
443
|
+
url_to_asset(source, { type: :audio }.merge!(options))
|
444
|
+
end
|
445
|
+
alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route
|
446
|
+
|
447
|
+
# Computes the path to a font asset.
|
448
|
+
# Full paths from the document root will be passed through.
|
449
|
+
#
|
450
|
+
# font_path("font") # => /fonts/font
|
451
|
+
# font_path("font.ttf") # => /fonts/font.ttf
|
452
|
+
# font_path("dir/font.ttf") # => /fonts/dir/font.ttf
|
453
|
+
# font_path("/dir/font.ttf") # => /dir/font.ttf
|
454
|
+
# font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
|
455
|
+
def font_path(source, options = {})
|
456
|
+
path_to_asset(source, { type: :font }.merge!(options))
|
457
|
+
end
|
458
|
+
alias_method :path_to_font, :font_path # aliased to avoid conflicts with a font_path named route
|
459
|
+
|
460
|
+
# Computes the full URL to a font asset.
|
461
|
+
# This will use +font_path+ internally, so most of their behaviors will be the same.
|
462
|
+
# Since +font_url+ is based on +asset_url+ method you can set +:host+ options. If +:host+
|
463
|
+
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
|
464
|
+
#
|
465
|
+
# font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/fonts/font.ttf
|
466
|
+
#
|
467
|
+
def font_url(source, options = {})
|
468
|
+
url_to_asset(source, { type: :font }.merge!(options))
|
469
|
+
end
|
470
|
+
alias_method :url_to_font, :font_url # aliased to avoid conflicts with a font_url named route
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
|
5
|
+
module ActionView
|
6
|
+
module Helpers # :nodoc:
|
7
|
+
# = Action View Atom Feed \Helpers
|
8
|
+
module AtomFeedHelper
|
9
|
+
# Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
|
10
|
+
# template languages).
|
11
|
+
#
|
12
|
+
# Full usage example:
|
13
|
+
#
|
14
|
+
# config/routes.rb:
|
15
|
+
# Rails.application.routes.draw do
|
16
|
+
# resources :posts
|
17
|
+
# root to: "posts#index"
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# app/controllers/posts_controller.rb:
|
21
|
+
# class PostsController < ApplicationController
|
22
|
+
# # GET /posts.html
|
23
|
+
# # GET /posts.atom
|
24
|
+
# def index
|
25
|
+
# @posts = Post.all
|
26
|
+
#
|
27
|
+
# respond_to do |format|
|
28
|
+
# format.html
|
29
|
+
# format.atom
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# app/views/posts/index.atom.builder:
|
35
|
+
# atom_feed do |feed|
|
36
|
+
# feed.title("My great blog!")
|
37
|
+
# feed.updated(@posts[0].created_at) if @posts.length > 0
|
38
|
+
#
|
39
|
+
# @posts.each do |post|
|
40
|
+
# feed.entry(post) do |entry|
|
41
|
+
# entry.title(post.title)
|
42
|
+
# entry.content(post.body, type: 'html')
|
43
|
+
#
|
44
|
+
# entry.author do |author|
|
45
|
+
# author.name("DHH")
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# The options for atom_feed are:
|
52
|
+
#
|
53
|
+
# * <tt>:language</tt>: Defaults to "en-US".
|
54
|
+
# * <tt>:root_url</tt>: The HTML alternative that this feed is doubling for. Defaults to / on the current host.
|
55
|
+
# * <tt>:url</tt>: The URL for this feed. Defaults to the current URL.
|
56
|
+
# * <tt>:id</tt>: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case.
|
57
|
+
# * <tt>:schema_date</tt>: The date at which the tag scheme for the feed was first used. A good default is the year you
|
58
|
+
# created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified,
|
59
|
+
# 2005 is used (as an "I don't care" value).
|
60
|
+
# * <tt>:instruct</tt>: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]}
|
61
|
+
#
|
62
|
+
# Other namespaces can be added to the root element:
|
63
|
+
#
|
64
|
+
# app/views/posts/index.atom.builder:
|
65
|
+
# atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app',
|
66
|
+
# 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed|
|
67
|
+
# feed.title("My great blog!")
|
68
|
+
# feed.updated((@posts.first.created_at))
|
69
|
+
# feed.tag!('openSearch:totalResults', 10)
|
70
|
+
#
|
71
|
+
# @posts.each do |post|
|
72
|
+
# feed.entry(post) do |entry|
|
73
|
+
# entry.title(post.title)
|
74
|
+
# entry.content(post.body, type: 'html')
|
75
|
+
# entry.tag!('app:edited', Time.now)
|
76
|
+
#
|
77
|
+
# entry.author do |author|
|
78
|
+
# author.name("DHH")
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# The Atom spec defines five elements (content rights title subtitle
|
85
|
+
# summary) which may directly contain XHTML content if type: 'xhtml'
|
86
|
+
# is specified as an attribute. If so, this helper will take care of
|
87
|
+
# the enclosing div and XHTML namespace declaration. Example usage:
|
88
|
+
#
|
89
|
+
# entry.summary type: 'xhtml' do |xhtml|
|
90
|
+
# xhtml.p pluralize(order.line_items.count, "line item")
|
91
|
+
# xhtml.p "Shipped to #{order.address}"
|
92
|
+
# xhtml.p "Paid by #{order.pay_type}"
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
#
|
96
|
+
# <tt>atom_feed</tt> yields an +AtomFeedBuilder+ instance. Nested elements yield
|
97
|
+
# an +AtomBuilder+ instance.
|
98
|
+
def atom_feed(options = {}, &block)
|
99
|
+
if options[:schema_date]
|
100
|
+
options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime)
|
101
|
+
else
|
102
|
+
options[:schema_date] = "2005" # The Atom spec copyright date
|
103
|
+
end
|
104
|
+
|
105
|
+
xml = options.delete(:xml) || block.binding.local_variable_get(:xml)
|
106
|
+
xml.instruct!
|
107
|
+
if options[:instruct]
|
108
|
+
options[:instruct].each do |target, attrs|
|
109
|
+
if attrs.respond_to?(:keys)
|
110
|
+
xml.instruct!(target, attrs)
|
111
|
+
elsif attrs.respond_to?(:each)
|
112
|
+
attrs.each { |attr_group| xml.instruct!(target, attr_group) }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
feed_opts = { "xml:lang" => options[:language] || "en-US", "xmlns" => "http://www.w3.org/2005/Atom" }
|
118
|
+
feed_opts.merge!(options).select! { |k, _| k.start_with?("xml") }
|
119
|
+
|
120
|
+
xml.feed(feed_opts) do
|
121
|
+
xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}")
|
122
|
+
xml.link(rel: "alternate", type: "text/html", href: options[:root_url] || (request.protocol + request.host_with_port))
|
123
|
+
xml.link(rel: "self", type: "application/atom+xml", href: options[:url] || request.url)
|
124
|
+
|
125
|
+
yield AtomFeedBuilder.new(xml, self, options)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class AtomBuilder # :nodoc:
|
130
|
+
XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
|
131
|
+
|
132
|
+
def initialize(xml)
|
133
|
+
@xml = xml
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
# Delegate to XML Builder, first wrapping the element in an XHTML
|
138
|
+
# namespaced div element if the method and arguments indicate
|
139
|
+
# that an xhtml_block? is desired.
|
140
|
+
def method_missing(method, *arguments, &block)
|
141
|
+
if xhtml_block?(method, arguments)
|
142
|
+
@xml.__send__(method, *arguments) do
|
143
|
+
@xml.div(xmlns: "http://www.w3.org/1999/xhtml") do |xhtml|
|
144
|
+
block.call(xhtml)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
else
|
148
|
+
@xml.__send__(method, *arguments, &block)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# True if the method name matches one of the five elements defined
|
153
|
+
# in the Atom spec as potentially containing XHTML content and
|
154
|
+
# if type: 'xhtml' is, in fact, specified.
|
155
|
+
def xhtml_block?(method, arguments)
|
156
|
+
if XHTML_TAG_NAMES.include?(method.to_s)
|
157
|
+
last = arguments.last
|
158
|
+
last.is_a?(Hash) && last[:type].to_s == "xhtml"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class AtomFeedBuilder < AtomBuilder # :nodoc:
|
164
|
+
def initialize(xml, view, feed_options = {})
|
165
|
+
@xml, @view, @feed_options = xml, view, feed_options
|
166
|
+
end
|
167
|
+
|
168
|
+
# Accepts a Date or Time object and inserts it in the proper format. If +nil+ is passed, current time in UTC is used.
|
169
|
+
def updated(date_or_time = nil)
|
170
|
+
@xml.updated((date_or_time || Time.now.utc).xmlschema)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Creates an entry tag for a specific record and prefills the id using class and id.
|
174
|
+
#
|
175
|
+
# Options:
|
176
|
+
#
|
177
|
+
# * <tt>:published</tt>: Time first published. Defaults to the created_at attribute on the record if one such exists.
|
178
|
+
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
|
179
|
+
# * <tt>:url</tt>: The URL for this entry or +false+ or +nil+ for not having a link tag. Defaults to the +polymorphic_url+ for the record.
|
180
|
+
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
|
181
|
+
# * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
|
182
|
+
def entry(record, options = {})
|
183
|
+
@xml.entry do
|
184
|
+
@xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
|
185
|
+
|
186
|
+
if options[:published] || (record.respond_to?(:created_at) && record.created_at)
|
187
|
+
@xml.published((options[:published] || record.created_at).xmlschema)
|
188
|
+
end
|
189
|
+
|
190
|
+
if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at)
|
191
|
+
@xml.updated((options[:updated] || record.updated_at).xmlschema)
|
192
|
+
end
|
193
|
+
|
194
|
+
type = options.fetch(:type, "text/html")
|
195
|
+
|
196
|
+
url = options.fetch(:url) { @view.polymorphic_url(record) }
|
197
|
+
@xml.link(rel: "alternate", type: type, href: url) if url
|
198
|
+
|
199
|
+
yield AtomBuilder.new(@xml)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|