diff options
Diffstat (limited to 'actionpack')
63 files changed, 1512 insertions, 3497 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 490eb17df3..48ba1518e0 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,86 @@ ## Rails 4.0.0 (unreleased) ## +* `date_select` helper accepts `with_css_classes: true` to add css classes similar with type + of generated select tags. + + *Pavel Nikitin* + +* Only non-js/css under app/assets path will be included in default config.assets.precompile. + + *Josh Peek* + +* Remove support for the RAILS_ASSET_ID environment configuration + (no longer needed now that we have the asset pipeline). + + *Josh Peek* + +* Remove old asset_path configuration (no longer needed now that we have the asset pipeline). + + *Josh Peek* + +* `assert_template` can be used to assert on the same template with different locals + Fix #3675 + + *Yves Senn* + +* Remove old asset tag concatenation (no longer needed now that we have the asset pipeline). + + *Josh Peek* + +* Accept :remote as symbolic option for `link_to` helper. *Riley Lynch* + +* Warn when the `:locals` option is passed to `assert_template` outside of a view test case + Fix #3415 + + *Yves Senn* + +* The `Rack::Cache` middleware is now disabled by default. To enable it, + set `config.action_dispatch.rack_cache = true` and add `gem rack-cache` to your Gemfile. + + *Guillermo Iguaran* + +* `ActionController::Base.page_cache_extension` option is deprecated + in favour of `ActionController::Base.default_static_extension`. + + *Francesco Rodriguez* + +* Action and Page caching has been extracted from Action Dispatch + as `actionpack-action_caching` and `actionpack-page_caching` gems. + Please read the `README.md` file on both gems for the usage. + + *Francesco Rodriguez* + +* Failsafe exception returns text/plain. *Steve Klabnik* + +* Remove `rack-cache` dependency from Action Pack and declare it on Gemfile + + *Guillermo Iguaran* + +* Rename internal variables on ActionController::TemplateAssertions to prevent + naming collisions. @partials, @templates and @layouts are now prefixed with an underscore. + Fix #7459 + + *Yves Senn* + +* `resource` and `resources` don't modify the passed options hash + Fix #7777 + + *Yves Senn* + +* Precompiled assets include aliases from foo.js to foo/index.js and vice versa. + + # Precompiles phone-<digest>.css and aliases phone/index.css to phone.css. + config.assets.precompile = [ 'phone.css' ] + + # Precompiles phone/index-<digest>.css and aliases phone.css to phone/index.css. + config.assets.precompile = [ 'phone/index.css' ] + + # Both of these work with either precompile thanks to their aliases. + <%= stylesheet_link_tag 'phone', media: 'all' %> + <%= stylesheet_link_tag 'phone/index', media: 'all' %> + + *Jeremy Kemper* + * `assert_template` is no more passing with what ever string that matches with the template name. diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index fd09d3b55b..7d292ac17c 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -18,7 +18,6 @@ Gem::Specification.new do |s| s.requirements << 'none' s.add_dependency('activesupport', version) - s.add_dependency('rack-cache', '~> 1.2') s.add_dependency('builder', '~> 3.1.0') s.add_dependency('rack', '~> 1.4.1') s.add_dependency('rack-test', '~> 0.6.1') 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/caching.rb b/actionpack/lib/action_controller/caching.rb index fc27a0774b..462f147371 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -2,11 +2,9 @@ require 'fileutils' require 'uri' require 'set' -module ActionController #:nodoc: +module ActionController # \Caching is a cheap way of speeding up slow applications by keeping the result of # calculations, renderings, and database calls around for subsequent requests. - # Action Controller affords you three approaches in varying levels of granularity: - # Page, Action, Fragment. # # You can read more about each approach and the sweeping assistance by clicking the # modules below. @@ -17,8 +15,7 @@ module ActionController #:nodoc: # == \Caching stores # # All the caching stores from ActiveSupport::Cache are available to be used as backends - # for Action Controller caching. This setting only affects action and fragment caching - # as page caching is always written to disk. + # for Action Controller caching. # # Configuration examples (MemoryStore is the default): # @@ -32,9 +29,7 @@ module ActionController #:nodoc: extend ActiveSupport::Autoload eager_autoload do - autoload :Actions autoload :Fragments - autoload :Pages autoload :Sweeper, 'action_controller/caching/sweeping' autoload :Sweeping, 'action_controller/caching/sweeping' end @@ -58,12 +53,25 @@ module ActionController #:nodoc: include AbstractController::Callbacks include ConfigMethods - include Pages, Actions, Fragments + include Fragments include Sweeping if defined?(ActiveRecord) included do extend ConfigMethods + config_accessor :default_static_extension + self.default_static_extension ||= '.html' + + def self.page_cache_extension=(extension) + ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension) + self.default_static_extension = extension + end + + def self.page_cache_extension + ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension) + default_static_extension + end + config_accessor :perform_caching self.perform_caching = true if perform_caching.nil? end diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb deleted file mode 100644 index bf16fe267c..0000000000 --- a/actionpack/lib/action_controller/caching/actions.rb +++ /dev/null @@ -1,189 +0,0 @@ -require 'set' - -module ActionController - module Caching - # Action caching is similar to page caching by the fact that the entire - # output of the response is cached, but unlike page caching, every - # request still goes through Action Pack. The key benefit of this is - # that filters run before the cache is served, which allows for - # authentication and other restrictions on whether someone is allowed - # to execute such action. - # - # class ListsController < ApplicationController - # before_filter :authenticate, except: :public - # - # caches_page :public - # caches_action :index, :show - # end - # - # In this example, the +public+ action doesn't require authentication - # so it's possible to use the faster page caching. On the other hand - # +index+ and +show+ require authentication. They can still be cached, - # but we need action caching for them. - # - # Action caching uses fragment caching internally and an around - # filter to do the job. The fragment cache is named according to - # the host and path of the request. A page that is accessed at - # <tt>http://david.example.com/lists/show/1</tt> will result in a fragment named - # <tt>david.example.com/lists/show/1</tt>. This allows the cacher to - # differentiate between <tt>david.example.com/lists/</tt> and - # <tt>jamis.example.com/lists/</tt> -- which is a helpful way of assisting - # the subdomain-as-account-key pattern. - # - # Different representations of the same resource, e.g. - # <tt>http://david.example.com/lists</tt> and - # <tt>http://david.example.com/lists.xml</tt> - # are treated like separate requests and so are cached separately. - # Keep in mind when expiring an action cache that - # <tt>action: 'lists'</tt> is not the same as - # <tt>action: 'list', format: :xml</tt>. - # - # You can modify the default action cache path by passing a - # <tt>:cache_path</tt> option. This will be passed directly to - # <tt>ActionCachePath.new</tt>. This is handy for actions with - # multiple possible routes that should be cached differently. If a - # block is given, it is called with the current controller instance. - # - # And you can also use <tt>:if</tt> (or <tt>:unless</tt>) to pass a - # proc that specifies when the action should be cached. - # - # As of Rails 3.0, you can also pass <tt>:expires_in</tt> with a time - # interval (in seconds) to schedule expiration of the cached item. - # - # The following example depicts some of the points made above: - # - # class ListsController < ApplicationController - # before_filter :authenticate, except: :public - # - # caches_page :public - # - # caches_action :index, if: Proc.new do - # !request.format.json? # cache if is not a JSON request - # end - # - # caches_action :show, cache_path: { project: 1 }, - # expires_in: 1.hour - # - # caches_action :feed, cache_path: Proc.new do - # if params[:user_id] - # user_list_url(params[:user_id, params[:id]) - # else - # list_url(params[:id]) - # end - # end - # end - # - # If you pass <tt>layout: false</tt>, it will only cache your action - # content. That's useful when your layout has dynamic information. - # - # Warning: If the format of the request is determined by the Accept HTTP - # header the Content-Type of the cached response could be wrong because - # no information about the MIME type is stored in the cache key. So, if - # you first ask for MIME type M in the Accept header, a cache entry is - # created, and then perform a second request to the same resource asking - # for a different MIME type, you'd get the content cached for M. - # - # The <tt>:format</tt> parameter is taken into account though. The safest - # way to cache by MIME type is to pass the format in the route. - module Actions - extend ActiveSupport::Concern - - module ClassMethods - # Declares that +actions+ should be cached. - # See ActionController::Caching::Actions for details. - def caches_action(*actions) - return unless cache_configured? - options = actions.extract_options! - options[:layout] = true unless options.key?(:layout) - filter_options = options.extract!(:if, :unless).merge(:only => actions) - cache_options = options.extract!(:layout, :cache_path).merge(:store_options => options) - - around_filter ActionCacheFilter.new(cache_options), filter_options - end - end - - def _save_fragment(name, options) - content = "" - response_body.each do |parts| - content << parts - end - - if caching_allowed? - write_fragment(name, content, options) - else - content - end - end - - protected - def expire_action(options = {}) - return unless cache_configured? - - if options.is_a?(Hash) && options[:action].is_a?(Array) - options[:action].each {|action| expire_action(options.merge(:action => action)) } - else - expire_fragment(ActionCachePath.new(self, options, false).path) - end - end - - class ActionCacheFilter #:nodoc: - def initialize(options, &block) - @cache_path, @store_options, @cache_layout = - options.values_at(:cache_path, :store_options, :layout) - end - - def around(controller) - cache_layout = @cache_layout.respond_to?(:call) ? @cache_layout.call(controller) : @cache_layout - - path_options = if @cache_path.respond_to?(:call) - controller.instance_exec(controller, &@cache_path) - else - @cache_path - end - - cache_path = ActionCachePath.new(controller, path_options || {}) - - body = controller.read_fragment(cache_path.path, @store_options) - - unless body - controller.action_has_layout = false unless cache_layout - yield - controller.action_has_layout = true - body = controller._save_fragment(cache_path.path, @store_options) - end - - body = controller.render_to_string(:text => body, :layout => true) unless cache_layout - - controller.response_body = body - controller.content_type = Mime[cache_path.extension || :html] - end - end - - class ActionCachePath - attr_reader :path, :extension - - # If +infer_extension+ is +true+, the cache path extension is looked up from the request's - # path and format. This is desirable when reading and writing the cache, but not when - # expiring the cache - +expire_action+ should expire the same files regardless of the - # request format. - def initialize(controller, options = {}, infer_extension = true) - if infer_extension - @extension = controller.params[:format] - options.reverse_merge!(:format => @extension) if options.is_a?(Hash) - end - - path = controller.url_for(options).split('://', 2).last - @path = normalize!(path) - end - - private - def normalize!(path) - ext = URI.parser.escape(extension) if extension - path << 'index' if path[-1] == ?/ - path << ".#{ext}" if extension and !path.split('?', 2).first.ends_with?(".#{ext}") - URI.parser.unescape(path) - end - end - end - end -end diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb deleted file mode 100644 index 3cf8d965ff..0000000000 --- a/actionpack/lib/action_controller/caching/pages.rb +++ /dev/null @@ -1,202 +0,0 @@ -require 'fileutils' -require 'active_support/core_ext/class/attribute_accessors' - -module ActionController - module Caching - # Page caching is an approach to caching where the entire action output of is - # stored as a HTML file that the web server can serve without going through - # Action Pack. This is the fastest way to cache your content as opposed to going - # dynamically through the process of generating the content. Unfortunately, this - # incredible speed-up is only available to stateless pages where all visitors are - # treated the same. Content management systems -- including weblogs and wikis -- - # have many pages that are a great fit for this approach, but account-based systems - # where people log in and manipulate their own data are often less likely candidates. - # - # Specifying which actions to cache is done through the +caches_page+ class method: - # - # class WeblogController < ActionController::Base - # caches_page :show, :new - # end - # - # This will generate cache files such as <tt>weblog/show/5.html</tt> and - # <tt>weblog/new.html</tt>, which match the URLs used that would normally trigger - # dynamic page generation. Page caching works by configuring a web server to first - # check for the existence of files on disk, and to serve them directly when found, - # without passing the request through to Action Pack. This is much faster than - # handling the full dynamic request in the usual way. - # - # Expiration of the cache is handled by deleting the cached file, which results - # in a lazy regeneration approach where the cache is not restored before another - # hit is made against it. The API for doing so mimics the options from +url_for+ and friends: - # - # class WeblogController < ActionController::Base - # def update - # List.update(params[:list][:id], params[:list]) - # expire_page action: 'show', id: params[:list][:id] - # redirect_to action: 'show', id: params[:list][:id] - # end - # end - # - # Additionally, you can expire caches using Sweepers that act on changes in - # the model to determine when a cache is supposed to be expired. - module Pages - extend ActiveSupport::Concern - - included do - # The cache directory should be the document root for the web server and is - # set using <tt>Base.page_cache_directory = "/document/root"</tt>. For Rails, - # this directory has already been set to Rails.public_path (which is usually - # set to <tt>Rails.root + "/public"</tt>). Changing this setting can be useful - # to avoid naming conflicts with files in <tt>public/</tt>, but doing so will - # likely require configuring your web server to look in the new location for - # cached files. - class_attribute :page_cache_directory - self.page_cache_directory ||= '' - - # Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. - # In these cases, the page caching mechanism will add one in order to make it - # easy for the cached files to be picked up properly by the web server. By - # default, this cache extension is <tt>.html</tt>. If you want something else, - # like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. - # In cases where a request already has an extension, such as <tt>.xml</tt> - # or <tt>.rss</tt>, page caching will not add an extension. This allows it - # to work well with RESTful apps. - class_attribute :page_cache_extension - self.page_cache_extension ||= '.html' - - # The compression used for gzip. If +false+ (default), the page is not compressed. - # If can be a symbol showing the ZLib compression method, for example, <tt>:best_compression</tt> - # or <tt>:best_speed</tt> or an integer configuring the compression level. - class_attribute :page_cache_compression - self.page_cache_compression ||= false - end - - module ClassMethods - # Expires the page that was cached with the +path+ as a key. - # - # expire_page '/lists/show' - def expire_page(path) - return unless perform_caching - path = page_cache_path(path) - - instrument_page_cache :expire_page, path do - File.delete(path) if File.exist?(path) - File.delete(path + '.gz') if File.exist?(path + '.gz') - end - end - - # Manually cache the +content+ in the key determined by +path+. - # - # cache_page "I'm the cached content", '/lists/show' - def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION) - return unless perform_caching - path = page_cache_path(path, extension) - - instrument_page_cache :write_page, path do - FileUtils.makedirs(File.dirname(path)) - File.open(path, "wb+") { |f| f.write(content) } - if gzip - Zlib::GzipWriter.open(path + '.gz', gzip) { |f| f.write(content) } - end - end - end - - # Caches the +actions+ using the page-caching approach that'll store - # the cache in a path within the +page_cache_directory+ that - # matches the triggering url. - # - # You can also pass a <tt>:gzip</tt> option to override the class configuration one. - # - # # cache the index action - # caches_page :index - # - # # cache the index action except for JSON requests - # caches_page :index, if: Proc.new { !request.format.json? } - # - # # don't gzip images - # caches_page :image, gzip: false - def caches_page(*actions) - return unless perform_caching - options = actions.extract_options! - - gzip_level = options.fetch(:gzip, page_cache_compression) - gzip_level = case gzip_level - when Symbol - Zlib.const_get(gzip_level.upcase) - when Fixnum - gzip_level - when false - nil - else - Zlib::BEST_COMPRESSION - end - - after_filter({:only => actions}.merge(options)) do |c| - c.cache_page(nil, nil, gzip_level) - end - end - - private - def page_cache_file(path, extension) - name = (path.empty? || path == "/") ? "/index" : URI.parser.unescape(path.chomp('/')) - unless (name.split('/').last || name).include? '.' - name << (extension || self.page_cache_extension) - end - return name - end - - def page_cache_path(path, extension = nil) - page_cache_directory.to_s + page_cache_file(path, extension) - end - - def instrument_page_cache(name, path) - ActiveSupport::Notifications.instrument("#{name}.action_controller", :path => path){ yield } - end - end - - # Expires the page that was cached with the +options+ as a key. - # - # expire_page controller: 'lists', action: 'show' - def expire_page(options = {}) - return unless self.class.perform_caching - - if options.is_a?(Hash) - if options[:action].is_a?(Array) - options[:action].each do |action| - self.class.expire_page(url_for(options.merge(:only_path => true, :action => action))) - end - else - self.class.expire_page(url_for(options.merge(:only_path => true))) - end - else - self.class.expire_page(options) - end - end - - # Manually cache the +content+ in the key determined by +options+. If no content is provided, - # the contents of response.body is used. If no options are provided, the url of the current - # request being handled is used. - # - # cache_page "I'm the cached content", controller: 'lists', action: 'show' - def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION) - return unless self.class.perform_caching && caching_allowed? - - path = case options - when Hash - url_for(options.merge(:only_path => true, :format => params[:format])) - when String - options - else - request.path - end - - if (type = Mime::LOOKUP[self.content_type]) && (type_symbol = type.symbol).present? - extension = ".#{type_symbol}" - end - - self.class.cache_page(content || response.body, path, extension, gzip) - end - - end - end -end diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index f41d1bb4b9..3d274e7dd7 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -4,6 +4,8 @@ module ActionController INTERNAL_PARAMS = %w(controller action format _method only_path) def start_processing(event) + return unless logger.info? + payload = event.payload params = payload[:params].except(*INTERNAL_PARAMS) format = payload[:format] @@ -14,6 +16,8 @@ module ActionController end def process_action(event) + return unless logger.info? + payload = event.payload additions = ActionController::Base.log_process_action(payload) @@ -22,35 +26,36 @@ module ActionController exception_class_name = payload[:exception].first status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name) end - message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration + message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" message << " (#{additions.join(" | ")})" unless additions.blank? info(message) end def halted_callback(event) - info "Filter chain halted as #{event.payload[:filter]} rendered or redirected" + info("Filter chain halted as #{event.payload[:filter]} rendered or redirected") end def send_file(event) - info("Sent file %s (%.1fms)" % [event.payload[:path], event.duration]) + info("Sent file #{event.payload[:path]} (#{event.duration.round(1)}ms)") end def redirect_to(event) - info "Redirected to #{event.payload[:location]}" + info("Redirected to #{event.payload[:location]}") end def send_data(event) - info("Sent data %s (%.1fms)" % [event.payload[:filename], event.duration]) + info("Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)") end %w(write_fragment read_fragment exist_fragment? expire_fragment expire_page write_page).each do |method| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{method}(event) + return unless logger.info? key_or_path = event.payload[:key] || event.payload[:path] human_name = #{method.to_s.humanize.inspect} - info("\#{human_name} \#{key_or_path} \#{"(%.1fms)" % event.duration}") + info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)") end METHOD end 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 c9a81e4866..e33201b273 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -112,6 +112,11 @@ module ActionController # params.permitted? # => true # Person.new(params) # => #<Person id: nil, name: "Francesco"> def permit! + each_pair do |key, value| + convert_hashes_to_parameters(key, value) + self[key].permit! if self[key].respond_to? :permit! + end + @permitted = true self end @@ -166,13 +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) + 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 diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index f2c68432c1..3e44155f73 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -32,10 +32,8 @@ module ActionController options.javascripts_dir ||= paths["public/javascripts"].first options.stylesheets_dir ||= paths["public/stylesheets"].first - options.page_cache_directory ||= paths["public"].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 20c6e2bf53..d911d47a1d 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -12,16 +12,16 @@ module ActionController end def setup_subscriptions - @partials = Hash.new(0) - @templates = Hash.new(0) - @layouts = Hash.new(0) + @_partials = Hash.new(0) + @_templates = Hash.new(0) + @_layouts = Hash.new(0) ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload| path = payload[:layout] if path - @layouts[path] += 1 + @_layouts[path] += 1 if path =~ /^layouts\/(.*)/ - @layouts[$1] += 1 + @_layouts[$1] += 1 end end end @@ -32,11 +32,11 @@ module ActionController partial = path =~ /^.*\/_[^\/]*$/ if partial - @partials[path] += 1 - @partials[path.split("/").last] += 1 + @_partials[path] += 1 + @_partials[path.split("/").last] += 1 end - @templates[path] += 1 + @_templates[path] += 1 end end @@ -46,9 +46,9 @@ module ActionController end def process(*args) - @partials = Hash.new(0) - @templates = Hash.new(0) - @layouts = Hash.new(0) + @_partials = Hash.new(0) + @_templates = Hash.new(0) + @_layouts = Hash.new(0) super end @@ -88,7 +88,7 @@ module ActionController case options when NilClass, Regexp, String, Symbol options = options.to_s if Symbol === options - rendered = @templates + rendered = @_templates msg = message || sprintf("expecting <%s> but rendering with <%s>", options.inspect, rendered.keys) matches_template = @@ -109,36 +109,41 @@ module ActionController if options.key?(:layout) expected_layout = options[:layout] msg = message || sprintf("expecting layout <%s> but action rendered <%s>", - expected_layout, @layouts.keys) + expected_layout, @_layouts.keys) case expected_layout when String, Symbol - assert_includes @layouts.keys, expected_layout.to_s, msg + assert_includes @_layouts.keys, expected_layout.to_s, msg when Regexp - assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg) + assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg) when nil, false - assert(@layouts.empty?, msg) + assert(@_layouts.empty?, msg) end end if expected_partial = options[:partial] if expected_locals = options[:locals] - actual_locals = @locals[expected_partial.to_s.sub(/^_/,'')] - expected_locals.each_pair do |k,v| - assert_equal(v, actual_locals[k]) + 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 elsif expected_count = options[:count] - actual_count = @partials[expected_partial] + actual_count = @_partials[expected_partial] msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)", expected_partial, expected_count, actual_count) assert(actual_count == expected_count.to_i, msg) else msg = message || sprintf("expecting partial <%s> but action rendered <%s>", - options[:partial], @partials.keys) - assert_includes @partials, expected_partial, msg + options[:partial], @_partials.keys) + assert_includes @_partials, expected_partial, msg end elsif options.key?(:partial) - assert @partials.empty?, + assert @_partials.empty?, "Expected no partials to be rendered" end else 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_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index ab740a0190..0de10695e0 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -6,7 +6,7 @@ module ActionDispatch # and calls an exceptions app that will wrap it in a format for the end user. # # The exceptions app should be passed as parameter on initialization - # of ShowExceptions. Everytime there is an exception, ShowExceptions will + # of ShowExceptions. Every time there is an exception, ShowExceptions will # store the exception in env["action_dispatch.exception"], rewrite the # PATH_INFO to the exception status code and call the rack app. # @@ -15,11 +15,11 @@ module ActionDispatch # If any exception happens inside the exceptions app, this middleware # catches the exceptions and returns a FAILSAFE_RESPONSE. class ShowExceptions - FAILSAFE_RESPONSE = [500, {'Content-Type' => 'text/html'}, - ["<html><body><h1>500 Internal Server Error</h1>" << + FAILSAFE_RESPONSE = [500, { 'Content-Type' => 'text/plain' }, + ["500 Internal Server Error\n" << "If you are the administrator of this website, then please read this web " << "application's log file and/or the web server's log file to find out what " << - "went wrong.</body></html>"]] + "went wrong."]] def initialize(app, exceptions_app) @app = app diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 9073e6582d..e3b15b43b9 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -29,7 +29,7 @@ module ActionDispatch def ext @ext ||= begin - ext = ::ActionController::Base.page_cache_extension + ext = ::ActionController::Base.default_static_extension "{,#{ext},/index#{ext}}" end end diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index ccc0435a39..284dd180db 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -12,12 +12,7 @@ module ActionDispatch config.action_dispatch.rescue_templates = { } config.action_dispatch.rescue_responses = { } config.action_dispatch.default_charset = nil - - config.action_dispatch.rack_cache = { - :metastore => "rails:/", - :entitystore => "rails:/", - :verbose => false - } + config.action_dispatch.rack_cache = false config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 49afa01d25..c5cf413c8f 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1038,7 +1038,7 @@ module ActionDispatch # === Options # Takes same options as +resources+. def resource(*resources, &block) - options = resources.extract_options! + options = resources.extract_options!.dup if apply_common_behavior_for(:resource, resources, options, &block) return self @@ -1204,7 +1204,7 @@ module ActionDispatch # # resource actions are at /admin/posts. # resources :posts, :path => "admin/posts" def resources(*resources, &block) - options = resources.extract_options! + options = resources.extract_options!.dup if apply_common_behavior_for(:resources, resources, options, &block) return self 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..4eac6514df 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>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.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="/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 + # 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_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb deleted file mode 100644 index e42e49fb04..0000000000 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +++ /dev/null @@ -1,145 +0,0 @@ -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/file' -require 'action_view/helpers/tag_helper' - -module ActionView - module Helpers - module AssetTagHelper - - class AssetIncludeTag #:nodoc: - include TagHelper - - attr_reader :config, :asset_paths - class_attribute :expansions - - def self.inherited(base) - base.expansions = { } - end - - def initialize(config, asset_paths) - @config = config - @asset_paths = asset_paths - end - - def asset_name - raise NotImplementedError - end - - def extension - raise NotImplementedError - end - - def custom_dir - raise NotImplementedError - end - - def asset_tag(source, options) - raise NotImplementedError - end - - def include_tag(*sources) - options = sources.extract_options!.stringify_keys - concat = options.delete("concat") - cache = concat || options.delete("cache") - recursive = options.delete("recursive") - - if concat || (config.perform_caching && cache) - joined_name = (cache == true ? "all" : cache) + ".#{extension}" - joined_path = File.join((joined_name[/^#{File::SEPARATOR}/] ? config.assets_dir : custom_dir), joined_name) - unless config.perform_caching && File.exists?(joined_path) - write_asset_file_contents(joined_path, compute_paths(sources, recursive)) - end - asset_tag(joined_name, options) - else - sources = expand_sources(sources, recursive) - ensure_sources!(sources) if cache - sources.collect { |source| asset_tag(source, options) }.join("\n").html_safe - end - end - - private - - def path_to_asset(source, options = {}) - asset_paths.compute_public_path(source, asset_name.to_s.pluralize, options.merge(:ext => extension)) - end - - def path_to_asset_source(source) - asset_paths.compute_source_path(source, asset_name.to_s.pluralize, extension) - end - - def compute_paths(*args) - expand_sources(*args).collect { |source| path_to_asset_source(source) } - end - - def expand_sources(sources, recursive) - if sources.first == :all - collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - else - sources.inject([]) do |list, source| - determined_source = determine_source(source, expansions) - update_source_list(list, determined_source) - end - end - end - - def update_source_list(list, source) - case source - when String - list.delete(source) - list << source - when Array - updated_sources = source - list - list.concat(updated_sources) - end - end - - def ensure_sources!(sources) - sources.each do |source| - asset_file_path!(path_to_asset_source(source)) - end - end - - def collect_asset_files(*path) - dir = path.first - - Dir[File.join(*path.compact)].collect do |file| - file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '') - end.sort - end - - def determine_source(source, collection) - case source - when Symbol - collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}") - else - source - end - end - - def join_asset_file_contents(paths) - paths.collect { |path| File.read(asset_file_path!(path, true)) }.join("\n\n") - end - - def write_asset_file_contents(joined_asset_path, asset_paths) - FileUtils.mkdir_p(File.dirname(joined_asset_path)) - File.atomic_write(joined_asset_path) { |cache| cache.write(join_asset_file_contents(asset_paths)) } - - # Set mtime to the latest of the combined files to allow for - # consistent ETag without a shared filesystem. - mt = asset_paths.map { |p| File.mtime(asset_file_path!(p)) }.max - File.utime(mt, mt, joined_asset_path) - end - - def asset_file_path!(absolute_path, error_if_file_is_uri = false) - if asset_paths.is_uri?(absolute_path) - raise(Errno::ENOENT, "Asset file #{path} is uri and cannot be merged into single file") if error_if_file_is_uri - else - raise(Errno::ENOENT, "Asset file not found at '#{absolute_path}'" ) unless File.exist?(absolute_path) - return absolute_path - end - end - 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 139f4d19ab..0000000000 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ /dev/null @@ -1,195 +0,0 @@ -require 'active_support/core_ext/file' -require 'action_view/helpers/asset_tag_helpers/asset_include_tag' - -module ActionView - module Helpers - module AssetTagHelper - - class JavascriptIncludeTag < AssetIncludeTag #:nodoc: - def asset_name - 'javascript' - end - - def extension - 'js' - end - - def asset_tag(source, options) - content_tag("script", "", { "src" => path_to_asset(source) }.merge(options)) - end - - def custom_dir - config.javascripts_dir - end - - private - - def expand_sources(sources, recursive = false) - if sources.include?(:all) - all_asset_files = (collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - ['application']) - add_application_js(all_asset_files, sources) - ((determine_source(:defaults, expansions).dup & all_asset_files) + all_asset_files).uniq - else - expanded_sources = sources.inject([]) do |list, source| - determined_source = determine_source(source, expansions) - update_source_list(list, determined_source) - end - add_application_js(expanded_sources, sources) - expanded_sources - end - end - - def add_application_js(expanded_sources, sources) - if (sources.include?(:defaults) || sources.include?(:all)) && File.exist?(File.join(custom_dir, "application.#{extension}")) - expanded_sources.delete('application') - expanded_sources << "application" - end - end - end - - - module JavascriptTagHelpers - extend ActiveSupport::Concern - - module ClassMethods - # Register one or more javascript files to be included when <tt>symbol</tt> - # is passed to <tt>javascript_include_tag</tt>. This method is typically intended - # to be called from plugin initialization to register javascript files - # that the plugin installed in <tt>public/javascripts</tt>. - # - # ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"] - # - # javascript_include_tag :monkey # => - # <script src="/javascripts/head.js"></script> - # <script src="/javascripts/body.js"></script> - # <script src="/javascripts/tail.js"></script> - def register_javascript_expansion(expansions) - js_expansions = JavascriptIncludeTag.expansions - expansions.each do |key, values| - js_expansions[key] = (js_expansions[key] || []) | Array(values) - 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) - 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. - # - # If the application is not using the asset pipeline, to include the default JavaScript - # expansion pass <tt>:defaults</tt> as source. By default, <tt>:defaults</tt> loads jQuery, - # and that can be overridden in <tt>config/application.rb</tt>: - # - # config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js) - # - # When using <tt>:defaults</tt> or <tt>:all</tt>, if an <tt>application.js</tt> file exists - # in <tt>public/javascripts</tt> it will be included as well at the end. - # - # 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> - # - # javascript_include_tag :defaults - # # => <script src="/javascripts/jquery.js?1284139606"></script> - # # <script src="/javascripts/rails.js?1284139606"></script> - # # <script src="/javascripts/application.js?1284139606"></script> - # - # Note: The application.js file is only referenced if it exists - # - # You can also include all JavaScripts in the +javascripts+ directory using <tt>:all</tt> as the source: - # - # javascript_include_tag :all - # # => <script src="/javascripts/jquery.js?1284139606"></script> - # # <script src="/javascripts/rails.js?1284139606"></script> - # # <script src="/javascripts/shop.js?1284139606"></script> - # # <script src="/javascripts/checkout.js?1284139606"></script> - # # <script src="/javascripts/application.js?1284139606"></script> - # - # Note that your defaults of choice will be included first, so they will be available to all subsequently - # included files. - # - # If you want Rails to search in all the subdirectories under <tt>public/javascripts</tt>, you should - # explicitly set <tt>:recursive</tt>: - # - # javascript_include_tag :all, :recursive => true - # - # == Caching multiple JavaScripts into one - # - # You can also cache multiple JavaScripts into one file, which requires less HTTP connections to download - # and can better be compressed by gzip (leading to faster transfers). Caching will only happen if - # <tt>config.perform_caching</tt> is set to true (which is the case by default for the Rails - # production environment, but not for the development environment). - # - # # assuming config.perform_caching is false - # javascript_include_tag :all, :cache => true - # # => <script src="/javascripts/jquery.js?1284139606"></script> - # # <script src="/javascripts/rails.js?1284139606"></script> - # # <script src="/javascripts/shop.js?1284139606"></script> - # # <script src="/javascripts/checkout.js?1284139606"></script> - # # <script src="/javascripts/application.js?1284139606"></script> - # - # # assuming config.perform_caching is true - # javascript_include_tag :all, :cache => true - # # => <script src="/javascripts/all.js?1344139789"></script> - # - # # assuming config.perform_caching is false - # javascript_include_tag "jquery", "cart", "checkout", :cache => "shop" - # # => <script src="/javascripts/jquery.js?1284139606"></script> - # # <script src="/javascripts/cart.js?1289139157"></script> - # # <script src="/javascripts/checkout.js?1299139816"></script> - # - # # assuming config.perform_caching is true - # javascript_include_tag "jquery", "cart", "checkout", :cache => "shop" - # # => <script src="/javascripts/shop.js?1299139816"></script> - # - # The <tt>:recursive</tt> option is also available for caching: - # - # javascript_include_tag :all, :cache => true, :recursive => true - def javascript_include_tag(*sources) - @javascript_include ||= JavascriptIncludeTag.new(config, asset_paths) - @javascript_include.include_tag(*sources) - 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 e3a86a8889..0000000000 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ /dev/null @@ -1,151 +0,0 @@ -require 'active_support/core_ext/file' -require 'action_view/helpers/asset_tag_helpers/asset_include_tag' - -module ActionView - module Helpers - module AssetTagHelper - - class StylesheetIncludeTag < AssetIncludeTag #:nodoc: - def asset_name - 'stylesheet' - end - - def extension - 'css' - end - - def asset_tag(source, options) - # We force the :request protocol here to avoid a double-download bug in IE7 and IE8 - tag("link", { "rel" => "stylesheet", "media" => "screen", "href" => path_to_asset(source, :protocol => :request) }.merge(options)) - end - - def custom_dir - config.stylesheets_dir - end - end - - - module StylesheetTagHelpers - extend ActiveSupport::Concern - - module ClassMethods - # Register one or more stylesheet files to be included when <tt>symbol</tt> - # is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended - # to be called from plugin initialization to register stylesheet files - # that the plugin installed in <tt>public/stylesheets</tt>. - # - # ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"] - # - # stylesheet_link_tag :monkey # => - # <link href="/stylesheets/head.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/body.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" /> - def register_stylesheet_expansion(expansions) - style_expansions = StylesheetIncludeTag.expansions - expansions.each do |key, values| - style_expansions[key] = (style_expansions[key] || []) | Array(values) - end - end - end - - # 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" /> - # - # You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source: - # - # stylesheet_link_tag :all # => - # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" /> - # - # If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>: - # - # stylesheet_link_tag :all, :recursive => true - # - # == Caching multiple stylesheets into one - # - # You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be - # compressed by gzip (leading to faster transfers). Caching will only happen if +config.perform_caching+ - # is set to true (which is the case by default for the Rails production environment, but not for the development - # environment). Examples: - # - # stylesheet_link_tag :all, :cache => true # when config.perform_caching is false => - # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" /> - # - # stylesheet_link_tag :all, :cache => true # when config.perform_caching is true => - # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" /> - # - # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is false => - # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" /> - # - # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is true => - # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" /> - # - # The <tt>:recursive</tt> option is also available for caching: - # - # stylesheet_link_tag :all, :cache => true, :recursive => true - # - # To force concatenation (even in development mode) set <tt>:concat</tt> to true. This is useful if - # you have too many stylesheets for IE to load. - # - # stylesheet_link_tag :all, :concat => true - # - def stylesheet_link_tag(*sources) - @stylesheet_include ||= StylesheetIncludeTag.new(config, asset_paths) - @stylesheet_include.include_tag(*sources) - 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/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index 59e1015976..ddac87a37d 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -2,11 +2,11 @@ module ActionView # = Action View Cache Helper module Helpers module CacheHelper - # This helper exposes a method for caching fragments of a view + # This helper exposes a method for caching fragments of a view # rather than an entire action or page. This technique is useful # caching pieces like menus, lists of newstopics, static HTML # fragments, and so on. This method takes a block that contains - # the content you wish to cache. + # the content you wish to cache. # # The best way to use this is by doing key-based cache expiration # on top of a cache store like Memcached that'll automatically @@ -56,40 +56,40 @@ module ActionView # # Most template dependencies can be derived from calls to render in the template itself. # Here are some examples of render calls that Cache Digests knows how to decode: - # + # # render partial: "comments/comment", collection: commentable.comments # render "comments/comments" # render 'comments/comments' # render('comments/comments') - # + # # render "header" => render("comments/header") - # + # # render(@topic) => render("topics/topic") # render(topics) => render("topics/topic") # render(message.topics) => render("topics/topic") - # + # # It's not possible to derive all render calls like that, though. Here are a few examples of things that can't be derived: - # + # # render group_of_attachments # render @project.documents.where(published: true).order('created_at') - # + # # You will have to rewrite those to the explicit form: - # + # # render partial: 'attachments/attachment', collection: group_of_attachments # render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at') # # === Explicit dependencies - # + # # Some times you'll have template dependencies that can't be derived at all. This is typically # the case when you have template rendering that happens in helpers. Here's an example: - # + # # <%= render_sortable_todolists @project.todolists %> - # + # # You'll need to use a special comment format to call those out: - # + # # <%# Template Dependency: todolists/todolist %> # <%= render_sortable_todolists @project.todolists %> - # + # # The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so. # You can only declare one template dependency per line. # @@ -113,6 +113,17 @@ module ActionView nil end + def fragment_name_with_digest(name) #:nodoc: + if @virtual_path + [ + *Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name), + Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context) + ] + else + name + end + end + private # TODO: Create an object that has caching read/write on it def fragment_for(name = {}, options = nil, &block) #:nodoc: @@ -131,17 +142,6 @@ module ActionView controller.write_fragment(name, fragment, options) end end - - def fragment_name_with_digest(name) - if @virtual_path - [ - *Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name), - Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context) - ] - else - name - end - end 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/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 0bb08cd7ff..b87c2e936f 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -207,7 +207,7 @@ module ActionView # if the object's class is +Post+. However, this can be overwritten using # the <tt>:as</tt> option, e.g. - # - # <%= form_for(@person, :as => :client) do |f| %> + # <%= form_for(@person, as: :client) do |f| %> # ... # <% end %> # @@ -242,7 +242,7 @@ module ActionView # # is then equivalent to something like: # - # <%= form_for @post, :as => :post, :url => post_path(@post), :method => :put, :html => { :class => "edit_post", :id => "edit_post_45" } do |f| %> + # <%= form_for @post, as: :post, url: post_path(@post), method: :put, html: { class: "edit_post", id: "edit_post_45" } do |f| %> # ... # <% end %> # @@ -254,19 +254,19 @@ module ActionView # # is equivalent to something like: # - # <%= form_for @post, :as => :post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %> + # <%= form_for @post, as: :post, url: posts_path, html: { class: "new_post", id: "new_post" } do |f| %> # ... # <% end %> # # However you can still overwrite individual conventions, such as: # - # <%= form_for(@post, :url => super_posts_path) do |f| %> + # <%= form_for(@post, url: super_posts_path) do |f| %> # ... # <% end %> # # You can also set the answer format, like this: # - # <%= form_for(@post, :format => :json) do |f| %> + # <%= form_for(@post, format: :json) do |f| %> # ... # <% end %> # @@ -290,7 +290,7 @@ module ActionView # # You can force the form to use the full array of HTTP verbs by setting # - # :method => (:get|:post|:patch|:put|:delete) + # method: (:get|:post|:patch|:put|:delete) # # in the options hash. If the verb is not GET or POST, which are natively # supported by HTML forms, the form will be set to POST and a hidden input @@ -300,7 +300,7 @@ module ActionView # # Specifying: # - # :remote => true + # remote: true # # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular @@ -310,7 +310,7 @@ module ActionView # # Example: # - # <%= form_for(@post, :remote => true) do |f| %> + # <%= form_for(@post, remote: true) do |f| %> # ... # <% end %> # @@ -354,7 +354,7 @@ module ActionView # Example: # # <%= form_for(@post) do |f| %> - # <%= f.fields_for(:comments, :include_id => false) do |cf| %> + # <%= f.fields_for(:comments, include_id: false) do |cf| %> # ... # <% end %> # <% end %> @@ -366,7 +366,7 @@ module ActionView # custom builder. For example, let's say you made a helper to # automatically add labels to form inputs. # - # <%= form_for @person, :url => { :action => "create" }, :builder => LabellingFormBuilder do |f| %> + # <%= form_for @person, url: { action: "create" }, builder: LabellingFormBuilder do |f| %> # <%= f.text_field :first_name %> # <%= f.text_field :last_name %> # <%= f.text_area :biography %> @@ -390,7 +390,7 @@ module ActionView # # def labelled_form_for(record_or_name_or_array, *args, &proc) # options = args.extract_options! - # form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc) + # form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &proc) # end # # If you don't need to attach a form to a model instance, then check out @@ -403,13 +403,13 @@ module ActionView # # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter # - # <%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f| + # <%= form_for @invoice, url: external_url, authenticity_token: 'external_token' do |f| # ... # <% end %> # # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>: # - # <%= form_for @invoice, :url => external_url, :authenticity_token => false do |f| + # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| # ... # <% end %> def form_for(record, options = {}, &proc) @@ -423,7 +423,7 @@ module ActionView object = nil else object = record.is_a?(Array) ? record.last : record - raise ArgumentError, "First argument in form cannot contain nil or be empty" if object.blank? + raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object object_name = options[:as] || model_name_from_record_or_class(object).param_key apply_form_for_options!(record, object, options) end @@ -435,7 +435,7 @@ module ActionView builder = options[:parent_builder] = instantiate_builder(object_name, object, options) fields_for = fields_for(object_name, object, options, &proc) - default_options = builder.multipart? ? { :multipart => true } : {} + default_options = builder.multipart? ? { multipart: true } : {} default_options.merge!(options.delete(:html)) form_tag(options.delete(:url) || {}, default_options) { fields_for } @@ -447,12 +447,12 @@ module ActionView as = options[:as] action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post] options[:html].reverse_merge!( - :class => as ? "#{action}_#{as}" : dom_class(object, action), - :id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence, - :method => method + class: as ? "#{action}_#{as}" : dom_class(object, action), + id: as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence, + method: method ) - options[:url] ||= polymorphic_path(record, :format => options.delete(:format)) + options[:url] ||= polymorphic_path(record, format: options.delete(:format)) end private :apply_form_for_options! @@ -578,7 +578,7 @@ module ActionView # # class Person < ActiveRecord::Base # has_one :address - # accepts_nested_attributes_for :address, :allow_destroy => true + # accepts_nested_attributes_for :address, allow_destroy: true # end # # Now, when you use a form element with the <tt>_destroy</tt> parameter, @@ -674,7 +674,7 @@ module ActionView # # class Person < ActiveRecord::Base # has_many :projects - # accepts_nested_attributes_for :projects, :allow_destroy => true + # accepts_nested_attributes_for :projects, allow_destroy: true # end # # This will allow you to specify which models to destroy in the @@ -747,10 +747,10 @@ module ActionView # label(:post, :title, "A short title") # # => <label for="post_title">A short title</label> # - # label(:post, :title, "A short title", :class => "title_label") + # label(:post, :title, "A short title", class: "title_label") # # => <label for="post_title" class="title_label">A short title</label> # - # label(:post, :privacy, "Public Post", :value => "public") + # label(:post, :privacy, "Public Post", value: "public") # # => <label for="post_privacy_public">Public Post</label> # # label(:post, :terms) do @@ -766,18 +766,17 @@ module ActionView # shown. # # ==== Examples - # text_field(:post, :title, :size => 20) + # text_field(:post, :title, size: 20) # # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" /> # - # text_field(:post, :title, :class => "create_input") + # text_field(:post, :title, class: "create_input") # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" /> # - # text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }") + # text_field(:session, :user, onchange: "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }") # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }"/> # - # text_field(:snippet, :code, :size => 20, :class => 'code_input') + # text_field(:snippet, :code, size: 20, class: 'code_input') # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" /> - # def text_field(object_name, method, options = {}) Tags::TextField.new(object_name, method, self, options).render end @@ -788,18 +787,17 @@ module ActionView # shown. For security reasons this field is blank by default; pass in a value via +options+ if this is not desired. # # ==== Examples - # password_field(:login, :pass, :size => 20) + # password_field(:login, :pass, size: 20) # # => <input type="password" id="login_pass" name="login[pass]" size="20" /> # - # password_field(:account, :secret, :class => "form_input", :value => @account.secret) + # password_field(:account, :secret, class: "form_input", value: @account.secret) # # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" /> # - # password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }") + # password_field(:user, :password, onchange: "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }") # # => <input type="password" id="user_password" name="user[password]" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/> # - # password_field(:account, :pin, :size => 20, :class => 'form_input') + # password_field(:account, :pin, size: 20, class: 'form_input') # # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" /> - # def password_field(object_name, method, options = {}) Tags::PasswordField.new(object_name, method, self, options).render end @@ -833,12 +831,11 @@ module ActionView # file_field(:user, :avatar) # # => <input type="file" id="user_avatar" name="user[avatar]" /> # - # file_field(:post, :attached, :accept => 'text/html') + # file_field(:post, :attached, accept: 'text/html') # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" /> # - # file_field(:attachment, :file, :class => 'file_input') + # file_field(:attachment, :file, class: 'file_input') # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" /> - # def file_field(object_name, method, options = {}) Tags::FileField.new(object_name, method, self, options).render end @@ -848,22 +845,22 @@ module ActionView # hash with +options+. # # ==== Examples - # text_area(:post, :body, :cols => 20, :rows => 40) + # text_area(:post, :body, cols: 20, rows: 40) # # => <textarea cols="20" rows="40" id="post_body" name="post[body]"> # # #{@post.body} # # </textarea> # - # text_area(:comment, :text, :size => "20x30") + # text_area(:comment, :text, size: "20x30") # # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]"> # # #{@comment.text} # # </textarea> # - # text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input') + # text_area(:application, :notes, cols: 40, rows: 15, class: 'app_input') # # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input"> # # #{@application.notes} # # </textarea> # - # text_area(:entry, :body, :size => "20x20", :disabled => 'disabled') + # text_area(:entry, :body, size: "20x20", disabled: 'disabled') # # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled"> # # #{@entry.body} # # </textarea> @@ -902,7 +899,7 @@ module ActionView # Unfortunately that workaround does not work when the check box goes # within an array-like parameter, as in # - # <%= fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %> + # <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %> # <%= form.check_box :paid %> # ... # <% end %> @@ -924,10 +921,9 @@ module ActionView # # => <input name="puppy[gooddog]" type="hidden" value="no" /> # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" /> # - # check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no") + # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no") # # => <input name="eula[accepted]" type="hidden" value="no" /> # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" /> - # def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0") Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render end @@ -936,7 +932,7 @@ module ActionView # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the # radio button will be checked. # - # To force the radio button to be checked pass <tt>:checked => true</tt> in the + # To force the radio button to be checked pass <tt>checked: true</tt> in the # +options+ hash. You may pass HTML options there as well. # # # Let's say that @post.category returns "rails": @@ -957,7 +953,6 @@ module ActionView # # color_field("car", "color") # # => <input id="car_color" name="car[color]" type="color" value="#000000" /> - # def color_field(object_name, method, options = {}) Tags::ColorField.new(object_name, method, self, options).render end @@ -968,18 +963,18 @@ module ActionView # # search_field(:user, :name) # # => <input id="user_name" name="user[name]" type="search" /> - # search_field(:user, :name, :autosave => false) + # search_field(:user, :name, autosave: false) # # => <input autosave="false" id="user_name" name="user[name]" type="search" /> - # search_field(:user, :name, :results => 3) + # search_field(:user, :name, results: 3) # # => <input id="user_name" name="user[name]" results="3" type="search" /> # # Assume request.host returns "www.example.com" - # search_field(:user, :name, :autosave => true) + # search_field(:user, :name, autosave: true) # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" type="search" /> - # search_field(:user, :name, :onsearch => true) + # search_field(:user, :name, onsearch: true) # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" /> - # search_field(:user, :name, :autosave => false, :onsearch => true) + # search_field(:user, :name, autosave: false, onsearch: true) # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" /> - # search_field(:user, :name, :autosave => true, :onsearch => true) + # search_field(:user, :name, autosave: true, onsearch: true) # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" /> def search_field(object_name, method, options = {}) Tags::SearchField.new(object_name, method, self, options).render @@ -1351,7 +1346,7 @@ module ActionView private def objectify_options(options) - @default_options.merge(options.merge(:object => @object)) + @default_options.merge(options.merge(object: @object)) end def submit_default_value @@ -1369,7 +1364,7 @@ module ActionView defaults << :"helpers.submit.#{key}" defaults << "#{key.to_s.humanize} #{model}" - I18n.t(defaults.shift, :model => model, :default => defaults) + I18n.t(defaults.shift, model: model, default: defaults) end def nested_attributes_association?(association_name) 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/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 3f65791aa0..5105d0e585 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -46,7 +46,6 @@ module ActionView end protected :_back_url - # Creates a link tag of the given +name+ using a URL created by the set of +options+. # See the valid options in the documentation for +url_for+. It's also possible to # pass a String instead of an options hash, which generates a link tag that uses the @@ -115,7 +114,7 @@ module ActionView # # in place of the older more verbose, non-resource-oriented # - # link_to "Profile", :controller => "profiles", :action => "show", :id => @profile + # link_to "Profile", controller: "profiles", action: "show", id: @profile # # => <a href="/profiles/show/1">Profile</a> # # Similarly, @@ -125,7 +124,7 @@ module ActionView # # is better than # - # link_to "Profiles", :controller => "profiles" + # link_to "Profiles", controller: "profiles" # # => <a href="/profiles">Profiles</a> # # You can use a block as well if your link target is hard to fit into the name parameter. ERB example: @@ -139,45 +138,46 @@ module ActionView # # Classes and ids for CSS are easy to produce: # - # link_to "Articles", articles_path, :id => "news", :class => "article" + # link_to "Articles", articles_path, id: "news", class: "article" # # => <a href="/articles" class="article" id="news">Articles</a> # # Be careful when using the older argument style, as an extra literal hash is needed: # - # link_to "Articles", { :controller => "articles" }, :id => "news", :class => "article" + # link_to "Articles", { controller: "articles" }, id: "news", class: "article" # # => <a href="/articles" class="article" id="news">Articles</a> # # Leaving the hash off gives the wrong link: # - # link_to "WRONG!", :controller => "articles", :id => "news", :class => "article" + # link_to "WRONG!", controller: "articles", id: "news", class: "article" # # => <a href="/articles/index/news?class=article">WRONG!</a> # # +link_to+ can also produce links with anchors or query strings: # - # link_to "Comment wall", profile_path(@profile, :anchor => "wall") + # link_to "Comment wall", profile_path(@profile, anchor: "wall") # # => <a href="/profiles/1#wall">Comment wall</a> # - # link_to "Ruby on Rails search", :controller => "searches", :query => "ruby on rails" + # link_to "Ruby on Rails search", controller: "searches", query: "ruby on rails" # # => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a> # - # link_to "Nonsense search", searches_path(:foo => "bar", :baz => "quux") + # link_to "Nonsense search", searches_path(foo: "bar", baz: "quux") # # => <a href="/searches?foo=bar&baz=quux">Nonsense search</a> # # The only option specific to +link_to+ (<tt>:method</tt>) is used as follows: # - # link_to("Destroy", "http://www.example.com", :method => :delete) + # link_to("Destroy", "http://www.example.com", method: :delete) # # => <a href='http://www.example.com' rel="nofollow" data-method="delete">Destroy</a> # # You can also use custom data attributes using the <tt>:data</tt> option: # - # link_to "Visit Other Site", "http://www.rubyonrails.org/", :data => { :confirm => "Are you sure?" } + # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" } # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?"">Visit Other Site</a> def link_to(name = nil, options = nil, html_options = nil, &block) html_options, options = options, name if block_given? options ||= {} - url = url_for(options) html_options = convert_options_to_data_attributes(options, html_options) + + url = url_for(options) html_options['href'] ||= url content_tag(:a, name || url, html_options, &block) @@ -196,7 +196,7 @@ module ActionView # the form submission and input element behavior using +html_options+. # This method accepts the <tt>:method</tt> modifier described in the +link_to+ documentation. # If no <tt>:method</tt> modifier is given, it will default to performing a POST operation. - # You can also disable the button by passing <tt>:disabled => true</tt> in +html_options+. + # You can also disable the button by passing <tt>disabled: true</tt> in +html_options+. # If you are using RESTful routes, you can pass the <tt>:method</tt> # to change the HTTP verb used to submit the form. # @@ -225,7 +225,7 @@ module ActionView # by the unobtrusive JavaScript driver. # # ==== Examples - # <%= button_to "New", :action => "new" %> + # <%= button_to "New", action: "new" %> # # => "<form method="post" action="/controller/new" class="button_to"> # # <div><input value="New" type="submit" /></div> # # </form>" @@ -241,13 +241,13 @@ module ActionView # # </div> # # </form>" # - # <%= button_to "New", :action => "new", :form_class => "new-thing" %> + # <%= button_to "New", action: "new", form_class: "new-thing" %> # # => "<form method="post" action="/controller/new" class="new-thing"> # # <div><input value="New" type="submit" /></div> # # </form>" # # - # <%= button_to "Create", :action => "create", :remote => true, :form => { "data-type" => "json" } %> + # <%= button_to "Create", action: "create", remote: true, form: { "data-type" => "json" } %> # # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json"> # # <div> # # <input value="Create" type="submit" /> @@ -256,8 +256,8 @@ module ActionView # # </form>" # # - # <%= button_to "Delete Image", { :action => "delete", :id => @image.id }, - # :method => :delete, :data => { :confirm => "Are you sure?" } %> + # <%= button_to "Delete Image", { action: "delete", id: @image.id }, + # method: :delete, data: { confirm: "Are you sure?" } %> # # => "<form method="post" action="/images/delete/1" class="button_to"> # # <div> # # <input type="hidden" name="_method" value="delete" /> @@ -268,7 +268,7 @@ module ActionView # # # <%= button_to('Destroy', 'http://www.example.com', - # :method => "delete", :remote => true, :data => { :confirm' => 'Are you sure?', :disable_with => 'loading...' }) %> + # method: "delete", remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %> # # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'> # # <div> # # <input name='_method' value='delete' type='hidden' /> @@ -294,7 +294,7 @@ module ActionView form_method = method == 'get' ? 'get' : 'post' form_options = html_options.delete('form') || {} form_options[:class] ||= html_options.delete('form_class') || 'button_to' - form_options.merge!(:method => form_method, :action => url) + form_options.merge!(method: form_method, action: url) form_options.merge!("data-remote" => "true") if remote request_token_tag = form_method == 'post' ? token_tag : '' @@ -313,7 +313,6 @@ module ActionView content_tag('form', content_tag('div', inner_tags), form_options) end - # Creates a link tag of the given +name+ using a URL created by the set of # +options+ unless the current request URI is the same as the links, in # which case only the name is returned (or the given block is yielded, if @@ -325,8 +324,8 @@ module ActionView # Let's say you have a navigation menu... # # <ul id="navbar"> - # <li><%= link_to_unless_current("Home", { :action => "index" }) %></li> - # <li><%= link_to_unless_current("About Us", { :action => "about" }) %></li> + # <li><%= link_to_unless_current("Home", { action: "index" }) %></li> + # <li><%= link_to_unless_current("About Us", { action: "about" }) %></li> # </ul> # # If in the "about" action, it will render... @@ -348,8 +347,8 @@ module ActionView # "Go Back" link instead of a link to the comments page, we could do something like this... # # <%= - # link_to_unless_current("Comment", { :controller => "comments", :action => "new" }) do - # link_to("Go back", { :controller => "posts", :action => "index" }) + # link_to_unless_current("Comment", { controller: "comments", action: "new" }) do + # link_to("Go back", { controller: "posts", action: "index" }) # end # %> def link_to_unless_current(name, options = {}, html_options = {}, &block) @@ -363,13 +362,13 @@ module ActionView # accepts the name or the full argument list for +link_to_unless+. # # ==== Examples - # <%= link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) %> + # <%= link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) %> # # If the user is logged in... # # => <a href="/controller/reply/">Reply</a> # # <%= - # link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) do |name| - # link_to(name, { :controller => "accounts", :action => "signup" }) + # link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) do |name| + # link_to(name, { controller: "accounts", action: "signup" }) # end # %> # # If the user is logged in... @@ -395,13 +394,13 @@ module ActionView # in +link_to_unless+). # # ==== Examples - # <%= link_to_if(@current_user.nil?, "Login", { :controller => "sessions", :action => "new" }) %> + # <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %> # # If the user isn't logged in... # # => <a href="/sessions/new/">Login</a> # # <%= - # link_to_if(@current_user.nil?, "Login", { :controller => "sessions", :action => "new" }) do - # link_to(@current_user.login, { :controller => "accounts", :action => "show", :id => @current_user }) + # link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) do + # link_to(@current_user.login, { controller: "accounts", action: "show", id: @current_user }) # end # %> # # If the user isn't logged in... @@ -442,17 +441,17 @@ module ActionView # mail_to "me@domain.com" # # => <a href="mailto:me@domain.com">me@domain.com</a> # - # mail_to "me@domain.com", "My email", :encode => "javascript" + # mail_to "me@domain.com", "My email", encode: "javascript" # # => <script>eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script> # - # mail_to "me@domain.com", "My email", :encode => "hex" + # mail_to "me@domain.com", "My email", encode: "hex" # # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a> # - # mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email" + # mail_to "me@domain.com", nil, replace_at: "_at_", replace_dot: "_dot_", class: "email" # # => <a href="mailto:me@domain.com" class="email">me_at_domain_dot_com</a> # - # mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com", - # :subject => "This is an example email" + # mail_to "me@domain.com", "My email", cc: "ccaddress@domain.com", + # subject: "This is an example email" # # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a> def mail_to(email_address, name = nil, html_options = {}) email_address = ERB::Util.html_escape(email_address) @@ -501,47 +500,47 @@ module ActionView # ==== Examples # Let's say we're in the <tt>/shop/checkout?order=desc</tt> action. # - # current_page?(:action => 'process') + # current_page?(action: 'process') # # => false # - # current_page?(:controller => 'shop', :action => 'checkout') + # current_page?(controller: 'shop', action: 'checkout') # # => true # - # current_page?(:controller => 'shop', :action => 'checkout', :order => 'asc') + # current_page?(controller: 'shop', action: 'checkout', order: 'asc') # # => false # - # current_page?(:action => 'checkout') + # current_page?(action: 'checkout') # # => true # - # current_page?(:controller => 'library', :action => 'checkout') + # current_page?(controller: 'library', action: 'checkout') # # => false # # Let's say we're in the <tt>/shop/checkout?order=desc&page=1</tt> action. # - # current_page?(:action => 'process') + # current_page?(action: 'process') # # => false # - # current_page?(:controller => 'shop', :action => 'checkout') + # current_page?(controller: 'shop', action: 'checkout') # # => true # - # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page => '1') + # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1') # # => true # - # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page => '2') + # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2') # # => false # - # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc') + # current_page?(controller: 'shop', action: 'checkout', order: 'desc') # # => false # - # current_page?(:action => 'checkout') + # current_page?(action: 'checkout') # # => true # - # current_page?(:controller => 'library', :action => 'checkout') + # current_page?(controller: 'library', action: 'checkout') # # => false # # Let's say we're in the <tt>/products</tt> action with method POST in case of invalid product. # - # current_page?(:controller => 'product', :action => 'index') + # current_page?(controller: 'product', action: 'index') # # => false # def current_page?(options) @@ -598,7 +597,9 @@ module ActionView end def link_to_remote_options?(options) - options.is_a?(Hash) && options.delete('remote') + if options.is_a?(Hash) + options.delete('remote') || options.delete(:remote) + end end def add_method_to_attributes!(html_options, method) @@ -639,14 +640,14 @@ module ActionView def token_tag(token=nil) if token != false && protect_against_forgery? token ||= form_authenticity_token - tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token) + tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token) else '' end end def method_tag(method) - tag('input', :type => 'hidden', :name => '_method', :value => method.to_s) + tag('input', type: 'hidden', name: '_method', value: method.to_s) end end end diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb index cc3a871576..fd9a543e0a 100644 --- a/actionpack/lib/action_view/log_subscriber.rb +++ b/actionpack/lib/action_view/log_subscriber.rb @@ -3,10 +3,13 @@ module ActionView # # Provides functionality so that Rails can output logs from Action View. class LogSubscriber < ActiveSupport::LogSubscriber + VIEWS_PATTERN = /^app\/views\//.freeze + def render_template(event) + return unless logger.info? message = " Rendered #{from_rails_root(event.payload[:identifier])}" message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] - message << (" (%.1fms)" % event.duration) + message << " (#{event.duration.round(1)}ms)" info(message) end alias :render_partial :render_template @@ -19,7 +22,7 @@ module ActionView protected def from_rails_root(string) - string.sub("#{Rails.root}/", "").sub(/^app\/views\//, "") + string.sub("#{Rails.root}/", "").sub(VIEWS_PATTERN, "") end end end diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb index 2d36deaa78..3875d88a9f 100644 --- a/actionpack/lib/action_view/railtie.rb +++ b/actionpack/lib/action_view/railtie.rb @@ -5,8 +5,6 @@ module ActionView # = Action View Railtie class Railtie < Rails::Railtie config.action_view = ActiveSupport::OrderedOptions.new - config.action_view.stylesheet_expansions = {} - config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) } config.action_view.embed_authenticity_token_in_remote_forms = false config.eager_load_namespaces << ActionView @@ -22,26 +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.javascript_expansions" do |app| - ActiveSupport.on_load(:action_view) do - ActionView::Helpers::AssetTagHelper.register_javascript_expansion( - app.config.action_view.delete(:javascript_expansions) - ) - - ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion( - app.config.action_view.delete(:stylesheet_expansions) - ) - 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 b7fd6ec7e1..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 @@ -196,17 +217,17 @@ module ActionView :@_result, :@_routes, :@controller, - :@layouts, - :@locals, + :@_layouts, + :@_rendered_views, :@method_name, :@output_buffer, - :@partials, + :@_partials, :@passed, :@rendered, :@request, :@routes, :@tagged_logger, - :@templates, + :@_templates, :@options, :@test_passed, :@view, diff --git a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb b/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb index 1eadfc0390..60b6783b19 100644 --- a/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb +++ b/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb @@ -160,7 +160,7 @@ module HTML # * <tt>:not(selector)</tt> -- Match the element only if the element does not # match the simple selector. # - # As you can see, <tt>:nth-child<tt> pseudo class and its variant can get quite + # As you can see, <tt>:nth-child</tt> pseudo class and its variant can get quite # tricky and the CSS specification doesn't do a much better job explaining it. # But after reading the examples and trying a few combinations, it's easy to # figure out. diff --git a/actionpack/test/active_record_unit.rb b/actionpack/test/active_record_unit.rb index 4f2b052720..4dd7406798 100644 --- a/actionpack/test/active_record_unit.rb +++ b/actionpack/test/active_record_unit.rb @@ -11,7 +11,7 @@ class ActiveRecordTestConnector end # Try to grab AR -unless defined?(ActiveRecord) && defined?(Fixtures) +unless defined?(ActiveRecord) && defined?(FixtureSet) begin PATH_TO_AR = "#{File.dirname(__FILE__)}/../../activerecord/lib" raise LoadError, "#{PATH_TO_AR} doesn't exist" unless File.directory?(PATH_TO_AR) diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 620479cb0c..65c18dfb64 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -6,744 +6,40 @@ CACHE_DIR = 'test_cache' # Don't change '/../temp/' cavalierly or you might hose something you don't want hosed FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR) -class CachingMetalController < ActionController::Metal +class FragmentCachingMetalTestController < ActionController::Metal abstract! include ActionController::Caching - self.page_cache_directory = FILE_STORE_PATH - self.cache_store = :file_store, FILE_STORE_PATH -end - -class PageCachingMetalTestController < CachingMetalController - caches_page :ok - - def ok - self.response_body = 'ok' - end + def some_action; end end -class PageCachingMetalTest < ActionController::TestCase - tests PageCachingMetalTestController - +class FragmentCachingMetalTest < ActionController::TestCase def setup - FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) - FileUtils.mkdir_p(FILE_STORE_PATH) - end - - def teardown - FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) + super + @store = ActiveSupport::Cache::MemoryStore.new + @controller = FragmentCachingMetalTestController.new + @controller.perform_caching = true + @controller.cache_store = @store + @params = { controller: 'posts', action: 'index'} + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @controller.params = @params + @controller.request = @request + @controller.response = @response end - def test_should_cache_get_with_ok_status - get :ok - assert_response :ok - assert File.exist?("#{FILE_STORE_PATH}/page_caching_metal_test/ok.html"), 'get with ok status should have been cached' + def test_fragment_cache_key + assert_equal 'views/what a key', @controller.fragment_cache_key('what a key') end end -ActionController::Base.page_cache_directory = FILE_STORE_PATH - class CachingController < ActionController::Base abstract! self.cache_store = :file_store, FILE_STORE_PATH end -class PageCachingTestController < CachingController - self.page_cache_compression = :best_compression - - caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? } - caches_page :found, :not_found - caches_page :about_me - caches_page :default_gzip - caches_page :no_gzip, :gzip => false - caches_page :gzip_level, :gzip => :best_speed - - def ok - head :ok - end - - def no_content - head :no_content - end - - def found - redirect_to :action => 'ok' - end - - def not_found - head :not_found - end - - def custom_path - render :text => "Super soaker" - cache_page("Super soaker", "/index.html") - end - - def default_gzip - render :text => "Text" - end - - def no_gzip - render :text => "PNG" - end - - def gzip_level - render :text => "Big text" - end - - def expire_custom_path - expire_page("/index.html") - head :ok - end - - def trailing_slash - render :text => "Sneak attack" - end - - def about_me - respond_to do |format| - format.html {render :text => 'I am html'} - format.xml {render :text => 'I am xml'} - end - end - -end - -class PageCachingTest < ActionController::TestCase - def setup - super - - @request = ActionController::TestRequest.new - @request.host = 'hostname.com' - @request.env.delete('PATH_INFO') - - @controller = PageCachingTestController.new - @controller.perform_caching = true - @controller.cache_store = :file_store, FILE_STORE_PATH - - @response = ActionController::TestResponse.new - - @params = {:controller => 'posts', :action => 'index', :only_path => true} - - FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) - FileUtils.mkdir_p(FILE_STORE_PATH) - end - - def teardown - FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) - @controller.perform_caching = false - end - - def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route - with_routing do |set| - set.draw do - get 'posts.:format', :to => 'posts#index', :as => :formatted_posts - get '/', :to => 'posts#index', :as => :main - end - @params[:format] = 'rss' - assert_equal '/posts.rss', @routes.url_for(@params) - @params[:format] = nil - assert_equal '/', @routes.url_for(@params) - end - end - - def test_should_cache_get_with_ok_status - get :ok - assert_response :ok - assert_page_cached :ok, "get with ok status should have been cached" - end - - def test_should_cache_with_custom_path - get :custom_path - assert File.exist?("#{FILE_STORE_PATH}/index.html") - end - - def test_should_expire_cache_with_custom_path - get :custom_path - assert File.exist?("#{FILE_STORE_PATH}/index.html") - - get :expire_custom_path - assert !File.exist?("#{FILE_STORE_PATH}/index.html") - end - - def test_should_gzip_cache - get :custom_path - assert File.exist?("#{FILE_STORE_PATH}/index.html.gz") - - get :expire_custom_path - assert !File.exist?("#{FILE_STORE_PATH}/index.html.gz") - end - - def test_should_allow_to_disable_gzip - get :no_gzip - assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html") - assert !File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html.gz") - end - - def test_should_use_config_gzip_by_default - @controller.expects(:cache_page).with(nil, nil, Zlib::BEST_COMPRESSION) - get :default_gzip - end - - def test_should_set_gzip_level - @controller.expects(:cache_page).with(nil, nil, Zlib::BEST_SPEED) - get :gzip_level - end - - def test_should_cache_without_trailing_slash_on_url - @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash' - assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html") - end - - def test_should_obey_http_accept_attribute - @request.env['HTTP_ACCEPT'] = 'text/xml' - get :about_me - assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/about_me.xml") - assert_equal 'I am xml', @response.body - end - - def test_cached_page_should_not_have_trailing_slash_even_if_url_has_trailing_slash - @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash/' - assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html") - end - - def test_should_cache_ok_at_custom_path - @request.env['PATH_INFO'] = '/index.html' - get :ok - assert_response :ok - assert File.exist?("#{FILE_STORE_PATH}/index.html") - end - - [:ok, :no_content, :found, :not_found].each do |status| - [:get, :post, :patch, :put, :delete].each do |method| - unless method == :get && status == :ok - define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do - send(method, status) - assert_response status - assert_page_not_cached status, "#{method} with #{status} status shouldn't have been cached" - end - end - end - end - - def test_page_caching_conditional_options - get :ok, :format=>'json' - assert_page_not_cached :ok - end - - def test_page_caching_directory_set_as_pathname - begin - ActionController::Base.page_cache_directory = Pathname.new(FILE_STORE_PATH) - get :ok - assert_response :ok - assert_page_cached :ok - ensure - ActionController::Base.page_cache_directory = FILE_STORE_PATH - end - end - - private - def assert_page_cached(action, message = "#{action} should have been cached") - assert page_cached?(action), message - end - - def assert_page_not_cached(action, message = "#{action} shouldn't have been cached") - assert !page_cached?(action), message - end - - def page_cached?(action) - File.exist? "#{FILE_STORE_PATH}/page_caching_test/#{action}.html" - end -end - -class ActionCachingTestController < CachingController - rescue_from(Exception) { head 500 } - rescue_from(ActionController::UnknownFormat) { head :not_acceptable } - if defined? ActiveRecord - rescue_from(ActiveRecord::RecordNotFound) { head :not_found } - end - - # Eliminate uninitialized ivar warning - before_filter { @title = nil } - - caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| c.request.format && !c.request.format.json? }, :expires_in => 1.hour - caches_action :show, :cache_path => 'http://test.host/custom/show' - caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" } - caches_action :with_layout - caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } } - caches_action :layout_false, :layout => false - caches_action :with_layout_proc_param, :layout => Proc.new { |c| c.params[:layout] } - caches_action :record_not_found, :four_oh_four, :simple_runtime_error - caches_action :streaming - caches_action :invalid - - layout 'talk_from_action' - - def index - @cache_this = MockTime.now.to_f.to_s - render :text => @cache_this - end - - def redirected - redirect_to :action => 'index' - end - - def forbidden - render :text => "Forbidden" - response.status = "403 Forbidden" - end - - def with_layout - @cache_this = MockTime.now.to_f.to_s - @title = nil - render :text => @cache_this, :layout => true - end - - def with_format_and_http_param - @cache_this = MockTime.now.to_f.to_s - render :text => @cache_this - end - - def record_not_found - raise ActiveRecord::RecordNotFound, "oops!" - end - - def four_oh_four - render :text => "404'd!", :status => 404 - end - - def simple_runtime_error - raise "oops!" - end - - alias_method :show, :index - alias_method :edit, :index - alias_method :destroy, :index - alias_method :layout_false, :with_layout - alias_method :with_layout_proc_param, :with_layout - - def expire - expire_action :controller => 'action_caching_test', :action => 'index' - render :nothing => true - end - - def expire_xml - expire_action :controller => 'action_caching_test', :action => 'index', :format => 'xml' - render :nothing => true - end - - def expire_with_url_string - expire_action url_for(:controller => 'action_caching_test', :action => 'index') - render :nothing => true - end - - def streaming - render :text => "streaming", :stream => true - end - - def invalid - @cache_this = MockTime.now.to_f.to_s - - respond_to do |format| - format.json{ render :json => @cache_this } - end - end -end - -class MockTime < Time - # Let Time spicy to assure that Time.now != Time.now - def to_f - super+rand - end -end - -class ActionCachingMockController - attr_accessor :mock_url_for - attr_accessor :mock_path - - def initialize - yield self if block_given? - end - - def url_for(*args) - @mock_url_for - end - - def params - request.parameters - end - - def request - Object.new.instance_eval(<<-EVAL) - def path; '#{@mock_path}' end - def format; 'all' end - def parameters; {:format => nil}; end - self - EVAL - end -end - -class ActionCacheTest < ActionController::TestCase - tests ActionCachingTestController - - def setup - super - @request.host = 'hostname.com' - FileUtils.mkdir_p(FILE_STORE_PATH) - @path_class = ActionController::Caching::Actions::ActionCachePath - @mock_controller = ActionCachingMockController.new - end - - def teardown - super - FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) - end - - def test_simple_action_cache - get :index - assert_response :success - cached_time = content_to_cache - assert_equal cached_time, @response.body - assert fragment_exist?('hostname.com/action_caching_test') - - get :index - assert_response :success - assert_equal cached_time, @response.body - end - - def test_simple_action_not_cached - get :destroy - assert_response :success - cached_time = content_to_cache - assert_equal cached_time, @response.body - assert !fragment_exist?('hostname.com/action_caching_test/destroy') - - get :destroy - assert_response :success - assert_not_equal cached_time, @response.body - end - - include RackTestUtils - - def test_action_cache_with_layout - get :with_layout - assert_response :success - cached_time = content_to_cache - assert_not_equal cached_time, @response.body - assert fragment_exist?('hostname.com/action_caching_test/with_layout') - - get :with_layout - assert_response :success - assert_not_equal cached_time, @response.body - body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout')) - assert_equal @response.body, body - end - - def test_action_cache_with_layout_and_layout_cache_false - get :layout_false - assert_response :success - cached_time = content_to_cache - assert_not_equal cached_time, @response.body - assert fragment_exist?('hostname.com/action_caching_test/layout_false') - - get :layout_false - assert_response :success - assert_not_equal cached_time, @response.body - body = body_to_string(read_fragment('hostname.com/action_caching_test/layout_false')) - assert_equal cached_time, body - end - - def test_action_cache_with_layout_and_layout_cache_false_via_proc - get :with_layout_proc_param, :layout => false - assert_response :success - cached_time = content_to_cache - assert_not_equal cached_time, @response.body - assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param') - - get :with_layout_proc_param, :layout => false - assert_response :success - assert_not_equal cached_time, @response.body - body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param')) - assert_equal cached_time, body - end - - def test_action_cache_with_layout_and_layout_cache_true_via_proc - get :with_layout_proc_param, :layout => true - assert_response :success - cached_time = content_to_cache - assert_not_equal cached_time, @response.body - assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param') - - get :with_layout_proc_param, :layout => true - assert_response :success - assert_not_equal cached_time, @response.body - body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param')) - assert_equal @response.body, body - end - - def test_action_cache_conditional_options - @request.env['HTTP_ACCEPT'] = 'application/json' - get :index - assert_response :success - assert !fragment_exist?('hostname.com/action_caching_test') - end - - def test_action_cache_with_format_and_http_param - get :with_format_and_http_param, :format => 'json' - assert_response :success - assert !fragment_exist?('hostname.com/action_caching_test/with_format_and_http_param.json?key=value.json') - assert fragment_exist?('hostname.com/action_caching_test/with_format_and_http_param.json?key=value') - end - - def test_action_cache_with_store_options - MockTime.expects(:now).returns(12345).once - @controller.expects(:read_fragment).with('hostname.com/action_caching_test', :expires_in => 1.hour).once - @controller.expects(:write_fragment).with('hostname.com/action_caching_test', '12345.0', :expires_in => 1.hour).once - get :index - assert_response :success - end - - def test_action_cache_with_custom_cache_path - get :show - assert_response :success - cached_time = content_to_cache - assert_equal cached_time, @response.body - assert fragment_exist?('test.host/custom/show') - - get :show - assert_response :success - assert_equal cached_time, @response.body - end - - def test_action_cache_with_custom_cache_path_in_block - get :edit - assert_response :success - assert fragment_exist?('test.host/edit') - - get :edit, :id => 1 - assert_response :success - assert fragment_exist?('test.host/1;edit') - end - - def test_cache_expiration - get :index - assert_response :success - cached_time = content_to_cache - - get :index - assert_response :success - assert_equal cached_time, @response.body - - get :expire - assert_response :success - - get :index - assert_response :success - new_cached_time = content_to_cache - assert_not_equal cached_time, @response.body - - get :index - assert_response :success - assert_equal new_cached_time, @response.body - end - - def test_cache_expiration_isnt_affected_by_request_format - get :index - cached_time = content_to_cache - - @request.request_uri = "/action_caching_test/expire.xml" - get :expire, :format => :xml - assert_response :success - - get :index - assert_response :success - assert_not_equal cached_time, @response.body - end - - def test_cache_expiration_with_url_string - get :index - cached_time = content_to_cache - - @request.request_uri = "/action_caching_test/expire_with_url_string" - get :expire_with_url_string - assert_response :success - - get :index - assert_response :success - assert_not_equal cached_time, @response.body - end - - def test_cache_is_scoped_by_subdomain - @request.host = 'jamis.hostname.com' - get :index - assert_response :success - jamis_cache = content_to_cache - - @request.host = 'david.hostname.com' - get :index - assert_response :success - david_cache = content_to_cache - assert_not_equal jamis_cache, @response.body - - @request.host = 'jamis.hostname.com' - get :index - assert_response :success - assert_equal jamis_cache, @response.body - - @request.host = 'david.hostname.com' - get :index - assert_response :success - assert_equal david_cache, @response.body - end - - def test_redirect_is_not_cached - get :redirected - assert_response :redirect - get :redirected - assert_response :redirect - end - - def test_forbidden_is_not_cached - get :forbidden - assert_response :forbidden - get :forbidden - assert_response :forbidden - end - - def test_xml_version_of_resource_is_treated_as_different_cache - with_routing do |set| - set.draw do - get ':controller(/:action(.:format))' - end - - get :index, :format => 'xml' - assert_response :success - cached_time = content_to_cache - assert_equal cached_time, @response.body - assert fragment_exist?('hostname.com/action_caching_test/index.xml') - - get :index, :format => 'xml' - assert_response :success - assert_equal cached_time, @response.body - assert_equal 'application/xml', @response.content_type - - get :expire_xml - assert_response :success - - get :index, :format => 'xml' - assert_response :success - assert_not_equal cached_time, @response.body - end - end - - def test_correct_content_type_is_returned_for_cache_hit - # run it twice to cache it the first time - get :index, :id => 'content-type', :format => 'xml' - get :index, :id => 'content-type', :format => 'xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - end - - def test_correct_content_type_is_returned_for_cache_hit_on_action_with_string_key - # run it twice to cache it the first time - get :show, :format => 'xml' - get :show, :format => 'xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - end - - def test_correct_content_type_is_returned_for_cache_hit_on_action_with_string_key_from_proc - # run it twice to cache it the first time - get :edit, :id => 1, :format => 'xml' - get :edit, :id => 1, :format => 'xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - end - - def test_empty_path_is_normalized - @mock_controller.mock_url_for = 'http://example.org/' - @mock_controller.mock_path = '/' - - assert_equal 'example.org/index', @path_class.new(@mock_controller, {}).path - end - - def test_file_extensions - get :index, :id => 'kitten.jpg' - get :index, :id => 'kitten.jpg' - - assert_response :success - end - - if defined? ActiveRecord - def test_record_not_found_returns_404_for_multiple_requests - get :record_not_found - assert_response 404 - get :record_not_found - assert_response 404 - end - end - - def test_four_oh_four_returns_404_for_multiple_requests - get :four_oh_four - assert_response 404 - get :four_oh_four - assert_response 404 - end - - def test_four_oh_four_renders_content - get :four_oh_four - assert_equal "404'd!", @response.body - end - - def test_simple_runtime_error_returns_500_for_multiple_requests - get :simple_runtime_error - assert_response 500 - get :simple_runtime_error - assert_response 500 - end - - def test_action_caching_plus_streaming - get :streaming - assert_response :success - assert_match(/streaming/, @response.body) - assert fragment_exist?('hostname.com/action_caching_test/streaming') - end - - def test_invalid_format_returns_not_acceptable - get :invalid, :format => "json" - assert_response :success - cached_time = content_to_cache - assert_equal cached_time, @response.body - - assert fragment_exist?("hostname.com/action_caching_test/invalid.json") - - get :invalid, :format => "json" - assert_response :success - assert_equal cached_time, @response.body - - get :invalid, :format => "xml" - assert_response :not_acceptable - - get :invalid, :format => "\xC3\x83" - assert_response :not_acceptable - end - - private - def content_to_cache - assigns(:cache_this) - end - - def fragment_exist?(path) - @controller.fragment_exist?(path) - end - - def read_fragment(path) - @controller.read_fragment(path) - end -end - class FragmentCachingTestController < CachingController def some_action; end; end @@ -988,3 +284,17 @@ class CacheHelperOutputBufferTest < ActionController::TestCase end end +class DeprecatedPageCacheExtensionTest < ActiveSupport::TestCase + def test_page_cache_extension_binds_default_static_extension + deprecation_behavior = ActiveSupport::Deprecation.behavior + ActiveSupport::Deprecation.behavior = :silence + old_extension = ActionController::Base.default_static_extension + + ActionController::Base.page_cache_extension = '.rss' + + assert_equal '.rss', ActionController::Base.default_static_extension + ensure + ActiveSupport::Deprecation.behavior = deprecation_behavior + ActionController::Base.default_static_extension = old_extension + end +end diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index a72b6dde1a..9efb6ab95f 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -42,11 +42,6 @@ module Another render :inline => "<%= cache('foo%bar'){ 'Contains % sign in key' } %>" end - def with_page_cache - cache_page("Super soaker", "/index.html") - render :nothing => true - end - def with_exception raise Exception end @@ -71,7 +66,6 @@ class ACLogSubscriberTest < ActionController::TestCase @old_logger = ActionController::Base.logger @cache_path = File.expand_path('../temp/test_cache', File.dirname(__FILE__)) - ActionController::Base.page_cache_directory = @cache_path @controller.cache_store = :file_store, @cache_path ActionController::LogSubscriber.attach_to :action_controller end @@ -199,18 +193,6 @@ class ACLogSubscriberTest < ActionController::TestCase @controller.config.perform_caching = true end - def test_with_page_cache - @controller.config.perform_caching = true - get :with_page_cache - wait - - assert_equal 3, logs.size - assert_match(/Write page/, logs[1]) - assert_match(/\/index\.html/, logs[1]) - ensure - @controller.config.perform_caching = true - end - def test_process_action_with_exception_includes_http_status_code begin get :with_exception diff --git a/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb new file mode 100644 index 0000000000..15338059bc --- /dev/null +++ b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb @@ -0,0 +1,38 @@ +require 'abstract_unit' +require 'action_controller/metal/strong_parameters' + +class MultiParameterAttributesTest < ActiveSupport::TestCase + test "permitted multi-parameter attribute keys" do + params = ActionController::Parameters.new({ + book: { + "shipped_at(1i)" => "2012", + "shipped_at(2i)" => "3", + "shipped_at(3i)" => "25", + "shipped_at(4i)" => "10", + "shipped_at(5i)" => "15", + "published_at(1i)" => "1999", + "published_at(2i)" => "2", + "published_at(3i)" => "5", + "price(1)" => "R$", + "price(2f)" => "2.02" + } + }) + + permitted = params.permit book: [ :shipped_at, :price ] + + assert permitted.permitted? + + assert_equal "2012", permitted[:book]["shipped_at(1i)"] + assert_equal "3", permitted[:book]["shipped_at(2i)"] + assert_equal "25", permitted[:book]["shipped_at(3i)"] + assert_equal "10", permitted[:book]["shipped_at(4i)"] + assert_equal "15", permitted[:book]["shipped_at(5i)"] + + assert_equal "R$", permitted[:book]["price(1)"] + assert_equal "2.02", permitted[:book]["price(2f)"] + + assert_nil permitted[:book]["published_at(1i)"] + assert_nil permitted[:book]["published_at(2i)"] + assert_nil permitted[:book]["published_at(3i)"] + end +end diff --git a/actionpack/test/controller/parameters/nested_parameters_test.rb b/actionpack/test/controller/parameters/nested_parameters_test.rb index 41f5b6e127..d287e79cba 100644 --- a/actionpack/test/controller/parameters/nested_parameters_test.rb +++ b/actionpack/test/controller/parameters/nested_parameters_test.rb @@ -15,18 +15,22 @@ class NestedParametersTest < ActiveSupport::TestCase details: { pages: 200, genre: "Tragedy" + }, + id: { + isbn: 'x' } }, magazine: "Mjallo!" }) - permitted = params.permit book: [ :title, { authors: [ :name ] }, { details: :pages } ] + permitted = params.permit book: [ :title, { authors: [ :name ] }, { details: :pages }, :id ] assert permitted.permitted? assert_equal "Romeo and Juliet", permitted[:book][:title] assert_equal "William Shakespeare", permitted[:book][:authors][0][:name] assert_equal "Christopher Marlowe", permitted[:book][:authors][1][:name] assert_equal 200, permitted[:book][:details][:pages] + assert_nil permitted[:book][:id] assert_nil permitted[:book][:details][:genre] assert_nil permitted[:book][:authors][0][:born] assert_nil permitted[:magazine] diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index 7fe8e6051b..ad970f0a9a 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -3,7 +3,7 @@ require 'action_controller/metal/strong_parameters' class ParametersPermitTest < ActiveSupport::TestCase setup do - @params = ActionController::Parameters.new({ person: { + @params = ActionController::Parameters.new({ person: { age: "32", name: { first: "David", last: "Heinemeier Hansson" } }}) end @@ -57,6 +57,13 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal @params.permitted?, @params.dup.permitted? end + test "permit is recursive" do + @params.permit! + assert @params.permitted? + assert @params[:person].permitted? + assert @params[:person][:name].permitted? + end + test "permitted takes a default value when Parameters.permit_all_parameters is set" do begin ActionController::Parameters.permit_all_parameters = true diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index fd8f87e377..aa33f01d02 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -1428,6 +1428,17 @@ class RenderTest < ActionController::TestCase assert_equal "Bonjour: davidBonjour: mary", @response.body end + def test_locals_option_to_assert_template_is_not_supported + warning_buffer = StringIO.new + $stderr = warning_buffer + + get :partial_collection_with_locals + assert_template partial: 'customer_greeting', locals: { greeting: 'Bonjour' } + assert_equal "the :locals option to #assert_template is only supported in a ActionView::TestCase\n", warning_buffer.string + ensure + $stderr = STDERR + end + def test_partial_collection_with_spacer get :partial_collection_with_spacer assert_equal "Hello: davidonly partialHello: mary", @response.body diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb index 351b9c4cfa..ab1bd0e3b6 100644 --- a/actionpack/test/controller/show_exceptions_test.rb +++ b/actionpack/test/controller/show_exceptions_test.rb @@ -93,4 +93,20 @@ module ShowExceptions assert_equal 'text/html', response.content_type.to_s end end + + class ShowFailsafeExceptionsTest < ActionDispatch::IntegrationTest + def test_render_failsafe_exception + @app = ShowExceptionsOverridenController.action(:boom) + @exceptions_app = @app.instance_variable_get(:@exceptions_app) + @app.instance_variable_set(:@exceptions_app, nil) + $stderr = StringIO.new + + get '/', {}, 'HTTP_ACCEPT' => 'text/json' + assert_response :internal_server_error + assert_equal 'text/plain', response.content_type.to_s + + @app.instance_variable_set(:@exceptions_app, @exceptions_app) + $stderr = STDERR + end + end end diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb index bc7cad8db5..42432510c3 100644 --- a/actionpack/test/dispatch/header_test.rb +++ b/actionpack/test/dispatch/header_test.rb @@ -7,6 +7,26 @@ class HeaderTest < ActiveSupport::TestCase ) end + def test_each + headers = [] + @headers.each { |pair| headers << pair } + assert_equal [["HTTP_CONTENT_TYPE", "text/plain"]], headers + end + + def test_setter + @headers['foo'] = "bar" + assert_equal "bar", @headers['foo'] + end + + def test_key? + assert @headers.key?('HTTP_CONTENT_TYPE') + assert @headers.include?('HTTP_CONTENT_TYPE') + end + + def test_fetch_with_block + assert_equal 'omg', @headers.fetch('notthere') { 'omg' } + end + test "content type" do assert_equal "text/plain", @headers["Content-Type"] assert_equal "text/plain", @headers["content-type"] diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 4e83ad16d7..93d89f7568 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1124,6 +1124,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/sheep/1/_it', _it_sheep_path(1) end + def test_resource_does_not_modify_passed_options + options = {:id => /.+?/, :format => /json|xml/} + self.class.stub_controllers do |routes| + routes.draw do + resource :user, options + end + end + assert_equal({:id => /.+?/, :format => /json|xml/}, options) + end + + def test_resources_does_not_modify_passed_options + options = {:id => /.+?/, :format => /json|xml/} + self.class.stub_controllers do |routes| + routes.draw do + resources :users, options + end + end + assert_equal({:id => /.+?/, :format => /json|xml/}, options) + end + def test_path_names get '/pt/projetos' assert_equal 'projects#index', @response.body diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb index a74e165826..b8479e8836 100644 --- a/actionpack/test/dispatch/session/cache_store_test.rb +++ b/actionpack/test/dispatch/session/cache_store_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'fixtures/session_autoload_test/session_autoload_test/foo' class CacheStoreTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base diff --git a/actionpack/test/fixtures/digestor/level/below/_header.html.erb b/actionpack/test/fixtures/digestor/level/below/_header.html.erb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/actionpack/test/fixtures/digestor/level/below/_header.html.erb diff --git a/actionpack/test/fixtures/digestor/level/below/index.html.erb b/actionpack/test/fixtures/digestor/level/below/index.html.erb new file mode 100644 index 0000000000..b92f49a8f8 --- /dev/null +++ b/actionpack/test/fixtures/digestor/level/below/index.html.erb @@ -0,0 +1 @@ +<%= render partial: "header" %> diff --git a/actionpack/test/fixtures/test/render_two_partials.html.erb b/actionpack/test/fixtures/test/render_two_partials.html.erb new file mode 100644 index 0000000000..3db6025860 --- /dev/null +++ b/actionpack/test/fixtures/test/render_two_partials.html.erb @@ -0,0 +1,2 @@ +<%= render :partial => 'partial', :locals => {'first' => '1'} %> +<%= render :partial => 'partial', :locals => {'second' => '2'} %> diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index a04694714d..eb1a54a81f 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -13,27 +13,10 @@ end class AssetTagHelperTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper + attr_reader :request + def setup super - silence_warnings do - ActionView::Helpers::AssetTagHelper.send( - :const_set, - :JAVASCRIPTS_DIR, - File.dirname(__FILE__) + "/../fixtures/public/javascripts" - ) - - ActionView::Helpers::AssetTagHelper.send( - :const_set, - :STYLESHEETS_DIR, - File.dirname(__FILE__) + "/../fixtures/public/stylesheets" - ) - - ActionView::Helpers::AssetTagHelper.send( - :const_set, - :ASSETS_DIR, - File.dirname(__FILE__) + "/../fixtures/public" - ) - end @controller = BasicController.new @@ -42,24 +25,33 @@ class AssetTagHelperTest < ActionView::TestCase def protocol() 'http://' end def ssl?() false end def host_with_port() 'localhost' end + def base_url() 'http://www.example.com' end end.new @controller.request = @request - - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :defaults => ['prototype', 'effects', 'dragdrop', 'controls', 'rails'] end def url_for(*args) "http://www.example.com" end - def teardown - config.perform_caching = false - ENV.delete('RAILS_ASSET_ID') + AssetPathToTag = { + %(asset_path("foo")) => %(/foo), + %(asset_path("style.css")) => %(/style.css), + %(asset_path("xmlhr.js")) => %(/xmlhr.js), + %(asset_path("xml.png")) => %(/xml.png), + %(asset_path("dir/xml.png")) => %(/dir/xml.png), + %(asset_path("/dir/xml.png")) => %(/dir/xml.png), - JavascriptIncludeTag.expansions.clear - StylesheetIncludeTag.expansions.clear - end + %(asset_path("script.min")) => %(/script.min), + %(asset_path("script.min.js")) => %(/script.min.js), + %(asset_path("style.min")) => %(/style.min), + %(asset_path("style.min.css")) => %(/style.min.css), + + %(asset_path("style", type: :stylesheet)) => %(/stylesheets/style.css), + %(asset_path("xmlhr", type: :javascript)) => %(/javascripts/xmlhr.js), + %(asset_path("xml.png", type: :image)) => %(/images/xml.png) + } AutoDiscoveryToTag = { %(auto_discovery_link_tag) => %(<link href="http://www.example.com" rel="alternate" title="RSS" type="application/rss+xml" />), @@ -79,7 +71,14 @@ class AssetTagHelperTest < ActionView::TestCase JavascriptPathToTag = { %(javascript_path("xmlhr")) => %(/javascripts/xmlhr.js), %(javascript_path("super/xmlhr")) => %(/javascripts/super/xmlhr.js), - %(javascript_path("/super/xmlhr.js")) => %(/super/xmlhr.js) + %(javascript_path("/super/xmlhr.js")) => %(/super/xmlhr.js), + %(javascript_path("xmlhr.min")) => %(/javascripts/xmlhr.min.js), + %(javascript_path("xmlhr.min.js")) => %(/javascripts/xmlhr.min.js), + + %(javascript_path("xmlhr.js?123")) => %(/javascripts/xmlhr.js?123), + %(javascript_path("xmlhr.js?body=1")) => %(/javascripts/xmlhr.js?body=1), + %(javascript_path("xmlhr.js#hash")) => %(/javascripts/xmlhr.js#hash), + %(javascript_path("xmlhr.js?123#hash")) => %(/javascripts/xmlhr.js?123#hash) } PathToJavascriptToTag = { @@ -104,13 +103,6 @@ class AssetTagHelperTest < ActionView::TestCase %(javascript_include_tag("bank")) => %(<script src="/javascripts/bank.js" ></script>), %(javascript_include_tag("bank.js")) => %(<script src="/javascripts/bank.js" ></script>), %(javascript_include_tag("bank", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/bank.js" ></script>), - %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" ></script>\n<script src="/elsewhere/cools.js" ></script>), - %(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" ></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), - %(javascript_include_tag(:all)) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>), - %(javascript_include_tag(:all, :recursive => true)) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/subdir/subdir.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>), - %(javascript_include_tag(:defaults, "bank")) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/application.js"></script>), - %(javascript_include_tag(:defaults, "application")) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), - %(javascript_include_tag("bank", :defaults)) => %(<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), %(javascript_include_tag("http://example.com/all")) => %(<script src="http://example.com/all"></script>), %(javascript_include_tag("http://example.com/all.js")) => %(<script src="http://example.com/all.js"></script>), @@ -121,14 +113,17 @@ class AssetTagHelperTest < ActionView::TestCase %(stylesheet_path("bank")) => %(/stylesheets/bank.css), %(stylesheet_path("bank.css")) => %(/stylesheets/bank.css), %(stylesheet_path('subdir/subdir')) => %(/stylesheets/subdir/subdir.css), - %(stylesheet_path('/subdir/subdir.css')) => %(/subdir/subdir.css) + %(stylesheet_path('/subdir/subdir.css')) => %(/subdir/subdir.css), + %(stylesheet_path("style.min")) => %(/stylesheets/style.min.css), + %(stylesheet_path("style.min.css")) => %(/stylesheets/style.min.css) } PathToStyleToTag = { %(path_to_stylesheet("style")) => %(/stylesheets/style.css), %(path_to_stylesheet("style.css")) => %(/stylesheets/style.css), %(path_to_stylesheet('dir/file')) => %(/stylesheets/dir/file.css), - %(path_to_stylesheet('/dir/file.rcss')) => %(/dir/file.rcss) + %(path_to_stylesheet('/dir/file.rcss', :extname => false)) => %(/dir/file.rcss), + %(path_to_stylesheet('/dir/file', :extname => '.rcss')) => %(/dir/file.rcss) } StyleUrlToTag = { @@ -142,7 +137,8 @@ class AssetTagHelperTest < ActionView::TestCase %(url_to_stylesheet("style")) => %(http://www.example.com/stylesheets/style.css), %(url_to_stylesheet("style.css")) => %(http://www.example.com/stylesheets/style.css), %(url_to_stylesheet('dir/file')) => %(http://www.example.com/stylesheets/dir/file.css), - %(url_to_stylesheet('/dir/file.rcss')) => %(http://www.example.com/dir/file.rcss) + %(url_to_stylesheet('/dir/file.rcss', :extname => false)) => %(http://www.example.com/dir/file.rcss), + %(url_to_stylesheet('/dir/file', :extname => '.rcss')) => %(http://www.example.com/dir/file.rcss) } StyleLinkToTag = { @@ -151,10 +147,6 @@ class AssetTagHelperTest < ActionView::TestCase %(stylesheet_link_tag("/elsewhere/file")) => %(<link href="/elsewhere/file.css" media="screen" rel="stylesheet" />), %(stylesheet_link_tag("subdir/subdir")) => %(<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />), %(stylesheet_link_tag("bank", :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" />), - %(stylesheet_link_tag(:all)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />), - %(stylesheet_link_tag(:all, :recursive => true)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />), - %(stylesheet_link_tag(:all, :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="all" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="all" rel="stylesheet" />), - %(stylesheet_link_tag("random.styles", "/elsewhere/file")) => %(<link href="/stylesheets/random.styles" media="screen" rel="stylesheet" />\n<link href="/elsewhere/file.css" media="screen" rel="stylesheet" />), %(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style" media="screen" rel="stylesheet" />), %(stylesheet_link_tag("http://www.example.com/styles/style.css")) => %(<link href="http://www.example.com/styles/style.css" media="screen" rel="stylesheet" />), @@ -298,6 +290,14 @@ class AssetTagHelperTest < ActionView::TestCase %(audio_tag(["audio.mp3", "audio.ogg"], :autobuffer => true, :controls => true)) => %(<audio autobuffer="autobuffer" controls="controls"><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>) } + FontPathToTag = { + %(font_path("font.eot")) => %(/fonts/font.eot), + %(font_path("font.eot#iefix")) => %(/fonts/font.eot#iefix), + %(font_path("font.woff")) => %(/fonts/font.woff), + %(font_path("font.ttf")) => %(/fonts/font.ttf), + %(font_path("font.ttf?123")) => %(/fonts/font.ttf?123) + } + def test_autodiscovery_link_tag_deprecated_types result = nil assert_deprecated do @@ -308,6 +308,18 @@ class AssetTagHelperTest < ActionView::TestCase assert_equal expected, result end + def test_asset_path_tag + AssetPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + + def test_compute_asset_public_path + assert_equal "/robots.txt", compute_asset_path("robots.txt") + assert_equal "/robots.txt", compute_asset_path("/robots.txt") + assert_equal "/javascripts/foo.js", compute_asset_path("foo.js", :type => :javascript) + assert_equal "/javascripts/foo.js", compute_asset_path("/foo.js", :type => :javascript) + assert_equal "/stylesheets/foo.css", compute_asset_path("foo.css", :type => :stylesheet) + end + def test_auto_discovery_link_tag AutoDiscoveryToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -328,8 +340,7 @@ class AssetTagHelperTest < ActionView::TestCase UrlToJavascriptToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end - def test_javascript_include_tag_with_blank_asset_id - ENV["RAILS_ASSET_ID"] = "" + def test_javascript_include_tag JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -339,104 +350,15 @@ class AssetTagHelperTest < ActionView::TestCase } assert_nothing_raised { - javascript_include_tag(:defaults, 'missing_security_guard') - } - - assert_nothing_raised { javascript_include_tag('http://example.com/css/missing_security_guard') } end - def test_javascript_include_tag_with_given_asset_id - ENV["RAILS_ASSET_ID"] = "1" - assert_dom_equal(%(<script src="/javascripts/prototype.js?1"></script>\n<script src="/javascripts/effects.js?1"></script>\n<script src="/javascripts/dragdrop.js?1"></script>\n<script src="/javascripts/controls.js?1"></script>\n<script src="/javascripts/rails.js?1"></script>\n<script src="/javascripts/application.js?1"></script>), javascript_include_tag(:defaults)) - end - def test_javascript_include_tag_is_html_safe - assert javascript_include_tag(:defaults).html_safe? assert javascript_include_tag("prototype").html_safe? end - def test_custom_javascript_expansions - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"] - assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/effects.js"></script>), javascript_include_tag('controls', :robbery, 'effects') - end - - def test_custom_javascript_expansions_return_unique_set - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :defaults => %w(prototype effects dragdrop controls rails application) - assert_dom_equal %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag(:defaults) - end - - def test_custom_javascript_expansions_and_defaults_puts_application_js_at_the_end - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"] - assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('controls',:defaults, :robbery, 'effects') - end - - def test_javascript_include_tag_should_not_output_the_same_asset_twice - ENV["RAILS_ASSET_ID"] = "" - assert_dom_equal %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('prototype', 'effects', :defaults) - end - - def test_javascript_include_tag_should_not_output_the_same_expansion_twice - ENV["RAILS_ASSET_ID"] = "" - assert_dom_equal %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag(:defaults, :defaults) - end - - def test_single_javascript_asset_keys_should_take_precedence_over_expansions - ENV["RAILS_ASSET_ID"] = "" - assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('controls', :defaults, 'effects') - assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('controls', 'effects', :defaults) - end - - def test_registering_javascript_expansions_merges_with_existing_expansions - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :can_merge => ['bank'] - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :can_merge => ['robber'] - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :can_merge => ['bank'] - assert_dom_equal %(<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>), javascript_include_tag(:can_merge) - end - - def test_custom_javascript_expansions_with_undefined_symbol - assert_raise(ArgumentError) { javascript_include_tag('first', :unknown, 'last') } - end - - def test_custom_javascript_expansions_with_nil_value - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :monkey => nil - assert_dom_equal %(<script src="/javascripts/first.js"></script>\n<script src="/javascripts/last.js"></script>), javascript_include_tag('first', :monkey, 'last') - end - - def test_custom_javascript_expansions_with_empty_array_value - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :monkey => [] - assert_dom_equal %(<script src="/javascripts/first.js"></script>\n<script src="/javascripts/last.js"></script>), javascript_include_tag('first', :monkey, 'last') - end - - def test_custom_javascript_and_stylesheet_expansion_with_same_name - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"] - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :robbery => ["money", "security"] - assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/effects.js"></script>), javascript_include_tag('controls', :robbery, 'effects') - assert_dom_equal %(<link href="/stylesheets/style.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/money.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/security.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/print.css" rel="stylesheet" media="screen" />), stylesheet_link_tag('style', :robbery, 'print') - end - - def test_reset_javascript_expansions - JavascriptIncludeTag.expansions.clear - assert_raise(ArgumentError) { javascript_include_tag(:defaults) } - end - - def test_all_javascript_expansion_not_include_application_js_if_not_exists - FileUtils.mv(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'application.js'), - File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'application.bak')) - assert_no_match(/application\.js/, javascript_include_tag(:all)) - ensure - FileUtils.mv(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'application.bak'), - File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'application.js')) - end - def test_stylesheet_path - ENV["RAILS_ASSET_ID"] = "" StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -445,7 +367,6 @@ class AssetTagHelperTest < ActionView::TestCase end def test_stylesheet_url - ENV["RAILS_ASSET_ID"] = "" StyleUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -454,7 +375,6 @@ class AssetTagHelperTest < ActionView::TestCase end def test_stylesheet_link_tag - ENV["RAILS_ASSET_ID"] = "" StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -469,7 +389,6 @@ class AssetTagHelperTest < ActionView::TestCase end def test_stylesheet_link_tag_is_html_safe - ENV["RAILS_ASSET_ID"] = "" assert stylesheet_link_tag('dir/file').html_safe? assert stylesheet_link_tag('dir/other/file', 'dir/file2').html_safe? end @@ -478,57 +397,10 @@ class AssetTagHelperTest < ActionView::TestCase assert_dom_equal %(<link href="/file.css" media="<script>" rel="stylesheet" />), stylesheet_link_tag('/file', :media => '<script>') end - def test_custom_stylesheet_expansions - ENV["RAILS_ASSET_ID"] = '' - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :robbery => ["bank", "robber"] - assert_dom_equal %(<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('version.1.0', :robbery, 'subdir/subdir') - end - - def test_custom_stylesheet_expansions_return_unique_set - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :cities => %w(wellington amsterdam london) - assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/london.css" media="screen" rel="stylesheet" />), stylesheet_link_tag(:cities) - end - def test_stylesheet_link_tag_should_not_output_the_same_asset_twice - ENV["RAILS_ASSET_ID"] = "" assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington', 'wellington', 'amsterdam') end - def test_stylesheet_link_tag_should_not_output_the_same_expansion_twice - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :cities => %w(wellington amsterdam london) - assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/london.css" media="screen" rel="stylesheet" />), stylesheet_link_tag(:cities, :cities) - end - - def test_single_stylesheet_asset_keys_should_take_precedence_over_expansions - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :cities => %w(wellington amsterdam london) - assert_dom_equal %(<link href="/stylesheets/london.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('london', :cities) - end - - def test_custom_stylesheet_expansions_with_unknown_symbol - assert_raise(ArgumentError) { stylesheet_link_tag('first', :unknown, 'last') } - end - - def test_custom_stylesheet_expansions_with_nil_value - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :monkey => nil - assert_dom_equal %(<link href="/stylesheets/first.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/last.css" rel="stylesheet" media="screen" />), stylesheet_link_tag('first', :monkey, 'last') - end - - def test_custom_stylesheet_expansions_with_empty_array_value - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :monkey => [] - assert_dom_equal %(<link href="/stylesheets/first.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/last.css" rel="stylesheet" media="screen" />), stylesheet_link_tag('first', :monkey, 'last') - end - - def test_registering_stylesheet_expansions_merges_with_existing_expansions - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :can_merge => ['bank'] - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :can_merge => ['robber'] - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :can_merge => ['bank'] - assert_dom_equal %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />), stylesheet_link_tag(:can_merge) - end - def test_image_path ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -567,21 +439,6 @@ class AssetTagHelperTest < ActionView::TestCase FaviconLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end - def test_image_tag_windows_behaviour - old_asset_id, ENV["RAILS_ASSET_ID"] = ENV["RAILS_ASSET_ID"], "1" - # This simulates the behavior of File#exist? on windows when testing a file ending in "." - # If the file "rails.png" exists, windows will return true when asked if "rails.png." exists (notice trailing ".") - # OS X, linux etc will return false in this case. - File.stubs(:exist?).with('template/../fixtures/public/images/rails.png.').returns(true) - assert_equal '<img alt="Rails" src="/images/rails.png?1" />', image_tag('rails.png') - ensure - if old_asset_id - ENV["RAILS_ASSET_ID"] = old_asset_id - else - ENV.delete("RAILS_ASSET_ID") - end - end - def test_video_path VideoPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -622,6 +479,10 @@ class AssetTagHelperTest < ActionView::TestCase AudioLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_font_path + FontPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + def test_video_audio_tag_does_not_modify_options options = {:autoplay => true} video_tag('video', options) @@ -630,27 +491,6 @@ class AssetTagHelperTest < ActionView::TestCase assert_equal({:autoplay => true}, options) end - def test_timebased_asset_id - expected_time = File.mtime(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).to_i.to_s - assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png") - end - - def test_string_asset_id - @controller.config.asset_path = "/assets.v12345%s" - - expected_path = "/assets.v12345/images/rails.png" - assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png") - end - - def test_proc_asset_id - @controller.config.asset_path = Proc.new do |asset_path| - "/assets.v12345#{asset_path}" - end - - expected_path = "/assets.v12345/images/rails.png" - assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png") - end - def test_image_tag_interpreting_email_cid_correctly # An inline image has no need for an alt tag to be automatically generated from the cid: assert_equal '<img src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid") @@ -660,37 +500,6 @@ class AssetTagHelperTest < ActionView::TestCase assert_equal '<img alt="Image" src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid", :alt => "Image") end - def test_timebased_asset_id_with_relative_url_root - @controller.config.relative_url_root = "/collaboration/hieraki" - expected_time = File.mtime(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).to_i.to_s - assert_equal %(<img alt="Rails" src="#{@controller.config.relative_url_root}/images/rails.png?#{expected_time}" />), image_tag("rails.png") - end - - # Same as above, but with script_name - def test_timebased_asset_id_with_script_name - @request.script_name = "/collaboration/hieraki" - expected_time = File.mtime(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).to_i.to_s - assert_equal %(<img alt="Rails" src="#{@request.script_name}/images/rails.png?#{expected_time}" />), image_tag("rails.png") - end - - def test_should_skip_asset_id_on_complete_url - assert_equal %(<img alt="Rails" src="http://www.example.com/rails.png" />), image_tag("http://www.example.com/rails.png") - end - - def test_should_skip_asset_id_on_scheme_relative_url - assert_equal %(<img alt="Rails" src="//www.example.com/rails.png" />), image_tag("//www.example.com/rails.png") - end - - def test_should_use_preset_asset_id - ENV["RAILS_ASSET_ID"] = "4500" - assert_equal %(<img alt="Rails" src="/images/rails.png?4500" />), image_tag("rails.png") - end - - def test_preset_empty_asset_id - ENV["RAILS_ASSET_ID"] = "" - assert_equal %(<img alt="Rails" src="/images/rails.png" />), image_tag("rails.png") - end - def test_should_not_modify_source_string source = '/images/rails.png' copy = source.dup @@ -699,7 +508,6 @@ class AssetTagHelperTest < ActionView::TestCase end def test_caching_image_path_with_caching_and_proc_asset_host_using_request - ENV['RAILS_ASSET_ID'] = '' @controller.config.asset_host = Proc.new do |source, request| if request.ssl? "#{request.protocol}#{request.host_with_port}" @@ -714,630 +522,20 @@ class AssetTagHelperTest < ActionView::TestCase @controller.request.stubs(:ssl?).returns(true) assert_equal "http://localhost/images/xml.png", image_path("xml.png") end - - def test_caching_javascript_include_tag_when_caching_on - ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = 'http://a0.example.com' - config.perform_caching = true - - assert_dom_equal( - %(<script src="http://a0.example.com/javascripts/all.js"></script>), - javascript_include_tag(:all, :cache => true) - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_dom_equal( - %(<script src="http://a0.example.com/javascripts/money.js"></script>), - javascript_include_tag(:all, :cache => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - - assert_dom_equal( - %(<script src="http://a0.example.com/absolute/test.js"></script>), - javascript_include_tag(:all, :cache => "/absolute/test") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::ASSETS_DIR, 'absolute', 'test.js')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::ASSETS_DIR, 'absolute')) - end - - def test_caching_javascript_include_tag_when_caching_on_with_proc_asset_host - ENV['RAILS_ASSET_ID'] = '' - @controller.config.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } - config.perform_caching = true - - assert_equal '/javascripts/scripts.js'.length, 23 - assert_dom_equal( - %(<script src="http://a23.example.com/javascripts/scripts.js"></script>), - javascript_include_tag(:all, :cache => 'scripts') - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'scripts.js')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'scripts.js')) - end - - def test_caching_javascript_include_tag_when_caching_on_with_2_argument_proc_asset_host - ENV['RAILS_ASSET_ID'] = '' - @controller.config.asset_host = Proc.new { |source, request| - if request.ssl? - "#{request.protocol}#{request.host_with_port}" - else - "#{request.protocol}assets#{source.length}.example.com" - end - } - config.perform_caching = true - - assert_equal '/javascripts/vanilla.js'.length, 23 - assert_dom_equal( - %(<script src="http://assets23.example.com/javascripts/vanilla.js"></script>), - javascript_include_tag(:all, :cache => 'vanilla') - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'vanilla.js')) - - class << @controller.request - def protocol() 'https://' end - def ssl?() true end - end - - assert_equal '/javascripts/secure.js'.length, 22 - assert_dom_equal( - %(<script src="https://localhost/javascripts/secure.js"></script>), - javascript_include_tag(:all, :cache => 'secure') - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'vanilla.js')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) - end - - def test_caching_javascript_include_tag_when_caching_on_with_2_argument_object_asset_host - ENV['RAILS_ASSET_ID'] = '' - @controller.config.asset_host = Class.new do - def call(source, request) - if request.ssl? - "#{request.protocol}#{request.host_with_port}" - else - "#{request.protocol}assets#{source.length}.example.com" - end - end - end.new - - config.perform_caching = true - - assert_equal '/javascripts/vanilla.js'.length, 23 - assert_dom_equal( - %(<script src="http://assets23.example.com/javascripts/vanilla.js"></script>), - javascript_include_tag(:all, :cache => 'vanilla') - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'vanilla.js')) - - class << @controller.request - def protocol() 'https://' end - def ssl?() true end - end - - assert_equal '/javascripts/secure.js'.length, 22 - assert_dom_equal( - %(<script src="https://localhost/javascripts/secure.js"></script>), - javascript_include_tag(:all, :cache => 'secure') - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'vanilla.js')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) - end - - def test_caching_javascript_include_tag_when_caching_on_and_using_subdirectory - ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = 'http://a%d.example.com' - config.perform_caching = true - - number = Zlib.crc32('/javascripts/cache/money.js') % 4 - assert_dom_equal( - %(<script src="http://a#{number}.example.com/javascripts/cache/money.js"></script>), - javascript_include_tag(:all, :cache => "cache/money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'cache', 'money.js')) - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'cache', 'money.js')) - end - - def test_caching_javascript_include_tag_with_all_and_recursive_puts_defaults_at_the_start_of_the_file - ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = 'http://a0.example.com' - config.perform_caching = true - - assert_dom_equal( - %(<script src="http://a0.example.com/javascripts/combined.js"></script>), - javascript_include_tag(:all, :cache => "combined", :recursive => true) - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js')) - - assert_equal( - %(// prototype js\n\n// effects js\n\n// dragdrop js\n\n// controls js\n\n// bank js\n\n// robber js\n\n// subdir js\n\n\n// version.1.0 js\n\n// application js), - IO.read(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js')) - ) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js')) - end - - def test_caching_javascript_include_tag_with_all_puts_defaults_at_the_start_of_the_file - ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = 'http://a0.example.com' - config.perform_caching = true - - assert_dom_equal( - %(<script src="http://a0.example.com/javascripts/combined.js"></script>), - javascript_include_tag(:all, :cache => "combined") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js')) - - assert_equal( - %(// prototype js\n\n// effects js\n\n// dragdrop js\n\n// controls js\n\n// bank js\n\n// robber js\n\n// version.1.0 js\n\n// application js), - IO.read(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js')) - ) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js')) - end - - def test_caching_javascript_include_tag_with_relative_url_root - ENV["RAILS_ASSET_ID"] = "" - @controller.config.relative_url_root = "/collaboration/hieraki" - config.perform_caching = true - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/all.js"></script>), - javascript_include_tag(:all, :cache => true) - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/money.js"></script>), - javascript_include_tag(:all, :cache => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - # Same as above, but with script_name - def test_caching_javascript_include_tag_with_script_name - ENV["RAILS_ASSET_ID"] = "" - @request.script_name = "/collaboration/hieraki" - config.perform_caching = true - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/all.js"></script>), - javascript_include_tag(:all, :cache => true) - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/money.js"></script>), - javascript_include_tag(:all, :cache => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - def test_caching_javascript_include_tag_with_named_paths_and_relative_url_root_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - @controller.config.relative_url_root = "/collaboration/hieraki" - config.perform_caching = false - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>), - javascript_include_tag('robber', :cache => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>), - javascript_include_tag('robber', :cache => "money", :recursive => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - # Same as above, but with script_name - def test_caching_javascript_include_tag_with_named_paths_and_script_name_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - @request.script_name = "/collaboration/hieraki" - config.perform_caching = false - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>), - javascript_include_tag('robber', :cache => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>), - javascript_include_tag('robber', :cache => "money", :recursive => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - def test_caching_javascript_include_tag_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = false - - assert_dom_equal( - %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>), - javascript_include_tag(:all, :cache => true) - ) - - assert_dom_equal( - %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/subdir/subdir.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>), - javascript_include_tag(:all, :cache => true, :recursive => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_dom_equal( - %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>), - javascript_include_tag(:all, :cache => "money") - ) - - assert_dom_equal( - %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/subdir/subdir.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>), - javascript_include_tag(:all, :cache => "money", :recursive => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - def test_caching_javascript_include_tag_when_caching_on_and_missing_javascript_file - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = true - - assert_raise(Errno::ENOENT) { - javascript_include_tag('bank', 'robber', 'missing_security_guard', :cache => true) - } - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_raise(Errno::ENOENT) { - javascript_include_tag('bank', 'robber', 'missing_security_guard', :cache => "money") - } - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - def test_caching_javascript_include_tag_when_caching_on_and_javascript_file_is_uri - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = true - - assert_raise(Errno::ENOENT) { - javascript_include_tag('bank', 'robber', 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.js', :cache => true) - } - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - end - - def test_caching_javascript_include_tag_when_caching_off_and_missing_javascript_file - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = false - - assert_raise(Errno::ENOENT) { - javascript_include_tag('bank', 'robber', 'missing_security_guard', :cache => true) - } - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_raise(Errno::ENOENT) { - javascript_include_tag('bank', 'robber', 'missing_security_guard', :cache => "money") - } - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - def test_caching_stylesheet_link_tag_when_caching_on - ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = 'a0.example.com' - config.perform_caching = true - - assert_dom_equal( - %(<link href="http://a0.example.com/stylesheets/all.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => true) - ) - - files_to_be_joined = Dir["#{ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR}/[^all]*.css"] - - expected_mtime = files_to_be_joined.map { |p| File.mtime(p) }.max - assert_equal expected_mtime, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - bytes_added_by_join = "\n\n".size * files_to_be_joined.size - "\n\n".size - expected_size = files_to_be_joined.sum { |p| File.size(p) } + bytes_added_by_join - assert_equal expected_size, File.size(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="http://a0.example.com/stylesheets/money.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - - assert_dom_equal( - %(<link href="http://a0.example.com/absolute/test.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => "/absolute/test") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::ASSETS_DIR, 'absolute', 'test.css')) - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::ASSETS_DIR, 'absolute')) - end - - def test_concat_stylesheet_link_tag_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - - assert_dom_equal( - %(<link href="/stylesheets/all.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :concat => true) - ) - - expected = Dir["#{ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR}/*.css"].map { |p| File.mtime(p) }.max - assert_equal expected, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="/stylesheets/money.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :concat => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - - assert_dom_equal( - %(<link href="/absolute/test.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :concat => "/absolute/test") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::ASSETS_DIR, 'absolute', 'test.css')) - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::ASSETS_DIR, 'absolute')) - end - - def test_caching_stylesheet_link_tag_when_caching_on_and_missing_css_file - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = true - - assert_raise(Errno::ENOENT) { - stylesheet_link_tag('bank', 'robber', 'missing_security_guard', :cache => true) - } - - assert ! File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_raise(Errno::ENOENT) { - stylesheet_link_tag('bank', 'robber', 'missing_security_guard', :cache => "money") - } - - assert ! File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - def test_caching_stylesheet_link_tag_when_caching_off_and_missing_css_file - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = false - - assert_raise(Errno::ENOENT) { - stylesheet_link_tag('bank', 'robber', 'missing_security_guard', :cache => true) - } - - assert ! File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_raise(Errno::ENOENT) { - stylesheet_link_tag('bank', 'robber', 'missing_security_guard', :cache => "money") - } - - assert ! File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - def test_caching_stylesheet_link_tag_when_caching_on_with_proc_asset_host - ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = Proc.new { |source| "a#{source.length}.example.com" } - config.perform_caching = true - - assert_equal '/stylesheets/styles.css'.length, 23 - assert_dom_equal( - %(<link href="http://a23.example.com/stylesheets/styles.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => 'styles') - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'styles.css')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'styles.css')) - end - - def test_caching_stylesheet_link_tag_with_relative_url_root - ENV["RAILS_ASSET_ID"] = "" - @controller.config.relative_url_root = "/collaboration/hieraki" - config.perform_caching = true - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/all.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => true) - ) - - files_to_be_joined = Dir["#{ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR}/[^all]*.css"] - - expected_mtime = files_to_be_joined.map { |p| File.mtime(p) }.max - assert_equal expected_mtime, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/money.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - # Same as above, but with script_name - def test_caching_stylesheet_link_tag_with_script_name - ENV["RAILS_ASSET_ID"] = "" - @request.script_name = "/collaboration/hieraki" - config.perform_caching = true - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/all.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => true) - ) - - files_to_be_joined = Dir["#{ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR}/[^all]*.css"] - - expected_mtime = files_to_be_joined.map { |p| File.mtime(p) }.max - assert_equal expected_mtime, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/money.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - - def test_caching_stylesheet_link_tag_with_named_paths_and_relative_url_root_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - @controller.config.relative_url_root = "/collaboration/hieraki" - config.perform_caching = false - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag('robber', :cache => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag('robber', :cache => "money") - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - # Same as above, but with script_name - def test_caching_stylesheet_link_tag_with_named_paths_and_script_name_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - @request.script_name = "/collaboration/hieraki" - config.perform_caching = false - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag('robber', :cache => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag('robber', :cache => "money") - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - def test_caching_stylesheet_include_tag_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = false - - assert_dom_equal( - %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => true) - ) - - assert_dom_equal( - %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => true, :recursive => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => "money") - ) - - assert_dom_equal( - %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => "money", :recursive => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - def test_caching_stylesheet_include_tag_with_absolute_uri - ENV["RAILS_ASSET_ID"] = "" - - assert_dom_equal( - %(<link href="/stylesheets/all.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag("/foo/baz", :cache => true) - ) - - FileUtils.rm(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - end end class AssetTagHelperNonVhostTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper + attr_reader :request + def setup super @controller = BasicController.new @controller.config.relative_url_root = "/collaboration/hieraki" - @request = Struct.new(:protocol).new("gopher://") + @request = Struct.new(:protocol, :base_url).new("gopher://", "gopher://www.example.com") @controller.request = @request - - JavascriptIncludeTag.expansions.clear end def url_for(options) @@ -1351,10 +549,33 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase assert_dom_equal(%(/collaboration/hieraki/images/xml.png), image_path("xml.png")) end + def test_should_return_nothing_if_asset_host_isnt_configured + assert_equal nil, compute_asset_host("foo") + end + + def test_should_current_request_host_is_always_returned_for_request + assert_equal "gopher://www.example.com", compute_asset_host("foo", :protocol => :request) + end + def test_should_ignore_relative_root_path_on_complete_url assert_dom_equal(%(http://www.example.com/images/xml.png), image_path("http://www.example.com/images/xml.png")) end + def test_should_return_simple_string_asset_host + @controller.config.asset_host = "assets.example.com" + assert_equal "gopher://assets.example.com", compute_asset_host("foo") + end + + def test_should_return_relative_asset_host + @controller.config.asset_host = "assets.example.com" + assert_equal "//assets.example.com", compute_asset_host("foo", :protocol => :relative) + end + + def test_should_return_custom_protocol_asset_host + @controller.config.asset_host = "assets.example.com" + assert_equal "ftp://assets.example.com", compute_asset_host("foo", :protocol => "ftp") + end + def test_should_compute_proper_path_with_asset_host @controller.config.asset_host = "assets.example.com" assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag) @@ -1387,6 +608,11 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_url("xml.png")) end + def test_should_return_asset_host_with_protocol + @controller.config.asset_host = "http://assets.example.com" + assert_equal "http://assets.example.com", compute_asset_host("foo") + end + def test_should_ignore_asset_host_on_complete_url @controller.config.asset_host = "http://assets.example.com" assert_dom_equal(%(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" />), stylesheet_link_tag("http://bar.example.com/stylesheets/style.css")) @@ -1397,6 +623,11 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase assert_dom_equal(%(<link href="//bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" />), stylesheet_link_tag("//bar.example.com/stylesheets/style.css")) end + def test_should_wildcard_asset_host + @controller.config.asset_host = 'http://a%d.example.com' + assert_match(%r(http://a[0123].example.com), compute_asset_host("foo")) + end + def test_should_wildcard_asset_host_between_zero_and_four @controller.config.asset_host = 'http://a%d.example.com' assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png')) @@ -1420,3 +651,73 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase assert_dom_equal(%(/collaboration/hieraki/stylesheets/foo.css), stylesheet_path("foo")) end end + +class AssetUrlHelperControllerTest < ActionView::TestCase + tests ActionView::Helpers::AssetUrlHelper + + def setup + super + + @controller = BasicController.new + @controller.extend ActionView::Helpers::AssetUrlHelper + + @request = Class.new do + attr_accessor :script_name + def protocol() 'http://' end + def ssl?() false end + def host_with_port() 'www.example.com' end + def base_url() 'http://www.example.com' end + end.new + + @controller.request = @request + end + + def test_asset_path + assert_equal "/foo", @controller.asset_path("foo") + end + + def test_asset_url + assert_equal "http://www.example.com/foo", @controller.asset_url("foo") + end +end + +class AssetUrlHelperEmptyModuleTest < ActionView::TestCase + tests ActionView::Helpers::AssetUrlHelper + + def setup + super + + @module = Module.new + @module.extend ActionView::Helpers::AssetUrlHelper + end + + def test_asset_path + assert_equal "/foo", @module.asset_path("foo") + end + + def test_asset_url + assert_equal "/foo", @module.asset_url("foo") + end + + def test_asset_url_with_request + @module.instance_eval do + def request + Struct.new(:base_url, :script_name).new("http://www.example.com", nil) + end + end + + assert @module.request + assert_equal "http://www.example.com/foo", @module.asset_url("foo") + end + + def test_asset_url_with_config_asset_host + @module.instance_eval do + def config + Struct.new(:asset_host).new("http://www.example.com") + end + end + + assert @module.config.asset_host + assert_equal "http://www.example.com/foo", @module.asset_url("foo") + end +end diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb index 63066d40cd..495a9d3f9d 100644 --- a/actionpack/test/template/date_helper_i18n_test.rb +++ b/actionpack/test/template/date_helper_i18n_test.rb @@ -36,16 +36,13 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase end end - def assert_distance_of_time_in_words_translates_key(passed, expected) - diff, passed_options = *passed - key, count = *expected - to = @from + diff - - options = {:locale => 'en', :scope => :'datetime.distance_in_words'} - options[:count] = count if count - - I18n.expects(:t).with(key, options) - distance_of_time_in_words(@from, to, passed_options.merge(:locale => 'en')) + def test_distance_of_time_in_words_calls_i18n_with_custom_scope + { + [30.days, { scope: :'datetime.distance_in_words_ago' }] => [:'about_x_months', 1], + [60.days, { scope: :'datetime.distance_in_words_ago' }] => [:'x_months', 2], + }.each do |passed, expected| + assert_distance_of_time_in_words_translates_key(passed, expected, scope: :'datetime.distance_in_words_ago') + end end def test_time_ago_in_words_passes_locale @@ -74,6 +71,18 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase assert_equal expected, I18n.t(key, :count => count, :scope => 'datetime.distance_in_words') end end + + def assert_distance_of_time_in_words_translates_key(passed, expected, expected_options = {}) + diff, passed_options = *passed + key, count = *expected + to = @from + diff + + options = { locale: 'en', scope: :'datetime.distance_in_words' }.merge!(expected_options) + options[:count] = count if count + + I18n.expects(:t).with(key, options) + distance_of_time_in_words(@from, to, passed_options.merge(locale: 'en')) + end end class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index a4da7cd4b0..8bd8eff3c0 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -1007,6 +1007,22 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :prefix => "date[first]", :use_hidden => true }) end + def test_select_date_with_css_classes_option + expected = %(<select id="date_first_year" name="date[first][year]" class="year">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_first_month" name="date[first][month]" class="month">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_first_day" name="date[first][day]" class="day">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]", :with_css_classes => true}) + end + def test_select_datetime expected = %(<select id="date_first_year" name="date[first][year]">\n) expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) diff --git a/actionpack/test/template/digestor_test.rb b/actionpack/test/template/digestor_test.rb index 01b101cb49..b9d26da3af 100644 --- a/actionpack/test/template/digestor_test.rb +++ b/actionpack/test/template/digestor_test.rb @@ -13,20 +13,24 @@ end class FixtureFinder FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor" - TMP_DIR = "#{File.dirname(__FILE__)}/../tmp" def find(logical_name, keys, partial, options) - FixtureTemplate.new("#{TMP_DIR}/digestor/#{partial ? logical_name.gsub(%r|/([^/]+)$|, '/_\1') : logical_name}.#{options[:formats].first}.erb") + FixtureTemplate.new("digestor/#{partial ? logical_name.gsub(%r|/([^/]+)$|, '/_\1') : logical_name}.#{options[:formats].first}.erb") end end class TemplateDigestorTest < ActionView::TestCase def setup - FileUtils.cp_r FixtureFinder::FIXTURES_DIR, FixtureFinder::TMP_DIR + @cwd = Dir.pwd + @tmp_dir = Dir.mktmpdir + + FileUtils.cp_r FixtureFinder::FIXTURES_DIR, @tmp_dir + Dir.chdir @tmp_dir end def teardown - FileUtils.rm_r File.join(FixtureFinder::TMP_DIR, "digestor") + Dir.chdir @cwd + FileUtils.rm_r @tmp_dir ActionView::Digestor.cache.clear end @@ -59,6 +63,12 @@ class TemplateDigestorTest < ActionView::TestCase change_template("comments/_comment") end end + + def test_directory_depth_dependency + assert_digest_difference("level/below/index") do + change_template("level/below/_header") + end + end def test_logging_of_missing_template assert_logged "Couldn't find template for digesting: messages/something_missing.html" do @@ -153,7 +163,7 @@ class TemplateDigestorTest < ActionView::TestCase end def change_template(template_name) - File.open("#{FixtureFinder::TMP_DIR}/digestor/#{template_name}.html.erb", "w") do |f| + File.open("digestor/#{template_name}.html.erb", "w") do |f| f.write "\nTHIS WAS CHANGED!" end end diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 246c4bfada..fbfc73deda 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -400,6 +400,12 @@ class FormHelperTest < ActionView::TestCase '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret") ) + + @post.secret = Set.new(['1']) + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />', + check_box("post", "secret") + ) end def test_check_box_with_include_hidden_false diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb index 324caef224..d9b57776c9 100644 --- a/actionpack/test/template/html-scanner/sanitizer_test.rb +++ b/actionpack/test/template/html-scanner/sanitizer_test.rb @@ -233,7 +233,7 @@ class SanitizerTest < ActionController::TestCase end def test_should_sanitize_attributes - assert_sanitized %(<SPAN title="'><script>alert()</script>">blah</SPAN>), %(<span title="'><script>alert()</script>">blah</span>) + assert_sanitized %(<SPAN title="'><script>alert()</script>">blah</SPAN>), %(<span title="#{CGI.escapeHTML "'><script>alert()</script>"}">blah</span>) end def test_should_sanitize_illegal_style_properties diff --git a/actionpack/test/template/sanitize_helper_test.rb b/actionpack/test/template/sanitize_helper_test.rb index 7626cdf386..12d5260a9d 100644 --- a/actionpack/test/template/sanitize_helper_test.rb +++ b/actionpack/test/template/sanitize_helper_test.rb @@ -17,7 +17,7 @@ class SanitizeHelperTest < ActionView::TestCase end def test_sanitize_form - assert_sanitized "<form action=\"/foo/bar\" method=\"post\"><input></form>", '' + assert_equal '', sanitize("<form action=\"/foo/bar\" method=\"post\"><input></form>") end def test_should_sanitize_illegal_style_properties @@ -48,8 +48,4 @@ class SanitizeHelperTest < ActionView::TestCase def test_sanitize_is_marked_safe assert sanitize("<html><script></script></html>").html_safe? end - - def assert_sanitized(text, expected = nil) - assert_equal((expected || text), sanitize(text)) - end end diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index 5265ae6b3a..c7231d9cd5 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -321,6 +321,14 @@ module ActionView assert_template :partial => "_partial_for_use_in_layout", :locals => { :name => "Somebody Else" } end end + + test 'supports different locals on the same partial' do + controller.controller_path = "test" + render(:template => "test/render_two_partials") + assert_template partial: '_partial', locals: { 'first' => '1' } + assert_template partial: '_partial', locals: { 'second' => '2' } + end + end module AHelperWithInitialize diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 134177d74d..bbfdf7f944 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -31,17 +31,17 @@ class UrlHelperTest < ActiveSupport::TestCase setup :_prepare_context def hash_for(options = {}) - { :controller => "foo", :action => "bar" }.merge!(options) + { controller: "foo", action: "bar" }.merge!(options) end alias url_hash hash_for def test_url_for_does_not_escape_urls - assert_equal "/?a=b&c=d", url_for(hash_for(:a => :b, :c => :d)) + assert_equal "/?a=b&c=d", url_for(hash_for(a: :b, c: :d)) end def test_url_for_with_back referer = 'http://www.example.com/referer' - @controller = Struct.new(:request).new(Struct.new(:env).new({"HTTP_REFERER" => referer})) + @controller = Struct.new(:request).new(Struct.new(:env).new("HTTP_REFERER" => referer)) assert_equal 'http://www.example.com/referer', url_for(:back) end @@ -53,7 +53,7 @@ class UrlHelperTest < ActiveSupport::TestCase # TODO: missing test cases def test_button_to_with_straight_url - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com") + assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com") end def test_button_to_with_straight_url_and_request_forgery @@ -68,143 +68,146 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_button_to_with_form_class - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"custom-class\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com", :form_class => 'custom-class') + assert_dom_equal %{<form method="post" action="http://www.example.com" class="custom-class"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com", form_class: 'custom-class') end def test_button_to_with_form_class_escapes - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"<script>evil_js</script>\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com", :form_class => '<script>evil_js</script>') + assert_dom_equal %{<form method="post" action="http://www.example.com" class="<script>evil_js</script>"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com", form_class: '<script>evil_js</script>') end def test_button_to_with_query - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&q2=v2") + assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2") end def test_button_to_with_html_safe_URL - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&q2=v2".html_safe) + assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2".html_safe) end def test_button_to_with_query_and_no_name - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com?q1=v1&q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"http://www.example.com?q1=v1&q2=v2\" /></div></form>", button_to(nil, "http://www.example.com?q1=v1&q2=v2") + assert_dom_equal %{<form method="post" action="http://www.example.com?q1=v1&q2=v2" class="button_to"><div><input type="submit" value="http://www.example.com?q1=v1&q2=v2" /></div></form>}, button_to(nil, "http://www.example.com?q1=v1&q2=v2") end def test_button_to_with_javascript_confirm assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :data => { :confirm => "Are you sure?" }) + %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", data: { confirm: "Are you sure?" }) ) end def test_button_to_with_deprecated_confirm assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :confirm => "Are you sure?") + %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", confirm: "Are you sure?") ) end end def test_button_to_with_javascript_disable_with assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input data-disable-with=\"Greeting...\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :data => { :disable_with => "Greeting..." }) + %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", data: { disable_with: "Greeting..." }) ) end def test_button_to_with_javascript_deprecated_disable_with assert_deprecated ":disable_with option is deprecated and will be removed from Rails 4.1. Use ':data => { :disable_with => \'Text\' }' instead" do assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input data-disable-with=\"Greeting...\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :disable_with => "Greeting...") + %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", disable_with: "Greeting...") ) end end def test_button_to_with_remote_and_form_options - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"custom-class\" data-remote=\"true\" data-type=\"json\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com", :remote => true, :form => { :class => "custom-class", "data-type" => "json" } ) + assert_dom_equal( + %{<form method="post" action="http://www.example.com" class="custom-class" data-remote="true" data-type="json"><div><input type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", remote: true, form: { class: "custom-class", "data-type" => "json" }) + ) end def test_button_to_with_remote_and_javascript_confirm assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :remote => true, :data => { :confirm => "Are you sure?" }) + %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", remote: true, data: { confirm: "Are you sure?" }) ) end def test_button_to_with_remote_and_javascript_with_deprecated_confirm assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :remote => true, :confirm => "Are you sure?") + %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", remote: true, confirm: "Are you sure?") ) end end def test_button_to_with_remote_and_javascript_disable_with assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-disable-with=\"Greeting...\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :remote => true, :data => { :disable_with => "Greeting..." }) + %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", remote: true, data: { disable_with: "Greeting..." }) ) end def test_button_to_with_remote_and_javascript_deprecated_disable_with assert_deprecated ":disable_with option is deprecated and will be removed from Rails 4.1. Use ':data => { :disable_with => \'Text\' }' instead" do assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-disable-with=\"Greeting...\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :remote => true, :disable_with => "Greeting...") + %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", remote: true, disable_with: "Greeting...") ) end end def test_button_to_with_remote_false assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :remote => false) + %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", remote: false) ) end def test_button_to_enabled_disabled assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :disabled => false) + %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", disabled: false) ) assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input disabled=\"disabled\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :disabled => true) + %{<form method="post" action="http://www.example.com" class="button_to"><div><input disabled="disabled" type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", disabled: true) ) end def test_button_to_with_method_delete assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"hidden\" name=\"_method\" value=\"delete\" /><input type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :method => :delete) + %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="hidden" name="_method" value="delete" /><input type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", method: :delete) ) end def test_button_to_with_method_get assert_dom_equal( - "<form method=\"get\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :method => :get) + %{<form method="get" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", method: :get) ) end def test_button_to_with_block assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><button type=\"submit\"><span>Hello</span></button></div></form>", + %{<form method="post" action="http://www.example.com" class="button_to"><div><button type="submit"><span>Hello</span></button></div></form>}, button_to("http://www.example.com") { content_tag(:span, 'Hello') } ) end def test_link_tag_with_straight_url - assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", "http://www.example.com") + assert_dom_equal %{<a href="http://www.example.com">Hello</a>}, link_to("Hello", "http://www.example.com") end def test_link_tag_without_host_option - assert_dom_equal(%q{<a href="/">Test Link</a>}, link_to('Test Link', url_hash)) + assert_dom_equal(%{<a href="/">Test Link</a>}, link_to('Test Link', url_hash)) end def test_link_tag_with_host_option - hash = hash_for(:host => "www.example.com") - expected = %q{<a href="http://www.example.com/">Test Link</a>} + hash = hash_for(host: "www.example.com") + expected = %{<a href="http://www.example.com/">Test Link</a>} assert_dom_equal(expected, link_to('Test Link', hash)) end @@ -243,131 +246,143 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_link_tag_with_custom_onclick - link = link_to("Hello", "http://www.example.com", :onclick => "alert('yay!')") + link = link_to("Hello", "http://www.example.com", onclick: "alert('yay!')") expected = %{<a href="http://www.example.com" onclick="alert('yay!')">Hello</a>} assert_dom_equal expected, link end def test_link_tag_with_javascript_confirm assert_dom_equal( - "<a href=\"http://www.example.com\" data-confirm=\"Are you sure?\">Hello</a>", - link_to("Hello", "http://www.example.com", :data => { :confirm => "Are you sure?" }) + %{<a href="http://www.example.com" data-confirm="Are you sure?">Hello</a>}, + link_to("Hello", "http://www.example.com", data: { confirm: "Are you sure?" }) ) assert_dom_equal( - "<a href=\"http://www.example.com\" data-confirm=\"You cant possibly be sure, can you?\">Hello</a>", - link_to("Hello", "http://www.example.com", :data => { :confirm => "You cant possibly be sure, can you?" }) + %{<a href="http://www.example.com" data-confirm="You cant possibly be sure, can you?">Hello</a>}, + link_to("Hello", "http://www.example.com", data: { confirm: "You cant possibly be sure, can you?" }) ) assert_dom_equal( - "<a href=\"http://www.example.com\" data-confirm=\"You cant possibly be sure,\n can you?\">Hello</a>", - link_to("Hello", "http://www.example.com", :data => { :confirm => "You cant possibly be sure,\n can you?" }) + %{<a href="http://www.example.com" data-confirm="You cant possibly be sure,\n can you?">Hello</a>}, + link_to("Hello", "http://www.example.com", data: { confirm: "You cant possibly be sure,\n can you?" }) ) end def test_link_tag_with_deprecated_confirm assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<a href=\"http://www.example.com\" data-confirm=\"Are you sure?\">Hello</a>", - link_to("Hello", "http://www.example.com", :confirm => "Are you sure?") + %{<a href="http://www.example.com" data-confirm="Are you sure?">Hello</a>}, + link_to("Hello", "http://www.example.com", confirm: "Are you sure?") ) end assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<a href=\"http://www.example.com\" data-confirm=\"You cant possibly be sure, can you?\">Hello</a>", - link_to("Hello", "http://www.example.com", :confirm => "You cant possibly be sure, can you?") + %{<a href="http://www.example.com" data-confirm="You cant possibly be sure, can you?">Hello</a>}, + link_to("Hello", "http://www.example.com", confirm: "You cant possibly be sure, can you?") ) end assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<a href=\"http://www.example.com\" data-confirm=\"You cant possibly be sure,\n can you?\">Hello</a>", - link_to("Hello", "http://www.example.com", :confirm => "You cant possibly be sure,\n can you?") + %{<a href="http://www.example.com" data-confirm="You cant possibly be sure,\n can you?">Hello</a>}, + link_to("Hello", "http://www.example.com", confirm: "You cant possibly be sure,\n can you?") ) end end def test_link_to_with_remote assert_dom_equal( - "<a href=\"http://www.example.com\" data-remote=\"true\">Hello</a>", - link_to("Hello", "http://www.example.com", :remote => true) + %{<a href="http://www.example.com" data-remote="true">Hello</a>}, + link_to("Hello", "http://www.example.com", remote: true) ) end def test_link_to_with_remote_false assert_dom_equal( - "<a href=\"http://www.example.com\">Hello</a>", - link_to("Hello", "http://www.example.com", :remote => false) + %{<a href="http://www.example.com">Hello</a>}, + link_to("Hello", "http://www.example.com", remote: false) + ) + end + + def test_link_to_with_symbolic_remote_in_non_html_options + assert_dom_equal( + %{<a href="/" data-remote="true">Hello</a>}, + link_to("Hello", hash_for(remote: true), {}) + ) + end + + def test_link_to_with_string_remote_in_non_html_options + assert_dom_equal( + %{<a href="/" data-remote="true">Hello</a>}, + link_to("Hello", hash_for('remote' => true), {}) ) end def test_link_tag_using_post_javascript assert_dom_equal( - "<a href='http://www.example.com' data-method=\"post\" rel=\"nofollow\">Hello</a>", - link_to("Hello", "http://www.example.com", :method => :post) + %{<a href="http://www.example.com" data-method="post" rel="nofollow">Hello</a>}, + link_to("Hello", "http://www.example.com", method: :post) ) end def test_link_tag_using_delete_javascript assert_dom_equal( - "<a href='http://www.example.com' rel=\"nofollow\" data-method=\"delete\">Destroy</a>", - link_to("Destroy", "http://www.example.com", :method => :delete) + %{<a href="http://www.example.com" rel="nofollow" data-method="delete">Destroy</a>}, + link_to("Destroy", "http://www.example.com", method: :delete) ) end def test_link_tag_using_delete_javascript_and_href assert_dom_equal( - "<a href='\#' rel=\"nofollow\" data-method=\"delete\">Destroy</a>", - link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#') + %{<a href="\#" rel="nofollow" data-method="delete">Destroy</a>}, + link_to("Destroy", "http://www.example.com", method: :delete, href: '#') ) end def test_link_tag_using_post_javascript_and_rel assert_dom_equal( - "<a href='http://www.example.com' data-method=\"post\" rel=\"example nofollow\">Hello</a>", - link_to("Hello", "http://www.example.com", :method => :post, :rel => 'example') + %{<a href="http://www.example.com" data-method="post" rel="example nofollow">Hello</a>}, + link_to("Hello", "http://www.example.com", method: :post, rel: 'example') ) end def test_link_tag_using_post_javascript_and_confirm assert_dom_equal( - "<a href=\"http://www.example.com\" data-method=\"post\" rel=\"nofollow\" data-confirm=\"Are you serious?\">Hello</a>", - link_to("Hello", "http://www.example.com", :method => :post, :data => { :confirm => "Are you serious?" }) + %{<a href="http://www.example.com" data-method="post" rel="nofollow" data-confirm="Are you serious?">Hello</a>}, + link_to("Hello", "http://www.example.com", method: :post, data: { confirm: "Are you serious?" }) ) end def test_link_tag_using_post_javascript_and_with_deprecated_confirm assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<a href=\"http://www.example.com\" data-method=\"post\" rel=\"nofollow\" data-confirm=\"Are you serious?\">Hello</a>", - link_to("Hello", "http://www.example.com", :method => :post, :confirm => "Are you serious?") + %{<a href="http://www.example.com" data-method="post" rel="nofollow" data-confirm="Are you serious?">Hello</a>}, + link_to("Hello", "http://www.example.com", method: :post, confirm: "Are you serious?") ) end end def test_link_tag_using_delete_javascript_and_href_and_confirm assert_dom_equal( - "<a href='\#' rel=\"nofollow\" data-confirm=\"Are you serious?\" data-method=\"delete\">Destroy</a>", - link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#', :data => { :confirm => "Are you serious?" }), - "When specifying url, form should be generated with it, but not this.href" + %{<a href="\#" rel="nofollow" data-confirm="Are you serious?" data-method="delete">Destroy</a>}, + link_to("Destroy", "http://www.example.com", method: :delete, href: '#', data: { confirm: "Are you serious?" }) ) end def test_link_tag_using_delete_javascript_and_href_and_with_deprecated_confirm assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<a href='\#' rel=\"nofollow\" data-confirm=\"Are you serious?\" data-method=\"delete\">Destroy</a>", - link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#', :confirm => "Are you serious?"), - "When specifying url, form should be generated with it, but not this.href" + %{<a href="\#" rel="nofollow" data-confirm="Are you serious?" data-method="delete">Destroy</a>}, + link_to("Destroy", "http://www.example.com", method: :delete, href: '#', confirm: "Are you serious?") ) end end def test_link_tag_with_block - assert_dom_equal '<a href="/"><span>Example site</span></a>', + assert_dom_equal %{<a href="/"><span>Example site</span></a>}, link_to('/') { content_tag(:span, 'Example site') } end def test_link_tag_with_block_and_html_options - assert_dom_equal '<a class="special" href="/"><span>Example site</span></a>', - link_to('/', :class => "special") { content_tag(:span, 'Example site') } + assert_dom_equal %{<a class="special" href="/"><span>Example site</span></a>}, + link_to('/', class: "special") { content_tag(:span, 'Example site') } end def test_link_tag_using_block_in_erb @@ -377,18 +392,18 @@ class UrlHelperTest < ActiveSupport::TestCase def test_link_tag_with_html_safe_string assert_dom_equal( - "<a href=\"/article/Gerd_M%C3%BCller\">Gerd Müller</a>", + %{<a href="/article/Gerd_M%C3%BCller">Gerd Müller</a>}, link_to("Gerd Müller", article_path("Gerd_Müller".html_safe)) ) end def test_link_tag_escapes_content - assert_dom_equal '<a href="/">Malicious <script>content</script></a>', + assert_dom_equal %{<a href="/">Malicious <script>content</script></a>}, link_to("Malicious <script>content</script>", "/") end def test_link_tag_does_not_escape_html_safe_content - assert_dom_equal '<a href="/">Malicious <script>content</script></a>', + assert_dom_equal %{<a href="/">Malicious <script>content</script></a>}, link_to("Malicious <script>content</script>".html_safe, "/") end @@ -438,12 +453,12 @@ class UrlHelperTest < ActiveSupport::TestCase def test_current_page_with_params_that_match @request = request_for_url("/?order=desc&page=1") - assert current_page?(hash_for(:order => "desc", :page => "1")) + assert current_page?(hash_for(order: "desc", page: "1")) assert current_page?("http://www.example.com/?order=desc&page=1") end def test_current_page_with_not_get_verb - @request = request_for_url("/events", :method => :post) + @request = request_for_url("/events", method: :post) assert !current_page?('/events') end @@ -466,20 +481,20 @@ class UrlHelperTest < ActiveSupport::TestCase @request = request_for_url("/?order=desc&page=1") assert_equal "Showing", - link_to_unless_current("Showing", hash_for(:order => 'desc', :page => '1')) + link_to_unless_current("Showing", hash_for(order: 'desc', page: '1')) assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=1") @request = request_for_url("/?order=desc") assert_equal %{<a href="/?order=asc">Showing</a>}, - link_to_unless_current("Showing", hash_for(:order => :asc)) + link_to_unless_current("Showing", hash_for(order: :asc)) assert_equal %{<a href="http://www.example.com/?order=asc">Showing</a>}, link_to_unless_current("Showing", "http://www.example.com/?order=asc") @request = request_for_url("/?order=desc") assert_equal %{<a href="/?order=desc&page=2\">Showing</a>}, - link_to_unless_current("Showing", hash_for(:order => "desc", :page => 2)) + link_to_unless_current("Showing", hash_for(order: "desc", page: 2)) assert_equal %{<a href="http://www.example.com/?order=desc&page=2">Showing</a>}, link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=2") @@ -492,56 +507,86 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_mail_to - assert_dom_equal "<a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>", mail_to("david@loudthinking.com") - assert_dom_equal "<a href=\"mailto:david@loudthinking.com\">David Heinemeier Hansson</a>", mail_to("david@loudthinking.com", "David Heinemeier Hansson") + assert_dom_equal %{<a href="mailto:david@loudthinking.com">david@loudthinking.com</a>}, mail_to("david@loudthinking.com") + assert_dom_equal %{<a href="mailto:david@loudthinking.com">David Heinemeier Hansson</a>}, mail_to("david@loudthinking.com", "David Heinemeier Hansson") assert_dom_equal( - "<a class=\"admin\" href=\"mailto:david@loudthinking.com\">David Heinemeier Hansson</a>", + %{<a class="admin" href="mailto:david@loudthinking.com">David Heinemeier Hansson</a>}, mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin") ) assert_equal mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin"), - mail_to("david@loudthinking.com", "David Heinemeier Hansson", :class => "admin") + mail_to("david@loudthinking.com", "David Heinemeier Hansson", class: "admin") end def test_mail_to_with_javascript - snippet = mail_to("me@domain.com", "My email", :encode => "javascript") + snippet = mail_to("me@domain.com", "My email", encode: "javascript") assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet end def test_mail_to_with_javascript_unicode - snippet = mail_to("unicode@example.com", "únicode", :encode => "javascript") + snippet = mail_to("unicode@example.com", "únicode", encode: "javascript") assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%5c%22%3e%c3%ba%6e%69%63%6f%64%65%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet end def test_mail_with_options assert_dom_equal( - %(<a href="mailto:me@example.com?cc=ccaddress%40example.com&bcc=bccaddress%40example.com&body=This%20is%20the%20body%20of%20the%20message.&subject=This%20is%20an%20example%20email">My email</a>), - mail_to("me@example.com", "My email", :cc => "ccaddress@example.com", :bcc => "bccaddress@example.com", :subject => "This is an example email", :body => "This is the body of the message.") + %{<a href="mailto:me@example.com?cc=ccaddress%40example.com&bcc=bccaddress%40example.com&body=This%20is%20the%20body%20of%20the%20message.&subject=This%20is%20an%20example%20email">My email</a>}, + mail_to("me@example.com", "My email", cc: "ccaddress@example.com", bcc: "bccaddress@example.com", subject: "This is an example email", body: "This is the body of the message.") ) end def test_mail_to_with_img - assert_dom_equal %(<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>), + assert_dom_equal %{<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>}, mail_to('feedback@example.com', '<img src="/feedback.png" />'.html_safe) end def test_mail_to_with_hex - assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex") - assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me@domain.com</a>", mail_to("me@domain.com", nil, :encode => "hex") + assert_dom_equal( + %{<a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>}, + mail_to("me@domain.com", "My email", encode: "hex") + ) + + assert_dom_equal( + %{<a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">me@domain.com</a>}, + mail_to("me@domain.com", nil, encode: "hex") + ) end def test_mail_to_with_replace_options - assert_dom_equal "<a href=\"mailto:wolfgang@stufenlos.net\">wolfgang(at)stufenlos(dot)net</a>", mail_to("wolfgang@stufenlos.net", nil, :replace_at => "(at)", :replace_dot => "(dot)") - assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain.com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)") - assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)") - assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain(dot)com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)") - assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") - assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") + assert_dom_equal( + %{<a href="mailto:wolfgang@stufenlos.net">wolfgang(at)stufenlos(dot)net</a>}, + mail_to("wolfgang@stufenlos.net", nil, replace_at: "(at)", replace_dot: "(dot)") + ) + + assert_dom_equal( + %{<a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">me(at)domain.com</a>}, + mail_to("me@domain.com", nil, encode: "hex", replace_at: "(at)") + ) + + assert_dom_equal( + %{<a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>}, + mail_to("me@domain.com", "My email", encode: "hex", replace_at: "(at)") + ) + + assert_dom_equal( + %{<a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">me(at)domain(dot)com</a>}, + mail_to("me@domain.com", nil, encode: "hex", replace_at: "(at)", replace_dot: "(dot)") + ) + + assert_dom_equal( + %{<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>}, + mail_to("me@domain.com", "My email", encode: "javascript", replace_at: "(at)", replace_dot: "(dot)") + ) + + assert_dom_equal( + %{<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>}, + mail_to("me@domain.com", nil, encode: "javascript", replace_at: "(at)", replace_dot: "(dot)") + ) end def test_mail_to_returns_html_safe_string assert mail_to("david@loudthinking.com").html_safe? - assert mail_to("me@domain.com", "My email", :encode => "javascript").html_safe? - assert mail_to("me@domain.com", "My email", :encode => "hex").html_safe? + assert mail_to("me@domain.com", "My email", encode: "javascript").html_safe? + assert mail_to("me@domain.com", "My email", encode: "hex").html_safe? end def protect_against_forgery? @@ -568,62 +613,62 @@ class UrlHelperControllerTest < ActionController::TestCase class UrlHelperController < ActionController::Base test_routes do get 'url_helper_controller_test/url_helper/show/:id', - :to => 'url_helper_controller_test/url_helper#show', - :as => :show + to: 'url_helper_controller_test/url_helper#show', + as: :show get 'url_helper_controller_test/url_helper/profile/:name', - :to => 'url_helper_controller_test/url_helper#show', - :as => :profile + to: 'url_helper_controller_test/url_helper#show', + as: :profile get 'url_helper_controller_test/url_helper/show_named_route', - :to => 'url_helper_controller_test/url_helper#show_named_route', - :as => :show_named_route + to: 'url_helper_controller_test/url_helper#show_named_route', + as: :show_named_route get "/:controller(/:action(/:id))" get 'url_helper_controller_test/url_helper/normalize_recall_params', - :to => UrlHelperController.action(:normalize_recall), - :as => :normalize_recall_params + to: UrlHelperController.action(:normalize_recall), + as: :normalize_recall_params get '/url_helper_controller_test/url_helper/override_url_helper/default', - :to => 'url_helper_controller_test/url_helper#override_url_helper', - :as => :override_url_helper + to: 'url_helper_controller_test/url_helper#override_url_helper', + as: :override_url_helper end def show if params[:name] - render :inline => 'ok' + render inline: 'ok' else redirect_to profile_path(params[:id]) end end def show_url_for - render :inline => "<%= url_for :controller => 'url_helper_controller_test/url_helper', :action => 'show_url_for' %>" + render inline: "<%= url_for controller: 'url_helper_controller_test/url_helper', action: 'show_url_for' %>" end def show_overriden_url_for - render :inline => "<%= url_for params.merge(:controller => 'url_helper_controller_test/url_helper', :action => 'show_url_for') %>" + render inline: "<%= url_for params.merge(controller: 'url_helper_controller_test/url_helper', action: 'show_url_for') %>" end def show_named_route - render :inline => "<%= show_named_route_#{params[:kind]} %>" + render inline: "<%= show_named_route_#{params[:kind]} %>" end def nil_url_for - render :inline => '<%= url_for(nil) %>' + render inline: '<%= url_for(nil) %>' end def normalize_recall_params - render :inline => '<%= normalize_recall_params_path %>' + render inline: '<%= normalize_recall_params_path %>' end def recall_params_not_changed - render :inline => '<%= url_for(:action => :show_url_for) %>' + render inline: '<%= url_for(action: :show_url_for) %>' end def override_url_helper - render :inline => '<%= override_url_helper_path %>' + render inline: '<%= override_url_helper_path %>' end def override_url_helper_path @@ -645,13 +690,13 @@ class UrlHelperControllerTest < ActionController::TestCase end def test_named_route_url_shows_host_and_path - get :show_named_route, :kind => 'url' + get :show_named_route, kind: 'url' assert_equal 'http://test.host/url_helper_controller_test/url_helper/show_named_route', @response.body end def test_named_route_path_shows_only_path - get :show_named_route, :kind => 'path' + get :show_named_route, kind: 'path' assert_equal '/url_helper_controller_test/url_helper/show_named_route', @response.body end @@ -663,11 +708,11 @@ class UrlHelperControllerTest < ActionController::TestCase def test_named_route_should_show_host_and_path_using_controller_default_url_options class << @controller def default_url_options - {:host => 'testtwo.host'} + { host: 'testtwo.host' } end end - get :show_named_route, :kind => 'url' + get :show_named_route, kind: 'url' assert_equal 'http://testtwo.host/url_helper_controller_test/url_helper/show_named_route', @response.body end @@ -682,11 +727,11 @@ class UrlHelperControllerTest < ActionController::TestCase end def test_recall_params_should_normalize_id - get :show, :id => '123' + get :show, id: '123' assert_equal 302, @response.status assert_equal 'http://test.host/url_helper_controller_test/url_helper/profile/123', @response.location - get :show, :name => '123' + get :show, name: '123' assert_equal 'ok', @response.body end @@ -711,9 +756,8 @@ class TasksController < ActionController::Base protected def render_default - render :inline => - "<%= link_to_unless_current(\"tasks\", tasks_path) %>\n" + - "<%= link_to_unless_current(\"tasks\", tasks_url) %>" + render inline: "<%= link_to_unless_current('tasks', tasks_path) %>\n" + + "<%= link_to_unless_current('tasks', tasks_url) %>" end end @@ -726,9 +770,9 @@ class LinkToUnlessCurrentWithControllerTest < ActionController::TestCase end def test_link_to_unless_current_shows_link - get :show, :id => 1 - assert_equal "<a href=\"/tasks\">tasks</a>\n" + - "<a href=\"#{@request.protocol}#{@request.host_with_port}/tasks\">tasks</a>", + get :show, id: 1 + assert_equal %{<a href="/tasks">tasks</a>\n} + + %{<a href="#{@request.protocol}#{@request.host_with_port}/tasks">tasks</a>}, @response.body end end @@ -760,12 +804,12 @@ class WorkshopsController < ActionController::Base def index @workshop = Workshop.new(nil) - render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" + render inline: "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" end def show @workshop = Workshop.new(params[:id]) - render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" + render inline: "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" end end @@ -779,13 +823,13 @@ class SessionsController < ActionController::Base def index @workshop = Workshop.new(params[:workshop_id]) @session = Session.new(nil) - render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>" + render inline: "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>" end def show @workshop = Workshop.new(params[:workshop_id]) @session = Session.new(params[:id]) - render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>" + render inline: "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>" end end @@ -794,27 +838,27 @@ class PolymorphicControllerTest < ActionController::TestCase @controller = WorkshopsController.new get :index - assert_equal "/workshops\n<a href=\"/workshops\">Workshop</a>", @response.body + assert_equal %{/workshops\n<a href="/workshops">Workshop</a>}, @response.body end def test_existing_resource @controller = WorkshopsController.new - get :show, :id => 1 - assert_equal "/workshops/1\n<a href=\"/workshops/1\">Workshop</a>", @response.body + get :show, id: 1 + assert_equal %{/workshops/1\n<a href="/workshops/1">Workshop</a>}, @response.body end def test_new_nested_resource @controller = SessionsController.new - get :index, :workshop_id => 1 - assert_equal "/workshops/1/sessions\n<a href=\"/workshops/1/sessions\">Session</a>", @response.body + get :index, workshop_id: 1 + assert_equal %{/workshops/1/sessions\n<a href="/workshops/1/sessions">Session</a>}, @response.body end def test_existing_nested_resource @controller = SessionsController.new - get :show, :workshop_id => 1, :id => 1 - assert_equal "/workshops/1/sessions/1\n<a href=\"/workshops/1/sessions/1\">Session</a>", @response.body + get :show, workshop_id: 1, id: 1 + assert_equal %{/workshops/1/sessions/1\n<a href="/workshops/1/sessions/1">Session</a>}, @response.body end end |