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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +141 -272
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/action_view/base.rb +33 -21
  6. data/lib/action_view/buffers.rb +1 -1
  7. data/lib/action_view/context.rb +1 -1
  8. data/lib/action_view/dependency_tracker.rb +52 -20
  9. data/lib/action_view/digestor.rb +86 -83
  10. data/lib/action_view/flows.rb +9 -11
  11. data/lib/action_view/gem_version.rb +3 -3
  12. data/lib/action_view/helpers/active_model_helper.rb +8 -8
  13. data/lib/action_view/helpers/asset_tag_helper.rb +74 -38
  14. data/lib/action_view/helpers/asset_url_helper.rb +160 -59
  15. data/lib/action_view/helpers/atom_feed_helper.rb +16 -16
  16. data/lib/action_view/helpers/cache_helper.rb +90 -35
  17. data/lib/action_view/helpers/capture_helper.rb +7 -6
  18. data/lib/action_view/helpers/controller_helper.rb +3 -2
  19. data/lib/action_view/helpers/csrf_helper.rb +3 -3
  20. data/lib/action_view/helpers/date_helper.rb +156 -108
  21. data/lib/action_view/helpers/debug_helper.rb +3 -4
  22. data/lib/action_view/helpers/form_helper.rb +475 -94
  23. data/lib/action_view/helpers/form_options_helper.rb +87 -47
  24. data/lib/action_view/helpers/form_tag_helper.rb +88 -57
  25. data/lib/action_view/helpers/javascript_helper.rb +10 -10
  26. data/lib/action_view/helpers/number_helper.rb +76 -59
  27. data/lib/action_view/helpers/output_safety_helper.rb +34 -4
  28. data/lib/action_view/helpers/record_tag_helper.rb +12 -99
  29. data/lib/action_view/helpers/rendering_helper.rb +3 -3
  30. data/lib/action_view/helpers/sanitize_helper.rb +17 -14
  31. data/lib/action_view/helpers/tag_helper.rb +198 -73
  32. data/lib/action_view/helpers/tags/base.rb +132 -97
  33. data/lib/action_view/helpers/tags/check_box.rb +17 -17
  34. data/lib/action_view/helpers/tags/collection_check_boxes.rb +9 -33
  35. data/lib/action_view/helpers/tags/collection_helpers.rb +68 -36
  36. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +3 -11
  37. data/lib/action_view/helpers/tags/collection_select.rb +2 -2
  38. data/lib/action_view/helpers/tags/date_select.rb +36 -36
  39. data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
  40. data/lib/action_view/helpers/tags/grouped_collection_select.rb +2 -2
  41. data/lib/action_view/helpers/tags/label.rb +5 -1
  42. data/lib/action_view/helpers/tags/password_field.rb +1 -1
  43. data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
  44. data/lib/action_view/helpers/tags/radio_button.rb +4 -4
  45. data/lib/action_view/helpers/tags/search_field.rb +12 -9
  46. data/lib/action_view/helpers/tags/select.rb +9 -9
  47. data/lib/action_view/helpers/tags/text_area.rb +1 -1
  48. data/lib/action_view/helpers/tags/text_field.rb +5 -6
  49. data/lib/action_view/helpers/tags/translator.rb +15 -13
  50. data/lib/action_view/helpers/text_helper.rb +47 -30
  51. data/lib/action_view/helpers/translation_helper.rb +60 -30
  52. data/lib/action_view/helpers/url_helper.rb +132 -104
  53. data/lib/action_view/helpers.rb +1 -1
  54. data/lib/action_view/layouts.rb +59 -54
  55. data/lib/action_view/log_subscriber.rb +56 -7
  56. data/lib/action_view/lookup_context.rb +76 -61
  57. data/lib/action_view/model_naming.rb +1 -1
  58. data/lib/action_view/path_set.rb +28 -19
  59. data/lib/action_view/railtie.rb +30 -6
  60. data/lib/action_view/record_identifier.rb +51 -25
  61. data/lib/action_view/renderer/abstract_renderer.rb +19 -15
  62. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +55 -0
  63. data/lib/action_view/renderer/partial_renderer.rb +208 -206
  64. data/lib/action_view/renderer/renderer.rb +2 -6
  65. data/lib/action_view/renderer/streaming_template_renderer.rb +46 -48
  66. data/lib/action_view/renderer/template_renderer.rb +65 -66
  67. data/lib/action_view/rendering.rb +16 -9
  68. data/lib/action_view/routing_url_for.rb +25 -17
  69. data/lib/action_view/tasks/cache_digests.rake +23 -0
  70. data/lib/action_view/template/error.rb +14 -13
  71. data/lib/action_view/template/handlers/builder.rb +7 -7
  72. data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +9 -0
  73. data/lib/action_view/template/handlers/erb/erubi.rb +81 -0
  74. data/lib/action_view/template/handlers/erb/erubis.rb +81 -0
  75. data/lib/action_view/template/handlers/erb.rb +9 -76
  76. data/lib/action_view/template/handlers/html.rb +9 -0
  77. data/lib/action_view/template/handlers/raw.rb +1 -3
  78. data/lib/action_view/template/handlers.rb +8 -6
  79. data/lib/action_view/template/html.rb +2 -4
  80. data/lib/action_view/template/resolver.rb +133 -109
  81. data/lib/action_view/template/text.rb +5 -8
  82. data/lib/action_view/template/types.rb +15 -17
  83. data/lib/action_view/template.rb +51 -28
  84. data/lib/action_view/test_case.rb +32 -27
  85. data/lib/action_view/testing/resolvers.rb +29 -31
  86. data/lib/action_view/version.rb +1 -1
  87. data/lib/action_view/view_paths.rb +26 -32
  88. data/lib/action_view.rb +5 -5
  89. data/lib/assets/compiled/rails-ujs.js +685 -0
  90. metadata +23 -23
  91. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,22 +1,26 @@
