aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller/asset_paths.rb2
-rw-r--r--actionpack/lib/abstract_controller/base.rb6
-rw-r--r--actionpack/lib/action_controller/base.rb18
-rw-r--r--actionpack/lib/action_controller/metal/hide_actions.rb10
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb31
-rw-r--r--actionpack/lib/action_controller/railtie.rb1
-rw-r--r--actionpack/lib/action_controller/test_case.rb11
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb29
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb43
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb90
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb8
-rw-r--r--actionpack/lib/action_view.rb1
-rw-r--r--actionpack/lib/action_view/asset_paths.rb143
-rw-r--r--actionpack/lib/action_view/digestor.rb40
-rw-r--r--actionpack/lib/action_view/helpers.rb6
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb358
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb93
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb70
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb71
-rw-r--r--actionpack/lib/action_view/helpers/asset_url_helper.rb355
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb9
-rw-r--r--actionpack/lib/action_view/helpers/tags/check_box.rb8
-rw-r--r--actionpack/lib/action_view/railtie.rb8
-rw-r--r--actionpack/lib/action_view/test_case.rb37
24 files changed, 660 insertions, 788 deletions
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb
index 822254b1a4..e6170228d9 100644
--- a/actionpack/lib/abstract_controller/asset_paths.rb
+++ b/actionpack/lib/abstract_controller/asset_paths.rb
@@ -3,7 +3,7 @@ module AbstractController
extend ActiveSupport::Concern
included do
- config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir,
+ config_accessor :asset_host, :assets_dir, :javascripts_dir,
:stylesheets_dir, :default_asset_host_protocol, :relative_url_root
end
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 9c3960961b..388e043f0b 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -217,8 +217,10 @@ module AbstractController
# * <tt>string</tt> - The name of the method that handles the action
# * <tt>nil</tt> - No method name could be found. Raise ActionNotFound.
def method_for_action(action_name)
- if action_method?(action_name) then action_name
- elsif respond_to?(:action_missing, true) then "_handle_action_missing"
+ if action_method?(action_name)
+ action_name
+ elsif respond_to?(:action_missing, true)
+ "_handle_action_missing"
end
end
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 6b8d9384d4..9b3bf99fc3 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -43,7 +43,7 @@ module ActionController
#
# def server_ip
# location = request.env["SERVER_ADDR"]
- # render :text => "This server hosted at #{location}"
+ # render text: "This server hosted at #{location}"
# end
#
# == Parameters
@@ -113,9 +113,9 @@ module ActionController
# def search
# @results = Search.find(params[:query])
# case @results.count
- # when 0 then render :action => "no_results"
- # when 1 then render :action => "show"
- # when 2..10 then render :action => "show_many"
+ # when 0 then render action: "no_results"
+ # when 1 then render action: "show"
+ # when 2..10 then render action: "show_many"
# end
# end
#
@@ -131,7 +131,7 @@ module ActionController
# @entry = Entry.new(params[:entry])
# if @entry.save
# # The entry was saved correctly, redirect to show
- # redirect_to :action => 'show', :id => @entry.id
+ # redirect_to action: 'show', id: @entry.id
# else
# # things didn't go so well, do something else
# end
@@ -148,15 +148,15 @@ module ActionController
# An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError:
#
# def do_something
- # redirect_to :action => "elsewhere"
- # render :action => "overthere" # raises DoubleRenderError
+ # redirect_to action: "elsewhere"
+ # render action: "overthere" # raises DoubleRenderError
# end
#
# If you need to redirect on the condition of something, then be sure to add "and return" to halt execution.
#
# def do_something
- # redirect_to(:action => "elsewhere") and return if monkeys.nil?
- # render :action => "overthere" # won't be called if monkeys is nil
+ # redirect_to(action: "elsewhere") and return if monkeys.nil?
+ # render action: "overthere" # won't be called if monkeys is nil
# end
#
class Base < Metal
diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb
index 420b22cf56..2aa6b7adaf 100644
--- a/actionpack/lib/action_controller/metal/hide_actions.rb
+++ b/actionpack/lib/action_controller/metal/hide_actions.rb
@@ -26,20 +26,14 @@ module ActionController
self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze
end
- def inherited(klass)
- klass.class_eval { @visible_actions = {} }
- super
- end
-
def visible_action?(action_name)
- return @visible_actions[action_name] if @visible_actions.key?(action_name)
- @visible_actions[action_name] = !hidden_actions.include?(action_name)
+ action_methods.include?(action_name)
end
# Overrides AbstractController::Base#action_methods to remove any methods
# that are listed as hidden methods.
def action_methods
- @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) })
+ @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }).freeze
end
end
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index e4d6b65d9a..73f2e94cd1 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -171,14 +171,39 @@ module ActionController
# permitted[:person][:age] # => nil
# permitted[:person][:pets][0][:name] # => "Purplish"
# permitted[:person][:pets][0][:category] # => nil
+ #
+ # Note that if you use +permit+ in a key that points to a hash,
+ # it won't allow all the hash. You also need to specify which
+ # attributes inside the hash should be whitelisted.
+ #
+ # params = ActionController::Parameters.new({
+ # person: {
+ # contact: {
+ # email: 'none@test.com'
+ # phone: '555-1234'
+ # }
+ # }
+ # })
+ #
+ # params.require(:person).permit(:contact)
+ # # => {}
+ #
+ # params.require(:person).permit(contact: :phone)
+ # # => {"contact"=>{"phone"=>"555-1234"}}
+ #
+ # params.require(:person).permit(contact: [ :email, :phone ])
+ # # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}}
def permit(*filters)
params = self.class.new
filters.each do |filter|
case filter
when Symbol, String then
- params[filter] = self[filter] if has_key?(filter)
- keys.grep(/\A#{Regexp.escape(filter)}\(\di\)\z/) { |key| params[key] = self[key] }
+ if has_key?(filter)
+ _value = self[filter]
+ params[filter] = _value unless Hash === _value
+ end
+ keys.grep(/\A#{Regexp.escape(filter)}\(\d+[if]?\)\z/) { |key| params[key] = self[key] }
when Hash then
self.slice(*filter.keys).each do |key, values|
return unless values
@@ -336,7 +361,7 @@ module ActionController
# # It's mandatory to specify the nested attributes that should be whitelisted.
# # If you use `permit` with just the key that points to the nested attributes hash,
# # it will return an empty hash.
- # params.require(:person).permit(:name, :age, pets_attributes: { :name, :category })
+ # params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ])
# end
# end
#
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index ee0f053bad..3e44155f73 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -34,7 +34,6 @@ module ActionController
options.stylesheets_dir ||= paths["public/stylesheets"].first
# Ensure readers methods get compiled
- options.asset_path ||= app.config.asset_path
options.asset_host ||= app.config.asset_host
options.relative_url_root ||= app.config.relative_url_root
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index ace5a2c822..d911d47a1d 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -123,11 +123,12 @@ module ActionController
if expected_partial = options[:partial]
if expected_locals = options[:locals]
- if defined?(@locals)
- actual_locals = @locals[expected_partial.to_s.sub(/^_/,'')]
- expected_locals.each_pair do |k,v|
- assert_equal(v, actual_locals[k])
- end
+ if defined?(@_rendered_views)
+ view = expected_partial.to_s.sub(/^_/,'')
+ msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial,
+ expected_locals,
+ @_rendered_views.locals_for(view)]
+ assert(@_rendered_views.view_rendered?(view, options[:locals]), msg)
else
warn "the :locals option to #assert_template is only supported in a ActionView::TestCase"
end
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 47cf41cfa3..4a7df6b657 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -1,3 +1,4 @@
+require 'mutex_m'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/object/duplicable'
@@ -20,9 +21,18 @@ module ActionDispatch
# end
# => reverses the value to all keys matching /secret/i
module FilterParameters
- extend ActiveSupport::Concern
+ @@parameter_filter_for = {}.extend(Mutex_m)
- @@parameter_filter_for = {}
+ ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
+ NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
+ NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc:
+
+ def initialize(env)
+ super
+ @filtered_parameters = nil
+ @filtered_env = nil
+ @filtered_path = nil
+ end
# Return a hash of parameters with all sensitive data replaced.
def filtered_parameters
@@ -42,15 +52,24 @@ module ActionDispatch
protected
def parameter_filter
- parameter_filter_for(@env["action_dispatch.parameter_filter"])
+ parameter_filter_for @env.fetch("action_dispatch.parameter_filter") {
+ return NULL_PARAM_FILTER
+ }
end
def env_filter
- parameter_filter_for(Array(@env["action_dispatch.parameter_filter"]) + [/RAW_POST_DATA/, "rack.request.form_vars"])
+ user_key = @env.fetch("action_dispatch.parameter_filter") {
+ return NULL_ENV_FILTER
+ }
+ parameter_filter_for(Array(user_key) + ENV_MATCH)
end
def parameter_filter_for(filters)
- @@parameter_filter_for[filters] ||= ParameterFilter.new(filters)
+ @@parameter_filter_for.synchronize do
+ # Do we *actually* need this cache? Constructing ParameterFilters
+ # doesn't seem too expensive.
+ @@parameter_filter_for[filters] ||= ParameterFilter.new(filters)
+ end
end
KV_RE = '[^&;=]+'
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index a3bb25f75a..dc04d4577b 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -1,32 +1,39 @@
module ActionDispatch
module Http
- class Headers < ::Hash
- @@env_cache = Hash.new { |h,k| h[k] = "HTTP_#{k.upcase.gsub(/-/, '_')}" }
+ class Headers
+ include Enumerable
- def initialize(*args)
-
- if args.size == 1 && args[0].is_a?(Hash)
- super()
- update(args[0])
- else
- super
- end
+ def initialize(env = {})
+ @headers = env
end
def [](header_name)
- super env_name(header_name)
+ @headers[env_name(header_name)]
+ end
+
+ def []=(k,v); @headers[k] = v; end
+ def key?(k); @headers.key? k; end
+ alias :include? :key?
+
+ def fetch(header_name, *args, &block)
+ @headers.fetch env_name(header_name), *args, &block
end
- def fetch(header_name, default=nil, &block)
- super env_name(header_name), default, &block
+ def each(&block)
+ @headers.each(&block)
end
private
- # Converts a HTTP header name to an environment variable name if it is
- # not contained within the headers hash.
- def env_name(header_name)
- include?(header_name) ? header_name : @@env_cache[header_name]
- end
+
+ # Converts a HTTP header name to an environment variable name if it is
+ # not contained within the headers hash.
+ def env_name(header_name)
+ @headers.include?(header_name) ? header_name : cgi_name(header_name)
+ end
+
+ def cgi_name(k)
+ "HTTP_#{k.upcase.gsub(/-/, '_')}"
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index 490b46c990..b655a54865 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -1,74 +1,72 @@
module ActionDispatch
module Http
class ParameterFilter
+ FILTERED = '[FILTERED]'.freeze # :nodoc:
- def initialize(filters)
+ def initialize(filters = [])
@filters = filters
end
def filter(params)
- if enabled?
- compiled_filter.call(params)
- else
- params.dup
- end
+ compiled_filter.call(params)
end
private
- def enabled?
- @filters.present?
+ def compiled_filter
+ @compiled_filter ||= CompiledFilter.compile(@filters)
end
- FILTERED = '[FILTERED]'.freeze
+ class CompiledFilter # :nodoc:
+ def self.compile(filters)
+ return lambda { |params| params.dup } if filters.empty?
- def compiled_filter
- @compiled_filter ||= begin
- regexps, blocks = compile_filter
+ strings, regexps, blocks = [], [], []
- lambda do |original_params|
- filtered_params = {}
+ filters.each do |item|
+ case item
+ when Proc
+ blocks << item
+ when Regexp
+ regexps << item
+ else
+ strings << item.to_s
+ end
+ end
- original_params.each do |key, value|
- if regexps.find { |r| key =~ r }
- value = FILTERED
- elsif value.is_a?(Hash)
- value = filter(value)
- elsif value.is_a?(Array)
- value = value.map { |v| v.is_a?(Hash) ? filter(v) : v }
- elsif blocks.present?
- key = key.dup
- value = value.dup if value.duplicable?
- blocks.each { |b| b.call(key, value) }
- end
+ regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
+ new regexps, blocks
+ end
- filtered_params[key] = value
- end
+ attr_reader :regexps, :blocks
- filtered_params
- end
+ def initialize(regexps, blocks)
+ @regexps = regexps
+ @blocks = blocks
end
- end
- def compile_filter
- strings, regexps, blocks = [], [], []
+ def call(original_params)
+ filtered_params = {}
+
+ original_params.each do |key, value|
+ if regexps.any? { |r| key =~ r }
+ value = FILTERED
+ elsif value.is_a?(Hash)
+ value = call(value)
+ elsif value.is_a?(Array)
+ value = value.map { |v| v.is_a?(Hash) ? call(v) : v }
+ elsif blocks.any?
+ key = key.dup
+ value = value.dup if value.duplicable?
+ blocks.each { |b| b.call(key, value) }
+ end
- @filters.each do |item|
- case item
- when NilClass
- when Proc
- blocks << item
- when Regexp
- regexps << item
- else
- strings << item.to_s
+ filtered_params[key] = value
end
- end
- regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
- [regexps, blocks]
+ filtered_params
+ end
end
-
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index b8ebeb408f..fc8825d6d9 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -70,7 +70,13 @@ module ActionDispatch
RFC5789 = %w(PATCH)
HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789
- HTTP_METHOD_LOOKUP = Hash.new { |h, m| h[m] = m.underscore.to_sym if HTTP_METHODS.include?(m) }
+
+ HTTP_METHOD_LOOKUP = {}
+
+ # Populate the HTTP method lookup cache
+ HTTP_METHODS.each { |method|
+ HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym
+ }
# Returns the HTTP \method that the application should see.
# In the case where the \method was overridden by a middleware
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 091b0d8cd2..8bbf52382a 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -29,7 +29,6 @@ module ActionView
extend ActiveSupport::Autoload
eager_autoload do
- autoload :AssetPaths
autoload :Base
autoload :Context
autoload :CompiledTemplates, "action_view/context"
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
deleted file mode 100644
index 4bbb31b3ee..0000000000
--- a/actionpack/lib/action_view/asset_paths.rb
+++ /dev/null
@@ -1,143 +0,0 @@
-require 'zlib'
-require 'active_support/core_ext/file'
-
-module ActionView
- class AssetPaths #:nodoc:
- URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}
-
- attr_reader :config, :controller
-
- def initialize(config, controller = nil)
- @config = config
- @controller = controller
- end
-
- # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched.
- # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
- # roots. Rewrite the asset path for cache-busting asset ids. Include
- # asset host, if configured, with the correct request protocol.
- #
- # When :relative (default), the protocol will be determined by the client using current protocol
- # When :request, the protocol will be the request protocol
- # Otherwise, the protocol is used (E.g. :http, :https, etc)
- def compute_public_path(source, dir, options = {})
- source = source.to_s
- return source if is_uri?(source)
-
- source = rewrite_extension(source, dir, options[:ext]) if options[:ext]
- source = rewrite_asset_path(source, dir, options)
- source = rewrite_relative_url_root(source, relative_url_root)
- source = rewrite_host_and_protocol(source, options[:protocol])
- source
- end
-
- # Return the filesystem path for the source
- def compute_source_path(source, dir, ext)
- source = rewrite_extension(source, dir, ext) if ext
-
- sources = []
- sources << config.assets_dir
- sources << dir unless source[0] == ?/
- sources << source
-
- File.join(sources)
- end
-
- def is_uri?(path)
- path =~ URI_REGEXP
- end
-
- private
-
- def rewrite_extension(source, dir, ext)
- raise NotImplementedError
- end
-
- def rewrite_asset_path(source, path = nil)
- raise NotImplementedError
- end
-
- def rewrite_relative_url_root(source, relative_url_root)
- relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source
- end
-
- def has_request?
- controller.respond_to?(:request)
- end
-
- def rewrite_host_and_protocol(source, protocol = nil)
- host = compute_asset_host(source)
- if host && !is_uri?(host)
- if (protocol || default_protocol) == :request && !has_request?
- host = nil
- else
- host = "#{compute_protocol(protocol)}#{host}"
- end
- end
- host ? "#{host}#{source}" : source
- end
-
- def compute_protocol(protocol)
- protocol ||= default_protocol
- case protocol
- when :relative
- "//"
- when :request
- unless @controller
- invalid_asset_host!("The protocol requested was :request. Consider using :relative instead.")
- end
- @controller.request.protocol
- else
- "#{protocol}://"
- end
- end
-
- def default_protocol
- @config.default_asset_host_protocol || (has_request? ? :request : :relative)
- end
-
- def invalid_asset_host!(help_message)
- raise ActionView::MissingRequestError, "This asset host cannot be computed without a request in scope. #{help_message}"
- end
-
- # Pick an asset host for this source. Returns +nil+ if no host is set,
- # the host if no wildcard is set, the host interpolated with the
- # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
- # or the value returned from invoking call on an object responding to call
- # (proc or otherwise).
- def compute_asset_host(source)
- if host = asset_host_config
- if host.respond_to?(:call)
- args = [source]
- arity = arity_of(host)
- if (arity > 1 || arity < -2) && !has_request?
- invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request, or make it optional.")
- end
- args << current_request if (arity > 1 || arity < 0) && has_request?
- host.call(*args)
- else
- (host =~ /%d/) ? host % (Zlib.crc32(source) % 4) : host
- end
- end
- end
-
- def relative_url_root
- config.relative_url_root || current_request.try(:script_name)
- end
-
- def asset_host_config
- config.asset_host
- end
-
- # Returns the current request if one exists.
- def current_request
- controller.request if has_request?
- end
-
- # Returns the arity of a callable
- def arity_of(callable)
- callable.respond_to?(:arity) ? callable.arity : callable.method(:call).arity
- end
-
- end
-end
diff --git a/actionpack/lib/action_view/digestor.rb b/actionpack/lib/action_view/digestor.rb
index 5d3add4091..1c6eaf36f7 100644
--- a/actionpack/lib/action_view/digestor.rb
+++ b/actionpack/lib/action_view/digestor.rb
@@ -1,3 +1,5 @@
+require 'mutex_m'
+
module ActionView
class Digestor
EXPLICIT_DEPENDENCY = /# Template Dependency: ([^ ]+)/
@@ -19,16 +21,30 @@ module ActionView
/x
cattr_reader(:cache)
- @@cache = Hash.new
+ @@cache = Hash.new.extend Mutex_m
def self.digest(name, format, finder, options = {})
- cache["#{name}.#{format}"] ||= new(name, format, finder, options).digest
+ cache.synchronize do
+ unsafe_digest name, format, finder, options
+ end
end
- attr_reader :name, :format, :finder, :options
+ ###
+ # This method is NOT thread safe. DO NOT CALL IT DIRECTLY, instead call
+ # Digestor.digest
+ def self.unsafe_digest(name, format, finder, options = {}) # :nodoc:
+ key = "#{name}.#{format}"
- def initialize(name, format, finder, options = {})
- @name, @format, @finder, @options = name, format, finder, options
+ cache.fetch(key) do
+ klass = options[:partial] || name.include?("/_") ? PartialDigestor : Digestor
+ cache[key] = klass.new(name, format, finder).digest
+ end
+ end
+
+ attr_reader :name, :format, :finder
+
+ def initialize(name, format, finder)
+ @name, @format, @finder = name, format, finder
end
def digest
@@ -48,7 +64,7 @@ module ActionView
def nested_dependencies
dependencies.collect do |dependency|
- dependencies = Digestor.new(dependency, format, finder, partial: true).nested_dependencies
+ dependencies = PartialDigestor.new(dependency, format, finder).nested_dependencies
dependencies.any? ? { dependency => dependencies } : dependency
end
end
@@ -64,11 +80,11 @@ module ActionView
end
def directory
- name.split("/").first
+ name.split("/")[0..-2].join("/")
end
def partial?
- options[:partial] || name.include?("/_")
+ false
end
def source
@@ -77,7 +93,7 @@ module ActionView
def dependency_digest
dependencies.collect do |template_name|
- Digestor.digest(template_name, format, finder, partial: true)
+ Digestor.unsafe_digest(template_name, format, finder, partial: true)
end.join("-")
end
@@ -101,4 +117,10 @@ module ActionView
source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
end
end
+
+ class PartialDigestor < Digestor # :nodoc:
+ def partial?
+ true
+ end
+ end
end
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index f2a3a494bc..269e78a021 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -4,6 +4,7 @@ module ActionView #:nodoc:
autoload :ActiveModelHelper
autoload :AssetTagHelper
+ autoload :AssetUrlHelper
autoload :AtomFeedHelper
autoload :BenchmarkHelper
autoload :CacheHelper
@@ -28,12 +29,9 @@ module ActionView #:nodoc:
extend ActiveSupport::Concern
- included do
- extend SanitizeHelper::ClassMethods
- end
-
include ActiveModelHelper
include AssetTagHelper
+ include AssetUrlHelper
include AtomFeedHelper
include BenchmarkHelper
include CacheHelper
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 5b5fc84e90..31316fcdd9 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -1,8 +1,6 @@
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/keys'
-require 'action_view/helpers/asset_tag_helpers/javascript_tag_helpers'
-require 'action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers'
-require 'action_view/helpers/asset_tag_helpers/asset_paths'
+require 'action_view/helpers/asset_url_helper'
require 'action_view/helpers/tag_helper'
module ActionView
@@ -17,187 +15,87 @@ module ActionView
# stylesheet_link_tag("application")
# # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
#
- #
- # === Using asset hosts
- #
- # By default, Rails links to these assets on the current host in the public
- # folder, but you can direct Rails to link to assets from a dedicated asset
- # server by setting <tt>ActionController::Base.asset_host</tt> in the application
- # configuration, typically in <tt>config/environments/production.rb</tt>.
- # For example, you'd define <tt>assets.example.com</tt> to be your asset
- # host this way, inside the <tt>configure</tt> block of your environment-specific
- # configuration files or <tt>config/application.rb</tt>:
- #
- # config.action_controller.asset_host = "assets.example.com"
- #
- # Helpers take that into account:
- #
- # image_tag("rails.png")
- # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" />
- # stylesheet_link_tag("application")
- # # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" />
- #
- # Browsers typically open at most two simultaneous connections to a single
- # host, which means your assets often have to wait for other assets to finish
- # downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the
- # +asset_host+. For example, "assets%d.example.com". If that wildcard is
- # present Rails distributes asset requests among the corresponding four hosts
- # "assets0.example.com", ..., "assets3.example.com". With this trick browsers
- # will open eight simultaneous connections rather than two.
- #
- # image_tag("rails.png")
- # # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" />
- # stylesheet_link_tag("application")
- # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
- #
- # To do this, you can either setup four actual hosts, or you can use wildcard
- # DNS to CNAME the wildcard to a single asset host. You can read more about
- # setting up your DNS CNAME records from your ISP.
- #
- # Note: This is purely a browser performance optimization and is not meant
- # for server load balancing. See http://www.die.net/musings/page_load_time/
- # for background.
- #
- # Alternatively, you can exert more control over the asset host by setting
- # +asset_host+ to a proc like this:
- #
- # ActionController::Base.asset_host = Proc.new { |source|
- # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com"
- # }
- # image_tag("rails.png")
- # # => <img alt="Rails" src="http://assets1.example.com/assets/rails.png" />
- # stylesheet_link_tag("application")
- # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
- #
- # The example above generates "http://assets1.example.com" and
- # "http://assets2.example.com". This option is useful for example if
- # you need fewer/more than four hosts, custom host names, etc.
- #
- # As you see the proc takes a +source+ parameter. That's a string with the
- # absolute path of the asset, for example "/assets/rails.png".
- #
- # ActionController::Base.asset_host = Proc.new { |source|
- # if source.ends_with?('.css')
- # "http://stylesheets.example.com"
- # else
- # "http://assets.example.com"
- # end
- # }
- # image_tag("rails.png")
- # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" />
- # stylesheet_link_tag("application")
- # # => <link href="http://stylesheets.example.com/assets/application.css" media="screen" rel="stylesheet" />
- #
- # Alternatively you may ask for a second parameter +request+. That one is
- # particularly useful for serving assets from an SSL-protected page. The
- # example proc below disables asset hosting for HTTPS connections, while
- # still sending assets for plain HTTP requests from asset hosts. If you don't
- # have SSL certificates for each of the asset hosts this technique allows you
- # to avoid warnings in the client about mixed media.
- #
- # config.action_controller.asset_host = Proc.new { |source, request|
- # if request.ssl?
- # "#{request.protocol}#{request.host_with_port}"
- # else
- # "#{request.protocol}assets.example.com"
- # end
- # }
- #
- # You can also implement a custom asset host object that responds to +call+
- # and takes either one or two parameters just like the proc.
- #
- # config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
- # "http://asset%d.example.com", "https://asset1.example.com"
- # )
- #
- # === Customizing the asset path
- #
- # By default, Rails appends asset's timestamps to all asset paths. This allows
- # you to set a cache-expiration date for the asset far into the future, but
- # still be able to instantly invalidate it by simply updating the file (and
- # hence updating the timestamp, which then updates the URL as the timestamp
- # is part of that, which in turn busts the cache).
- #
- # It's the responsibility of the web server you use to set the far-future
- # expiration date on cache assets that you need to take advantage of this
- # feature. Here's an example for Apache:
- #
- # # Asset Expiration
- # ExpiresActive On
- # <FilesMatch "\.(ico|gif|jpe?g|png|js|css)$">
- # ExpiresDefault "access plus 1 year"
- # </FilesMatch>
- #
- # Also note that in order for this to work, all your application servers must
- # return the same timestamps. This means that they must have their clocks
- # synchronized. If one of them drifts out of sync, you'll see different
- # timestamps at random and the cache won't work. In that case the browser
- # will request the same assets over and over again even thought they didn't
- # change. You can use something like Live HTTP Headers for Firefox to verify
- # that the cache is indeed working.
- #
- # This strategy works well enough for most server setups and requires the
- # least configuration, but if you deploy several application servers at
- # different times - say to handle a temporary spike in load - then the
- # asset time stamps will be out of sync. In a setup like this you may want
- # to set the way that asset paths are generated yourself.
- #
- # Altering the asset paths that Rails generates can be done in two ways.
- # The easiest is to define the RAILS_ASSET_ID environment variable. The
- # contents of this variable will always be used in preference to
- # calculated timestamps. A more complex but flexible way is to set
- # <tt>ActionController::Base.config.asset_path</tt> to a proc
- # that takes the unmodified asset path and returns the path needed for
- # your asset caching to work. Typically you'd do something like this in
- # <tt>config/environments/production.rb</tt>:
- #
- # # Normally you'd calculate RELEASE_NUMBER at startup.
- # RELEASE_NUMBER = 12345
- # config.action_controller.asset_path = proc { |asset_path|
- # "/release-#{RELEASE_NUMBER}#{asset_path}"
- # }
- #
- # This example would cause the following behavior on all servers no
- # matter when they were deployed:
- #
- # image_tag("rails.png")
- # # => <img alt="Rails" src="/release-12345/images/rails.png" />
- # stylesheet_link_tag("application")
- # # => <link href="/release-12345/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" />
- #
- # Changing the asset_path does require that your web servers have
- # knowledge of the asset template paths that you rewrite to so it's not
- # suitable for out-of-the-box use. To use the example given above you
- # could use something like this in your Apache VirtualHost configuration:
- #
- # <LocationMatch "^/release-\d+/(images|javascripts|stylesheets)/.*$">
- # # Some browsers still send conditional-GET requests if there's a
- # # Last-Modified header or an ETag header even if they haven't
- # # reached the expiry date sent in the Expires header.
- # Header unset Last-Modified
- # Header unset ETag
- # FileETag None
- #
- # # Assets requested using a cache-busting filename should be served
- # # only once and then cached for a really long time. The HTTP/1.1
- # # spec frowns on hugely-long expiration times though and suggests
- # # that assets which never expire be served with an expiration date
- # # 1 year from access.
- # ExpiresActive On
- # ExpiresDefault "access plus 1 year"
- # </LocationMatch>
- #
- # # We use cached-busting location names with the far-future expires
- # # headers to ensure that if a file does change it can force a new
- # # request. The actual asset filenames are still the same though so we
- # # need to rewrite the location from the cache-busting location to the
- # # real asset location so that we can serve it.
- # RewriteEngine On
- # RewriteRule ^/release-\d+/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L]
module AssetTagHelper
+ extend ActiveSupport::Concern
+
+ include AssetUrlHelper
include TagHelper
- include JavascriptTagHelpers
- include StylesheetTagHelpers
+
+ # Returns an HTML script tag for each of the +sources+ provided.
+ #
+ # Sources may be paths to JavaScript files. Relative paths are assumed to be relative
+ # to <tt>assets/javascripts</tt>, full paths are assumed to be relative to the document
+ # root. Relative paths are idiomatic, use absolute paths only when needed.
+ #
+ # When passing paths, the ".js" extension is optional.
+ #
+ # You can modify the HTML attributes of the script tag by passing a hash as the
+ # last argument.
+ #
+ # javascript_include_tag "xmlhr"
+ # # => <script src="/assets/xmlhr.js?1284139606"></script>
+ #
+ # javascript_include_tag "xmlhr.js"
+ # # => <script src="/assets/xmlhr.js?1284139606"></script>
+ #
+ # javascript_include_tag "common.javascript", "/elsewhere/cools"
+ # # => <script src="/assets/common.javascript?1284139606"></script>
+ # # <script src="/elsewhere/cools.js?1423139606"></script>
+ #
+ # javascript_include_tag "http://www.example.com/xmlhr"
+ # # => <script src="http://www.example.com/xmlhr"></script>
+ #
+ # javascript_include_tag "http://www.example.com/xmlhr.js"
+ # # => <script src="http://www.example.com/xmlhr.js"></script>
+ #
+ def javascript_include_tag(*sources)
+ options = sources.extract_options!.stringify_keys
+ sources.uniq.map { |source|
+ tag_options = {
+ "src" => path_to_javascript(source)
+ }.merge(options)
+ content_tag(:script, "", tag_options)
+ }.join("\n").html_safe
+ end
+
+ # Returns a stylesheet link tag for the sources specified as arguments. If
+ # you don't specify an extension, <tt>.css</tt> will be appended automatically.
+ # You can modify the link attributes by passing a hash as the last argument.
+ # For historical reasons, the 'media' attribute will always be present and defaults
+ # to "screen", so you must explicitely set it to "all" for the stylesheet(s) to
+ # apply to all media types.
+ #
+ # stylesheet_link_tag "style"
+ # # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
+ #
+ # stylesheet_link_tag "style.css"
+ # # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
+ #
+ # stylesheet_link_tag "http://www.example.com/style.css"
+ # # => <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
+ #
+ # stylesheet_link_tag "style", :media => "all"
+ # # => <link href="/assets/style.css" media="all" rel="stylesheet" />
+ #
+ # stylesheet_link_tag "style", :media => "print"
+ # # => <link href="/assets/style.css" media="print" rel="stylesheet" />
+ #
+ # stylesheet_link_tag "random.styles", "/css/stylish"
+ # # => <link href="/assets/random.styles" media="screen" rel="stylesheet" />
+ # # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
+ #
+ def stylesheet_link_tag(*sources)
+ options = sources.extract_options!.stringify_keys
+ sources.uniq.map { |source|
+ tag_options = {
+ "rel" => "stylesheet",
+ "media" => "screen",
+ "href" => path_to_stylesheet(source)
+ }.merge(options)
+ tag(:link, tag_options)
+ }.join("\n").html_safe
+ end
+
# Returns a link tag that browsers and news readers can use to auto-detect
# an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or
# <tt>:atom</tt>. Control the link options in url_for format using the
@@ -268,93 +166,6 @@ module ActionView
}.merge(options.symbolize_keys))
end
- # Computes the path to an image asset.
- # Full paths from the document root will be passed through.
- # Used internally by +image_tag+ to build the image path:
- #
- # image_path("edit") # => "/assets/edit"
- # image_path("edit.png") # => "/assets/edit.png"
- # image_path("icons/edit.png") # => "/assets/icons/edit.png"
- # image_path("/icons/edit.png") # => "/icons/edit.png"
- # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
- #
- # If you have images as application resources this method may conflict with their named routes.
- # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
- # plugin authors are encouraged to do so.
- def image_path(source)
- source.present? ? asset_paths.compute_public_path(source, 'images') : ""
- end
- alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
-
- # Computes the full URL to an image asset.
- # This will use +image_path+ internally, so most of their behaviors will be the same.
- def image_url(source)
- URI.join(current_host, path_to_image(source)).to_s
- end
- alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route
-
- # Computes the path to a video asset in the public videos directory.
- # Full paths from the document root will be passed through.
- # Used internally by +video_tag+ to build the video path.
- #
- # video_path("hd") # => /videos/hd
- # video_path("hd.avi") # => /videos/hd.avi
- # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
- # video_path("/trailers/hd.avi") # => /trailers/hd.avi
- # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi
- def video_path(source)
- asset_paths.compute_public_path(source, 'videos')
- end
- alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
-
- # Computes the full URL to a video asset in the public videos directory.
- # This will use +video_path+ internally, so most of their behaviors will be the same.
- def video_url(source)
- URI.join(current_host, path_to_video(source)).to_s
- end
- alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route
-
- # Computes the path to an audio asset in the public audios directory.
- # Full paths from the document root will be passed through.
- # Used internally by +audio_tag+ to build the audio path.
- #
- # audio_path("horse") # => /audios/horse
- # audio_path("horse.wav") # => /audios/horse.wav
- # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav
- # audio_path("/sounds/horse.wav") # => /sounds/horse.wav
- # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav
- def audio_path(source)
- asset_paths.compute_public_path(source, 'audios')
- end
- alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
-
- # Computes the full URL to an audio asset in the public audios directory.
- # This will use +audio_path+ internally, so most of their behaviors will be the same.
- def audio_url(source)
- URI.join(current_host, path_to_audio(source)).to_s
- end
- alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route
-
- # Computes the path to a font asset.
- # Full paths from the document root will be passed through.
- #
- # font_path("font") # => /assets/font
- # font_path("font.ttf") # => /assets/font.ttf
- # font_path("dir/font.ttf") # => /assets/dir/font.ttf
- # font_path("/dir/font.ttf") # => /dir/font.ttf
- # font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
- def font_path(source)
- asset_paths.compute_public_path(source, 'fonts')
- end
- alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route
-
- # Computes the full URL to a font asset.
- # This will use +font_path+ internally, so most of their behaviors will be the same.
- def font_url(source)
- URI.join(current_host, path_to_font(source)).to_s
- end
- alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route
-
# Returns an html image tag for the +source+. The +source+ can be a full
# path or a file.
#
@@ -462,11 +273,6 @@ module ActionView
end
private
-
- def asset_paths
- @asset_paths ||= AssetTagHelper::AssetPaths.new(config, controller)
- end
-
def multiple_sources_tag(type, sources)
options = sources.extract_options!.symbolize_keys
sources.flatten!
@@ -482,10 +288,6 @@ module ActionView
content_tag(type, nil, options)
end
end
-
- def current_host
- url_for(:only_path => false)
- end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
deleted file mode 100644
index 35f91cec18..0000000000
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-require 'thread'
-require 'active_support/core_ext/file'
-require 'active_support/core_ext/module/attribute_accessors'
-
-module ActionView
- module Helpers
- module AssetTagHelper
-
- class AssetPaths < ::ActionView::AssetPaths #:nodoc:
- # You can enable or disable the asset tag ids cache.
- # With the cache enabled, the asset tag helper methods will make fewer
- # expensive file system calls (the default implementation checks the file
- # system timestamp). However this prevents you from modifying any asset
- # files while the server is running.
- #
- # ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids = false
- mattr_accessor :cache_asset_ids
-
- # Add or change an asset id in the asset id cache. This can be used
- # for SASS on Heroku.
- # :api: public
- def add_to_asset_ids_cache(source, asset_id)
- self.asset_ids_cache_guard.synchronize do
- self.asset_ids_cache[source] = asset_id
- end
- end
-
- private
-
- def rewrite_extension(source, dir, ext)
- source_ext = File.extname(source)
-
- source_with_ext = if source_ext.empty?
- "#{source}.#{ext}"
- elsif ext != source_ext[1..-1]
- with_ext = "#{source}.#{ext}"
- with_ext if File.exist?(File.join(config.assets_dir, dir, with_ext))
- end
-
- source_with_ext || source
- end
-
- # Break out the asset path rewrite in case plugins wish to put the asset id
- # someplace other than the query string.
- def rewrite_asset_path(source, dir, options = nil)
- source = "/#{dir}/#{source}" unless source[0] == ?/
- path = config.asset_path
-
- if path && path.respond_to?(:call)
- return path.call(source)
- elsif path && path.is_a?(String)
- return path % [source]
- end
-
- asset_id = rails_asset_id(source)
- if asset_id.empty?
- source
- else
- "#{source}?#{asset_id}"
- end
- end
-
- mattr_accessor :asset_ids_cache
- self.asset_ids_cache = {}
-
- mattr_accessor :asset_ids_cache_guard
- self.asset_ids_cache_guard = Mutex.new
-
- # Use the RAILS_ASSET_ID environment variable or the source's
- # modification time as its cache-busting asset id.
- def rails_asset_id(source)
- if asset_id = ENV["RAILS_ASSET_ID"]
- asset_id
- else
- if self.cache_asset_ids && (asset_id = self.asset_ids_cache[source])
- asset_id
- else
- path = File.join(config.assets_dir, source)
- asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
-
- if self.cache_asset_ids
- add_to_asset_ids_cache(source, asset_id)
- end
-
- asset_id
- end
- end
- end
- end
-
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
deleted file mode 100644
index b6b9eadc88..0000000000
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-require 'active_support/core_ext/file'
-
-module ActionView
- module Helpers
- module AssetTagHelper
- module JavascriptTagHelpers
- extend ActiveSupport::Concern
-
- # Computes the path to a javascript asset in the public javascripts directory.
- # If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
- # Full paths from the document root will be passed through.
- # Used internally by javascript_include_tag to build the script path.
- #
- # javascript_path "xmlhr" # => /javascripts/xmlhr.js
- # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
- # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
- # javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
- # javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
- def javascript_path(source)
- asset_paths.compute_public_path(source, 'javascripts', :ext => 'js')
- end
- alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
-
- # Computes the full URL to a javascript asset in the public javascripts directory.
- # This will use +javascript_path+ internally, so most of their behaviors will be the same.
- def javascript_url(source)
- URI.join(current_host, path_to_javascript(source)).to_s
- end
- alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
-
- # Returns an HTML script tag for each of the +sources+ provided.
- #
- # Sources may be paths to JavaScript files. Relative paths are assumed to be relative
- # to <tt>public/javascripts</tt>, full paths are assumed to be relative to the document
- # root. Relative paths are idiomatic, use absolute paths only when needed.
- #
- # When passing paths, the ".js" extension is optional.
- #
- # You can modify the HTML attributes of the script tag by passing a hash as the
- # last argument.
- #
- # javascript_include_tag "xmlhr"
- # # => <script src="/javascripts/xmlhr.js?1284139606"></script>
- #
- # javascript_include_tag "xmlhr.js"
- # # => <script src="/javascripts/xmlhr.js?1284139606"></script>
- #
- # javascript_include_tag "common.javascript", "/elsewhere/cools"
- # # => <script src="/javascripts/common.javascript?1284139606"></script>
- # # <script src="/elsewhere/cools.js?1423139606"></script>
- #
- # javascript_include_tag "http://www.example.com/xmlhr"
- # # => <script src="http://www.example.com/xmlhr"></script>
- #
- # javascript_include_tag "http://www.example.com/xmlhr.js"
- # # => <script src="http://www.example.com/xmlhr.js"></script>
- #
- def javascript_include_tag(*sources)
- options = sources.extract_options!.stringify_keys
- sources.dup.map { |source|
- tag_options = {
- "src" => path_to_javascript(source)
- }.merge(options)
- content_tag(:script, "", tag_options)
- }.join("\n").html_safe
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
deleted file mode 100644
index bf59f8e81a..0000000000
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-require 'active_support/core_ext/file'
-
-module ActionView
- module Helpers
- module AssetTagHelper
- module StylesheetTagHelpers
- extend ActiveSupport::Concern
-
- # Computes the path to a stylesheet asset in the public stylesheets directory.
- # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
- # Full paths from the document root will be passed through.
- # Used internally by +stylesheet_link_tag+ to build the stylesheet path.
- #
- # stylesheet_path "style" # => /stylesheets/style.css
- # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
- # stylesheet_path "/dir/style.css" # => /dir/style.css
- # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
- # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
- def stylesheet_path(source)
- asset_paths.compute_public_path(source, 'stylesheets', :ext => 'css', :protocol => :request)
- end
- alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
-
- # Computes the full URL to a stylesheet asset in the public stylesheets directory.
- # This will use +stylesheet_path+ internally, so most of their behaviors will be the same.
- def stylesheet_url(source)
- URI.join(current_host, path_to_stylesheet(source)).to_s
- end
- alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route
-
- # Returns a stylesheet link tag for the sources specified as arguments. If
- # you don't specify an extension, <tt>.css</tt> will be appended automatically.
- # You can modify the link attributes by passing a hash as the last argument.
- # For historical reasons, the 'media' attribute will always be present and defaults
- # to "screen", so you must explicitely set it to "all" for the stylesheet(s) to
- # apply to all media types.
- #
- # stylesheet_link_tag "style" # =>
- # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
- #
- # stylesheet_link_tag "style.css" # =>
- # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
- #
- # stylesheet_link_tag "http://www.example.com/style.css" # =>
- # <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
- #
- # stylesheet_link_tag "style", :media => "all" # =>
- # <link href="/stylesheets/style.css" media="all" rel="stylesheet" />
- #
- # stylesheet_link_tag "style", :media => "print" # =>
- # <link href="/stylesheets/style.css" media="print" rel="stylesheet" />
- #
- # stylesheet_link_tag "random.styles", "/css/stylish" # =>
- # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" />
- # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
- #
- def stylesheet_link_tag(*sources)
- options = sources.extract_options!.stringify_keys
- sources.uniq.map { |source|
- tag_options = {
- "rel" => "stylesheet",
- "media" => "screen",
- "href" => path_to_stylesheet(source)
- }.merge(options)
- tag(:link, tag_options)
- }.join("\n").html_safe
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/helpers/asset_url_helper.rb b/actionpack/lib/action_view/helpers/asset_url_helper.rb
new file mode 100644
index 0000000000..0bb5e739bb
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/asset_url_helper.rb
@@ -0,0 +1,355 @@
+require 'zlib'
+
+module ActionView
+ # = Action View Asset URL Helpers
+ module Helpers #:nodoc:
+ # This module provides methods for generating asset paths and
+ # urls.
+ #
+ # image_path("rails.png")
+ # # => "/assets/rails.png"
+ #
+ # image_url("rails.png")
+ # # => "http://www.example.com/assets/rails.png"
+ #
+ # === Using asset hosts
+ #
+ # By default, Rails links to these assets on the current host in the public
+ # folder, but you can direct Rails to link to assets from a dedicated asset
+ # server by setting <tt>ActionController::Base.asset_host</tt> in the application
+ # configuration, typically in <tt>config/environments/production.rb</tt>.
+ # For example, you'd define <tt>assets.example.com</tt> to be your asset
+ # host this way, inside the <tt>configure</tt> block of your environment-specific
+ # configuration files or <tt>config/application.rb</tt>:
+ #
+ # config.action_controller.asset_host = "assets.example.com"
+ #
+ # Helpers take that into account:
+ #
+ # image_tag("rails.png")
+ # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" />
+ # stylesheet_link_tag("application")
+ # # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" />
+ #
+ # Browsers typically open at most two simultaneous connections to a single
+ # host, which means your assets often have to wait for other assets to finish
+ # downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the
+ # +asset_host+. For example, "assets%d.example.com". If that wildcard is
+ # present Rails distributes asset requests among the corresponding four hosts
+ # "assets0.example.com", ..., "assets3.example.com". With this trick browsers
+ # will open eight simultaneous connections rather than two.
+ #
+ # image_tag("rails.png")
+ # # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" />
+ # stylesheet_link_tag("application")
+ # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
+ #
+ # To do this, you can either setup four actual hosts, or you can use wildcard
+ # DNS to CNAME the wildcard to a single asset host. You can read more about
+ # setting up your DNS CNAME records from your ISP.
+ #
+ # Note: This is purely a browser performance optimization and is not meant
+ # for server load balancing. See http://www.die.net/musings/page_load_time/
+ # for background.
+ #
+ # Alternatively, you can exert more control over the asset host by setting
+ # +asset_host+ to a proc like this:
+ #
+ # ActionController::Base.asset_host = Proc.new { |source|
+ # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com"
+ # }
+ # image_tag("rails.png")
+ # # => <img alt="Rails" src="http://assets1.example.com/assets/rails.png" />
+ # stylesheet_link_tag("application")
+ # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
+ #
+ # The example above generates "http://assets1.example.com" and
+ # "http://assets2.example.com". This option is useful for example if
+ # you need fewer/more than four hosts, custom host names, etc.
+ #
+ # As you see the proc takes a +source+ parameter. That's a string with the
+ # absolute path of the asset, for example "/assets/rails.png".
+ #
+ # ActionController::Base.asset_host = Proc.new { |source|
+ # if source.ends_with?('.css')
+ # "http://stylesheets.example.com"
+ # else
+ # "http://assets.example.com"
+ # end
+ # }
+ # image_tag("rails.png")
+ # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" />
+ # stylesheet_link_tag("application")
+ # # => <link href="http://stylesheets.example.com/assets/application.css" media="screen" rel="stylesheet" />
+ #
+ # Alternatively you may ask for a second parameter +request+. That one is
+ # particularly useful for serving assets from an SSL-protected page. The
+ # example proc below disables asset hosting for HTTPS connections, while
+ # still sending assets for plain HTTP requests from asset hosts. If you don't
+ # have SSL certificates for each of the asset hosts this technique allows you
+ # to avoid warnings in the client about mixed media.
+ #
+ # config.action_controller.asset_host = Proc.new { |source, request|
+ # if request.ssl?
+ # "#{request.protocol}#{request.host_with_port}"
+ # else
+ # "#{request.protocol}assets.example.com"
+ # end
+ # }
+ #
+ # You can also implement a custom asset host object that responds to +call+
+ # and takes either one or two parameters just like the proc.
+ #
+ # config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
+ # "http://asset%d.example.com", "https://asset1.example.com"
+ # )
+ #
+ module AssetUrlHelper
+ URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}
+
+ # Computes the path to asset in public directory. If :type
+ # options is set, a file extension will be appended and scoped
+ # to the corresponding public directory.
+ #
+ # All other asset *_path helpers delegate through this method.
+ #
+ # asset_path "application.js" # => /application.js
+ # asset_path "application", type: :javascript # => /javascripts/application.js
+ # asset_path "application", type: :stylesheet # => /stylesheets/application.css
+ # asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
+ def asset_path(source, options = {})
+ source = source.to_s
+ return "" unless source.present?
+ return source if source =~ URI_REGEXP
+
+ tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '')
+
+ if extname = compute_asset_extname(source, options)
+ source = "#{source}#{extname}"
+ end
+
+ if source[0] != ?/
+ source = compute_asset_path(source, options)
+ end
+
+ relative_url_root = (defined?(config.relative_url_root) && config.relative_url_root) ||
+ (respond_to?(:request) && request.try(:script_name))
+ if relative_url_root
+ source = "#{relative_url_root}#{source}" unless source.starts_with?("#{relative_url_root}/")
+ end
+
+ if host = compute_asset_host(source, options)
+ source = "#{host}#{source}"
+ end
+
+ "#{source}#{tail}"
+ end
+ alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with a asset_path named route
+
+ # Computes the full URL to a asset in the public directory. This
+ # will use +asset_path+ internally, so most of their behaviors
+ # will be the same.
+ def asset_url(source, options = {})
+ path_to_asset(source, options.merge(:protocol => :request))
+ end
+ alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route
+
+ ASSET_EXTENSIONS = {
+ javascript: '.js',
+ stylesheet: '.css'
+ }
+
+ # Compute extname to append to asset path. Returns nil if
+ # nothing should be added.
+ def compute_asset_extname(source, options = {})
+ return if options[:extname] == false
+ extname = options[:extname] || ASSET_EXTENSIONS[options[:type]]
+ extname if extname && File.extname(source) != extname
+ end
+
+ # Maps asset types to public directory.
+ ASSET_PUBLIC_DIRECTORIES = {
+ audio: '/audios',
+ font: '/fonts',
+ image: '/images',
+ javascript: '/javascripts',
+ stylesheet: '/stylesheets',
+ video: '/videos'
+ }
+
+ # Computes asset path to public directory. Plugins and
+ # extensions can override this method to point to custom assets
+ # or generate digested paths or query strings.
+ def compute_asset_path(source, options = {})
+ dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || ""
+ File.join(dir, source)
+ end
+
+ # Pick an asset host for this source. Returns +nil+ if no host is set,
+ # the host if no wildcard is set, the host interpolated with the
+ # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
+ # or the value returned from invoking call on an object responding to call
+ # (proc or otherwise).
+ def compute_asset_host(source = "", options = {})
+ request = self.request if respond_to?(:request)
+ host = config.asset_host if defined? config.asset_host
+ host ||= request.base_url if request && options[:protocol] == :request
+ return unless host
+
+ if host.respond_to?(:call)
+ arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
+ args = [source]
+ args << request if request && (arity > 1 || arity < 0)
+ host = host.call(*args)
+ elsif host =~ /%d/
+ host = host % (Zlib.crc32(source) % 4)
+ end
+
+ if host =~ URI_REGEXP
+ host
+ else
+ protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative)
+ case protocol
+ when :relative
+ "//#{host}"
+ when :request
+ "#{request.protocol}#{host}"
+ else
+ "#{protocol}://#{host}"
+ end
+ end
+ end
+
+ # Computes the path to a javascript asset in the public javascripts directory.
+ # If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
+ # Full paths from the document root will be passed through.
+ # Used internally by javascript_include_tag to build the script path.
+ #
+ # javascript_path "xmlhr" # => /javascripts/xmlhr.js
+ # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
+ # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
+ # javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
+ # javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
+ def javascript_path(source, options = {})
+ path_to_asset(source, {type: :javascript}.merge!(options))
+ end
+ alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
+
+ # Computes the full URL to a javascript asset in the public javascripts directory.
+ # This will use +javascript_path+ internally, so most of their behaviors will be the same.
+ def javascript_url(source, options = {})
+ url_to_asset(source, {type: :javascript}.merge!(options))
+ end
+ alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
+
+ # Computes the path to a stylesheet asset in the public stylesheets directory.
+ # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
+ # Full paths from the document root will be passed through.
+ # Used internally by +stylesheet_link_tag+ to build the stylesheet path.
+ #
+ # stylesheet_path "style" # => /stylesheets/style.css
+ # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
+ # stylesheet_path "/dir/style.css" # => /dir/style.css
+ # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
+ # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
+ def stylesheet_path(source, options = {})
+ path_to_asset(source, {type: :stylesheet}.merge!(options))
+ end
+ alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
+
+ # Computes the full URL to a stylesheet asset in the public stylesheets directory.
+ # This will use +stylesheet_path+ internally, so most of their behaviors will be the same.
+ def stylesheet_url(source, options = {})
+ url_to_asset(source, {type: :stylesheet}.merge!(options))
+ end
+ alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route
+
+ # Computes the path to an image asset.
+ # Full paths from the document root will be passed through.
+ # Used internally by +image_tag+ to build the image path:
+ #
+ # image_path("edit") # => "/assets/edit"
+ # image_path("edit.png") # => "/assets/edit.png"
+ # image_path("icons/edit.png") # => "/assets/icons/edit.png"
+ # image_path("/icons/edit.png") # => "/icons/edit.png"
+ # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
+ #
+ # If you have images as application resources this method may conflict with their named routes.
+ # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
+ # plugin authors are encouraged to do so.
+ def image_path(source, options = {})
+ path_to_asset(source, {type: :image}.merge!(options))
+ end
+ alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
+
+ # Computes the full URL to an image asset.
+ # This will use +image_path+ internally, so most of their behaviors will be the same.
+ def image_url(source, options = {})
+ url_to_asset(source, {type: :image}.merge!(options))
+ end
+ alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route
+
+ # Computes the path to a video asset in the public videos directory.
+ # Full paths from the document root will be passed through.
+ # Used internally by +video_tag+ to build the video path.
+ #
+ # video_path("hd") # => /videos/hd
+ # video_path("hd.avi") # => /videos/hd.avi
+ # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
+ # video_path("/trailers/hd.avi") # => /trailers/hd.avi
+ # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi
+ def video_path(source, options = {})
+ path_to_asset(source, {type: :video}.merge!(options))
+ end
+ alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
+
+ # Computes the full URL to a video asset in the public videos directory.
+ # This will use +video_path+ internally, so most of their behaviors will be the same.
+ def video_url(source, options = {})
+ url_to_asset(source, {type: :video}.merge!(options))
+ end
+ alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route
+
+ # Computes the path to an audio asset in the public audios directory.
+ # Full paths from the document root will be passed through.
+ # Used internally by +audio_tag+ to build the audio path.
+ #
+ # audio_path("horse") # => /audios/horse
+ # audio_path("horse.wav") # => /audios/horse.wav
+ # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav
+ # audio_path("/sounds/horse.wav") # => /sounds/horse.wav
+ # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav
+ def audio_path(source, options = {})
+ path_to_asset(source, {type: :audio}.merge!(options))
+ end
+ alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
+
+ # Computes the full URL to an audio asset in the public audios directory.
+ # This will use +audio_path+ internally, so most of their behaviors will be the same.
+ def audio_url(source, options = {})
+ url_to_asset(source, {type: :audio}.merge!(options))
+ end
+ alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route
+
+ # Computes the path to a font asset.
+ # Full paths from the document root will be passed through.
+ #
+ # font_path("font") # => /assets/font
+ # font_path("font.ttf") # => /assets/font.ttf
+ # font_path("dir/font.ttf") # => /assets/dir/font.ttf
+ # font_path("/dir/font.ttf") # => /dir/font.ttf
+ # font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
+ def font_path(source, options = {})
+ path_to_asset(source, {type: :font}.merge!(options))
+ end
+ alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route
+
+ # Computes the full URL to a font asset.
+ # This will use +font_path+ internally, so most of their behaviors will be the same.
+ def font_url(source, options = {})
+ url_to_asset(source, {type: :font}.merge!(options))
+ end
+ alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 387dfeab17..f43d20c6ed 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -73,13 +73,17 @@ module ActionView
options[:include_seconds] ||= !!include_seconds_or_options
end
+ options = {
+ scope: :'datetime.distance_in_words'
+ }.merge!(options)
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
from_time, to_time = to_time, from_time if from_time > to_time
distance_in_minutes = ((to_time - from_time)/60.0).round
distance_in_seconds = (to_time - from_time).round
- I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
+ I18n.with_options :locale => options[:locale], :scope => options[:scope] do |locale|
case distance_in_minutes
when 0..1
return distance_in_minutes == 0 ?
@@ -196,6 +200,8 @@ module ActionView
# for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>.
# Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds)
# or the given prompt string.
+ # * <tt>:with_css_classes</tt> - Set to true if you want assign different styles for 'select' tags. This option
+ # automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second' for your 'select' tags.
#
# If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
#
@@ -937,6 +943,7 @@ module ActionView
:name => input_name_from_type(type)
}.merge!(@html_options)
select_options[:disabled] = 'disabled' if @options[:disabled]
+ select_options[:class] = type if @options[:with_css_classes]
select_html = "\n"
select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
diff --git a/actionpack/lib/action_view/helpers/tags/check_box.rb b/actionpack/lib/action_view/helpers/tags/check_box.rb
index 9d17a1dde3..e21cc07746 100644
--- a/actionpack/lib/action_view/helpers/tags/check_box.rb
+++ b/actionpack/lib/action_view/helpers/tags/check_box.rb
@@ -46,10 +46,12 @@ module ActionView
false
when String
value == @checked_value
- when Array
- value.include?(@checked_value)
else
- value.to_i == @checked_value.to_i
+ if value.respond_to?(:include?)
+ value.include?(@checked_value)
+ else
+ value.to_i == @checked_value.to_i
+ end
end
end
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 55f6ea5522..3875d88a9f 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -20,14 +20,6 @@ module ActionView
ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
end
- initializer "action_view.cache_asset_ids" do |app|
- unless app.config.cache_classes
- ActiveSupport.on_load(:action_view) do
- ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids = false
- end
- end
- end
-
initializer "action_view.set_configs" do |app|
ActiveSupport.on_load(:action_view) do
app.config.action_view.each do |k,v|
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 5434b3421e..a548b44780 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -119,8 +119,29 @@ module ActionView
output
end
- def locals
- @locals ||= {}
+ def rendered_views
+ @_rendered_views ||= RenderedViewsCollection.new
+ end
+
+ class RenderedViewsCollection
+ def initialize
+ @rendered_views ||= {}
+ end
+
+ def add(view, locals)
+ @rendered_views[view] ||= []
+ @rendered_views[view] << locals
+ end
+
+ def locals_for(view)
+ @rendered_views[view]
+ end
+
+ def view_rendered?(view, expected_locals)
+ locals_for(view).any? do |actual_locals|
+ expected_locals.all? {|key, value| value == actual_locals[key] }
+ end
+ end
end
included do
@@ -156,18 +177,18 @@ module ActionView
end
module Locals
- attr_accessor :locals
+ attr_accessor :rendered_views
def render(options = {}, local_assigns = {})
case options
when Hash
if block_given?
- locals[options[:layout]] = options[:locals]
+ rendered_views.add options[:layout], options[:locals]
elsif options.key?(:partial)
- locals[options[:partial]] = options[:locals]
+ rendered_views.add options[:partial], options[:locals]
end
else
- locals[options] = local_assigns
+ rendered_views.add options, local_assigns
end
super
@@ -180,7 +201,7 @@ module ActionView
view = @controller.view_context
view.singleton_class.send :include, _helpers
view.extend(Locals)
- view.locals = self.locals
+ view.rendered_views = self.rendered_views
view.output_buffer = self.output_buffer
view
end
@@ -197,7 +218,7 @@ module ActionView
:@_routes,
:@controller,
:@_layouts,
- :@locals,
+ :@_rendered_views,
:@method_name,
:@output_buffer,
:@_partials,