actionview 4.2.10 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +141 -272
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/action_view/base.rb +33 -21
- data/lib/action_view/buffers.rb +1 -1
- data/lib/action_view/context.rb +1 -1
- data/lib/action_view/dependency_tracker.rb +52 -20
- data/lib/action_view/digestor.rb +86 -83
- data/lib/action_view/flows.rb +9 -11
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/active_model_helper.rb +8 -8
- data/lib/action_view/helpers/asset_tag_helper.rb +74 -38
- data/lib/action_view/helpers/asset_url_helper.rb +160 -59
- data/lib/action_view/helpers/atom_feed_helper.rb +16 -16
- data/lib/action_view/helpers/cache_helper.rb +90 -35
- data/lib/action_view/helpers/capture_helper.rb +7 -6
- data/lib/action_view/helpers/controller_helper.rb +3 -2
- data/lib/action_view/helpers/csrf_helper.rb +3 -3
- data/lib/action_view/helpers/date_helper.rb +156 -108
- data/lib/action_view/helpers/debug_helper.rb +3 -4
- data/lib/action_view/helpers/form_helper.rb +475 -94
- data/lib/action_view/helpers/form_options_helper.rb +87 -47
- data/lib/action_view/helpers/form_tag_helper.rb +88 -57
- data/lib/action_view/helpers/javascript_helper.rb +10 -10
- data/lib/action_view/helpers/number_helper.rb +76 -59
- data/lib/action_view/helpers/output_safety_helper.rb +34 -4
- data/lib/action_view/helpers/record_tag_helper.rb +12 -99
- data/lib/action_view/helpers/rendering_helper.rb +3 -3
- data/lib/action_view/helpers/sanitize_helper.rb +17 -14
- data/lib/action_view/helpers/tag_helper.rb +198 -73
- data/lib/action_view/helpers/tags/base.rb +132 -97
- data/lib/action_view/helpers/tags/check_box.rb +17 -17
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +9 -33
- data/lib/action_view/helpers/tags/collection_helpers.rb +68 -36
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +3 -11
- data/lib/action_view/helpers/tags/collection_select.rb +2 -2
- data/lib/action_view/helpers/tags/date_select.rb +36 -36
- data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +2 -2
- data/lib/action_view/helpers/tags/label.rb +5 -1
- data/lib/action_view/helpers/tags/password_field.rb +1 -1
- data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
- data/lib/action_view/helpers/tags/radio_button.rb +4 -4
- data/lib/action_view/helpers/tags/search_field.rb +12 -9
- data/lib/action_view/helpers/tags/select.rb +9 -9
- data/lib/action_view/helpers/tags/text_area.rb +1 -1
- data/lib/action_view/helpers/tags/text_field.rb +5 -6
- data/lib/action_view/helpers/tags/translator.rb +15 -13
- data/lib/action_view/helpers/text_helper.rb +47 -30
- data/lib/action_view/helpers/translation_helper.rb +60 -30
- data/lib/action_view/helpers/url_helper.rb +132 -104
- data/lib/action_view/helpers.rb +1 -1
- data/lib/action_view/layouts.rb +59 -54
- data/lib/action_view/log_subscriber.rb +56 -7
- data/lib/action_view/lookup_context.rb +76 -61
- data/lib/action_view/model_naming.rb +1 -1
- data/lib/action_view/path_set.rb +28 -19
- data/lib/action_view/railtie.rb +30 -6
- data/lib/action_view/record_identifier.rb +51 -25
- data/lib/action_view/renderer/abstract_renderer.rb +19 -15
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +55 -0
- data/lib/action_view/renderer/partial_renderer.rb +208 -206
- data/lib/action_view/renderer/renderer.rb +2 -6
- data/lib/action_view/renderer/streaming_template_renderer.rb +46 -48
- data/lib/action_view/renderer/template_renderer.rb +65 -66
- data/lib/action_view/rendering.rb +16 -9
- data/lib/action_view/routing_url_for.rb +25 -17
- data/lib/action_view/tasks/cache_digests.rake +23 -0
- data/lib/action_view/template/error.rb +14 -13
- data/lib/action_view/template/handlers/builder.rb +7 -7
- data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +9 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +81 -0
- data/lib/action_view/template/handlers/erb/erubis.rb +81 -0
- data/lib/action_view/template/handlers/erb.rb +9 -76
- data/lib/action_view/template/handlers/html.rb +9 -0
- data/lib/action_view/template/handlers/raw.rb +1 -3
- data/lib/action_view/template/handlers.rb +8 -6
- data/lib/action_view/template/html.rb +2 -4
- data/lib/action_view/template/resolver.rb +133 -109
- data/lib/action_view/template/text.rb +5 -8
- data/lib/action_view/template/types.rb +15 -17
- data/lib/action_view/template.rb +51 -28
- data/lib/action_view/test_case.rb +32 -27
- data/lib/action_view/testing/resolvers.rb +29 -31
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +26 -32
- data/lib/action_view.rb +5 -5
- data/lib/assets/compiled/rails-ujs.js +685 -0
- metadata +23 -23
- data/lib/action_view/tasks/dependencies.rake +0 -23
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "concurrent/map"
|
|
2
|
+
require "action_view/path_set"
|
|
2
3
|
|
|
3
4
|
module ActionView
|
|
4
5
|
class DependencyTracker # :nodoc:
|
|
5
|
-
@trackers =
|
|
6
|
+
@trackers = Concurrent::Map.new
|
|
6
7
|
|
|
7
|
-
def self.find_dependencies(name, template)
|
|
8
|
+
def self.find_dependencies(name, template, view_paths = nil)
|
|
8
9
|
tracker = @trackers[template.handler]
|
|
10
|
+
return [] unless tracker
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
tracker.call(name, template)
|
|
12
|
-
else
|
|
13
|
-
[]
|
|
14
|
-
end
|
|
12
|
+
tracker.call(name, template, view_paths)
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
def self.register_tracker(extension, tracker)
|
|
18
16
|
handler = Template.handler_for_extension(extension)
|
|
19
|
-
|
|
17
|
+
if tracker.respond_to?(:supports_view_paths?)
|
|
18
|
+
@trackers[handler] = tracker
|
|
19
|
+
else
|
|
20
|
+
@trackers[handler] = lambda { |name, template, _|
|
|
21
|
+
tracker.call(name, template)
|
|
22
|
+
}
|
|
23
|
+
end
|
|
20
24
|
end
|
|
21
25
|
|
|
22
26
|
def self.remove_tracker(handler)
|
|
@@ -76,12 +80,22 @@ module ActionView
|
|
|
76
80
|
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
|
|
77
81
|
/xm
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
LAYOUT_DEPENDENCY = /\A
|
|
84
|
+
(?:\s*\(?\s*) # optional opening paren surrounded by spaces
|
|
85
|
+
(?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration
|
|
86
|
+
(?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest
|
|
87
|
+
/xm
|
|
88
|
+
|
|
89
|
+
def self.supports_view_paths? # :nodoc:
|
|
90
|
+
true
|
|
81
91
|
end
|
|
82
92
|
|
|
83
|
-
def
|
|
84
|
-
|
|
93
|
+
def self.call(name, template, view_paths = nil)
|
|
94
|
+
new(name, template, view_paths).dependencies
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def initialize(name, template, view_paths = nil)
|
|
98
|
+
@name, @template, @view_paths = name, template, view_paths
|
|
85
99
|
end
|
|
86
100
|
|
|
87
101
|
def dependencies
|
|
@@ -91,7 +105,6 @@ module ActionView
|
|
|
91
105
|
attr_reader :name, :template
|
|
92
106
|
private :name, :template
|
|
93
107
|
|
|
94
|
-
|
|
95
108
|
private
|
|
96
109
|
def source
|
|
97
110
|
template.source
|
|
@@ -106,15 +119,20 @@ module ActionView
|
|
|
106
119
|
render_calls = source.split(/\brender\b/).drop(1)
|
|
107
120
|
|
|
108
121
|
render_calls.each do |arguments|
|
|
109
|
-
arguments
|
|
110
|
-
|
|
111
|
-
add_static_dependency(render_dependencies, Regexp.last_match[:static])
|
|
112
|
-
end
|
|
122
|
+
add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY)
|
|
123
|
+
add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS)
|
|
113
124
|
end
|
|
114
125
|
|
|
115
126
|
render_dependencies.uniq
|
|
116
127
|
end
|
|
117
128
|
|
|
129
|
+
def add_dependencies(render_dependencies, arguments, pattern)
|
|
130
|
+
arguments.scan(pattern) do
|
|
131
|
+
add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic])
|
|
132
|
+
add_static_dependency(render_dependencies, Regexp.last_match[:static])
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
118
136
|
def add_dynamic_dependency(dependencies, dependency)
|
|
119
137
|
if dependency
|
|
120
138
|
dependencies << "#{dependency.pluralize}/#{dependency.singularize}"
|
|
@@ -123,7 +141,7 @@ module ActionView
|
|
|
123
141
|
|
|
124
142
|
def add_static_dependency(dependencies, dependency)
|
|
125
143
|
if dependency
|
|
126
|
-
if dependency.include?(
|
|
144
|
+
if dependency.include?("/")
|
|
127
145
|
dependencies << dependency
|
|
128
146
|
else
|
|
129
147
|
dependencies << "#{directory}/#{dependency}"
|
|
@@ -131,8 +149,22 @@ module ActionView
|
|
|
131
149
|
end
|
|
132
150
|
end
|
|
133
151
|
|
|
152
|
+
def resolve_directories(wildcard_dependencies)
|
|
153
|
+
return [] unless @view_paths
|
|
154
|
+
|
|
155
|
+
wildcard_dependencies.flat_map { |query, templates|
|
|
156
|
+
@view_paths.find_all_with_query(query).map do |template|
|
|
157
|
+
"#{File.dirname(query)}/#{File.basename(template).split('.').first}"
|
|
158
|
+
end
|
|
159
|
+
}.sort
|
|
160
|
+
end
|
|
161
|
+
|
|
134
162
|
def explicit_dependencies
|
|
135
|
-
source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
|
|
163
|
+
dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
|
|
164
|
+
|
|
165
|
+
wildcards, explicits = dependencies.partition { |dependency| dependency[-1] == "*" }
|
|
166
|
+
|
|
167
|
+
(explicits + resolve_directories(wildcards)).uniq
|
|
136
168
|
end
|
|
137
169
|
end
|
|
138
170
|
|
data/lib/action_view/digestor.rb
CHANGED
|
@@ -1,123 +1,126 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
1
|
+
require "concurrent/map"
|
|
2
|
+
require "action_view/dependency_tracker"
|
|
3
|
+
require "monitor"
|
|
4
4
|
|
|
5
5
|
module ActionView
|
|
6
6
|
class Digestor
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
@@digest_mutex = Mutex.new
|
|
8
|
+
|
|
9
|
+
module PerExecutionDigestCacheExpiry
|
|
10
|
+
def self.before(target)
|
|
11
|
+
ActionView::LookupContext::DetailsKey.clear
|
|
12
|
+
end
|
|
13
|
+
end
|
|
10
14
|
|
|
11
15
|
class << self
|
|
12
16
|
# Supported options:
|
|
13
17
|
#
|
|
14
18
|
# * <tt>name</tt> - Template name
|
|
15
|
-
# * <tt>finder</tt> - An instance of ActionView::LookupContext
|
|
19
|
+
# * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
|
|
16
20
|
# * <tt>dependencies</tt> - An array of dependent views
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
cache_key = ([ options[:name], options[:finder].details_key.hash ].compact + Array.wrap(options[:dependencies])).join('.')
|
|
21
|
+
def digest(name:, finder:, dependencies: [])
|
|
22
|
+
dependencies ||= []
|
|
23
|
+
cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".")
|
|
22
24
|
|
|
23
25
|
# this is a correctly done double-checked locking idiom
|
|
24
|
-
# (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
# (Concurrent::Map's lookups have volatile semantics)
|
|
27
|
+
finder.digest_cache[cache_key] || @@digest_mutex.synchronize do
|
|
28
|
+
finder.digest_cache.fetch(cache_key) do # re-check under lock
|
|
29
|
+
partial = name.include?("/_")
|
|
30
|
+
root = tree(name, finder, partial)
|
|
31
|
+
dependencies.each do |injected_dep|
|
|
32
|
+
root.children << Injected.new(injected_dep, nil, nil)
|
|
33
|
+
end
|
|
34
|
+
finder.digest_cache[cache_key] = root.digest(finder)
|
|
28
35
|
end
|
|
29
36
|
end
|
|
30
37
|
end
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# Prevent re-entry or else recursive templates will blow the stack.
|
|
36
|
-
# There is no need to worry about other threads seeing the +false+ value,
|
|
37
|
-
# as they will then have to wait for this thread to let go of the @@digest_monitor lock.
|
|
38
|
-
pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion
|
|
39
|
-
PartialDigestor
|
|
40
|
-
else
|
|
41
|
-
Digestor
|
|
42
|
-
end
|
|
39
|
+
def logger
|
|
40
|
+
ActionView::Base.logger || NullLogger
|
|
41
|
+
end
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
digest
|
|
48
|
-
ensure
|
|
49
|
-
# something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
|
|
50
|
-
@@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
|
|
51
|
-
end
|
|
52
|
-
end
|
|
43
|
+
# Create a dependency tree for template named +name+.
|
|
44
|
+
def tree(name, finder, partial = false, seen = {})
|
|
45
|
+
logical_name = name.gsub(%r|/_|, "/")
|
|
53
46
|
|
|
54
|
-
|
|
47
|
+
options = {}
|
|
48
|
+
options[:formats] = [finder.rendered_format] if finder.rendered_format
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
@options = options.except(:name, :finder)
|
|
59
|
-
end
|
|
50
|
+
if template = finder.disable_cache { finder.find_all(logical_name, [], partial, [], options).first }
|
|
51
|
+
finder.rendered_format ||= template.formats.first
|
|
60
52
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
rescue ActionView::MissingTemplate
|
|
66
|
-
logger.try :error, " Couldn't find template for digesting: #{name}"
|
|
67
|
-
''
|
|
68
|
-
end
|
|
53
|
+
if node = seen[template.identifier] # handle cycles in the tree
|
|
54
|
+
node
|
|
55
|
+
else
|
|
56
|
+
node = seen[template.identifier] = Node.create(name, logical_name, template, partial)
|
|
69
57
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
58
|
+
deps = DependencyTracker.find_dependencies(name, template, finder.view_paths)
|
|
59
|
+
deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file|
|
|
60
|
+
node.children << tree(dep_file, finder, true, seen)
|
|
61
|
+
end
|
|
62
|
+
node
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
unless name.include?("#") # Dynamic template partial names can never be tracked
|
|
66
|
+
logger.error " Couldn't find template for digesting: #{name}"
|
|
67
|
+
end
|
|
76
68
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
dependencies = PartialDigestor.new(name: dependency, finder: finder).nested_dependencies
|
|
80
|
-
dependencies.any? ? { dependency => dependencies } : dependency
|
|
69
|
+
seen[name] ||= Missing.new(name, logical_name, nil)
|
|
70
|
+
end
|
|
81
71
|
end
|
|
82
72
|
end
|
|
83
73
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
class Node
|
|
75
|
+
attr_reader :name, :logical_name, :template, :children
|
|
76
|
+
|
|
77
|
+
def self.create(name, logical_name, template, partial)
|
|
78
|
+
klass = partial ? Partial : Node
|
|
79
|
+
klass.new(name, logical_name, template, [])
|
|
87
80
|
end
|
|
88
81
|
|
|
89
|
-
def logical_name
|
|
90
|
-
name
|
|
82
|
+
def initialize(name, logical_name, template, children = [])
|
|
83
|
+
@name = name
|
|
84
|
+
@logical_name = logical_name
|
|
85
|
+
@template = template
|
|
86
|
+
@children = children
|
|
91
87
|
end
|
|
92
88
|
|
|
93
|
-
def
|
|
94
|
-
|
|
89
|
+
def digest(finder, stack = [])
|
|
90
|
+
Digest::MD5.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}")
|
|
95
91
|
end
|
|
96
92
|
|
|
97
|
-
def
|
|
98
|
-
|
|
93
|
+
def dependency_digest(finder, stack)
|
|
94
|
+
children.map do |node|
|
|
95
|
+
if stack.include?(node)
|
|
96
|
+
false
|
|
97
|
+
else
|
|
98
|
+
finder.digest_cache[node.name] ||= begin
|
|
99
|
+
stack.push node
|
|
100
|
+
node.digest(finder, stack).tap { stack.pop }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end.join("-")
|
|
99
104
|
end
|
|
100
105
|
|
|
101
|
-
def
|
|
102
|
-
|
|
106
|
+
def to_dep_map
|
|
107
|
+
children.any? ? { name => children.map(&:to_dep_map) } : name
|
|
103
108
|
end
|
|
109
|
+
end
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
template_digests = dependencies.collect do |template_name|
|
|
107
|
-
Digestor.digest(name: template_name, finder: finder, partial: true)
|
|
108
|
-
end
|
|
111
|
+
class Partial < Node; end
|
|
109
112
|
|
|
110
|
-
|
|
111
|
-
end
|
|
113
|
+
class Missing < Node
|
|
114
|
+
def digest(finder, _ = []) "" end
|
|
115
|
+
end
|
|
112
116
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
end
|
|
117
|
+
class Injected < Node
|
|
118
|
+
def digest(finder, _ = []) name end
|
|
119
|
+
end
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
class NullLogger
|
|
122
|
+
def self.debug(_); end
|
|
123
|
+
def self.error(_); end
|
|
121
124
|
end
|
|
122
125
|
end
|
|
123
126
|
end
|
data/lib/action_view/flows.rb
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "active_support/core_ext/string/output_safety"
|
|
2
2
|
|
|
3
3
|
module ActionView
|
|
4
4
|
class OutputFlow #:nodoc:
|
|
5
5
|
attr_reader :content
|
|
6
6
|
|
|
7
7
|
def initialize
|
|
8
|
-
@content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
|
|
8
|
+
@content = Hash.new { |h, k| h[k] = ActiveSupport::SafeBuffer.new }
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
# Called by _layout_for to read stored values.
|
|
@@ -23,7 +23,6 @@ module ActionView
|
|
|
23
23
|
@content[key] << value
|
|
24
24
|
end
|
|
25
25
|
alias_method :append!, :append
|
|
26
|
-
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
class StreamingFlow < OutputFlow #:nodoc:
|
|
@@ -37,9 +36,8 @@ module ActionView
|
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
# Try to get stored content. If the content
|
|
40
|
-
# is not available and we
|
|
41
|
-
#
|
|
42
|
-
# key and yield.
|
|
39
|
+
# is not available and we're inside the layout fiber,
|
|
40
|
+
# then it will begin waiting for the given key and yield.
|
|
43
41
|
def get(key)
|
|
44
42
|
return super if @content.key?(key)
|
|
45
43
|
|
|
@@ -60,8 +58,8 @@ module ActionView
|
|
|
60
58
|
end
|
|
61
59
|
|
|
62
60
|
# Appends the contents for the given key. This is called
|
|
63
|
-
# by
|
|
64
|
-
# the key it
|
|
61
|
+
# by providing and resuming back to the fiber,
|
|
62
|
+
# if that's the key it's waiting for.
|
|
65
63
|
def append!(key, value)
|
|
66
64
|
super
|
|
67
65
|
@fiber.resume if @waiting_for == key
|
|
@@ -69,8 +67,8 @@ module ActionView
|
|
|
69
67
|
|
|
70
68
|
private
|
|
71
69
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
def inside_fiber?
|
|
71
|
+
Fiber.current.object_id != @root
|
|
72
|
+
end
|
|
75
73
|
end
|
|
76
74
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
1
|
+
require "active_support/core_ext/module/attribute_accessors"
|
|
2
|
+
require "active_support/core_ext/enumerable"
|
|
3
3
|
|
|
4
4
|
module ActionView
|
|
5
5
|
# = Active Model Helpers
|
|
@@ -37,13 +37,13 @@ module ActionView
|
|
|
37
37
|
|
|
38
38
|
private
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
def object_has_errors?
|
|
41
|
+
object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
|
|
42
|
+
end
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
def tag_generate_errors?(options)
|
|
45
|
+
options["type"] != "hidden"
|
|
46
|
+
end
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
-
require
|
|
1
|
+
require "active_support/core_ext/array/extract_options"
|
|
2
|
+
require "active_support/core_ext/hash/keys"
|
|
3
|
+
require "action_view/helpers/asset_url_helper"
|
|
4
|
+
require "action_view/helpers/tag_helper"
|
|
5
5
|
|
|
6
6
|
module ActionView
|
|
7
7
|
# = Action View Asset Tag Helpers
|
|
@@ -35,18 +35,37 @@ module ActionView
|
|
|
35
35
|
# When the Asset Pipeline is enabled, you can pass the name of your manifest as
|
|
36
36
|
# source, and include other JavaScript or CoffeeScript files inside the manifest.
|
|
37
37
|
#
|
|
38
|
+
# ==== Options
|
|
39
|
+
#
|
|
40
|
+
# When the last parameter is a hash you can add HTML attributes using that
|
|
41
|
+
# parameter. The following options are supported:
|
|
42
|
+
#
|
|
43
|
+
# * <tt>:extname</tt> - Append an extension to the generated url unless the extension
|
|
44
|
+
# already exists. This only applies for relative urls.
|
|
45
|
+
# * <tt>:protocol</tt> - Sets the protocol of the generated url, this option only
|
|
46
|
+
# applies when a relative url and +host+ options are provided.
|
|
47
|
+
# * <tt>:host</tt> - When a relative url is provided the host is added to the
|
|
48
|
+
# that path.
|
|
49
|
+
# * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
|
|
50
|
+
# when it is set to true.
|
|
51
|
+
#
|
|
52
|
+
# ==== Examples
|
|
53
|
+
#
|
|
38
54
|
# javascript_include_tag "xmlhr"
|
|
39
|
-
# # => <script src="/assets/xmlhr.js
|
|
55
|
+
# # => <script src="/assets/xmlhr.debug-1284139606.js"></script>
|
|
56
|
+
#
|
|
57
|
+
# javascript_include_tag "xmlhr", host: "localhost", protocol: "https"
|
|
58
|
+
# # => <script src="https://localhost/assets/xmlhr.debug-1284139606.js"></script>
|
|
40
59
|
#
|
|
41
60
|
# javascript_include_tag "template.jst", extname: false
|
|
42
|
-
# # => <script src="/assets/template.jst
|
|
61
|
+
# # => <script src="/assets/template.debug-1284139606.jst"></script>
|
|
43
62
|
#
|
|
44
63
|
# javascript_include_tag "xmlhr.js"
|
|
45
|
-
# # => <script src="/assets/xmlhr.js
|
|
64
|
+
# # => <script src="/assets/xmlhr.debug-1284139606.js"></script>
|
|
46
65
|
#
|
|
47
66
|
# javascript_include_tag "common.javascript", "/elsewhere/cools"
|
|
48
|
-
# # => <script src="/assets/common.javascript
|
|
49
|
-
# # <script src="/elsewhere/cools.js
|
|
67
|
+
# # => <script src="/assets/common.javascript.debug-1284139606.js"></script>
|
|
68
|
+
# # <script src="/elsewhere/cools.debug-1284139606.js"></script>
|
|
50
69
|
#
|
|
51
70
|
# javascript_include_tag "http://www.example.com/xmlhr"
|
|
52
71
|
# # => <script src="http://www.example.com/xmlhr"></script>
|
|
@@ -55,12 +74,12 @@ module ActionView
|
|
|
55
74
|
# # => <script src="http://www.example.com/xmlhr.js"></script>
|
|
56
75
|
def javascript_include_tag(*sources)
|
|
57
76
|
options = sources.extract_options!.stringify_keys
|
|
58
|
-
path_options = options.extract!(
|
|
77
|
+
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
|
|
59
78
|
sources.uniq.map { |source|
|
|
60
79
|
tag_options = {
|
|
61
80
|
"src" => path_to_javascript(source, path_options)
|
|
62
81
|
}.merge!(options)
|
|
63
|
-
content_tag(
|
|
82
|
+
content_tag("script".freeze, "", tag_options)
|
|
64
83
|
}.join("\n").html_safe
|
|
65
84
|
end
|
|
66
85
|
|
|
@@ -91,8 +110,7 @@ module ActionView
|
|
|
91
110
|
# # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
|
|
92
111
|
def stylesheet_link_tag(*sources)
|
|
93
112
|
options = sources.extract_options!.stringify_keys
|
|
94
|
-
path_options = options.extract!(
|
|
95
|
-
|
|
113
|
+
path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
|
|
96
114
|
sources.uniq.map { |source|
|
|
97
115
|
tag_options = {
|
|
98
116
|
"rel" => "stylesheet",
|
|
@@ -136,9 +154,9 @@ module ActionView
|
|
|
136
154
|
tag(
|
|
137
155
|
"link",
|
|
138
156
|
"rel" => tag_options[:rel] || "alternate",
|
|
139
|
-
"type" => tag_options[:type] ||
|
|
157
|
+
"type" => tag_options[:type] || Template::Types[type].to_s,
|
|
140
158
|
"title" => tag_options[:title] || type.to_s.upcase,
|
|
141
|
-
"href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:
|
|
159
|
+
"href" => url_options.is_a?(Hash) ? url_for(url_options.merge(only_path: false)) : url_options
|
|
142
160
|
)
|
|
143
161
|
end
|
|
144
162
|
|
|
@@ -169,11 +187,11 @@ module ActionView
|
|
|
169
187
|
#
|
|
170
188
|
# favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
|
|
171
189
|
# # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" />
|
|
172
|
-
def favicon_link_tag(source=
|
|
173
|
-
tag(
|
|
174
|
-
:
|
|
175
|
-
:
|
|
176
|
-
:
|
|
190
|
+
def favicon_link_tag(source = "favicon.ico", options = {})
|
|
191
|
+
tag("link", {
|
|
192
|
+
rel: "shortcut icon",
|
|
193
|
+
type: "image/x-icon",
|
|
194
|
+
href: path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))
|
|
177
195
|
}.merge!(options.symbolize_keys))
|
|
178
196
|
end
|
|
179
197
|
|
|
@@ -205,13 +223,16 @@ module ActionView
|
|
|
205
223
|
# # => <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
|
|
206
224
|
# image_tag("/icons/icon.gif", class: "menu_icon")
|
|
207
225
|
# # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
|
|
208
|
-
|
|
226
|
+
# image_tag("/icons/icon.gif", data: { title: 'Rails Application' })
|
|
227
|
+
# # => <img data-title="Rails Application" src="/icons/icon.gif" />
|
|
228
|
+
def image_tag(source, options = {})
|
|
209
229
|
options = options.symbolize_keys
|
|
230
|
+
check_for_image_tag_errors(options)
|
|
210
231
|
|
|
211
|
-
src = options[:src] = path_to_image(source)
|
|
232
|
+
src = options[:src] = path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))
|
|
212
233
|
|
|
213
|
-
unless src
|
|
214
|
-
options[:alt] = options.fetch(:alt){ image_alt(src) }
|
|
234
|
+
unless src.start_with?("cid:") || src.start_with?("data:") || src.blank?
|
|
235
|
+
options[:alt] = options.fetch(:alt) { image_alt(src) }
|
|
215
236
|
end
|
|
216
237
|
|
|
217
238
|
options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
|
|
@@ -236,7 +257,7 @@ module ActionView
|
|
|
236
257
|
# image_alt('underscored_file_name.png')
|
|
237
258
|
# # => Underscored file name
|
|
238
259
|
def image_alt(src)
|
|
239
|
-
File.basename(src,
|
|
260
|
+
File.basename(src, ".*".freeze).sub(/-[[:xdigit:]]{32,64}\z/, "".freeze).tr("-_".freeze, " ".freeze).capitalize
|
|
240
261
|
end
|
|
241
262
|
|
|
242
263
|
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
|
|
@@ -254,6 +275,8 @@ module ActionView
|
|
|
254
275
|
# * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes
|
|
255
276
|
# width="30" and height="45", and "50" becomes width="50" and height="50".
|
|
256
277
|
# <tt>:size</tt> will be ignored if the value is not in the correct format.
|
|
278
|
+
# * <tt>:poster_skip_pipeline</tt> will bypass the asset pipeline when using
|
|
279
|
+
# the <tt>:poster</tt> option instead using an asset in the public folder.
|
|
257
280
|
#
|
|
258
281
|
# ==== Examples
|
|
259
282
|
#
|
|
@@ -261,10 +284,12 @@ module ActionView
|
|
|
261
284
|
# # => <video src="/videos/trailer"></video>
|
|
262
285
|
# video_tag("trailer.ogg")
|
|
263
286
|
# # => <video src="/videos/trailer.ogg"></video>
|
|
264
|
-
# video_tag("trailer.ogg", controls: true,
|
|
265
|
-
# # => <video
|
|
287
|
+
# video_tag("trailer.ogg", controls: true, preload: 'none')
|
|
288
|
+
# # => <video preload="none" controls="controls" src="/videos/trailer.ogg" ></video>
|
|
266
289
|
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
|
|
267
290
|
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video>
|
|
291
|
+
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true)
|
|
292
|
+
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="screenshot.png"></video>
|
|
268
293
|
# video_tag("/trailers/hd.avi", size: "16x16")
|
|
269
294
|
# # => <video src="/trailers/hd.avi" width="16" height="16"></video>
|
|
270
295
|
# video_tag("/trailers/hd.avi", size: "16")
|
|
@@ -278,9 +303,12 @@ module ActionView
|
|
|
278
303
|
# video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
|
|
279
304
|
# # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
|
|
280
305
|
def video_tag(*sources)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
306
|
+
options = sources.extract_options!.symbolize_keys
|
|
307
|
+
public_poster_folder = options.delete(:poster_skip_pipeline)
|
|
308
|
+
sources << options
|
|
309
|
+
multiple_sources_tag_builder("video", sources) do |tag_options|
|
|
310
|
+
tag_options[:poster] = path_to_image(tag_options[:poster], skip_pipeline: public_poster_folder) if tag_options[:poster]
|
|
311
|
+
tag_options[:width], tag_options[:height] = extract_dimensions(tag_options.delete(:size)) if tag_options[:size]
|
|
284
312
|
end
|
|
285
313
|
end
|
|
286
314
|
|
|
@@ -297,33 +325,41 @@ module ActionView
|
|
|
297
325
|
# audio_tag("sound.wav", "sound.mid")
|
|
298
326
|
# # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
|
|
299
327
|
def audio_tag(*sources)
|
|
300
|
-
|
|
328
|
+
multiple_sources_tag_builder("audio", sources)
|
|
301
329
|
end
|
|
302
330
|
|
|
303
331
|
private
|
|
304
|
-
def
|
|
305
|
-
options
|
|
332
|
+
def multiple_sources_tag_builder(type, sources)
|
|
333
|
+
options = sources.extract_options!.symbolize_keys
|
|
334
|
+
skip_pipeline = options.delete(:skip_pipeline)
|
|
306
335
|
sources.flatten!
|
|
307
336
|
|
|
308
337
|
yield options if block_given?
|
|
309
338
|
|
|
310
339
|
if sources.size > 1
|
|
311
340
|
content_tag(type, options) do
|
|
312
|
-
safe_join sources.map { |source| tag("source", :
|
|
341
|
+
safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) }
|
|
313
342
|
end
|
|
314
343
|
else
|
|
315
|
-
options[:src] = send("path_to_#{type}", sources.first)
|
|
344
|
+
options[:src] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline)
|
|
316
345
|
content_tag(type, nil, options)
|
|
317
346
|
end
|
|
318
347
|
end
|
|
319
348
|
|
|
320
349
|
def extract_dimensions(size)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
350
|
+
size = size.to_s
|
|
351
|
+
if /\A\d+x\d+\z/.match?(size)
|
|
352
|
+
size.split("x")
|
|
353
|
+
elsif /\A\d+\z/.match?(size)
|
|
324
354
|
[size, size]
|
|
325
355
|
end
|
|
326
356
|
end
|
|
357
|
+
|
|
358
|
+
def check_for_image_tag_errors(options)
|
|
359
|
+
if options[:size] && (options[:height] || options[:width])
|
|
360
|
+
raise ArgumentError, "Cannot pass a :size option with a :height or :width option"
|
|
361
|
+
end
|
|
362
|
+
end
|
|
327
363
|
end
|
|
328
364
|
end
|
|
329
365
|
end
|