1
- require 'thread_safe'
1
+ require "concurrent/map"
2
+ require "action_view/path_set"
2
3
 
3
4
  module ActionView
4
5
  class DependencyTracker # :nodoc:
5
- @trackers = ThreadSafe::Cache.new
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
- if tracker.present?
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
- @trackers[handler] = tracker
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
- def self.call(name, template)
80
- new(name, template).dependencies
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 initialize(name, template)
84
- @name, @template = name, template
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.scan(RENDER_ARGUMENTS) do
110
- add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic])
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
 
@@ -1,123 +1,126 @@
1
- require 'thread_safe'
2
- require 'action_view/dependency_tracker'
3
- require 'monitor'
1
+ require "concurrent/map"
2
+ require "action_view/dependency_tracker"
3
+ require "monitor"
4
4
 
5
5
  module ActionView
6
6
  class Digestor
7
- cattr_reader(:cache)
8
- @@cache = ThreadSafe::Cache.new
9
- @@digest_monitor = Monitor.new
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
- # * <tt>partial</tt> - Specifies whether the template is a partial
18
- def digest(options)
19
- options.assert_valid_keys(:name, :finder, :dependencies, :partial)
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
- # (ThreadSafe::Cache's lookups have volatile semantics)
25
- @@cache[cache_key] || @@digest_monitor.synchronize do
26
- @@cache.fetch(cache_key) do # re-check under lock
27
- compute_and_store_digest(cache_key, options)
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
- private
33
- def compute_and_store_digest(cache_key, options) # called under @@digest_monitor lock
34
- klass = if options[:partial] || options[:name].include?("/_")
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
- digest = klass.new(options).digest
45
- # Store the actual digest if config.cache_template_loading is true
46
- @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching?
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
- attr_reader :name, :finder, :options
47
+ options = {}
48
+ options[:formats] = [finder.rendered_format] if finder.rendered_format
55
49
 
56
- def initialize(options)
57
- @name, @finder = options.values_at(:name, :finder)
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
- def digest
62
- Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
63
- logger.try :debug, " Cache digest for #{template.inspect}: #{digest}"
64
- end
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
- def dependencies
71
- DependencyTracker.find_dependencies(name, template)
72
- rescue ActionView::MissingTemplate
73
- logger.try :error, " '#{name}' file doesn't exist, so no dependencies"
74
- []
75
- end
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
- def nested_dependencies
78
- dependencies.collect do |dependency|
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
- private
85
- def logger
86
- ActionView::Base.logger
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.gsub(%r|/_|, "/")
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 partial?
94
- false
89
+ def digest(finder, stack = [])
90
+ Digest::MD5.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}")
95
91
  end
