aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--actionpack/CHANGELOG.md28
-rw-r--r--actionpack/lib/action_controller/caching.rb24
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb189
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb202
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb6
-rw-r--r--actionpack/lib/action_controller/railtie.rb1
-rw-r--r--actionpack/lib/action_controller/test_case.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb2
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb7
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb50
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb101
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb105
-rw-r--r--actionpack/test/controller/caching_test.rb750
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb18
-rw-r--r--actionpack/test/controller/parameters/multi_parameter_attributes_test.rb33
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb7
-rw-r--r--actionpack/test/controller/render_test.rb11
-rw-r--r--actionpack/test/template/html-scanner/sanitizer_test.rb2
-rw-r--r--actionpack/test/template/url_helper_test.rb338
-rw-r--r--activemodel/lib/active_model/validations.rb4
-rw-r--r--activerecord/CHANGELOG.md22
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/counter_cache.rb9
-rw-r--r--activerecord/lib/active_record/persistence.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb6
-rw-r--r--activerecord/test/cases/base_test.rb337
-rw-r--r--activerecord/test/cases/multiparameter_attributes_test.rb350
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb5
-rw-r--r--activesupport/lib/active_support/key_generator.rb23
-rw-r--r--activesupport/test/key_generator_test.rb32
-rw-r--r--activesupport/test/ordered_hash_test.rb1
-rw-r--r--guides/CHANGELOG.md3
-rw-r--r--guides/assets/javascripts/guides.js50
-rw-r--r--guides/assets/javascripts/jquery.min.js4
-rwxr-xr-xguides/assets/javascripts/responsive-tables.js43
-rw-r--r--guides/assets/stylesheets/main.css667
-rwxr-xr-xguides/assets/stylesheets/responsive-tables.css50
-rw-r--r--guides/rails_guides/helpers.rb10
-rw-r--r--guides/source/action_controller_overview.md4
-rw-r--r--guides/source/layout.html.erb52
-rw-r--r--guides/source/migrations.md2
-rw-r--r--guides/source/upgrading_ruby_on_rails.md7
-rw-r--r--railties/CHANGELOG.md2
-rw-r--r--railties/lib/rails.rb2
-rw-r--r--railties/lib/rails/application.rb20
-rw-r--r--railties/lib/rails/generators/app_base.rb9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt5
-rw-r--r--railties/lib/rails/paths.rb1
-rw-r--r--railties/lib/rails/rack/logger.rb9
-rw-r--r--railties/lib/rails/test_help.rb4
-rw-r--r--railties/test/application/configuration_test.rb53
-rw-r--r--railties/test/application/initializers/frameworks_test.rb2
-rw-r--r--railties/test/application/middleware/cache_test.rb2
-rw-r--r--railties/test/application/middleware_test.rb11
-rwxr-xr-xtools/profile2
64 files changed, 1653 insertions, 2071 deletions
diff --git a/Gemfile b/Gemfile
index a2ebd579da..3b77524077 100644
--- a/Gemfile
+++ b/Gemfile
@@ -9,6 +9,8 @@ gem 'rack-test', github: 'brynary/rack-test'
gem 'rack-cache', "~> 1.2"
gem 'bcrypt-ruby', '~> 3.0.0'
gem 'jquery-rails'
+gem 'turbolinks'
+gem 'coffee-rails', github: 'rails/coffee-rails'
gem 'journey', github: 'rails/journey', branch: 'master'
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 5a5c4b33f1..77010ecc70 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,11 +1,33 @@
## Rails 4.0.0 (unreleased) ##
+* 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 actionpack's rack-cache dependency and declare the
- dependency in the Gemfile.
+* Remove `rack-cache` dependency from Action Pack and declare it on Gemfile
- *Guillermo IguarĂ¡n*
+ *Guillermo Iguaran*
* Rename internal variables on ActionController::TemplateAssertions to prevent
naming collisions. @partials, @templates and @layouts are now prefixed with an underscore.
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/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index c9a81e4866..398454d39f 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
@@ -173,6 +178,7 @@ module ActionController
case filter
when Symbol, String then
params[filter] = self[filter] if has_key?(filter)
+ keys.grep(/\A#{Regexp.escape(filter)}\(\di\)\z/) { |key| params[key] = self[key] }
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..ee0f053bad 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -32,7 +32,6 @@ 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
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 3af378173a..ace5a2c822 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -123,9 +123,13 @@ module ActionController
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?(@locals)
+ actual_locals = @locals[expected_partial.to_s.sub(/^_/,'')]
+ expected_locals.each_pair do |k,v|
+ assert_equal(v, actual_locals[k])
+ end
+ 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]
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_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/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/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&amp;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/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..2214ec769c
--- /dev/null
+++ b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb
@@ -0,0 +1,33 @@
+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"
+ }
+ })
+
+ permitted = params.permit book: [ :shipped_at ]
+
+ 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_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/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 7fe8e6051b..18bb51c5a3 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -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/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="'&gt;&lt;script&gt;alert()&lt;/script&gt;">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/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=\"&lt;script&gt;evil_js&lt;/script&gt;\"><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="&lt;script&gt;evil_js&lt;/script&gt;"><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&amp;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&amp;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&amp;q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&amp;q2=v2".html_safe)
+ assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&amp;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&amp;q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"http://www.example.com?q1=v1&amp;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&amp;q2=v2" class="button_to"><div><input type="submit" value="http://www.example.com?q1=v1&amp;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(&#39;yay!&#39;)">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 &lt;script&gt;content&lt;/script&gt;</a>',
+ assert_dom_equal %{<a href="/">Malicious &lt;script&gt;content&lt;/script&gt;</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&amp;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&amp;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&amp;bcc=bccaddress%40example.com&amp;body=This%20is%20the%20body%20of%20the%20message.&amp;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&amp;bcc=bccaddress%40example.com&amp;body=This%20is%20the%20body%20of%20the%20message.&amp;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=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%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=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#64;&#100;&#111;&#109;&#97;&#105;&#110;&#46;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex")
+ assert_dom_equal(
+ %{<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;%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="&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">&#109;&#101;&#64;&#100;&#111;&#109;&#97;&#105;&#110;&#46;&#99;&#111;&#109;</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=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#46;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)")
- assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%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=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#40;&#100;&#111;&#116;&#41;&#99;&#111;&#109;</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="&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#46;&#99;&#111;&#109;</a>},
+ mail_to("me@domain.com", nil, encode: "hex", replace_at: "(at)")
+ )
+
+ assert_dom_equal(
+ %{<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;%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="&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#40;&#100;&#111;&#116;&#41;&#99;&#111;&#109;</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
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 4762f39044..243d911f71 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -188,9 +188,9 @@ module ActiveModel
# # #<ActiveModel::Validations::InclusionValidator:0x007fe603bb8780 @attributes=[:age], @options={:in=>0..99}>
# # ]
def validators_on(*attributes)
- attributes.map do |attribute|
+ attributes.flat_map do |attribute|
_validators[attribute.to_sym]
- end.flatten
+ end
end
# Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 8ff4c4706c..ba292e7f0e 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,27 @@
## Rails 4.0.0 (unreleased) ##
+* The postgres adapter now supports tables with capital letters.
+ Fix #5920
+
+ *Yves Senn*
+
+* `CollectionAssociation#count` returns `0` without querying if the
+ parent record is not persisted.
+
+ Before:
+
+ person.pets.count
+ # SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" IS NULL
+ # => 0
+
+ After:
+
+ person.pets.count
+ # fires without sql query
+ # => 0
+
+ *Francesco Rodriguez*
+
* Fix `reset_counters` crashing on `has_many :through` associations.
Fix #7822.
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index fe3e5b00f7..96270ec0e9 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -174,6 +174,8 @@ module ActiveRecord
# association, it will be used for the query. Otherwise, construct options and pass them with
# scope to the target class's +count+.
def count(column_name = nil, count_options = {})
+ return 0 if owner.new_record?
+
column_name, count_options = nil, column_name if column_name.is_a?(Hash)
if options[:counter_sql] || options[:finder_sql]
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 8a073bf878..2264595751 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -314,7 +314,7 @@ module ActiveRecord
INNER JOIN pg_depend dep ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
WHERE cons.contype = 'p'
- AND dep.refobjid = '#{table}'::regclass
+ AND dep.refobjid = '#{quote_table_name(table)}'::regclass
end_sql
row && row.first
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index d28cd560d9..57838ff984 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -23,12 +23,11 @@ module ActiveRecord
has_many_association = reflect_on_association(association.to_sym)
if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
- foreign_key = has_many_association.through_reflection.foreign_key.to_s
- child_class = has_many_association.through_reflection.klass
- else
- foreign_key = has_many_association.foreign_key.to_s
- child_class = has_many_association.klass
+ has_many_association = has_many_association.through_reflection
end
+
+ foreign_key = has_many_association.foreign_key.to_s
+ child_class = has_many_association.klass
belongs_to = child_class.reflect_on_all_associations(:belongs_to)
reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
counter_name = reflection.counter_cache_column
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index f81eb5f5d1..0107667fbe 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -197,7 +197,7 @@ module ActiveRecord
end
end
- # Updates a single attribute of an object, without calling save.
+ # Updates a single attribute of an object, without having to call save on that object.
#
# * Validation is skipped.
# * Callbacks are skipped.
@@ -209,7 +209,7 @@ module ActiveRecord
update_columns(name => value)
end
- # Updates the attributes from the passed-in hash, without calling save.
+ # Updates the attributes from the passed-in hash, without having to call save on that object.
#
# * Validation is skipped.
# * Callbacks are skipped.
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 92e31a3e44..f1362dd15f 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -14,6 +14,10 @@ module ActiveRecord
assert_equal 'id', @connection.primary_key('ex')
end
+ def test_primary_key_works_tables_containing_capital_letters
+ assert_equal 'id', @connection.primary_key('CamelCase')
+ end
+
def test_non_standard_primary_key
@connection.exec_query('drop table if exists ex')
@connection.exec_query('create table ex(data character varying(255) primary key)')
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index f3520d43e0..42f5b69d4e 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -799,6 +799,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, developer.projects.count
end
+ def test_counting_should_not_fire_sql_if_parent_is_unsaved
+ assert_no_queries do
+ assert_equal 0, Developer.new.projects.count
+ end
+ end
+
unless current_adapter?(:PostgreSQLAdapter)
def test_count_with_finder_sql
assert_equal 3, projects(:active_record).developers_with_finder_sql.count
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 4b56037a08..50c23c863f 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -262,6 +262,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal firm.limited_clients.length, firm.limited_clients.count
end
+ def test_counting_should_not_fire_sql_if_parent_is_unsaved
+ assert_no_queries do
+ assert_equal 0, Person.new.readers.count
+ end
+ end
+
def test_finding
assert_equal 2, Firm.all.merge!(:order => "id").first.clients.length
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index d4ceae6f80..f0582a3090 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -767,6 +767,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal 1, authors(:mary).categories.general.count
end
+ def test_counting_should_not_fire_sql_if_parent_is_unsaved
+ assert_no_queries do
+ assert_equal 0, Person.new.posts.count
+ end
+ end
+
def test_has_many_through_belongs_to_should_update_when_the_through_foreign_key_changes
post = posts(:eager_other)
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index fbfdd0f07a..0f859bf452 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -617,343 +617,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal 'value2', weird.read_attribute('a$b')
end
- def test_multiparameter_attributes_on_date
- attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
- topic = Topic.find(1)
- topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
- assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
- end
-
- def test_multiparameter_attributes_on_date_with_empty_year
- attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" }
- topic = Topic.find(1)
- topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
- assert_nil topic.last_read
- end
-
- def test_multiparameter_attributes_on_date_with_empty_month
- attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" }
- topic = Topic.find(1)
- topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
- assert_nil topic.last_read
- end
-
- def test_multiparameter_attributes_on_date_with_empty_day
- attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
- topic = Topic.find(1)
- topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
- assert_nil topic.last_read
- end
-
- def test_multiparameter_attributes_on_date_with_empty_day_and_year
- attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" }
- topic = Topic.find(1)
- topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
- assert_nil topic.last_read
- end
-
- def test_multiparameter_attributes_on_date_with_empty_day_and_month
- attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" }
- topic = Topic.find(1)
- topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
- assert_nil topic.last_read
- end
-
- def test_multiparameter_attributes_on_date_with_empty_year_and_month
- attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" }
- topic = Topic.find(1)
- topic.attributes = attributes
- # note that extra #to_date call allows test to pass for Oracle, which
- # treats dates/times the same
- assert_nil topic.last_read
- end
-
- def test_multiparameter_attributes_on_date_with_all_empty
- attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_nil topic.last_read
- end
-
- def test_multiparameter_attributes_on_time
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
- end
-
- def test_multiparameter_attributes_on_time_with_no_date
- ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
- attributes = {
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- end
- assert_equal("written_on", ex.errors[0].attribute)
- end
-
- def test_multiparameter_attributes_on_time_with_invalid_time_params
- ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "2004", "written_on(5i)" => "36", "written_on(6i)" => "64",
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- end
- assert_equal("written_on", ex.errors[0].attribute)
- end
-
- def test_multiparameter_attributes_on_time_with_old_date
- attributes = {
- "written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- # testing against to_s(:db) representation because either a Time or a DateTime might be returned, depending on platform
- assert_equal "1850-06-24 16:24:00", topic.written_on.to_s(:db)
- end
-
- def test_multiparameter_attributes_on_time_will_raise_on_big_time_if_missing_date_parts
- ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
- attributes = {
- "written_on(4i)" => "16", "written_on(5i)" => "24"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- end
- assert_equal("written_on", ex.errors[0].attribute)
- end
-
- def test_multiparameter_attributes_on_time_with_raise_on_small_time_if_missing_date_parts
- ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
- attributes = {
- "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- end
- assert_equal("written_on", ex.errors[0].attribute)
- end
-
- def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12",
- "written_on(5i)" => "12", "written_on(6i)" => "02"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.local(2004, 12, 12, 0, 12, 2), topic.written_on
- end
-
- def test_multiparameter_attributes_on_time_will_ignore_hour_if_blank
- attributes = {
- "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
- "written_on(4i)" => "", "written_on(5i)" => "12", "written_on(6i)" => "02"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_nil topic.written_on
- end
-
- def test_multiparameter_attributes_on_time_will_ignore_date_if_empty
- attributes = {
- "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
- "written_on(4i)" => "16", "written_on(5i)" => "24"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_nil topic.written_on
- end
- def test_multiparameter_attributes_on_time_with_seconds_will_ignore_date_if_empty
- attributes = {
- "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
- "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_nil topic.written_on
- end
-
- def test_multiparameter_attributes_on_time_with_utc
- ActiveRecord::Base.default_timezone = :utc
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
- end
-
- def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes
- ActiveRecord::Base.time_zone_aware_attributes = true
- ActiveRecord::Base.default_timezone = :utc
- Time.zone = ActiveSupport::TimeZone[-28800]
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.utc(2004, 6, 24, 23, 24, 0), topic.written_on
- assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time
- assert_equal Time.zone, topic.written_on.time_zone
- end
-
- def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false
- Time.zone = ActiveSupport::TimeZone[-28800]
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
- assert_equal false, topic.written_on.respond_to?(:time_zone)
- end
-
- def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes
- ActiveRecord::Base.time_zone_aware_attributes = true
- ActiveRecord::Base.default_timezone = :utc
- Time.zone = ActiveSupport::TimeZone[-28800]
- Topic.skip_time_zone_conversion_for_attributes = [:written_on]
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
- assert_equal false, topic.written_on.respond_to?(:time_zone)
- ensure
- Topic.skip_time_zone_conversion_for_attributes = []
- end
-
- # Oracle, and Sybase do not have a TIME datatype.
- unless current_adapter?(:OracleAdapter, :SybaseAdapter)
- def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion
- ActiveRecord::Base.time_zone_aware_attributes = true
- ActiveRecord::Base.default_timezone = :utc
- Time.zone = ActiveSupport::TimeZone[-28800]
- attributes = {
- "bonus_time(1i)" => "2000", "bonus_time(2i)" => "1", "bonus_time(3i)" => "1",
- "bonus_time(4i)" => "16", "bonus_time(5i)" => "24"
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time
- assert topic.bonus_time.utc?
- end
- end
-
- def test_multiparameter_attributes_on_time_with_empty_seconds
- attributes = {
- "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
- "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
- }
- topic = Topic.find(1)
- topic.attributes = attributes
- assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
- end
-
- def test_multiparameter_attributes_setting_time_attribute
- return skip "Oracle does not have TIME data type" if current_adapter? :OracleAdapter
-
- topic = Topic.new( "bonus_time(4i)"=> "01", "bonus_time(5i)" => "05" )
- assert_equal 1, topic.bonus_time.hour
- assert_equal 5, topic.bonus_time.min
- end
-
- def test_multiparameter_attributes_setting_date_attribute
- topic = Topic.new( "written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11" )
- assert_equal 1952, topic.written_on.year
- assert_equal 3, topic.written_on.month
- assert_equal 11, topic.written_on.day
- end
-
- def test_multiparameter_attributes_setting_date_and_time_attribute
- topic = Topic.new(
- "written_on(1i)" => "1952",
- "written_on(2i)" => "3",
- "written_on(3i)" => "11",
- "written_on(4i)" => "13",
- "written_on(5i)" => "55")
- assert_equal 1952, topic.written_on.year
- assert_equal 3, topic.written_on.month
- assert_equal 11, topic.written_on.day
- assert_equal 13, topic.written_on.hour
- assert_equal 55, topic.written_on.min
- end
-
- def test_multiparameter_attributes_setting_time_but_not_date_on_date_field
- assert_raise( ActiveRecord::MultiparameterAssignmentErrors ) do
- Topic.new( "written_on(4i)" => "13", "written_on(5i)" => "55" )
- end
- end
-
- def test_multiparameter_assignment_of_aggregation
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country }
- customer.attributes = attributes
- assert_equal address, customer.address
- end
-
- def test_multiparameter_assignment_of_aggregation_out_of_order
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(3)" => address.country, "address(2)" => address.city, "address(1)" => address.street }
- customer.attributes = attributes
- assert_equal address, customer.address
- end
-
- def test_multiparameter_assignment_of_aggregation_with_missing_values
- ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(2)" => address.city, "address(3)" => address.country }
- customer.attributes = attributes
- end
- assert_equal("address", ex.errors[0].attribute)
- end
-
- def test_multiparameter_assignment_of_aggregation_with_blank_values
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(1)" => "", "address(2)" => address.city, "address(3)" => address.country }
- customer.attributes = attributes
- assert_equal Address.new(nil, "The City", "The Country"), customer.address
- end
-
- def test_multiparameter_assignment_of_aggregation_with_large_index
- ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
- customer = Customer.new
- address = Address.new("The Street", "The City", "The Country")
- attributes = { "address(1)" => "The Street", "address(2)" => address.city, "address(3000)" => address.country }
- customer.attributes = attributes
- end
-
- assert_equal("address", ex.errors[0].attribute)
- end
-
def test_attributes_on_dummy_time
# Oracle, and Sybase do not have a TIME datatype.
return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb
new file mode 100644
index 0000000000..1209f5460f
--- /dev/null
+++ b/activerecord/test/cases/multiparameter_attributes_test.rb
@@ -0,0 +1,350 @@
+require "cases/helper"
+require 'models/topic'
+require 'models/customer'
+
+class MultiParameterAttributeTest < ActiveRecord::TestCase
+ fixtures :topics
+
+ def setup
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ ActiveRecord::Base.default_timezone = :local
+ Time.zone = nil
+ end
+
+ def test_multiparameter_attributes_on_date
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_year
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_nil topic.last_read
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_month
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_nil topic.last_read
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_day
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_nil topic.last_read
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_day_and_year
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_nil topic.last_read
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_day_and_month
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_nil topic.last_read
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_year_and_month
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_nil topic.last_read
+ end
+
+ def test_multiparameter_attributes_on_date_with_all_empty
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_nil topic.last_read
+ end
+
+ def test_multiparameter_attributes_on_time
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
+ end
+
+ def test_multiparameter_attributes_on_time_with_no_date
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_with_invalid_time_params
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "2004", "written_on(5i)" => "36", "written_on(6i)" => "64",
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_with_old_date
+ attributes = {
+ "written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # testing against to_s(:db) representation because either a Time or a DateTime might be returned, depending on platform
+ assert_equal "1850-06-24 16:24:00", topic.written_on.to_s(:db)
+ end
+
+ def test_multiparameter_attributes_on_time_will_raise_on_big_time_if_missing_date_parts
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_with_raise_on_small_time_if_missing_date_parts
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ attributes = {
+ "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ end
+ assert_equal("written_on", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12",
+ "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 12, 12, 0, 12, 2), topic.written_on
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_hour_if_blank
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_nil topic.written_on
+ end
+
+ def test_multiparameter_attributes_on_time_will_ignore_date_if_empty
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "16", "written_on(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_nil topic.written_on
+ end
+ def test_multiparameter_attributes_on_time_with_seconds_will_ignore_date_if_empty
+ attributes = {
+ "written_on(1i)" => "", "written_on(2i)" => "", "written_on(3i)" => "",
+ "written_on(4i)" => "16", "written_on(5i)" => "12", "written_on(6i)" => "02"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_nil topic.written_on
+ end
+
+ def test_multiparameter_attributes_on_time_with_utc
+ ActiveRecord::Base.default_timezone = :utc
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
+ end
+
+ def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes
+ ActiveRecord::Base.time_zone_aware_attributes = true
+ ActiveRecord::Base.default_timezone = :utc
+ Time.zone = ActiveSupport::TimeZone[-28800]
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2004, 6, 24, 23, 24, 0), topic.written_on
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time
+ assert_equal Time.zone, topic.written_on.time_zone
+ end
+
+ def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false
+ Time.zone = ActiveSupport::TimeZone[-28800]
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
+ assert_equal false, topic.written_on.respond_to?(:time_zone)
+ end
+
+ def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes
+ ActiveRecord::Base.time_zone_aware_attributes = true
+ ActiveRecord::Base.default_timezone = :utc
+ Time.zone = ActiveSupport::TimeZone[-28800]
+ Topic.skip_time_zone_conversion_for_attributes = [:written_on]
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
+ assert_equal false, topic.written_on.respond_to?(:time_zone)
+ ensure
+ Topic.skip_time_zone_conversion_for_attributes = []
+ end
+
+ # Oracle, and Sybase do not have a TIME datatype.
+ unless current_adapter?(:OracleAdapter, :SybaseAdapter)
+ def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion
+ ActiveRecord::Base.time_zone_aware_attributes = true
+ ActiveRecord::Base.default_timezone = :utc
+ Time.zone = ActiveSupport::TimeZone[-28800]
+ attributes = {
+ "bonus_time(1i)" => "2000", "bonus_time(2i)" => "1", "bonus_time(3i)" => "1",
+ "bonus_time(4i)" => "16", "bonus_time(5i)" => "24"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time
+ assert topic.bonus_time.utc?
+ end
+ end
+
+ def test_multiparameter_attributes_on_time_with_empty_seconds
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
+ end
+
+ def test_multiparameter_attributes_setting_time_attribute
+ return skip "Oracle does not have TIME data type" if current_adapter? :OracleAdapter
+
+ topic = Topic.new( "bonus_time(4i)"=> "01", "bonus_time(5i)" => "05" )
+ assert_equal 1, topic.bonus_time.hour
+ assert_equal 5, topic.bonus_time.min
+ end
+
+ def test_multiparameter_attributes_setting_date_attribute
+ topic = Topic.new( "written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11" )
+ assert_equal 1952, topic.written_on.year
+ assert_equal 3, topic.written_on.month
+ assert_equal 11, topic.written_on.day
+ end
+
+ def test_multiparameter_attributes_setting_date_and_time_attribute
+ topic = Topic.new(
+ "written_on(1i)" => "1952",
+ "written_on(2i)" => "3",
+ "written_on(3i)" => "11",
+ "written_on(4i)" => "13",
+ "written_on(5i)" => "55")
+ assert_equal 1952, topic.written_on.year
+ assert_equal 3, topic.written_on.month
+ assert_equal 11, topic.written_on.day
+ assert_equal 13, topic.written_on.hour
+ assert_equal 55, topic.written_on.min
+ end
+
+ def test_multiparameter_attributes_setting_time_but_not_date_on_date_field
+ assert_raise( ActiveRecord::MultiparameterAssignmentErrors ) do
+ Topic.new( "written_on(4i)" => "13", "written_on(5i)" => "55" )
+ end
+ end
+
+ def test_multiparameter_assignment_of_aggregation
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country }
+ customer.attributes = attributes
+ assert_equal address, customer.address
+ end
+
+ def test_multiparameter_assignment_of_aggregation_out_of_order
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(3)" => address.country, "address(2)" => address.city, "address(1)" => address.street }
+ customer.attributes = attributes
+ assert_equal address, customer.address
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_missing_values
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(2)" => address.city, "address(3)" => address.country }
+ customer.attributes = attributes
+ end
+ assert_equal("address", ex.errors[0].attribute)
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_blank_values
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(1)" => "", "address(2)" => address.city, "address(3)" => address.country }
+ customer.attributes = attributes
+ assert_equal Address.new(nil, "The City", "The Country"), customer.address
+ end
+
+ def test_multiparameter_assignment_of_aggregation_with_large_index
+ ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(1)" => "The Street", "address(2)" => address.city, "address(3000)" => address.country }
+ customer.attributes = attributes
+ end
+
+ assert_equal("address", ex.errors[0].attribute)
+ end
+end
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index fbe82c3de5..30f4ded005 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -22,5 +22,5 @@ Gem::Specification.new do |s|
s.add_dependency('i18n', '~> 0.6')
s.add_dependency('multi_json', '~> 1.3')
s.add_dependency('tzinfo', '~> 0.3.33')
- s.add_dependency('minitest', '~> 4.0')
+ s.add_dependency('minitest', '~> 4.1')
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 41d77ab6c1..4e397ea110 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -48,6 +48,7 @@ module ActiveSupport
autoload :Gzip
autoload :Inflector
autoload :JSON
+ autoload :KeyGenerator
autoload :MessageEncryptor
autoload :MessageVerifier
autoload :Multibyte
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index 53d05c3817..f1aff8a8e3 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -34,7 +34,7 @@ module ActiveSupport
# Returns the backtrace after all filters and silencers have been run
# against it. Filters run first, then silencers.
def clean(backtrace, kind = :silent)
- filtered = filter(backtrace)
+ filtered = filter_backtrace(backtrace)
case kind
when :silent
@@ -45,6 +45,7 @@ module ActiveSupport
filtered
end
end
+ alias :filter :clean
# Adds a filter from the block provided. Each line in the backtrace will be
# mapped against this filter.
@@ -76,7 +77,7 @@ module ActiveSupport
end
private
- def filter(backtrace)
+ def filter_backtrace(backtrace)
@filters.each do |f|
backtrace = backtrace.map { |line| f.call(line) }
end
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
new file mode 100644
index 0000000000..04d170f801
--- /dev/null
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -0,0 +1,23 @@
+require 'openssl'
+
+module ActiveSupport
+ # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2
+ # It can be used to derive a number of keys for various purposes from a given secret.
+ # This lets rails applications have a single secure secret, but avoid reusing that
+ # key in multiple incompatible contexts.
+ class KeyGenerator
+ def initialize(secret, options = {})
+ @secret = secret
+ # The default iterations are higher than required for our key derivation uses
+ # on the off chance someone uses this for password storage
+ @iterations = options[:iterations] || 2**16
+ end
+
+ # Returns a derived key suitable for use. The default key_size is chosen
+ # to be compatible with the default settings of ActiveSupport::MessageVerifier.
+ # i.e. OpenSSL::Digest::SHA1#block_length
+ def generate_key(salt, key_size=64)
+ OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
+ end
+ end
+end
diff --git a/activesupport/test/key_generator_test.rb b/activesupport/test/key_generator_test.rb
new file mode 100644
index 0000000000..525082d313
--- /dev/null
+++ b/activesupport/test/key_generator_test.rb
@@ -0,0 +1,32 @@
+require 'abstract_unit'
+
+begin
+ require 'openssl'
+ OpenSSL::PKCS5
+rescue LoadError, NameError
+ $stderr.puts "Skipping KeyGenerator test: broken OpenSSL install"
+else
+
+require 'active_support/time'
+require 'active_support/json'
+
+class KeyGeneratorTest < ActiveSupport::TestCase
+ def setup
+ @secret = SecureRandom.hex(64)
+ @generator = ActiveSupport::KeyGenerator.new(@secret, :iterations=>2)
+ end
+
+ test "Generating a key of the default length" do
+ derived_key = @generator.generate_key("some_salt")
+ assert_kind_of String, derived_key
+ assert_equal OpenSSL::Digest::SHA1.new.block_length, derived_key.length, "Should have generated a key of the default size"
+ end
+
+ test "Generating a key of an alternative length" do
+ derived_key = @generator.generate_key("some_salt", 32)
+ assert_kind_of String, derived_key
+ assert_equal 32, derived_key.length, "Should have generated a key of the right size"
+ end
+end
+
+end
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index ac85ba1f21..14ba4e0076 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -216,7 +216,6 @@ class OrderedHashTest < ActiveSupport::TestCase
alternate = ActiveSupport::OrderedHash[ [
[1, 2],
[3, 4],
- "bad key value pair",
[ 'missing value' ]
]]
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
new file mode 100644
index 0000000000..d65e507a74
--- /dev/null
+++ b/guides/CHANGELOG.md
@@ -0,0 +1,3 @@
+## Rails 4.0.0 (unreleased) ##
+
+* Guides have a responsive design. *Joe Fiorini*
diff --git a/guides/assets/javascripts/guides.js b/guides/assets/javascripts/guides.js
index c4e4d459ea..7e494fb6d8 100644
--- a/guides/assets/javascripts/guides.js
+++ b/guides/assets/javascripts/guides.js
@@ -5,3 +5,53 @@ function guideMenu(){
document.getElementById('guides').style.display = "none";
}
}
+
+$.fn.selectGuide = function(guide){
+ $("select", this).val(guide);
+}
+
+guidesIndex = {
+ bind: function(){
+ var currentGuidePath = window.location.pathname;
+ var currentGuide = currentGuidePath.substring(currentGuidePath.lastIndexOf("/")+1);
+ $(".guides-index-small").
+ on("change", "select", guidesIndex.navigate).
+ selectGuide(currentGuide);
+ $(".more-info-button:visible").click(function(e){
+ e.stopPropagation();
+ if($(".more-info-links").is(":visible")){
+ $(".more-info-links").addClass("s-hidden").unwrap();
+ } else {
+ $(".more-info-links").wrap("<div class='more-info-container'></div>").removeClass("s-hidden");
+ }
+ $(document).on("click", function(e){
+ var $button = $(".more-info-button");
+ var element;
+
+ // Cross browser find the element that had the event
+ if (e.target) element = e.target;
+ else if (e.srcElement) element = e.srcElement;
+
+ // Defeat the older Safari bug:
+ // http://www.quirksmode.org/js/events_properties.html
+ if (element.nodeType == 3) element = element.parentNode;
+
+ var $element = $(element);
+
+ var $container = $element.parents(".more-info-container");
+
+ // We've captured a click outside the popup
+ if($container.length == 0){
+ $container = $button.next(".more-info-container");
+ $container.find(".more-info-links").addClass("s-hidden").unwrap();
+ $(document).off("click");
+ }
+ });
+ });
+ },
+ navigate: function(e){
+ var $list = $(e.target);
+ url = $list.val();
+ window.location = url;
+ }
+}
diff --git a/guides/assets/javascripts/jquery.min.js b/guides/assets/javascripts/jquery.min.js
new file mode 100644
index 0000000000..93adea19fd
--- /dev/null
+++ b/guides/assets/javascripts/jquery.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7.2 jquery.com | jquery.org/license */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"<!doctype html>":"")+"<html><body>"),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bD.test(a)?d(a,e):b_(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&f.type(b)==="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function bZ(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bS,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bZ(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bZ(a,c,d,e,"*",g));return l}function bY(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bO),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bB(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?1:0,g=4;if(d>0){if(c!=="border")for(;e<g;e+=2)c||(d-=parseFloat(f.css(a,"padding"+bx[e]))||0),c==="margin"?d+=parseFloat(f.css(a,c+bx[e]))||0:d-=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0;return d+"px"}d=by(a,b);if(d<0||d==null)d=a.style[b];if(bt.test(d))return d;d=parseFloat(d)||0;if(c)for(;e<g;e+=2)d+=parseFloat(f.css(a,"padding"+bx[e]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+bx[e]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+bx[e]))||0);return d+"px"}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;b.nodeType===1&&(b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?b.outerHTML=a.outerHTML:c!=="input"||a.type!=="checkbox"&&a.type!=="radio"?c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text):(a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value)),b.removeAttribute(f.expando),b.removeAttribute("_submit_attached"),b.removeAttribute("_change_attached"))}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c,i[c][d])}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h,i){var j,k=d==null,l=0,m=a.length;if(d&&typeof d=="object"){for(l in d)e.access(a,c,l,d[l],1,h,f);g=1}else if(f!==b){j=i===b&&e.isFunction(f),k&&(j?(j=c,c=function(a,b,c){return j.call(e(a),c)}):(c.call(a,f),c=null));if(c)for(;l<m;l++)c(a[l],d,j?f.call(a[l],l,c(a[l],d)):f,i);g=1}return g?a:k?c.call(a):m?c(a[0],d):h},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m,n=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?n(g):h==="function"&&(!a.unique||!p.has(g))&&c.push(g)},o=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,j=!0,m=k||0,k=0,l=c.length;for(;c&&m<l;m++)if(c[m].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}j=!1,c&&(a.once?e===!0?p.disable():c=[]:d&&d.length&&(e=d.shift(),p.fireWith(e[0],e[1])))},p={add:function(){if(c){var a=c.length;n(arguments),j?l=c.length:e&&e!==!0&&(k=a,o(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){j&&f<=l&&(l--,f<=m&&m--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&p.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(j?a.once||d.push([b,c]):(!a.once||!e)&&o(b,c));return this},fire:function(){p.fireWith(this,arguments);return this},fired:function(){return!!i}};return p};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p=c.createElement("div"),q=c.documentElement;p.setAttribute("className","t"),p.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="<div "+n+"display:block;'><div style='"+t+"0;display:block;overflow:hidden;'></div></div>"+"<table "+n+"' cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="<table><tr><td style='"+t+"0;display:none'></td><td>t</td></tr></table>",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="<div style='width:5px;'></div>",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h,i,j=this[0],k=0,m=null;if(a===b){if(this.length){m=f.data(j);if(j.nodeType===1&&!f._data(j,"parsedAttrs")){g=j.attributes;for(i=g.length;k<i;k++)h=g[k].name,h.indexOf("data-")===0&&(h=f.camelCase(h.substring(5)),l(j,h,m[h]));f._data(j,"parsedAttrs",!0)}}return m}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!";return f.access(this,function(c){if(c===b){m=this.triggerHandler("getData"+e,[d[0]]),m===b&&j&&(m=f.data(j,a),m=l(j,a,m));return m===b&&d[1]?this.data(d[0]):m}d[1]=c,this.each(function(){var b=f(this);b.triggerHandler("setData"+e,d),f.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length<d)return f.queue(this[0],a);return c===b?this:this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise(c)}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,f.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i<g;i++)e=d[i],e&&(c=f.propFix[e]||e,h=u.test(e),h||f.attr(a,e,""),a.removeAttribute(v?e:c),h&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0,coords:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(
+a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:g&&G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=f.event.special[c.type]||{},j=[],k,l,m,n,o,p,q,r,s,t,u;g[0]=c,c.delegateTarget=this;if(!i.preDispatch||i.preDispatch.call(this,c)!==!1){if(e&&(!c.button||c.type!=="click")){n=f(this),n.context=this.ownerDocument||this;for(m=c.target;m!=this;m=m.parentNode||this)if(m.disabled!==!0){p={},r=[],n[0]=m;for(k=0;k<e;k++)s=d[k],t=s.selector,p[t]===b&&(p[t]=s.quick?H(m,s.quick):n.is(t)),p[t]&&r.push(s);r.length&&j.push({elem:m,matches:r})}}d.length>e&&j.push({elem:this,matches:d.slice(e)});for(k=0;k<j.length&&!c.isPropagationStopped();k++){q=j[k],c.currentTarget=q.elem;for(l=0;l<q.matches.length&&!c.isImmediatePropagationStopped();l++){s=q.matches[l];if(h||!c.namespace&&!s.namespace||c.namespace_re&&c.namespace_re.test(s.namespace))c.data=s.data,c.handleObj=s,o=((f.event.special[s.origType]||{}).handle||s.handler).apply(q.elem,g),o!==b&&(c.result=o,o===!1&&(c.preventDefault(),c.stopPropagation()))}}i.postDispatch&&i.postDispatch.call(this,c);return c.result}},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),d._submit_attached=!0)})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9||d===11){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.globalPOS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")[\\s/>]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f
+.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(f.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(g){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,function(a,b){b.src?f.ajax({type:"GET",global:!1,url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1></$2>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]==="<table>"&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i<u;i++)bn(l[i]);else bn(l);l.nodeType?j.push(l):j=f.merge(j,l)}if(d){g=function(a){return!a.type||be.test(a.type)};for(k=0;j[k];k++){h=j[k];if(e&&f.nodeName(h,"script")&&(!h.type||be.test(h.type)))e.push(h.parentNode?h.parentNode.removeChild(h):h);else{if(h.nodeType===1){var v=f.grep(h.getElementsByTagName("script"),g);j.splice.apply(j,[k+1,0].concat(v))}d.appendChild(h)}}}return j},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bp=/alpha\([^)]*\)/i,bq=/opacity=([^)]*)/,br=/([A-Z]|^ms)/g,bs=/^[\-+]?(?:\d*\.)?\d+$/i,bt=/^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,bu=/^([\-+])=([\-+.\de]+)/,bv=/^margin/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Top","Right","Bottom","Left"],by,bz,bA;f.fn.css=function(a,c){return f.access(this,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)},a,c,arguments.length>1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),(e===""&&f.css(d,"display")==="none"||!f.contains(d.ownerDocument.documentElement,d))&&f._data(d,"olddisplay",cu(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(ct("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(ct("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o,p,q;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]);if((k=f.cssHooks[g])&&"expand"in k){l=k.expand(a[g]),delete a[g];for(i in l)i in a||(a[i]=l[i])}}for(g in a){h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cu(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cm.test(h)?(q=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),q?(f._data(this,"toggle"+i,q==="show"?"hide":"show"),j[q]()):j[h]()):(m=cn.exec(h),n=j.cur(),m?(o=parseFloat(m[2]),p=m[3]||(f.cssNumber[i]?"":"px"),p!=="px"&&(f.style(this,i,(o||1)+p),n=(o||1)/j.cur()*n,f.style(this,i,n+p)),m[1]&&(o=(m[1]==="-="?-1:1)*o+n),j.custom(n,o,p)):j.custom(n,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:ct("show",1),slideUp:ct("hide",1),slideToggle:ct("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a){return a},swing:function(a){return-Math.cos(a*Math.PI)/2+.5}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cq||cr(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){f._data(e.elem,"fxshow"+e.prop)===b&&(e.options.hide?f._data(e.elem,"fxshow"+e.prop,e.start):e.options.show&&f._data(e.elem,"fxshow"+e.prop,e.end))},h()&&f.timers.push(h)&&!co&&(co=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cq||cr(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(cp.concat.apply([],cp),function(a,b){b.indexOf("margin")&&(f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)})}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cv,cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?cv=function(a,b,c,d){try{d=a.getBoundingClientRect()}catch(e){}if(!d||!f.contains(c,a))return d?{top:d.top,left:d.left}:{top:0,left:0};var g=b.body,h=cy(b),i=c.clientTop||g.clientTop||0,j=c.clientLeft||g.clientLeft||0,k=h.pageYOffset||f.support.boxModel&&c.scrollTop||g.scrollTop,l=h.pageXOffset||f.support.boxModel&&c.scrollLeft||g.scrollLeft,m=d.top+k-i,n=d.left+l-j;return{top:m,left:n}}:cv=function(a,b,c){var d,e=a.offsetParent,g=a,h=b.body,i=b.defaultView,j=i?i.getComputedStyle(a,null):a.currentStyle,k=a.offsetTop,l=a.offsetLeft;while((a=a.parentNode)&&a!==h&&a!==c){if(f.support.fixedPosition&&j.position==="fixed")break;d=i?i.getComputedStyle(a,null):a.currentStyle,k-=a.scrollTop,l-=a.scrollLeft,a===e&&(k+=a.offsetTop,l+=a.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(a.nodeName))&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),g=e,e=a.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"&&(k+=parseFloat(d.borderTopWidth)||0,l+=parseFloat(d.borderLeftWidth)||0),j=d}if(j.position==="relative"||j.position==="static")k+=h.offsetTop,l+=h.offsetLeft;f.support.fixedPosition&&j.position==="fixed"&&(k+=Math.max(c.scrollTop,h.scrollTop),l+=Math.max(c.scrollLeft,h.scrollLeft));return{top:k,left:l}},f.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){f.offset.setOffset(this,a,b)});var c=this[0],d=c&&c.ownerDocument;if(!d)return null;if(c===d.body)return f.offset.bodyOffset(c);return cv(c,d,d.documentElement)},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file
diff --git a/guides/assets/javascripts/responsive-tables.js b/guides/assets/javascripts/responsive-tables.js
new file mode 100755
index 0000000000..8554a1343b
--- /dev/null
+++ b/guides/assets/javascripts/responsive-tables.js
@@ -0,0 +1,43 @@
+$(document).ready(function() {
+ var switched = false;
+ $("table").not(".syntaxhighlighter").addClass("responsive");
+ var updateTables = function() {
+ if (($(window).width() < 767) && !switched ){
+ switched = true;
+ $("table.responsive").each(function(i, element) {
+ splitTable($(element));
+ });
+ return true;
+ }
+ else if (switched && ($(window).width() > 767)) {
+ switched = false;
+ $("table.responsive").each(function(i, element) {
+ unsplitTable($(element));
+ });
+ }
+ };
+
+ $(window).load(updateTables);
+ $(window).bind("resize", updateTables);
+
+
+ function splitTable(original)
+ {
+ original.wrap("<div class='table-wrapper' />");
+
+ var copy = original.clone();
+ copy.find("td:not(:first-child), th:not(:first-child)").css("display", "none");
+ copy.removeClass("responsive");
+
+ original.closest(".table-wrapper").append(copy);
+ copy.wrap("<div class='pinned' />");
+ original.wrap("<div class='scrollable' />");
+ }
+
+ function unsplitTable(original) {
+ original.closest(".table-wrapper").find(".pinned").remove();
+ original.unwrap();
+ original.unwrap();
+ }
+
+});
diff --git a/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index a30e12fe7e..9f5e101d1c 100644
--- a/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -9,6 +9,9 @@
.left {float: left; margin-right: 1em;}
.right {float: right; margin-left: 1em;}
+@media screen and (max-width: 480px) {
+ .left, .right { float: none; }
+}
.small {font-size: smaller;}
.large {font-size: larger;}
.hide {display: none;}
@@ -29,6 +32,13 @@ pre,code {
font-family: "Anonymous Pro", "Inconsolata", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace;
line-height: 1.5;
}
+pre,tt,code,.note>p {
+ white-space: pre-wrap; /* css-3 */
+ white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
+ white-space: -pre-wrap; /* Opera 4-6 */
+ white-space: -o-pre-wrap; /* Opera 7 */
+ word-wrap: break-word; /* Internet Explorer 5.5+ */
+}
abbr, acronym { border-bottom: 1px dotted #666; }
address { margin: 0 0 1.5em; font-style: italic; }
@@ -52,23 +62,23 @@ textarea, select {
}
table {
- margin: 0 0 1.5em;
- border: 2px solid #CCC;
- background: #FFF;
- border-collapse: collapse;
+ margin: 0 0 1.5em;
+ border: 2px solid #CCC;
+ background: #FFF;
+ border-collapse: collapse;
}
table th, table td {
- padding: 0.25em 1em;
- border: 1px solid #CCC;
- border-collapse: collapse;
+ padding: 0.25em 1em;
+ border: 1px solid #CCC;
+ border-collapse: collapse;
}
table th {
- border-bottom: 2px solid #CCC;
- background: #EEE;
- font-weight: bold;
- padding: 0.5em 1em;
+ border-bottom: 2px solid #CCC;
+ background: #EEE;
+ font-weight: bold;
+ padding: 0.5em 1em;
}
@@ -76,84 +86,205 @@ table th {
--------------------------------------- */
body {
- text-align: center;
- font-family: Helvetica, Arial, sans-serif;
- font-size: 87.5%;
- line-height: 1.5em;
- background: #fff;
- min-width: 69em;
- color: #999;
- }
+ text-align: center;
+ font-family: Helvetica, Arial, sans-serif;
+ font-size: 87.5%;
+ line-height: 1.5em;
+ background: #fff;
+ color: #999;
+ }
.wrapper {
- text-align: left;
- margin: 0 auto;
- width: 69em;
- }
+ text-align: left;
+ margin: 0 auto;
+ max-width: 960px;
+ padding: 0 1em;
+ }
+
+.red-button {
+ display: inline-block;
+ border-top: 1px solid rgba(255,255,255,.5);
+ background: #751913;
+ background: -webkit-gradient(linear, left top, left bottom, from(#c52f24), to(#751913));
+ background: -webkit-linear-gradient(top, #c52f24, #751913);
+ background: -moz-linear-gradient(top, #c52f24, #751913);
+ background: -ms-linear-gradient(top, #c52f24, #751913);
+ background: -o-linear-gradient(top, #c52f24, #751913);
+ padding: 9px 18px;
+ -webkit-border-radius: 11px;
+ -moz-border-radius: 11px;
+ border-radius: 11px;
+ -webkit-box-shadow: rgba(0,0,0,1) 0 1px 0;
+ -moz-box-shadow: rgba(0,0,0,1) 0 1px 0;
+ box-shadow: rgba(0,0,0,1) 0 1px 0;
+ text-shadow: rgba(0,0,0,.4) 0 1px 0;
+ color: white;
+ font-size: 15px;
+ font-family: Helvetica, Arial, Sans-Serif;
+ text-decoration: none;
+ vertical-align: middle;
+}
+.red-button:active {
+ border-top: none;
+ padding-top: 10px;
+ background: -webkit-gradient(linear, left top, left bottom, from(#751913), to(#c52f24));
+ background: -webkit-linear-gradient(top, #751913, #c52f24);
+ background: -moz-linear-gradient(top, #751913, #c52f24);
+ background: -ms-linear-gradient(top, #751913, #c52f24);
+ background: -o-linear-gradient(top, #751913, #c52f24);
+}
#topNav {
- padding: 1em 0;
- color: #565656;
- background: #222;
+ padding: 1em 0;
+ color: #565656;
+ background: #222;
+}
+
+.s-hidden {
+ display: none;
+}
+
+@media screen and (min-width: 1025px) {
+ .more-info-button {
+ display: none;
+ }
+ .more-info-links {
+ list-style: none;
+ display: inline;
+ margin: 0;
+ }
+
+ .more-info {
+ display: inline-block;
+ }
+ .more-info:after {
+ content: " |";
+ }
+
+ .more-info:last-child:after {
+ content: "";
+ }
+
+}
+
+@media screen and (max-width: 1024px) {
+ #topNav .wrapper { text-align: center; }
+ .more-info-button {
+ position: relative;
+ z-index: 25;
+ }
+
+ .more-info-label {
+ display: none;
+ }
+
+ .more-info-container {
+ position: absolute;
+ top: .5em;
+ z-index: 20;
+ margin: 0 auto;
+ left: 0;
+ right: 0;
+ width: 20em;
+ }
+
+ .more-info-links {
+ display: block;
+ list-style: none;
+ background-color: #c52f24;
+ border-radius: 5px;
+ padding-top: 5.25em;
+ border: 1px #980905 solid;
+ }
+ .more-info-links.s-hidden {
+ display: none;
+ }
+ .more-info {
+ padding: .75em;
+ border-top: 1px #980905 solid;
+ }
+ .more-info a, .more-info a:link, .more-info a:visited {
+ display: block;
+ color: white;
+ width: 100%;
+ height: 100%;
+ text-decoration: none;
+ text-transform: uppercase;
+ }
}
#header {
- background: #c52f24 url(../images/header_tile.gif) repeat-x;
- color: #FFF;
- padding: 1.5em 0;
- position: relative;
- z-index: 99;
- }
+ background: #c52f24 url(../images/header_tile.gif) repeat-x;
+ color: #FFF;
+ padding: 1.5em 0;
+ z-index: 99;
+ }
#feature {
- background: #d5e9f6 url(../images/feature_tile.gif) repeat-x;
- color: #333;
- padding: 0.5em 0 1.5em;
+ background: #d5e9f6 url(../images/feature_tile.gif) repeat-x;
+ color: #333;
+ padding: 0.5em 0 1.5em;
}
#container {
- color: #333;
- padding: 0.5em 0 1.5em 0;
- }
+ color: #333;
+ padding: 0.5em 0 1.5em 0;
+ }
#mainCol {
- width: 45em;
- margin-left: 2em;
- }
+ max-width: 630px;
+ margin-left: 2em;
+ }
#subCol {
- position: absolute;
- z-index: 0;
- top: 0;
- right: 0;
- background: #FFF;
- padding: 1em 1.5em 1em 1.25em;
- width: 17em;
- font-size: 0.9285em;
- line-height: 1.3846em;
- }
+ position: absolute;
+ z-index: 0;
+ top: 0;
+ right: 0;
+ background: #FFF;
+ padding: 1em 1.5em 1em 1.25em;
+ width: 17em;
+ font-size: 0.9285em;
+ line-height: 1.3846em;
+ margin-right: 1em;
+ }
+
+
+@media screen and (max-width: 800px) {
+ #subCol {
+ position: static;
+ width: inherit;
+ margin-left: -1em;
+ margin-right: 0;
+ padding-right: 1.25em;
+ }
+}
#extraCol {display: none;}
#footer {
- padding: 2em 0;
- background: #222 url(../images/footer_tile.gif) repeat-x;
- }
+ padding: 2em 0;
+ background: #222 url(../images/footer_tile.gif) repeat-x;
+ }
#footer .wrapper {
- padding-left: 2em;
- width: 67em;
+ padding-left: 1em;
+ max-width: 960px;
}
-#header .wrapper, #topNav .wrapper, #feature .wrapper {padding-left: 1em; width: 68em;}
-#feature .wrapper {width: 45em; padding-right: 23em; position: relative; z-index: 0;}
+#header .wrapper, #topNav .wrapper, #feature .wrapper {padding-left: 1em; max-width: 960px;}
+#feature .wrapper {max-width: 640px; padding-right: 23em; position: relative; z-index: 0;}
+
+@media screen and (max-width: 800px) {
+ #feature .wrapper { padding-right: 0; }
+}
/* Links
--------------------------------------- */
a, a:link, a:visited {
- color: #ee3f3f;
- text-decoration: underline;
- }
+ color: #ee3f3f;
+ text-decoration: underline;
+ }
#mainCol a, #subCol a, #feature a {color: #980905;}
#mainCol a code, #subCol a code, #feature a code {color: #980905;}
@@ -162,201 +293,284 @@ a, a:link, a:visited {
/* Navigation
--------------------------------------- */
-.nav {margin: 0; padding: 0;}
-.nav li {display: inline; list-style: none;}
-
-#header .nav {
- float: right;
- margin-top: 1.5em;
- font-size: 1.2857em;
-}
-
-#header .nav li {margin: 0 0 0 0.5em;}
-#header .nav a {color: #FFF; text-decoration: none;}
-#header .nav a:hover {text-decoration: underline;}
-
-#header .nav .index {
- padding: 0.5em 1.5em;
- border-radius: 1em;
- -webkit-border-radius: 1em;
- -moz-border-radius: 1em;
- background: #980905;
- position: relative;
-}
-
-#header .nav .index a {
- background: #980905 url(../images/nav_arrow.gif) no-repeat right top;
- padding-right: 1em;
- position: relative;
- z-index: 15;
- padding-bottom: 0.125em;
+.nav {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ float: right;
+ margin-top: 1.5em;
+ font-size: 1.2857em;
+}
+
+.nav .nav-item {color: #FFF; text-decoration: none;}
+.nav .nav-item:hover {text-decoration: underline;}
+
+.guides-index-large, .guides-index-small .guides-index-item {
+ padding: 0.5em 1.5em;
+ border-radius: 1em;
+ -webkit-border-radius: 1em;
+ -moz-border-radius: 1em;
+ background: #980905;
+ position: relative;
+ color: white;
+ cursor: pointer;
+}
+
+.guides-index .guides-index-item {
+ background: #980905 url(../images/nav_arrow.gif) no-repeat right top;
+ padding-right: 1em;
+ position: relative;
+ z-index: 15;
+ padding-bottom: 0.125em;
+}
+
+.guides-index:hover .guides-index-item, .guides-index .guides-index-item:hover {
+ background-position: right -81px;
+ text-decoration: underline !important;
+}
+
+@media screen and (min-width: 481px) {
+ .nav {
+ float: right;
+ margin-top: 1.5em;
+ font-size: 1.2857em;
+ }
+ .nav>li {
+ display: inline;
+ margin-left: 0.5em;
+ }
+ .guides-index.guides-index-small {
+ display: none;
+ }
+}
+
+@media screen and (max-width: 480px) {
+
+ .nav {
+ float: none;
+ width: 100%;
+ text-align: center;
+ }
+ .nav .nav-item {
+ display: block;
+ margin: 0;
+ width: 100%;
+ background-color: #980905;
+ border: solid 1px #620c04;
+ border-top: 0;
+ padding: 15px 0;
+ text-align: center;
+ }
+ .nav .nav-item, .nav-item.guides-index-item {
+ text-transform: uppercase;
+ }
+ .nav .nav-item:first-child, .nav-item.guides-index-small {
+ border-top: solid 1px #620c04;
+ }
+ .guides-index.guides-index-small {
+ display: block;
+ margin-top: 1.5em;
+ }
+ .guides-index.guides-index-large {
+ display: none;
+ }
+ .guides-index-small .guides-index-item {
+ font: inherit;
+ padding-left: .75em;
+ font-size: .95em;
+ background-position: 96% -65px;
+ -webkit-appearance: none;
+ }
}
-#header .nav .index:hover a, #header .nav .index a:hover {background-position: right -81px;}
#guides {
- width: 27em;
- display: block;
- background: #980905;
- border-radius: 1em;
- -webkit-border-radius: 1em;
- -moz-border-radius: 1em;
- -webkit-box-shadow: 0.25em 0.25em 1em rgba(0,0,0,0.25);
- -moz-box-shadow: rgba(0,0,0,0.25) 0.25em 0.25em 1em;
- color: #f1938c;
- padding: 1.5em 2em;
- position: absolute;
- z-index: 10;
- top: -0.25em;
- right: 0;
- padding-top: 2em;
+ width: 27em;
+ display: block;
+ background: #980905;
+ border-radius: 1em;
+ -webkit-border-radius: 1em;
+ -moz-border-radius: 1em;
+ -webkit-box-shadow: 0.25em 0.25em 1em rgba(0,0,0,0.25);
+ -moz-box-shadow: rgba(0,0,0,0.25) 0.25em 0.25em 1em;
+ color: #f1938c;
+ padding: 1.5em 2em;
+ position: absolute;
+ z-index: 10;
+ top: -0.25em;
+ right: 0;
+ padding-top: 2em;
}
#guides dt, #guides dd {
- font-weight: normal;
- font-size: 0.722em;
- margin: 0;
- padding: 0;
+ font-weight: normal;
+ font-size: 0.722em;
+ margin: 0;
+ padding: 0;
}
#guides dt {padding:0; margin: 0.5em 0 0;}
#guides a {color: #FFF; background: none !important;}
#guides .L, #guides .R {float: left; width: 50%; margin: 0; padding: 0;}
#guides .R {float: right;}
#guides hr {
- display: block;
- border: none;
- height: 1px;
- color: #f1938c;
- background: #f1938c;
+ display: block;
+ border: none;
+ height: 1px;
+ color: #f1938c;
+ background: #f1938c;
}
/* Headings
--------------------------------------- */
h1 {
- font-size: 2.5em;
- line-height: 1em;
- margin: 0.6em 0 .2em;
- font-weight: bold;
- }
+ font-size: 2.5em;
+ line-height: 1em;
+ margin: 0.6em 0 .2em;
+ font-weight: bold;
+ }
h2 {
- font-size: 2.1428em;
- line-height: 1em;
- margin: 0.7em 0 .2333em;
- font-weight: bold;
- }
+ font-size: 2.1428em;
+ line-height: 1em;
+ margin: 0.7em 0 .2333em;
+ font-weight: bold;
+ }
+
+@media screen and (max-width: 480px) {
+ h2 {
+ font-size: 1.45em;
+ }
+}
h3 {
- font-size: 1.7142em;
- line-height: 1.286em;
- margin: 0.875em 0 0.2916em;
- font-weight: bold;
- }
+ font-size: 1.7142em;
+ line-height: 1.286em;
+ margin: 0.875em 0 0.2916em;
+ font-weight: bold;
+ }
+
+@media screen and (max-width: 480px) {
+ h3 {
+ font-size: 1.45em;
+ }
+}
h4 {
- font-size: 1.2857em;
- line-height: 1.2em;
- margin: 1.6667em 0 .3887em;
- font-weight: bold;
- }
+ font-size: 1.2857em;
+ line-height: 1.2em;
+ margin: 1.6667em 0 .3887em;
+ font-weight: bold;
+ }
h5 {
- font-size: 1em;
- line-height: 1.5em;
- margin: 1em 0 .5em;
- font-weight: bold;
+ font-size: 1em;
+ line-height: 1.5em;
+ margin: 1em 0 .5em;
+ font-weight: bold;
}
h6 {
- font-size: 1em;
- line-height: 1.5em;
- margin: 1em 0 .5em;
- font-weight: normal;
- }
+ font-size: 1em;
+ line-height: 1.5em;
+ margin: 1em 0 .5em;
+ font-weight: normal;
+ }
.section {
- padding-bottom: 0.25em;
- border-bottom: 1px solid #999;
+ padding-bottom: 0.25em;
+ border-bottom: 1px solid #999;
}
/* Content
--------------------------------------- */
.pic {
- margin: 0 2em 2em 0;
+ margin: 0 2em 2em 0;
}
#topNav strong {color: #999; margin-right: 0.5em;}
#topNav strong a {color: #FFF;}
#header h1 {
- float: left;
- background: url(../images/rails_guides_logo.gif) no-repeat;
- width: 297px;
- text-indent: -9999em;
- margin: 0;
- padding: 0;
+ float: left;
+ background: url(../images/rails_guides_logo.gif) no-repeat;
+ width: 297px;
+ text-indent: -9999em;
+ margin: 0;
+ padding: 0;
+}
+
+@media screen and (max-width: 480px) {
+ #header h1 {
+ float: none;
+ }
}
#header h1 a {
- text-decoration: none;
- display: block;
- height: 77px;
+ text-decoration: none;
+ display: block;
+ height: 77px;
}
#feature p {
- font-size: 1.2857em;
- margin-bottom: 0.75em;
+ font-size: 1.2857em;
+ margin-bottom: 0.75em;
+}
+
+@media screen and (max-width: 480px) {
+ #feature p {
+ font-size: 1em;
+ }
}
#feature ul {margin-left: 0;}
#feature ul li {
- list-style: none;
- background: url(../images/check_bullet.gif) no-repeat left 0.5em;
- padding: 0.5em 1.75em 0.5em 1.75em;
- font-size: 1.1428em;
- font-weight: bold;
+ list-style: none;
+ background: url(../images/check_bullet.gif) no-repeat left 0.5em;
+ padding: 0.5em 1.75em 0.5em 1.75em;
+ font-size: 1.1428em;
+ font-weight: bold;
}
#mainCol dd, #subCol dd {
- padding: 0.25em 0 1em;
- border-bottom: 1px solid #CCC;
- margin-bottom: 1em;
- margin-left: 0;
- /*padding-left: 28px;*/
- padding-left: 0;
+ padding: 0.25em 0 1em;
+ border-bottom: 1px solid #CCC;
+ margin-bottom: 1em;
+ margin-left: 0;
+ /*padding-left: 28px;*/
+ padding-left: 0;
}
#mainCol dt, #subCol dt {
- font-size: 1.2857em;
- padding: 0.125em 0 0.25em 0;
- margin-bottom: 0;
- /*background: url(../images/book_icon.gif) no-repeat left top;
- padding: 0.125em 0 0.25em 28px;*/
+ font-size: 1em;
+ padding: 0.125em 0 0.25em 0;
+ margin-bottom: 0;
+ /*background: url(../images/book_icon.gif) no-repeat left top;
+ padding: 0.125em 0 0.25em 28px;*/
}
#mainCol dd.work-in-progress, #subCol dd.work-in-progress {
- background: #fff9d8 url(../images/tab_yellow.gif) no-repeat left top;
- border: none;
- padding: 1.25em 1em 1.25em 48px;
- margin-left: 0;
- margin-top: 0.25em;
+ background: #fff9d8 url(../images/tab_yellow.gif) no-repeat left top;
+ border: none;
+ padding: 1.25em 1em 1.25em 48px;
+ margin-left: 0;
+ margin-top: 0.25em;
}
#mainCol dd.kindle, #subCol dd.kindle {
- background: #d5e9f6 url(../images/tab_info.gif) no-repeat left top;
- border: none;
- padding: 1.25em 1em 1.25em 48px;
- margin-left: 0;
- margin-top: 0.25em;
+ background: #d5e9f6 url(../images/tab_info.gif) no-repeat left top;
+ border: none;
+ padding: 1.25em 1em 1.25em 48px;
+ margin-left: 0;
+ margin-top: 0.25em;
}
#mainCol div.warning, #subCol dd.warning {
- background: #f9d9d8 url(../images/tab_red.gif) no-repeat left top;
- border: none;
- padding: 1.25em 1.25em 1.25em 48px;
- margin-left: 0;
- margin-top: 0.25em;
+ background: #f9d9d8 url(../images/tab_red.gif) no-repeat left top;
+ border: none;
+ padding: 1.25em 1.25em 1.25em 48px;
+ margin-left: 0;
+ margin-top: 0.25em;
}
#subCol .chapters {color: #980905;}
@@ -367,31 +581,31 @@ h6 {
#subCol h3.chapter img {vertical-align: text-bottom;}
#subCol .chapters ul {margin-left: 0; margin-top: 0.5em;}
#subCol .chapters ul li {
- list-style: none;
- padding: 0 0 0 1em;
- background: url(../images/bullet.gif) no-repeat left 0.45em;
- margin-left: 0;
- font-size: 1em;
- font-weight: normal;
+ list-style: none;
+ padding: 0 0 0 1em;
+ background: url(../images/bullet.gif) no-repeat left 0.45em;
+ margin-left: 0;
+ font-size: 1em;
+ font-weight: normal;
}
div.code_container {
- background: #EEE url(../images/tab_grey.gif) no-repeat left top;
- padding: 0.25em 1em 0.5em 48px;
+ background: #EEE url(../images/tab_grey.gif) no-repeat left top;
+ padding: 0.25em 1em 0.5em 48px;
}
.note {
- background: #fff9d8 url(../images/tab_note.gif) no-repeat left top;
- border: none;
- padding: 1em 1em 0.25em 48px;
- margin: 0.25em 0 1.5em 0;
+ background: #fff9d8 url(../images/tab_note.gif) no-repeat left top;
+ border: none;
+ padding: 1em 1em 0.25em 48px;
+ margin: 0.25em 0 1.5em 0;
}
.info {
- background: #d5e9f6 url(../images/tab_info.gif) no-repeat left top;
- border: none;
- padding: 1em 1em 0.25em 48px;
- margin: 0.25em 0 1.5em 0;
+ background: #d5e9f6 url(../images/tab_info.gif) no-repeat left top;
+ border: none;
+ padding: 1em 1em 0.25em 48px;
+ margin: 0.25em 0 1.5em 0;
}
#mainCol div.todo {
@@ -404,22 +618,22 @@ div.code_container {
.note code, .info code, .todo code {border:none; background: none; padding: 0;}
#mainCol ul li {
- list-style:none;
- background: url(../images/grey_bullet.gif) no-repeat left 0.5em;
- padding-left: 1em;
- margin-left: 0;
+ list-style:none;
+ background: url(../images/grey_bullet.gif) no-repeat left 0.5em;
+ padding-left: 1em;
+ margin-left: 0;
}
#subCol .content {
- font-size: 0.7857em;
- line-height: 1.5em;
+ font-size: 0.7857em;
+ line-height: 1.5em;
}
#subCol .content li {
- font-weight: normal;
- background: none;
- padding: 0 0 1em;
- font-size: 1.1667em;
+ font-weight: normal;
+ background: none;
+ padding: 0 0 1em;
+ font-size: 1.1667em;
}
/* Clearing
@@ -460,3 +674,28 @@ div.important p, div.caution p, div.warning p, div.note p, div.info p {
z-index: 100;
border: none;
}
+
+/* Foundation v2.1.4 http://foundation.zurb.com */
+/* Artfully masterminded by ZURB */
+
+table th { font-weight: bold; }
+table td, table th { padding: 9px 10px; text-align: left; }
+
+/* Mobile */
+@media only screen and (max-width: 767px) {
+
+ table.responsive { margin-bottom: 0; }
+
+ .pinned { position: absolute; left: 0; top: 0; background: #fff; width: 35%; overflow: hidden; overflow-x: scroll; border-right: 1px solid #ccc; border-left: 1px solid #ccc; }
+ .pinned table { border-right: none; border-left: none; width: 100%; }
+ .pinned table th, .pinned table td { white-space: nowrap; }
+ .pinned td:last-child { border-bottom: 0; }
+
+ div.table-wrapper { position: relative; margin-bottom: 20px; overflow: hidden; border-right: 1px solid #ccc; }
+ div.table-wrapper div.scrollable table { margin-left: 35%; }
+ div.table-wrapper div.scrollable { overflow: scroll; overflow-y: hidden; }
+
+ table.responsive td, table.responsive th { position: relative; white-space: nowrap; overflow: hidden; }
+ table.responsive th:first-child, table.responsive td:first-child, table.responsive td:first-child, table.responsive.pinned td { display: none; }
+
+}
diff --git a/guides/assets/stylesheets/responsive-tables.css b/guides/assets/stylesheets/responsive-tables.css
new file mode 100755
index 0000000000..f5fbcbf948
--- /dev/null
+++ b/guides/assets/stylesheets/responsive-tables.css
@@ -0,0 +1,50 @@
+/* Foundation v2.1.4 http://foundation.zurb.com */
+/* Artfully masterminded by ZURB */
+
+/* --------------------------------------------------
+ Table of Contents
+-----------------------------------------------------
+:: Shared Styles
+:: Page Name 1
+:: Page Name 2
+*/
+
+
+/* -----------------------------------------
+ Shared Styles
+----------------------------------------- */
+
+table th { font-weight: bold; }
+table td, table th { padding: 9px 10px; text-align: left; }
+
+/* Mobile */
+@media only screen and (max-width: 767px) {
+
+ table { margin-bottom: 0; }
+
+ .pinned { position: absolute; left: 0; top: 0; background: #fff; width: 35%; overflow: hidden; overflow-x: scroll; border-right: 1px solid #ccc; border-left: 1px solid #ccc; }
+ .pinned table { border-right: none; border-left: none; width: 100%; }
+ .pinned table th, .pinned table td { white-space: nowrap; }
+ .pinned td:last-child { border-bottom: 0; }
+
+ div.table-wrapper { position: relative; margin-bottom: 20px; overflow: hidden; border-right: 1px solid #ccc; }
+ div.table-wrapper div.scrollable table { margin-left: 35%; }
+ div.table-wrapper div.scrollable { overflow: scroll; overflow-y: hidden; }
+
+ table td, table th { position: relative; white-space: nowrap; overflow: hidden; }
+ table th:first-child, table td:first-child, table td:first-child, table.pinned td { display: none; }
+
+}
+
+/* -----------------------------------------
+ Page Name 1
+----------------------------------------- */
+
+
+
+
+/* -----------------------------------------
+ Page Name 2
+----------------------------------------- */
+
+
diff --git a/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb
index e6ef656474..a288d0f0f4 100644
--- a/guides/rails_guides/helpers.rb
+++ b/guides/rails_guides/helpers.rb
@@ -24,8 +24,14 @@ module RailsGuides
documents.reject { |document| document['work_in_progress'] }
end
- def docs_for_menu(position)
- position == 'L' ? documents_by_section.to(3) : documents_by_section.from(4)
+ def docs_for_menu(position=nil)
+ if position.nil?
+ documents_by_section
+ elsif position == 'L'
+ documents_by_section.to(3)
+ else
+ documents_by_section.from(4)
+ end
end
def author(name, nick, image = 'credits_pic_blank.gif', &block)
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index e6a6b05166..824ffb5d7a 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -174,8 +174,8 @@ Your application has a session for each user in which you can store small amount
* ActionDispatch::Session::CookieStore - Stores everything on the client.
* ActionDispatch::Session::CacheStore - Stores the data in the Rails cache.
-* ActionDispatch::Session::ActiveRecordStore - Stores the data in a database using Active Record. (require `activerecord-session_store` gem).
-* ActionDispatch::Session::MemCacheStore - Stores the data in a memcached cluster (this is a legacy implementation; consider using CacheStore instead).
+* @ActionDispatch::Session::ActiveRecordStore@ - Stores the data in a database using Active Record. (require `activerecord-session_store` gem).
+* @ActionDispatch::Session::MemCacheStore@ - Stores the data in a memcached cluster (this is a legacy implementation; consider using CacheStore instead).
All session stores use a cookie to store a unique ID for each session (you must use a cookie, Rails will not allow you to pass the session ID in the URL as this is less secure).
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 0a8daf7ae5..397dd62638 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -4,9 +4,9 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<meta name="viewport" content="width=device-width, initial-scale=1">
<title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title>
-
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print" />
@@ -25,24 +25,30 @@
<% end %>
<div id="topNav">
<div class="wrapper">
- <strong>More at <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
- <a href="http://rubyonrails.org/">Overview</a> |
- <a href="http://rubyonrails.org/download">Download</a> |
- <a href="http://rubyonrails.org/deploy">Deploy</a> |
- <a href="https://github.com/rails/rails">Code</a> |
- <a href="http://rubyonrails.org/screencasts">Screencasts</a> |
- <a href="http://rubyonrails.org/documentation">Documentation</a> |
- <a href="http://rubyonrails.org/ecosystem">Ecosystem</a> |
- <a href="http://rubyonrails.org/community">Community</a> |
- <a href="http://weblog.rubyonrails.org/">Blog</a>
+ <strong class="more-info-label">More at <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
+ <span class="red-button more-info-button">
+ More Ruby on Rails
+ </span>
+ <ul class="more-info-links s-hidden">
+ <li class="more-info"><a href="http://rubyonrails.org/">Overview</a></li>
+ <li class="more-info"><a href="http://rubyonrails.org/download">Download</a></li>
+ <li class="more-info"><a href="http://rubyonrails.org/deploy">Deploy</a></li>
+ <li class="more-info"><a href="https://github.com/rails/rails">Code</a></li>
+ <li class="more-info"><a href="http://rubyonrails.org/screencasts">Screencasts</a></li>
+ <li class="more-info"><a href="http://rubyonrails.org/documentation">Documentation</a></li>
+ <li class="more-info"><a href="http://rubyonrails.org/ecosystem">Ecosystem</a></li>
+ <li class="more-info"><a href="http://rubyonrails.org/community">Community</a></li>
+ <li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>
+ </ul>
</div>
</div>
<div id="header">
<div class="wrapper clearfix">
<h1><a href="index.html" title="Return to home page">Guides.rubyonrails.org</a></h1>
<ul class="nav">
- <li><a href="index.html">Home</a></li>
- <li class="index"><a href="index.html" onclick="guideMenu(); return false;" id="guidesMenu">Guides Index</a>
+ <li><a class="nav-item" href="index.html">Home</a></li>
+ <li class="guides-index guides-index-large">
+ <a href="index.html" onclick="guideMenu(); return false;" id="guidesMenu" class="guides-index-item nav-item">Guides Index</a>
<div id="guides" class="clearfix" style="display: none;">
<hr />
<% ['L', 'R'].each do |position| %>
@@ -57,9 +63,22 @@
<% end %>
</div>
</li>
- <li><a href="contributing_to_ruby_on_rails.html">Contribute</a></li>
- <li><a href="credits.html">Credits</a></li>
+ <li><a class="nav-item" href="contributing_to_ruby_on_rails.html">Contribute</a></li>
+ <li><a class="nav-item" href="credits.html">Credits</a></li>
+ <li class="guides-index guides-index-small">
+ <select class="guides-index-item nav-item">
+ <option value="index.html">Guides Index</option>
+ <% docs_for_menu.each do |section| %>
+ <optgroup label="<%= section['name'] %>">
+ <% finished_documents(section['documents']).each do |document| %>
+ <option value="<%= document['url'] %>"><%= document['name'] %></option>
+ <% end %>
+ </optgroup>
+ <% end %>
+ </select>
+ </li>
</ul>
+ </div>
</div>
</div>
<hr class="hide" />
@@ -113,6 +132,8 @@
</div>
</div>
+ <script type="text/javascript" src="javascripts/jquery.min.js"></script>
+ <script type="text/javascript" src="javascripts/responsive-tables.js"></script>
<script type="text/javascript" src="javascripts/guides.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shCore.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushRuby.js"></script>
@@ -121,6 +142,7 @@
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushPlain.js"></script>
<script type="text/javascript">
SyntaxHighlighter.all()
+ $(guidesIndex.bind);
</script>
</body>
</html>
diff --git a/guides/source/migrations.md b/guides/source/migrations.md
index ccbdffc9c7..705b65ee8b 100644
--- a/guides/source/migrations.md
+++ b/guides/source/migrations.md
@@ -330,7 +330,7 @@ end
As always, what has been generated for you is just a starting point. You can add
or remove from it as you see fit by editing the
-db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb file.
+@db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb@ file.
NOTE: The generated migration file for destructive migrations will still be
old-style using the `up` and `down` methods. This is because Rails needs to know
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index d6c425a356..0ae0e30ab7 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -69,6 +69,13 @@ in the `config/initializers/wrap_parameters.rb` file:
### Action Pack
+Rails 4.0 has deprecated `ActionController::Base.page_cache_extension` option. Use
+`ActionController::Base.default_static_extension` instead.
+
+Rails 4.0 has removed Action and Page caching from ActionPack. You will need to
+add the `actionpack-action_caching` gem in order to use `caches_action` and
+the `actionpack-page_caching` to use `caches_pages` in your controllers.
+
Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`.
Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, for example:
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 8c2b64d543..a422c5fe39 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* `Rails.public_path` now returns a Pathname object. *Prem Sichanugrist*
+
* Remove highly uncommon `config.assets.manifest` option for moving the manifest path.
This option is now unsupported in sprockets-rails.
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index a15965a9da..d7e22cc839 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -127,7 +127,7 @@ module Rails
end
def public_path
- application && application.paths["public"].first
+ application && Pathname.new(application.paths["public"].first)
end
end
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 0b9ed025db..b30e6ff615 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -101,6 +101,14 @@ module Rails
routes_reloader.reload!
end
+
+ # Return the application's KeyGenerator
+ def key_generator
+ # number of iterations selected based on consultation with the google security
+ # team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220
+ @key_generator ||= ActiveSupport::KeyGenerator.new(config.secret_token, :iterations=>1000)
+ end
+
# Stores some of the Rails initial environment parameters which
# will be used by middlewares and engines to configure themselves.
# Currently stores:
@@ -121,7 +129,8 @@ module Rails
"action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
"action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
"action_dispatch.logger" => Rails.logger,
- "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner
+ "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner,
+ "action_dispatch.key_generator" => key_generator
})
end
@@ -288,6 +297,15 @@ module Rails
error.message << ' Be sure to add rack-cache to your Gemfile'
raise
end
+
+ if rack_cache == true
+ rack_cache = {
+ :metastore => "rails:/",
+ :entitystore => "rails:/",
+ :verbose => false
+ }
+ end
+
require "action_dispatch/http/rack_cache"
middleware.use ::Rack::Cache, rack_cache
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 184c59cb90..bcff0b6eac 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -226,7 +226,14 @@ module Rails
end
def javascript_gemfile_entry
- "gem '#{options[:javascript]}-rails'" unless options[:skip_javascript]
+ unless options[:skip_javascript]
+ <<-GEMFILE.strip_heredoc
+ gem '#{options[:javascript]}-rails'
+
+ # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
+ gem 'turbolinks'
+ GEMFILE
+ end
end
def javascript_runtime_gemfile_entry
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 69027f2903..e5aa153e29 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -9,14 +9,13 @@ source 'https://rubygems.org'
<%= assets_gemfile_entry %>
<%= javascript_gemfile_entry %>
-# Puts a simple HTTP cache in front of your app.
-# For large-scale production use, consider using a caching reverse proxy like nginx, varnish, or squid.
-gem 'rack-cache', '~> 1.2'
+# Puts a simple HTTP cache in front of your app (and gets you ready for later upgrading to nginx/varnish/squid)
+# gem 'rack-cache', '~> 1.2'
# To use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.0.0'
-# To use Jbuilder templates for JSON
+# Build JSON APIs with ease. Read more: http://github.com/rails/jbuilder
# gem 'jbuilder'
# Use unicorn as the app server
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
index f33a7f4ac2..f61e481969 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt
@@ -13,5 +13,6 @@
<% unless options[:skip_javascript] -%>
//= require <%= options[:javascript] %>
//= require <%= options[:javascript] %>_ujs
+//= require turbolinks
<% end -%>
//= require_tree .
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index cb3e8b123e..3629920c30 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -14,6 +14,11 @@
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
+ # Enable Rack::Cache to put a simple HTTP cache in front of your application
+ # Add `rack-cache` to your Gemfile before enabling this.
+ # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
+ # config.action_dispatch.rack_cache = true
+
# Disable Rails's static asset server (Apache or nginx will already do this).
config.serve_static_assets = false
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index 3c2210aaf9..9826aecb54 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -115,7 +115,6 @@ module Rails
class Path
include Enumerable
- attr_reader :path
attr_accessor :glob
def initialize(root, current, paths, options = {})
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index d0d053e7ed..3f59bb8733 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -12,9 +12,6 @@ module Rails
def call(env)
request = ActionDispatch::Request.new(env)
- # Put some space between requests in development logs.
- Rails.logger.info "\n\n" if Rails.env.development?
-
if Rails.logger.respond_to?(:tagged)
Rails.logger.tagged(compute_tags(request)) { call_app(request, env) }
else
@@ -25,6 +22,12 @@ module Rails
protected
def call_app(request, env)
+ # Put some space between requests in development logs.
+ if Rails.env.development?
+ Rails.logger.info ''
+ Rails.logger.info ''
+ end
+
Rails.logger.info started_request_message(request)
@app.call(env)
ensure
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 581ceaf9ce..aa357dc418 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -7,6 +7,10 @@ require 'active_support/test_case'
require 'action_controller/test_case'
require 'action_dispatch/testing/integration'
+# Config Rails backtrace in tests.
+require 'rails/backtrace_cleaner'
+MiniTest.backtrace_filter = Rails.backtrace_cleaner
+
# Enable turn if it is available
begin
require 'turn'
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index d014e5e362..07d47dc67b 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -139,6 +139,14 @@ module ApplicationTests
assert_instance_of Pathname, Rails.root
end
+ test "Rails.public_path should be a Pathname" do
+ add_to_config <<-RUBY
+ config.paths["public"] = "somewhere"
+ RUBY
+ require "#{app_path}/config/environment"
+ assert_instance_of Pathname, Rails.public_path
+ end
+
test "initialize an eager loaded, cache classes app" do
add_to_config <<-RUBY
config.eager_load = true
@@ -227,7 +235,7 @@ module ApplicationTests
RUBY
require "#{app_path}/config/application"
- assert_equal File.join(app_path, "somewhere"), Rails.public_path
+ assert_equal Pathname.new(app_path).join("somewhere"), Rails.public_path
end
test "config.secret_token is sent in env" do
@@ -324,27 +332,6 @@ module ApplicationTests
assert last_response.body =~ /_xsrf_token_here/
end
- test "config.action_controller.perform_caching = true" do
- make_basic_app do |app|
- app.config.action_controller.perform_caching = true
- end
-
- class ::OmgController < ActionController::Base
- @@count = 0
-
- caches_action :index
- def index
- @@count += 1
- render :text => @@count
- end
- end
-
- get "/"
- res = last_response.body
- get "/"
- assert_equal res, last_response.body # value should be unchanged
- end
-
test "sets ActionDispatch.test_app" do
make_basic_app
assert_equal Rails.application, ActionDispatch.test_app
@@ -454,27 +441,6 @@ module ApplicationTests
end
end
- test "config.action_controller.perform_caching = false" do
- make_basic_app do |app|
- app.config.action_controller.perform_caching = false
- end
-
- class ::OmgController < ActionController::Base
- @@count = 0
-
- caches_action :index
- def index
- @@count += 1
- render :text => @@count
- end
- end
-
- get "/"
- res = last_response.body
- get "/"
- assert_not_equal res, last_response.body
- end
-
test "config.asset_path is not passed through env" do
make_basic_app do |app|
app.config.asset_path = "/omg%s"
@@ -634,6 +600,7 @@ module ApplicationTests
assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions
assert_equal app.env_config['action_dispatch.logger'], Rails.logger
assert_equal app.env_config['action_dispatch.backtrace_cleaner'], Rails.backtrace_cleaner
+ assert_equal app.env_config['action_dispatch.key_generator'], Rails.application.key_generator
end
test "config.colorize_logging default is true" do
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 5268257d62..81f6096be8 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -175,7 +175,7 @@ module ApplicationTests
Dir.chdir("#{app_path}/app") do
require "#{app_path}/config/environment"
- assert_raises(NoMethodError) { [1,2,3].forty_two }
+ assert_raises(NoMethodError) { "hello".exclude? "lo" }
end
end
diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb
index 19d3f3a397..fffe79f9cc 100644
--- a/railties/test/application/middleware/cache_test.rb
+++ b/railties/test/application/middleware/cache_test.rb
@@ -49,6 +49,8 @@ module ApplicationTests
get ':controller(/:action)'
end
RUBY
+
+ add_to_config "config.action_dispatch.rack_cache = true"
end
def test_cache_keeps_if_modified_since
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 3e096e99f2..b2443e6503 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -56,11 +56,20 @@ module ApplicationTests
assert !middleware.include?("Rack::Sendfile"), "Rack::Sendfile is not included in the default stack unless you set config.action_dispatch.x_sendfile_header"
end
- test "Rack::Cache is present when action_controller.perform_caching is set" do
+ test "Rack::Cache is not included by default" do
add_to_config "config.action_controller.perform_caching = true"
boot!
+ assert !middleware.include?("Rack::Cache"), "Rack::Cache is not included in the default stack unless you set config.action_dispatch.rack_cache"
+ end
+
+ test "Rack::Cache is present when action_controller.perform_caching is set and action_dispatch.rack_cache is set" do
+ add_to_config "config.action_controller.perform_caching = true"
+ add_to_config "config.action_dispatch.rack_cache = true"
+
+ boot!
+
assert_equal "Rack::Cache", middleware.first
end
diff --git a/tools/profile b/tools/profile
index 6cc935bce5..d4cfa13f0a 100755
--- a/tools/profile
+++ b/tools/profile
@@ -1,6 +1,6 @@
#!/usr/bin/env ruby
# Example:
-# tools/profile_requires activesupport/lib/active_support.rb
+# tools/profile activesupport/lib/active_support.rb
abort 'Use REE so you can profile memory and object allocation' unless GC.respond_to?(:enable_stats)
ENV['NO_RELOAD'] ||= '1'