96
92
 
97
- def template
98
- @template ||= finder.disable_cache { finder.find(logical_name, [], partial?) }
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 source
102
- template.source
106
+ def to_dep_map
107
+ children.any? ? { name => children.map(&:to_dep_map) } : name
103
108
  end
109
+ end
104
110
 
105
- def dependency_digest
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
- (template_digests + injected_dependencies).join("-")
111
- end
113
+ class Missing < Node
114
+ def digest(finder, _ = []) "" end
115
+ end
112
116
 
113
- def injected_dependencies
114
- Array.wrap(options[:dependencies])
115
- end
116
- end
117
+ class Injected < Node
118
+ def digest(finder, _ = []) name end
119
+ end
117
120
 
118
- class PartialDigestor < Digestor # :nodoc:
119
- def partial?
120
- true
121
+ class NullLogger
122
+ def self.debug(_); end
123
+ def self.error(_); end
121
124
  end
122
125
  end
123
126
  end
@@ -1,11 +1,11 @@
1
- require 'active_support/core_ext/string/output_safety'
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 are inside the layout
41
- # fiber, we set that we are waiting for the given
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 provides and resumes back to the fiber if it is
64
- # the key it is waiting for.
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
- def inside_fiber?
73
- Fiber.current.object_id != @root
74
- end
70
+ def inside_fiber?
71
+ Fiber.current.object_id != @root
72
+ end
75
73
  end
76
74
  end
@@ -5,9 +5,9 @@ module ActionView
5
5
  end
6
6
 
7
7
  module VERSION
8
- MAJOR = 4
9
- MINOR = 2
10
- TINY = 10
8
+ MAJOR = 5
9
+ MINOR = 1
10
+ TINY = 0
11
11
  PRE = nil
12
12
 
13
13
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -1,5 +1,5 @@
1
- require 'active_support/core_ext/module/attribute_accessors'
2
- require 'active_support/core_ext/enumerable'
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
- def object_has_errors?
41
- object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
42
- end
40
+ def object_has_errors?
41
+ object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
42
+ end
43
43
 
44
- def tag_generate_errors?(options)
45
- options['type'] != 'hidden'
46
- end
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 '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'
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?1284139606"></script>
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?1284139606"></script>
61
+ # # => <script src="/assets/template.debug-1284139606.jst"></script>
43
62
  #
44
63
  # javascript_include_tag "xmlhr.js"
45
- # # => <script src="/assets/xmlhr.js?1284139606"></script>
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?1284139606"></script>
49
- # # <script src="/elsewhere/cools.js?1423139606"></script>
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!('protocol', 'extname').symbolize_keys
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(:script, "", tag_options)
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!('protocol').symbolize_keys
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] || Mime::Type.lookup_by_extension(type.to_s).to_s,
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(:only_path => false)) : url_options
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='favicon.ico', options={})
173
- tag('link', {
174
- :rel => 'shortcut icon',
175
- :type => 'image/x-icon',
176
- :href => path_to_image(source)
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
- def image_tag(source, options={})
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 =~ /^(?:cid|data):/ || src.blank?
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, '.*').sub(/-[[:xdigit:]]{32,64}\z/, '').tr('-_', ' ').capitalize
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, autobuffer: true)
265
- # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" ></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
- multiple_sources_tag('video', sources) do |options|
282
- options[:poster] = path_to_image(options[:poster]) if options[:poster]
283
- options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
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
- multiple_sources_tag('audio', sources)
328
+ multiple_sources_tag_builder("audio", sources)
301
329
  end
302
330
 
303
331
  private
304
- def multiple_sources_tag(type, sources)
305
- options = sources.extract_options!.symbolize_keys
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", :src => send("path_to_#{type}", 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
- if size =~ %r{\A\d+x\d+\z}
322
- size.split('x')
323
- elsif size =~ %r{\A\d+\z}
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