diff options
103 files changed, 1967 insertions, 2364 deletions
@@ -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/actionmailer/Rakefile b/actionmailer/Rakefile index c1c4171cdf..45c238d302 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -3,7 +3,7 @@ require 'rake/packagetask' require 'rubygems/package_task' desc "Default Task" -task :default => [ :test ] +task default: [ :test ] # Run the unit tests Rake::TestTask.new { |t| @@ -29,7 +29,7 @@ Gem::PackageTask.new(spec) do |p| end desc "Release to gemcutter" -task :release => :package do +task release: :package do require 'rake/gemcutter' Rake::Gemcutter::Tasks.new(spec).define Rake::Task['gem:push'].invoke diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index bd33f81f1e..fee4a64248 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -23,13 +23,13 @@ module ActionMailer # Examples: # # class Notifier < ActionMailer::Base - # default :from => 'no-reply@example.com', - # :return_path => 'system@example.com' + # default from: 'no-reply@example.com', + # return_path: 'system@example.com' # # def welcome(recipient) # @account = recipient - # mail(:to => recipient.email_address_with_name, - # :bcc => ["bcc@example.com", "Order Watcher <watcher@example.com>"]) + # mail(to: recipient.email_address_with_name, + # bcc: ["bcc@example.com", "Order Watcher <watcher@example.com>"]) # end # end # @@ -62,21 +62,21 @@ module ActionMailer # # If you want to explicitly render only certain templates, pass a block: # - # mail(:to => user.email) do |format| + # mail(to: user.email) do |format| # format.text # format.html # end # # The block syntax is also useful in providing information specific to a part: # - # mail(:to => user.email) do |format| + # mail(to: user.email) do |format| # format.text(:content_transfer_encoding => "base64") # format.html # end # # Or even to render a special view: # - # mail(:to => user.email) do |format| + # mail(to: user.email) do |format| # format.text # format.html { render "some_other_template" } # end @@ -100,12 +100,12 @@ module ActionMailer # You can even use Action Pack helpers in these views. For example: # # You got a new note! - # <%= truncate(@note.body, :length => 25) %> + # <%= truncate(@note.body, length: 25) %> # # If you need to access the subject, from or the recipients in the view, you can do that through message object: # # You got a new note from <%= message.from %>! - # <%= truncate(@note.body, :length => 25) %> + # <%= truncate(@note.body, length: 25) %> # # # = Generating URLs @@ -116,11 +116,11 @@ module ActionMailer # # When using <tt>url_for</tt> you'll need to provide the <tt>:host</tt>, <tt>:controller</tt>, and <tt>:action</tt>: # - # <%= url_for(:host => "example.com", :controller => "welcome", :action => "greeting") %> + # <%= url_for(host: "example.com", controller: "welcome", action: "greeting") %> # # When using named routes you only need to supply the <tt>:host</tt>: # - # <%= users_url(:host => "example.com") %> + # <%= users_url(host: "example.com") %> # # You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the # <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will @@ -176,7 +176,7 @@ module ActionMailer # class ApplicationMailer < ActionMailer::Base # def welcome(recipient) # attachments['free_book.pdf'] = File.read('path/to/file.pdf') - # mail(:to => recipient, :subject => "New account information") + # mail(to: recipient, subject: "New account information") # end # end # @@ -192,7 +192,7 @@ module ActionMailer # class ApplicationMailer < ActionMailer::Base # def welcome(recipient) # attachments['free_book.pdf'] = File.read('path/to/file.pdf') - # mail(:to => recipient, :subject => "New account information", :body => "") + # mail(to: recipient, subject: "New account information", body: "") # end # end # @@ -204,7 +204,7 @@ module ActionMailer # class ApplicationMailer < ActionMailer::Base # def welcome(recipient) # attachments.inline['photo.png'] = File.read('path/to/photo.png') - # mail(:to => recipient, :subject => "Here is what we look like") + # mail(to: recipient, subject: "Here is what we look like") # end # end # @@ -220,7 +220,7 @@ module ActionMailer # # <h1>Please Don't Cringe</h1> # - # <%= image_tag attachments['photo.png'].url, :alt => 'Our Photo', :class => 'photo' -%> + # <%= image_tag attachments['photo.png'].url, alt: 'Our Photo', class: 'photo' -%> # # = Observing and Intercepting Mails # @@ -241,7 +241,7 @@ module ActionMailer # default method inside the class definition: # # class Notifier < ActionMailer::Base - # default :sender => 'system@example.com' + # default sender: 'system@example.com' # end # # You can pass in any header value that a <tt>Mail::Message</tt> accepts. Out of the box, @@ -260,7 +260,7 @@ module ActionMailer # # class Notifier < ActionMailer::Base # default 'Content-Transfer-Encoding' => '7bit', - # :content_description => 'This is a description' + # content_description: 'This is a description' # end # # Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you @@ -386,10 +386,10 @@ module ActionMailer class_attribute :default_params self.default_params = { - :mime_version => "1.0", - :charset => "UTF-8", - :content_type => "text/plain", - :parts_order => [ "text/plain", "text/enriched", "text/html" ] + mime_version: "1.0", + charset: "UTF-8", + content_type: "text/plain", + parts_order: [ "text/plain", "text/enriched", "text/html" ] }.freeze class_attribute :queue @@ -549,17 +549,17 @@ module ActionMailer # # You can also specify overrides if you want by passing a hash instead of a string: # - # mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip', - # :content => File.read('/path/to/filename.jpg')} + # mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip', + # content: File.read('/path/to/filename.jpg')} # # If you want to use a different encoding than Base64, you can pass an encoding in, # but then it is up to you to pass in the content pre-encoded, and don't expect # Mail to know how to decode this data: # # file_content = SpecialEncode(File.read('/path/to/filename.jpg')) - # mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip', - # :encoding => 'SpecialEncoding', - # :content => file_content } + # mail.attachments['filename.jpg'] = {mime_type: 'application/x-gzip', + # encoding: 'SpecialEncoding', + # content: file_content } # # You can also search for specific attachments: # @@ -597,9 +597,9 @@ module ActionMailer # class method: # # class Notifier < ActionMailer::Base - # self.default :from => 'no-reply@test.lindsaar.net', - # :bcc => 'email_logger@test.lindsaar.net', - # :reply_to => 'bounces@test.lindsaar.net' + # self.default from: 'no-reply@test.lindsaar.net', + # bcc: 'email_logger@test.lindsaar.net', + # reply_to: 'bounces@test.lindsaar.net' # end # # If you need other headers not listed above, you can either pass them in @@ -621,10 +621,10 @@ module ActionMailer # For example: # # class Notifier < ActionMailer::Base - # default :from => 'no-reply@test.lindsaar.net', + # default from: 'no-reply@test.lindsaar.net', # # def welcome - # mail(:to => 'mikel@test.lindsaar.net') + # mail(to: 'mikel@test.lindsaar.net') # end # end # @@ -633,22 +633,22 @@ module ActionMailer # # However, those can be customized: # - # mail(:template_path => 'notifications', :template_name => 'another') + # mail(template_path: 'notifications', template_name: 'another') # # And now it will look for all templates at "app/views/notifications" with name "another". # # If you do pass a block, you can render specific templates of your choice: # - # mail(:to => 'mikel@test.lindsaar.net') do |format| + # mail(to: 'mikel@test.lindsaar.net') do |format| # format.text # format.html # end # # You can even render text directly without using a template: # - # mail(:to => 'mikel@test.lindsaar.net') do |format| - # format.text { render :text => "Hello Mikel!" } - # format.html { render :text => "<h1>Hello Mikel!</h1>" } + # mail(to: 'mikel@test.lindsaar.net') do |format| + # format.text { render text: "Hello Mikel!" } + # format.html { render text: "<h1>Hello Mikel!</h1>" } # end # # Which will render a <tt>multipart/alternative</tt> email with <tt>text/plain</tt> and @@ -657,7 +657,7 @@ module ActionMailer # The block syntax also allows you to customize the part headers if desired: # # mail(:to => 'mikel@test.lindsaar.net') do |format| - # format.text(:content_transfer_encoding => "base64") + # format.text(content_transfer_encoding: "base64") # format.html # end # @@ -730,7 +730,7 @@ module ActionMailer # humanized version of the <tt>action_name</tt>. def default_i18n_subject #:nodoc: mailer_scope = self.class.mailer_name.tr('/', '.') - I18n.t(:subject, :scope => [mailer_scope, action_name], :default => action_name.humanize) + I18n.t(:subject, scope: [mailer_scope, action_name], default: action_name.humanize) end def collect_responses_and_parts_order(headers) #:nodoc: @@ -743,8 +743,8 @@ module ActionMailer responses = collector.responses elsif headers[:body] responses << { - :body => headers.delete(:body), - :content_type => self.class.default[:content_type] || "text/plain" + body: headers.delete(:body), + content_type: self.class.default[:content_type] || "text/plain" } else templates_path = headers.delete(:template_path) || self.class.mailer_name @@ -754,8 +754,8 @@ module ActionMailer self.formats = template.formats responses << { - :body => render(:template => template), - :content_type => template.type.to_s + body: render(template: template), + content_type: template.type.to_s } end end diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb index 569a09bd2a..cbb2778fae 100644 --- a/actionmailer/lib/action_mailer/collector.rb +++ b/actionmailer/lib/action_mailer/collector.rb @@ -21,7 +21,7 @@ module ActionMailer alias :all :any def custom(mime, options={}) - options.reverse_merge!(:content_type => mime.to_s) + options.reverse_merge!(content_type: mime.to_s) @context.formats = [mime.to_sym] options[:body] = block_given? ? yield : @default_render.call @responses << options diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb index 110f6769e6..caea3d7535 100644 --- a/actionmailer/lib/action_mailer/delivery_methods.rb +++ b/actionmailer/lib/action_mailer/delivery_methods.rb @@ -20,27 +20,27 @@ module ActionMailer self.delivery_method = :smtp add_delivery_method :smtp, Mail::SMTP, - :address => "localhost", - :port => 25, - :domain => 'localhost.localdomain', - :user_name => nil, - :password => nil, - :authentication => nil, - :enable_starttls_auto => true + address: "localhost", + port: 25, + domain: 'localhost.localdomain', + user_name: nil, + password: nil, + authentication: nil, + enable_starttls_auto: true add_delivery_method :file, Mail::FileDelivery, - :location => defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" + location: defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" add_delivery_method :sendmail, Mail::Sendmail, - :location => '/usr/sbin/sendmail', - :arguments => '-i -t' + location: '/usr/sbin/sendmail', + arguments: '-i -t' add_delivery_method :test, Mail::TestMailer end module ClassMethods # Provides a list of emails that have been delivered by Mail::TestMailer - delegate :deliveries, :deliveries=, :to => Mail::TestMailer + delegate :deliveries, :deliveries=, to: Mail::TestMailer # Adds a new delivery method through the given class using the given # symbol as alias and the default options supplied. diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb index dd7fa640c9..d5bf864595 100644 --- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb +++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb @@ -3,7 +3,7 @@ module Rails class MailerGenerator < NamedBase source_root File.expand_path("../templates", __FILE__) - argument :actions, :type => :array, :default => [], :banner => "method method" + argument :actions, type: :array, default: [], banner: "method method" check_class_collision def create_mailer_file diff --git a/actionmailer/test/asset_host_test.rb b/actionmailer/test/asset_host_test.rb index 696a9f1174..32ae76a7c5 100644 --- a/actionmailer/test/asset_host_test.rb +++ b/actionmailer/test/asset_host_test.rb @@ -3,9 +3,9 @@ require 'action_controller' class AssetHostMailer < ActionMailer::Base def email_with_asset - mail :to => 'test@localhost', - :subject => 'testing email containing asset path while asset_host is set', - :from => 'tester@example.com' + mail to: 'test@localhost', + subject: 'testing email containing asset path while asset_host is set', + from: 'tester@example.com' end end diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 17ce8b7072..1cb3ce63fe 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -30,21 +30,21 @@ class BaseTest < ActiveSupport::TestCase end test "mail() with from overwrites the class level default" do - email = BaseMailer.welcome(:from => 'someone@example.com', - :to => 'another@example.org') + email = BaseMailer.welcome(from: 'someone@example.com', + to: 'another@example.org') assert_equal(['someone@example.com'], email.from) assert_equal(['another@example.org'], email.to) end test "mail() with bcc, cc, content_type, charset, mime_version, reply_to and date" do time = Time.now.beginning_of_day.to_datetime - email = BaseMailer.welcome(:bcc => 'bcc@test.lindsaar.net', - :cc => 'cc@test.lindsaar.net', - :content_type => 'multipart/mixed', - :charset => 'iso-8559-1', - :mime_version => '2.0', - :reply_to => 'reply-to@test.lindsaar.net', - :date => time) + email = BaseMailer.welcome(bcc: 'bcc@test.lindsaar.net', + cc: 'cc@test.lindsaar.net', + content_type: 'multipart/mixed', + charset: 'iso-8559-1', + mime_version: '2.0', + reply_to: 'reply-to@test.lindsaar.net', + date: time) assert_equal(['bcc@test.lindsaar.net'], email.bcc) assert_equal(['cc@test.lindsaar.net'], email.cc) assert_equal('multipart/mixed; charset=iso-8559-1', email.content_type) @@ -60,7 +60,7 @@ class BaseTest < ActiveSupport::TestCase end test "can pass in :body to the mail method hash" do - email = BaseMailer.welcome(:body => "Hello there") + email = BaseMailer.welcome(body: "Hello there") assert_equal("text/plain", email.mime_type) assert_equal("Hello there", email.body.encoded) end @@ -142,7 +142,7 @@ class BaseTest < ActiveSupport::TestCase end test "adds the given :body as part" do - email = BaseMailer.attachment_with_content(:body => "I'm the eggman") + email = BaseMailer.attachment_with_content(body: "I'm the eggman") assert_equal(2, email.parts.length) assert_equal("multipart/mixed", email.mime_type) assert_equal("text/plain", email.parts[0].mime_type) @@ -165,31 +165,31 @@ class BaseTest < ActiveSupport::TestCase # Defaults values test "uses default charset from class" do - with_default BaseMailer, :charset => "US-ASCII" do + with_default BaseMailer, charset: "US-ASCII" do email = BaseMailer.welcome assert_equal("US-ASCII", email.charset) - email = BaseMailer.welcome(:charset => "iso-8559-1") + email = BaseMailer.welcome(charset: "iso-8559-1") assert_equal("iso-8559-1", email.charset) end end test "uses default content type from class" do - with_default BaseMailer, :content_type => "text/html" do + with_default BaseMailer, content_type: "text/html" do email = BaseMailer.welcome assert_equal("text/html", email.mime_type) - email = BaseMailer.welcome(:content_type => "text/plain") + email = BaseMailer.welcome(content_type: "text/plain") assert_equal("text/plain", email.mime_type) end end test "uses default mime version from class" do - with_default BaseMailer, :mime_version => "2.0" do + with_default BaseMailer, mime_version: "2.0" do email = BaseMailer.welcome assert_equal("2.0", email.mime_version) - email = BaseMailer.welcome(:mime_version => "1.0") + email = BaseMailer.welcome(mime_version: "1.0") assert_equal("1.0", email.mime_version) end end @@ -202,17 +202,17 @@ class BaseTest < ActiveSupport::TestCase end test "subject gets default from I18n" do - BaseMailer.default :subject => nil - email = BaseMailer.welcome(:subject => nil) + BaseMailer.default subject: nil + email = BaseMailer.welcome(subject: nil) assert_equal "Welcome", email.subject - I18n.backend.store_translations('en', :base_mailer => {:welcome => {:subject => "New Subject!"}}) - email = BaseMailer.welcome(:subject => nil) + I18n.backend.store_translations('en', base_mailer: {welcome: {subject: "New Subject!"}}) + email = BaseMailer.welcome(subject: nil) assert_equal "New Subject!", email.subject end test "translations are scoped properly" do - I18n.backend.store_translations('en', :base_mailer => {:email_with_translations => {:greet_user => "Hello %{name}!"}}) + I18n.backend.store_translations('en', base_mailer: {email_with_translations: {greet_user: "Hello %{name}!"}}) email = BaseMailer.email_with_translations assert_equal 'Hello lifo!', email.body.encoded end @@ -230,19 +230,19 @@ class BaseTest < ActiveSupport::TestCase test "implicit multipart with sort order" do order = ["text/html", "text/plain"] - with_default BaseMailer, :parts_order => order do + with_default BaseMailer, parts_order: order do email = BaseMailer.implicit_multipart assert_equal("text/html", email.parts[0].mime_type) assert_equal("text/plain", email.parts[1].mime_type) - email = BaseMailer.implicit_multipart(:parts_order => order.reverse) + email = BaseMailer.implicit_multipart(parts_order: order.reverse) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("text/html", email.parts[1].mime_type) end end test "implicit multipart with attachments creates nested parts" do - email = BaseMailer.implicit_multipart(:attachments => true) + email = BaseMailer.implicit_multipart(attachments: true) assert_equal("application/pdf", email.parts[0].mime_type) assert_equal("multipart/alternative", email.parts[1].mime_type) assert_equal("text/plain", email.parts[1].parts[0].mime_type) @@ -253,8 +253,8 @@ class BaseTest < ActiveSupport::TestCase test "implicit multipart with attachments and sort order" do order = ["text/html", "text/plain"] - with_default BaseMailer, :parts_order => order do - email = BaseMailer.implicit_multipart(:attachments => true) + with_default BaseMailer, parts_order: order do + email = BaseMailer.implicit_multipart(attachments: true) assert_equal("application/pdf", email.parts[0].mime_type) assert_equal("multipart/alternative", email.parts[1].mime_type) assert_equal("text/plain", email.parts[1].parts[1].mime_type) @@ -273,7 +273,7 @@ class BaseTest < ActiveSupport::TestCase end test "implicit multipart with other locale" do - swap I18n, :locale => :pl do + swap I18n, locale: :pl do email = BaseMailer.implicit_with_locale assert_equal(2, email.parts.size) assert_equal("multipart/alternative", email.mime_type) @@ -324,19 +324,19 @@ class BaseTest < ActiveSupport::TestCase test "explicit multipart does not sort order" do order = ["text/html", "text/plain"] - with_default BaseMailer, :parts_order => order do + with_default BaseMailer, parts_order: order do email = BaseMailer.explicit_multipart assert_equal("text/plain", email.parts[0].mime_type) assert_equal("text/html", email.parts[1].mime_type) - email = BaseMailer.explicit_multipart(:parts_order => order.reverse) + email = BaseMailer.explicit_multipart(parts_order: order.reverse) assert_equal("text/plain", email.parts[0].mime_type) assert_equal("text/html", email.parts[1].mime_type) end end test "explicit multipart with attachments creates nested parts" do - email = BaseMailer.explicit_multipart(:attachments => true) + email = BaseMailer.explicit_multipart(attachments: true) assert_equal("application/pdf", email.parts[0].mime_type) assert_equal("multipart/alternative", email.parts[1].mime_type) assert_equal("text/plain", email.parts[1].parts[0].mime_type) @@ -651,7 +651,7 @@ class BaseTest < ActiveSupport::TestCase test "default_from can be set" do class DefaultFromMailer < ActionMailer::Base - default :to => 'system@test.lindsaar.net' + default to: 'system@test.lindsaar.net' self.default_options = {from: "robert.pankowecki@gmail.com"} def welcome diff --git a/actionmailer/test/delivery_methods_test.rb b/actionmailer/test/delivery_methods_test.rb index 7109f23e4c..61a037ea18 100644 --- a/actionmailer/test/delivery_methods_test.rb +++ b/actionmailer/test/delivery_methods_test.rb @@ -22,24 +22,24 @@ end class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase test "default smtp settings" do - settings = { :address => "localhost", - :port => 25, - :domain => 'localhost.localdomain', - :user_name => nil, - :password => nil, - :authentication => nil, - :enable_starttls_auto => true } + settings = { address: "localhost", + port: 25, + domain: 'localhost.localdomain', + user_name: nil, + password: nil, + authentication: nil, + enable_starttls_auto: true } assert_equal settings, ActionMailer::Base.smtp_settings end test "default file delivery settings" do - settings = {:location => "#{Dir.tmpdir}/mails"} + settings = {location: "#{Dir.tmpdir}/mails"} assert_equal settings, ActionMailer::Base.file_settings end test "default sendmail settings" do - settings = {:location => '/usr/sbin/sendmail', - :arguments => '-i -t'} + settings = {location: '/usr/sbin/sendmail', + arguments: '-i -t'} assert_equal settings, ActionMailer::Base.sendmail_settings end end @@ -63,8 +63,8 @@ class CustomDeliveryMethodsTest < ActiveSupport::TestCase end test "allow to customize custom settings" do - ActionMailer::Base.custom_settings = { :foo => :bar } - assert_equal Hash[:foo => :bar], ActionMailer::Base.custom_settings + ActionMailer::Base.custom_settings = { foo: :bar } + assert_equal Hash[foo: :bar], ActionMailer::Base.custom_settings end test "respond to custom settings" do @@ -82,8 +82,8 @@ end class MailDeliveryTest < ActiveSupport::TestCase class DeliveryMailer < ActionMailer::Base DEFAULT_HEADERS = { - :to => 'mikel@test.lindsaar.net', - :from => 'jose@test.plataformatec.com' + to: 'mikel@test.lindsaar.net', + from: 'jose@test.plataformatec.com' } def welcome(hash={}) @@ -110,7 +110,7 @@ class MailDeliveryTest < ActiveSupport::TestCase test "delivery method can be customized per instance" do email = DeliveryMailer.welcome.deliver assert_instance_of Mail::SMTP, email.delivery_method - email = DeliveryMailer.welcome(:delivery_method => :test).deliver + email = DeliveryMailer.welcome(delivery_method: :test).deliver assert_instance_of Mail::TestMailer, email.delivery_method end @@ -125,7 +125,7 @@ class MailDeliveryTest < ActiveSupport::TestCase test "delivery method options default to class level options" do default_options = {a: "b"} ActionMailer::Base.add_delivery_method :optioned, MyOptionedDelivery, default_options - mail_instance = DeliveryMailer.welcome(:delivery_method => :optioned) + mail_instance = DeliveryMailer.welcome(delivery_method: :optioned) assert_equal default_options, mail_instance.delivery_method.options end @@ -133,21 +133,21 @@ class MailDeliveryTest < ActiveSupport::TestCase default_options = {a: "b"} ActionMailer::Base.add_delivery_method :optioned, MyOptionedDelivery, default_options overridden_options = {a: "a"} - mail_instance = DeliveryMailer.welcome(:delivery_method => :optioned, :delivery_method_options => overridden_options) + mail_instance = DeliveryMailer.welcome(delivery_method: :optioned, delivery_method_options: overridden_options) assert_equal overridden_options, mail_instance.delivery_method.options end test "default delivery options can be overridden per mail instance" do - settings = { :address => "localhost", - :port => 25, - :domain => 'localhost.localdomain', - :user_name => nil, - :password => nil, - :authentication => nil, - :enable_starttls_auto => true } + settings = { address: "localhost", + port: 25, + domain: 'localhost.localdomain', + user_name: nil, + password: nil, + authentication: nil, + enable_starttls_auto: true } assert_equal settings, ActionMailer::Base.smtp_settings - overridden_options = {user_name: "overridden", :password => "somethingobtuse"} - mail_instance = DeliveryMailer.welcome(:delivery_method_options => overridden_options) + overridden_options = {user_name: "overridden", password: "somethingobtuse"} + mail_instance = DeliveryMailer.welcome(delivery_method_options: overridden_options) delivery_method_instance = mail_instance.delivery_method assert_equal "overridden", delivery_method_instance.settings[:user_name] assert_equal "somethingobtuse", delivery_method_instance.settings[:password] diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb index 68ed86e0d4..a3e93c9c31 100644 --- a/actionmailer/test/i18n_with_controller_test.rb +++ b/actionmailer/test/i18n_with_controller_test.rb @@ -9,15 +9,15 @@ class I18nTestMailer < ActionMailer::Base def mail_with_i18n_subject(recipient) @recipient = recipient I18n.locale = :de - mail(:to => recipient, :subject => "#{I18n.t :email_subject} #{recipient}", - :from => "system@loudthinking.com", :date => Time.local(2004, 12, 12)) + mail(to: recipient, subject: "#{I18n.t :email_subject} #{recipient}", + from: "system@loudthinking.com", date: Time.local(2004, 12, 12)) end end class TestController < ActionController::Base def send_mail I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver - render :text => 'Mail sent' + render text: 'Mail sent' end end @@ -32,7 +32,7 @@ class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest end def setup - I18n.backend.store_translations('de', :email_subject => '[Signed up] Welcome') + I18n.backend.store_translations('de', email_subject: '[Signed up] Welcome') end def teardown diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb index d8a73e6c46..24ccaab8df 100644 --- a/actionmailer/test/mail_helper_test.rb +++ b/actionmailer/test/mail_helper_test.rb @@ -10,7 +10,7 @@ class HelperMailer < ActionMailer::Base "it off!" mail_with_defaults do |format| - format.html { render(:inline => "<%= block_format @text %>") } + format.html { render(inline: "<%= block_format @text %>") } end end @@ -18,7 +18,7 @@ class HelperMailer < ActionMailer::Base @text = "But soft! What light through yonder window breaks?" mail_with_defaults do |format| - format.html { render(:inline => "<%= format_paragraph @text, 15, 1 %>") } + format.html { render(inline: "<%= format_paragraph @text, 15, 1 %>") } end end @@ -26,19 +26,19 @@ class HelperMailer < ActionMailer::Base @text = "Antidisestablishmentarianism is very long." mail_with_defaults do |format| - format.html { render(:inline => "<%= format_paragraph @text, 10, 1 %>") } + format.html { render(inline: "<%= format_paragraph @text, 10, 1 %>") } end end def use_mailer mail_with_defaults do |format| - format.html { render(:inline => "<%= mailer.message.subject %>") } + format.html { render(inline: "<%= mailer.message.subject %>") } end end def use_message mail_with_defaults do |format| - format.html { render(:inline => "<%= message.subject %>") } + format.html { render(inline: "<%= message.subject %>") } end end @@ -55,15 +55,15 @@ The second TEXT mail_with_defaults do |format| - format.html { render(:inline => "<%= block_format @text %>") } + format.html { render(inline: "<%= block_format @text %>") } end end protected def mail_with_defaults(&block) - mail(:to => "test@localhost", :from => "tester@example.com", - :subject => "using helpers", &block) + mail(to: "test@localhost", from: "tester@example.com", + subject: "using helpers", &block) end end diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb index 71e93c29f1..7f959282cb 100644 --- a/actionmailer/test/mail_layout_test.rb +++ b/actionmailer/test/mail_layout_test.rb @@ -1,9 +1,9 @@ require 'abstract_unit' class AutoLayoutMailer < ActionMailer::Base - default :to => 'test@localhost', - :subject => "You have a mail", - :from => "tester@example.com" + default to: 'test@localhost', + subject: "You have a mail", + from: "tester@example.com" def hello mail() @@ -11,16 +11,16 @@ class AutoLayoutMailer < ActionMailer::Base def spam @world = "Earth" - mail(:body => render(:inline => "Hello, <%= @world %>", :layout => 'spam')) + mail(body: render(inline: "Hello, <%= @world %>", layout: 'spam')) end def nolayout @world = "Earth" - mail(:body => render(:inline => "Hello, <%= @world %>", :layout => false)) + mail(body: render(inline: "Hello, <%= @world %>", layout: false)) end def multipart(type = nil) - mail(:content_type => type) do |format| + mail(content_type: type) do |format| format.text { render } format.html { render } end @@ -28,11 +28,11 @@ class AutoLayoutMailer < ActionMailer::Base end class ExplicitLayoutMailer < ActionMailer::Base - layout 'spam', :except => [:logout] + layout 'spam', except: [:logout] - default :to => 'test@localhost', - :subject => "You have a mail", - :from => "tester@example.com" + default to: 'test@localhost', + subject: "You have a mail", + from: "tester@example.com" def signup mail() diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb index e55d72fdb4..f25d9b9aff 100644 --- a/actionmailer/test/mailers/base_mailer.rb +++ b/actionmailer/test/mailers/base_mailer.rb @@ -1,13 +1,13 @@ class BaseMailer < ActionMailer::Base self.mailer_name = "base_mailer" - default :to => 'system@test.lindsaar.net', - :from => 'jose@test.plataformatec.com', - :reply_to => 'mikel@test.lindsaar.net' + default to: 'system@test.lindsaar.net', + from: 'jose@test.plataformatec.com', + reply_to: 'mikel@test.lindsaar.net' def welcome(hash = {}) headers['X-SPAM'] = "Not SPAM" - mail({:subject => "The first email on new API!"}.merge!(hash)) + mail({subject: "The first email on new API!"}.merge!(hash)) end def welcome_with_headers(hash = {}) @@ -16,7 +16,7 @@ class BaseMailer < ActionMailer::Base end def welcome_from_another_path(path) - mail(:template_name => "welcome", :template_path => path) + mail(template_name: "welcome", template_path: path) end def html_only(hash = {}) @@ -38,15 +38,15 @@ class BaseMailer < ActionMailer::Base end def attachment_with_hash - attachments['invoice.jpg'] = { :data => "\312\213\254\232)b", - :mime_type => "image/x-jpg", - :transfer_encoding => "base64" } + attachments['invoice.jpg'] = { data: "\312\213\254\232)b", + mime_type: "image/x-jpg", + transfer_encoding: "base64" } mail end def attachment_with_hash_default_encoding - attachments['invoice.jpg'] = { :data => "\312\213\254\232)b", - :mime_type => "image/x-jpg" } + attachments['invoice.jpg'] = { data: "\312\213\254\232)b", + mime_type: "image/x-jpg" } mail end @@ -62,8 +62,8 @@ class BaseMailer < ActionMailer::Base def explicit_multipart(hash = {}) attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments) mail(hash) do |format| - format.text { render :text => "TEXT Explicit Multipart" } - format.html { render :text => "HTML Explicit Multipart" } + format.text { render text: "TEXT Explicit Multipart" } + format.html { render text: "HTML Explicit Multipart" } end end @@ -76,13 +76,13 @@ class BaseMailer < ActionMailer::Base def explicit_multipart_with_any(hash = {}) mail(hash) do |format| - format.any(:text, :html){ render :text => "Format with any!" } + format.any(:text, :html){ render text: "Format with any!" } end end def explicit_multipart_with_options(include_html = false) mail do |format| - format.text(:content_transfer_encoding => "base64"){ render "welcome" } + format.text(content_transfer_encoding: "base64"){ render "welcome" } format.html{ render "welcome" } if include_html end end @@ -95,24 +95,24 @@ class BaseMailer < ActionMailer::Base end def implicit_different_template(template_name='') - mail(:template_name => template_name) + mail(template_name: template_name) end def explicit_different_template(template_name='') mail do |format| - format.text { render :template => "#{mailer_name}/#{template_name}" } - format.html { render :template => "#{mailer_name}/#{template_name}" } + format.text { render template: "#{mailer_name}/#{template_name}" } + format.html { render template: "#{mailer_name}/#{template_name}" } end end def different_layout(layout_name='') mail do |format| - format.text { render :layout => layout_name } - format.html { render :layout => layout_name } + format.text { render layout: layout_name } + format.html { render layout: layout_name } end end def email_with_translations - mail :body => render("email_with_translations", :formats => [:html]) + mail body: render("email_with_translations", formats: [:html]) end end diff --git a/actionmailer/test/mailers/proc_mailer.rb b/actionmailer/test/mailers/proc_mailer.rb index 43916e1421..733633b575 100644 --- a/actionmailer/test/mailers/proc_mailer.rb +++ b/actionmailer/test/mailers/proc_mailer.rb @@ -1,7 +1,7 @@ class ProcMailer < ActionMailer::Base - default :to => 'system@test.lindsaar.net', + default to: 'system@test.lindsaar.net', 'X-Proc-Method' => Proc.new { Time.now.to_i.to_s }, - :subject => Proc.new { give_a_greeting } + subject: Proc.new { give_a_greeting } def welcome mail diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb index dd62164176..7c7f0b6fdc 100644 --- a/actionmailer/test/test_helper_test.rb +++ b/actionmailer/test/test_helper_test.rb @@ -3,9 +3,9 @@ require 'abstract_unit' class TestHelperMailer < ActionMailer::Base def test @world = "Earth" - mail :body => render(:inline => "Hello, <%= @world %>"), - :to => "test@example.com", - :from => "tester@example.com" + mail body: render(inline: "Hello, <%= @world %>"), + to: "test@example.com", + from: "tester@example.com" end end diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb index 2ea1723434..dcd80f46b5 100644 --- a/actionmailer/test/url_test.rb +++ b/actionmailer/test/url_test.rb @@ -19,9 +19,9 @@ class UrlTestMailer < ActionMailer::Base def signed_up_with_url(recipient) @recipient = recipient - @welcome_url = url_for :host => "example.com", :controller => "welcome", :action => "greeting" - mail(:to => recipient, :subject => "[Signed up] Welcome #{recipient}", - :from => "system@loudthinking.com", :date => Time.local(2004, 12, 12)) + @welcome_url = url_for host: "example.com", controller: "welcome", action: "greeting" + mail(to: recipient, subject: "[Signed up] Welcome #{recipient}", + from: "system@loudthinking.com", date: Time.local(2004, 12, 12)) end end @@ -58,7 +58,7 @@ class ActionMailerUrlTest < ActionMailer::TestCase AppRoutes.draw do get ':controller(/:action(/:id))' - get '/welcome' => "foo#bar", :as => "welcome" + get '/welcome' => "foo#bar", as: "welcome" end expected = new_mail 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&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/active_record_unit.rb b/actionpack/test/active_record_unit.rb index 4f2b052720..4dd7406798 100644 --- a/actionpack/test/active_record_unit.rb +++ b/actionpack/test/active_record_unit.rb @@ -11,7 +11,7 @@ class ActiveRecordTestConnector end # Try to grab AR -unless defined?(ActiveRecord) && defined?(Fixtures) +unless defined?(ActiveRecord) && defined?(FixtureSet) begin PATH_TO_AR = "#{File.dirname(__FILE__)}/../../activerecord/lib" raise LoadError, "#{PATH_TO_AR} doesn't exist" unless File.directory?(PATH_TO_AR) diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 620479cb0c..65c18dfb64 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -6,744 +6,40 @@ CACHE_DIR = 'test_cache' # Don't change '/../temp/' cavalierly or you might hose something you don't want hosed FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR) -class CachingMetalController < ActionController::Metal +class FragmentCachingMetalTestController < ActionController::Metal abstract! include ActionController::Caching - self.page_cache_directory = FILE_STORE_PATH - self.cache_store = :file_store, FILE_STORE_PATH -end - -class PageCachingMetalTestController < CachingMetalController - caches_page :ok - - def ok - self.response_body = 'ok' - end + def some_action; end end -class PageCachingMetalTest < ActionController::TestCase - tests PageCachingMetalTestController - +class FragmentCachingMetalTest < ActionController::TestCase def setup - FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) - FileUtils.mkdir_p(FILE_STORE_PATH) - end - - def teardown - FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) + super + @store = ActiveSupport::Cache::MemoryStore.new + @controller = FragmentCachingMetalTestController.new + @controller.perform_caching = true + @controller.cache_store = @store + @params = { controller: 'posts', action: 'index'} + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @controller.params = @params + @controller.request = @request + @controller.response = @response end - def test_should_cache_get_with_ok_status - get :ok - assert_response :ok - assert File.exist?("#{FILE_STORE_PATH}/page_caching_metal_test/ok.html"), 'get with ok status should have been cached' + def test_fragment_cache_key + assert_equal 'views/what a key', @controller.fragment_cache_key('what a key') end end -ActionController::Base.page_cache_directory = FILE_STORE_PATH - class CachingController < ActionController::Base abstract! self.cache_store = :file_store, FILE_STORE_PATH end -class PageCachingTestController < CachingController - self.page_cache_compression = :best_compression - - caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? } - caches_page :found, :not_found - caches_page :about_me - caches_page :default_gzip - caches_page :no_gzip, :gzip => false - caches_page :gzip_level, :gzip => :best_speed - - def ok - head :ok - end - - def no_content - head :no_content - end - - def found - redirect_to :action => 'ok' - end - - def not_found - head :not_found - end - - def custom_path - render :text => "Super soaker" - cache_page("Super soaker", "/index.html") - end - - def default_gzip - render :text => "Text" - end - - def no_gzip - render :text => "PNG" - end - - def gzip_level - render :text => "Big text" - end - - def expire_custom_path - expire_page("/index.html") - head :ok - end - - def trailing_slash - render :text => "Sneak attack" - end - - def about_me - respond_to do |format| - format.html {render :text => 'I am html'} - format.xml {render :text => 'I am xml'} - end - end - -end - -class PageCachingTest < ActionController::TestCase - def setup - super - - @request = ActionController::TestRequest.new - @request.host = 'hostname.com' - @request.env.delete('PATH_INFO') - - @controller = PageCachingTestController.new - @controller.perform_caching = true - @controller.cache_store = :file_store, FILE_STORE_PATH - - @response = ActionController::TestResponse.new - - @params = {:controller => 'posts', :action => 'index', :only_path => true} - - FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) - FileUtils.mkdir_p(FILE_STORE_PATH) - end - - def teardown - FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) - @controller.perform_caching = false - end - - def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route - with_routing do |set| - set.draw do - get 'posts.:format', :to => 'posts#index', :as => :formatted_posts - get '/', :to => 'posts#index', :as => :main - end - @params[:format] = 'rss' - assert_equal '/posts.rss', @routes.url_for(@params) - @params[:format] = nil - assert_equal '/', @routes.url_for(@params) - end - end - - def test_should_cache_get_with_ok_status - get :ok - assert_response :ok - assert_page_cached :ok, "get with ok status should have been cached" - end - - def test_should_cache_with_custom_path - get :custom_path - assert File.exist?("#{FILE_STORE_PATH}/index.html") - end - - def test_should_expire_cache_with_custom_path - get :custom_path - assert File.exist?("#{FILE_STORE_PATH}/index.html") - - get :expire_custom_path - assert !File.exist?("#{FILE_STORE_PATH}/index.html") - end - - def test_should_gzip_cache - get :custom_path - assert File.exist?("#{FILE_STORE_PATH}/index.html.gz") - - get :expire_custom_path - assert !File.exist?("#{FILE_STORE_PATH}/index.html.gz") - end - - def test_should_allow_to_disable_gzip - get :no_gzip - assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html") - assert !File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html.gz") - end - - def test_should_use_config_gzip_by_default - @controller.expects(:cache_page).with(nil, nil, Zlib::BEST_COMPRESSION) - get :default_gzip - end - - def test_should_set_gzip_level - @controller.expects(:cache_page).with(nil, nil, Zlib::BEST_SPEED) - get :gzip_level - end - - def test_should_cache_without_trailing_slash_on_url - @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash' - assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html") - end - - def test_should_obey_http_accept_attribute - @request.env['HTTP_ACCEPT'] = 'text/xml' - get :about_me - assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/about_me.xml") - assert_equal 'I am xml', @response.body - end - - def test_cached_page_should_not_have_trailing_slash_even_if_url_has_trailing_slash - @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash/' - assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html") - end - - def test_should_cache_ok_at_custom_path - @request.env['PATH_INFO'] = '/index.html' - get :ok - assert_response :ok - assert File.exist?("#{FILE_STORE_PATH}/index.html") - end - - [:ok, :no_content, :found, :not_found].each do |status| - [:get, :post, :patch, :put, :delete].each do |method| - unless method == :get && status == :ok - define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do - send(method, status) - assert_response status - assert_page_not_cached status, "#{method} with #{status} status shouldn't have been cached" - end - end - end - end - - def test_page_caching_conditional_options - get :ok, :format=>'json' - assert_page_not_cached :ok - end - - def test_page_caching_directory_set_as_pathname - begin - ActionController::Base.page_cache_directory = Pathname.new(FILE_STORE_PATH) - get :ok - assert_response :ok - assert_page_cached :ok - ensure - ActionController::Base.page_cache_directory = FILE_STORE_PATH - end - end - - private - def assert_page_cached(action, message = "#{action} should have been cached") - assert page_cached?(action), message - end - - def assert_page_not_cached(action, message = "#{action} shouldn't have been cached") - assert !page_cached?(action), message - end - - def page_cached?(action) - File.exist? "#{FILE_STORE_PATH}/page_caching_test/#{action}.html" - end -end - -class ActionCachingTestController < CachingController - rescue_from(Exception) { head 500 } - rescue_from(ActionController::UnknownFormat) { head :not_acceptable } - if defined? ActiveRecord - rescue_from(ActiveRecord::RecordNotFound) { head :not_found } - end - - # Eliminate uninitialized ivar warning - before_filter { @title = nil } - - caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| c.request.format && !c.request.format.json? }, :expires_in => 1.hour - caches_action :show, :cache_path => 'http://test.host/custom/show' - caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" } - caches_action :with_layout - caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } } - caches_action :layout_false, :layout => false - caches_action :with_layout_proc_param, :layout => Proc.new { |c| c.params[:layout] } - caches_action :record_not_found, :four_oh_four, :simple_runtime_error - caches_action :streaming - caches_action :invalid - - layout 'talk_from_action' - - def index - @cache_this = MockTime.now.to_f.to_s - render :text => @cache_this - end - - def redirected - redirect_to :action => 'index' - end - - def forbidden - render :text => "Forbidden" - response.status = "403 Forbidden" - end - - def with_layout - @cache_this = MockTime.now.to_f.to_s - @title = nil - render :text => @cache_this, :layout => true - end - - def with_format_and_http_param - @cache_this = MockTime.now.to_f.to_s - render :text => @cache_this - end - - def record_not_found - raise ActiveRecord::RecordNotFound, "oops!" - end - - def four_oh_four - render :text => "404'd!", :status => 404 - end - - def simple_runtime_error - raise "oops!" - end - - alias_method :show, :index - alias_method :edit, :index - alias_method :destroy, :index - alias_method :layout_false, :with_layout - alias_method :with_layout_proc_param, :with_layout - - def expire - expire_action :controller => 'action_caching_test', :action => 'index' - render :nothing => true - end - - def expire_xml - expire_action :controller => 'action_caching_test', :action => 'index', :format => 'xml' - render :nothing => true - end - - def expire_with_url_string - expire_action url_for(:controller => 'action_caching_test', :action => 'index') - render :nothing => true - end - - def streaming - render :text => "streaming", :stream => true - end - - def invalid - @cache_this = MockTime.now.to_f.to_s - - respond_to do |format| - format.json{ render :json => @cache_this } - end - end -end - -class MockTime < Time - # Let Time spicy to assure that Time.now != Time.now - def to_f - super+rand - end -end - -class ActionCachingMockController - attr_accessor :mock_url_for - attr_accessor :mock_path - - def initialize - yield self if block_given? - end - - def url_for(*args) - @mock_url_for - end - - def params - request.parameters - end - - def request - Object.new.instance_eval(<<-EVAL) - def path; '#{@mock_path}' end - def format; 'all' end - def parameters; {:format => nil}; end - self - EVAL - end -end - -class ActionCacheTest < ActionController::TestCase - tests ActionCachingTestController - - def setup - super - @request.host = 'hostname.com' - FileUtils.mkdir_p(FILE_STORE_PATH) - @path_class = ActionController::Caching::Actions::ActionCachePath - @mock_controller = ActionCachingMockController.new - end - - def teardown - super - FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) - end - - def test_simple_action_cache - get :index - assert_response :success - cached_time = content_to_cache - assert_equal cached_time, @response.body - assert fragment_exist?('hostname.com/action_caching_test') - - get :index - assert_response :success - assert_equal cached_time, @response.body - end - - def test_simple_action_not_cached - get :destroy - assert_response :success - cached_time = content_to_cache - assert_equal cached_time, @response.body - assert !fragment_exist?('hostname.com/action_caching_test/destroy') - - get :destroy - assert_response :success - assert_not_equal cached_time, @response.body - end - - include RackTestUtils - - def test_action_cache_with_layout - get :with_layout - assert_response :success - cached_time = content_to_cache - assert_not_equal cached_time, @response.body - assert fragment_exist?('hostname.com/action_caching_test/with_layout') - - get :with_layout - assert_response :success - assert_not_equal cached_time, @response.body - body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout')) - assert_equal @response.body, body - end - - def test_action_cache_with_layout_and_layout_cache_false - get :layout_false - assert_response :success - cached_time = content_to_cache - assert_not_equal cached_time, @response.body - assert fragment_exist?('hostname.com/action_caching_test/layout_false') - - get :layout_false - assert_response :success - assert_not_equal cached_time, @response.body - body = body_to_string(read_fragment('hostname.com/action_caching_test/layout_false')) - assert_equal cached_time, body - end - - def test_action_cache_with_layout_and_layout_cache_false_via_proc - get :with_layout_proc_param, :layout => false - assert_response :success - cached_time = content_to_cache - assert_not_equal cached_time, @response.body - assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param') - - get :with_layout_proc_param, :layout => false - assert_response :success - assert_not_equal cached_time, @response.body - body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param')) - assert_equal cached_time, body - end - - def test_action_cache_with_layout_and_layout_cache_true_via_proc - get :with_layout_proc_param, :layout => true - assert_response :success - cached_time = content_to_cache - assert_not_equal cached_time, @response.body - assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param') - - get :with_layout_proc_param, :layout => true - assert_response :success - assert_not_equal cached_time, @response.body - body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param')) - assert_equal @response.body, body - end - - def test_action_cache_conditional_options - @request.env['HTTP_ACCEPT'] = 'application/json' - get :index - assert_response :success - assert !fragment_exist?('hostname.com/action_caching_test') - end - - def test_action_cache_with_format_and_http_param - get :with_format_and_http_param, :format => 'json' - assert_response :success - assert !fragment_exist?('hostname.com/action_caching_test/with_format_and_http_param.json?key=value.json') - assert fragment_exist?('hostname.com/action_caching_test/with_format_and_http_param.json?key=value') - end - - def test_action_cache_with_store_options - MockTime.expects(:now).returns(12345).once - @controller.expects(:read_fragment).with('hostname.com/action_caching_test', :expires_in => 1.hour).once - @controller.expects(:write_fragment).with('hostname.com/action_caching_test', '12345.0', :expires_in => 1.hour).once - get :index - assert_response :success - end - - def test_action_cache_with_custom_cache_path - get :show - assert_response :success - cached_time = content_to_cache - assert_equal cached_time, @response.body - assert fragment_exist?('test.host/custom/show') - - get :show - assert_response :success - assert_equal cached_time, @response.body - end - - def test_action_cache_with_custom_cache_path_in_block - get :edit - assert_response :success - assert fragment_exist?('test.host/edit') - - get :edit, :id => 1 - assert_response :success - assert fragment_exist?('test.host/1;edit') - end - - def test_cache_expiration - get :index - assert_response :success - cached_time = content_to_cache - - get :index - assert_response :success - assert_equal cached_time, @response.body - - get :expire - assert_response :success - - get :index - assert_response :success - new_cached_time = content_to_cache - assert_not_equal cached_time, @response.body - - get :index - assert_response :success - assert_equal new_cached_time, @response.body - end - - def test_cache_expiration_isnt_affected_by_request_format - get :index - cached_time = content_to_cache - - @request.request_uri = "/action_caching_test/expire.xml" - get :expire, :format => :xml - assert_response :success - - get :index - assert_response :success - assert_not_equal cached_time, @response.body - end - - def test_cache_expiration_with_url_string - get :index - cached_time = content_to_cache - - @request.request_uri = "/action_caching_test/expire_with_url_string" - get :expire_with_url_string - assert_response :success - - get :index - assert_response :success - assert_not_equal cached_time, @response.body - end - - def test_cache_is_scoped_by_subdomain - @request.host = 'jamis.hostname.com' - get :index - assert_response :success - jamis_cache = content_to_cache - - @request.host = 'david.hostname.com' - get :index - assert_response :success - david_cache = content_to_cache - assert_not_equal jamis_cache, @response.body - - @request.host = 'jamis.hostname.com' - get :index - assert_response :success - assert_equal jamis_cache, @response.body - - @request.host = 'david.hostname.com' - get :index - assert_response :success - assert_equal david_cache, @response.body - end - - def test_redirect_is_not_cached - get :redirected - assert_response :redirect - get :redirected - assert_response :redirect - end - - def test_forbidden_is_not_cached - get :forbidden - assert_response :forbidden - get :forbidden - assert_response :forbidden - end - - def test_xml_version_of_resource_is_treated_as_different_cache - with_routing do |set| - set.draw do - get ':controller(/:action(.:format))' - end - - get :index, :format => 'xml' - assert_response :success - cached_time = content_to_cache - assert_equal cached_time, @response.body - assert fragment_exist?('hostname.com/action_caching_test/index.xml') - - get :index, :format => 'xml' - assert_response :success - assert_equal cached_time, @response.body - assert_equal 'application/xml', @response.content_type - - get :expire_xml - assert_response :success - - get :index, :format => 'xml' - assert_response :success - assert_not_equal cached_time, @response.body - end - end - - def test_correct_content_type_is_returned_for_cache_hit - # run it twice to cache it the first time - get :index, :id => 'content-type', :format => 'xml' - get :index, :id => 'content-type', :format => 'xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - end - - def test_correct_content_type_is_returned_for_cache_hit_on_action_with_string_key - # run it twice to cache it the first time - get :show, :format => 'xml' - get :show, :format => 'xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - end - - def test_correct_content_type_is_returned_for_cache_hit_on_action_with_string_key_from_proc - # run it twice to cache it the first time - get :edit, :id => 1, :format => 'xml' - get :edit, :id => 1, :format => 'xml' - assert_response :success - assert_equal 'application/xml', @response.content_type - end - - def test_empty_path_is_normalized - @mock_controller.mock_url_for = 'http://example.org/' - @mock_controller.mock_path = '/' - - assert_equal 'example.org/index', @path_class.new(@mock_controller, {}).path - end - - def test_file_extensions - get :index, :id => 'kitten.jpg' - get :index, :id => 'kitten.jpg' - - assert_response :success - end - - if defined? ActiveRecord - def test_record_not_found_returns_404_for_multiple_requests - get :record_not_found - assert_response 404 - get :record_not_found - assert_response 404 - end - end - - def test_four_oh_four_returns_404_for_multiple_requests - get :four_oh_four - assert_response 404 - get :four_oh_four - assert_response 404 - end - - def test_four_oh_four_renders_content - get :four_oh_four - assert_equal "404'd!", @response.body - end - - def test_simple_runtime_error_returns_500_for_multiple_requests - get :simple_runtime_error - assert_response 500 - get :simple_runtime_error - assert_response 500 - end - - def test_action_caching_plus_streaming - get :streaming - assert_response :success - assert_match(/streaming/, @response.body) - assert fragment_exist?('hostname.com/action_caching_test/streaming') - end - - def test_invalid_format_returns_not_acceptable - get :invalid, :format => "json" - assert_response :success - cached_time = content_to_cache - assert_equal cached_time, @response.body - - assert fragment_exist?("hostname.com/action_caching_test/invalid.json") - - get :invalid, :format => "json" - assert_response :success - assert_equal cached_time, @response.body - - get :invalid, :format => "xml" - assert_response :not_acceptable - - get :invalid, :format => "\xC3\x83" - assert_response :not_acceptable - end - - private - def content_to_cache - assigns(:cache_this) - end - - def fragment_exist?(path) - @controller.fragment_exist?(path) - end - - def read_fragment(path) - @controller.read_fragment(path) - end -end - class FragmentCachingTestController < CachingController def some_action; end; end @@ -988,3 +284,17 @@ class CacheHelperOutputBufferTest < ActionController::TestCase end end +class DeprecatedPageCacheExtensionTest < ActiveSupport::TestCase + def test_page_cache_extension_binds_default_static_extension + deprecation_behavior = ActiveSupport::Deprecation.behavior + ActiveSupport::Deprecation.behavior = :silence + old_extension = ActionController::Base.default_static_extension + + ActionController::Base.page_cache_extension = '.rss' + + assert_equal '.rss', ActionController::Base.default_static_extension + ensure + ActiveSupport::Deprecation.behavior = deprecation_behavior + ActionController::Base.default_static_extension = old_extension + end +end diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index a72b6dde1a..9efb6ab95f 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -42,11 +42,6 @@ module Another render :inline => "<%= cache('foo%bar'){ 'Contains % sign in key' } %>" end - def with_page_cache - cache_page("Super soaker", "/index.html") - render :nothing => true - end - def with_exception raise Exception end @@ -71,7 +66,6 @@ class ACLogSubscriberTest < ActionController::TestCase @old_logger = ActionController::Base.logger @cache_path = File.expand_path('../temp/test_cache', File.dirname(__FILE__)) - ActionController::Base.page_cache_directory = @cache_path @controller.cache_store = :file_store, @cache_path ActionController::LogSubscriber.attach_to :action_controller end @@ -199,18 +193,6 @@ class ACLogSubscriberTest < ActionController::TestCase @controller.config.perform_caching = true end - def test_with_page_cache - @controller.config.perform_caching = true - get :with_page_cache - wait - - assert_equal 3, logs.size - assert_match(/Write page/, logs[1]) - assert_match(/\/index\.html/, logs[1]) - ensure - @controller.config.perform_caching = true - end - def test_process_action_with_exception_includes_http_status_code begin get :with_exception diff --git a/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb new file mode 100644 index 0000000000..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="'><script>alert()</script>">blah</span>) + assert_sanitized %(<SPAN title="'><script>alert()</script>">blah</SPAN>), %(<span title="#{CGI.escapeHTML "'><script>alert()</script>"}">blah</span>) end def test_should_sanitize_illegal_style_properties diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 134177d74d..bbfdf7f944 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -31,17 +31,17 @@ class UrlHelperTest < ActiveSupport::TestCase setup :_prepare_context def hash_for(options = {}) - { :controller => "foo", :action => "bar" }.merge!(options) + { controller: "foo", action: "bar" }.merge!(options) end alias url_hash hash_for def test_url_for_does_not_escape_urls - assert_equal "/?a=b&c=d", url_for(hash_for(:a => :b, :c => :d)) + assert_equal "/?a=b&c=d", url_for(hash_for(a: :b, c: :d)) end def test_url_for_with_back referer = 'http://www.example.com/referer' - @controller = Struct.new(:request).new(Struct.new(:env).new({"HTTP_REFERER" => referer})) + @controller = Struct.new(:request).new(Struct.new(:env).new("HTTP_REFERER" => referer)) assert_equal 'http://www.example.com/referer', url_for(:back) end @@ -53,7 +53,7 @@ class UrlHelperTest < ActiveSupport::TestCase # TODO: missing test cases def test_button_to_with_straight_url - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com") + assert_dom_equal %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com") end def test_button_to_with_straight_url_and_request_forgery @@ -68,143 +68,146 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_button_to_with_form_class - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"custom-class\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com", :form_class => 'custom-class') + assert_dom_equal %{<form method="post" action="http://www.example.com" class="custom-class"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com", form_class: 'custom-class') end def test_button_to_with_form_class_escapes - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"<script>evil_js</script>\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com", :form_class => '<script>evil_js</script>') + assert_dom_equal %{<form method="post" action="http://www.example.com" class="<script>evil_js</script>"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com", form_class: '<script>evil_js</script>') end def test_button_to_with_query - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&q2=v2") + assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2") end def test_button_to_with_html_safe_URL - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&q2=v2".html_safe) + assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&q2=v2" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, button_to("Hello", "http://www.example.com/q1=v1&q2=v2".html_safe) end def test_button_to_with_query_and_no_name - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com?q1=v1&q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"http://www.example.com?q1=v1&q2=v2\" /></div></form>", button_to(nil, "http://www.example.com?q1=v1&q2=v2") + assert_dom_equal %{<form method="post" action="http://www.example.com?q1=v1&q2=v2" class="button_to"><div><input type="submit" value="http://www.example.com?q1=v1&q2=v2" /></div></form>}, button_to(nil, "http://www.example.com?q1=v1&q2=v2") end def test_button_to_with_javascript_confirm assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :data => { :confirm => "Are you sure?" }) + %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", data: { confirm: "Are you sure?" }) ) end def test_button_to_with_deprecated_confirm assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :confirm => "Are you sure?") + %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", confirm: "Are you sure?") ) end end def test_button_to_with_javascript_disable_with assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input data-disable-with=\"Greeting...\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :data => { :disable_with => "Greeting..." }) + %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", data: { disable_with: "Greeting..." }) ) end def test_button_to_with_javascript_deprecated_disable_with assert_deprecated ":disable_with option is deprecated and will be removed from Rails 4.1. Use ':data => { :disable_with => \'Text\' }' instead" do assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input data-disable-with=\"Greeting...\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :disable_with => "Greeting...") + %{<form method="post" action="http://www.example.com" class="button_to"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", disable_with: "Greeting...") ) end end def test_button_to_with_remote_and_form_options - assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"custom-class\" data-remote=\"true\" data-type=\"json\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com", :remote => true, :form => { :class => "custom-class", "data-type" => "json" } ) + assert_dom_equal( + %{<form method="post" action="http://www.example.com" class="custom-class" data-remote="true" data-type="json"><div><input type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", remote: true, form: { class: "custom-class", "data-type" => "json" }) + ) end def test_button_to_with_remote_and_javascript_confirm assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :remote => true, :data => { :confirm => "Are you sure?" }) + %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", remote: true, data: { confirm: "Are you sure?" }) ) end def test_button_to_with_remote_and_javascript_with_deprecated_confirm assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :remote => true, :confirm => "Are you sure?") + %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-confirm="Are you sure?" type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", remote: true, confirm: "Are you sure?") ) end end def test_button_to_with_remote_and_javascript_disable_with assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-disable-with=\"Greeting...\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :remote => true, :data => { :disable_with => "Greeting..." }) + %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", remote: true, data: { disable_with: "Greeting..." }) ) end def test_button_to_with_remote_and_javascript_deprecated_disable_with assert_deprecated ":disable_with option is deprecated and will be removed from Rails 4.1. Use ':data => { :disable_with => \'Text\' }' instead" do assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-disable-with=\"Greeting...\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :remote => true, :disable_with => "Greeting...") + %{<form method="post" action="http://www.example.com" class="button_to" data-remote="true"><div><input data-disable-with="Greeting..." type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", remote: true, disable_with: "Greeting...") ) end end def test_button_to_with_remote_false assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :remote => false) + %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", remote: false) ) end def test_button_to_enabled_disabled assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :disabled => false) + %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", disabled: false) ) assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input disabled=\"disabled\" type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :disabled => true) + %{<form method="post" action="http://www.example.com" class="button_to"><div><input disabled="disabled" type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", disabled: true) ) end def test_button_to_with_method_delete assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"hidden\" name=\"_method\" value=\"delete\" /><input type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :method => :delete) + %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="hidden" name="_method" value="delete" /><input type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", method: :delete) ) end def test_button_to_with_method_get assert_dom_equal( - "<form method=\"get\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", - button_to("Hello", "http://www.example.com", :method => :get) + %{<form method="get" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /></div></form>}, + button_to("Hello", "http://www.example.com", method: :get) ) end def test_button_to_with_block assert_dom_equal( - "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><button type=\"submit\"><span>Hello</span></button></div></form>", + %{<form method="post" action="http://www.example.com" class="button_to"><div><button type="submit"><span>Hello</span></button></div></form>}, button_to("http://www.example.com") { content_tag(:span, 'Hello') } ) end def test_link_tag_with_straight_url - assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", "http://www.example.com") + assert_dom_equal %{<a href="http://www.example.com">Hello</a>}, link_to("Hello", "http://www.example.com") end def test_link_tag_without_host_option - assert_dom_equal(%q{<a href="/">Test Link</a>}, link_to('Test Link', url_hash)) + assert_dom_equal(%{<a href="/">Test Link</a>}, link_to('Test Link', url_hash)) end def test_link_tag_with_host_option - hash = hash_for(:host => "www.example.com") - expected = %q{<a href="http://www.example.com/">Test Link</a>} + hash = hash_for(host: "www.example.com") + expected = %{<a href="http://www.example.com/">Test Link</a>} assert_dom_equal(expected, link_to('Test Link', hash)) end @@ -243,131 +246,143 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_link_tag_with_custom_onclick - link = link_to("Hello", "http://www.example.com", :onclick => "alert('yay!')") + link = link_to("Hello", "http://www.example.com", onclick: "alert('yay!')") expected = %{<a href="http://www.example.com" onclick="alert('yay!')">Hello</a>} assert_dom_equal expected, link end def test_link_tag_with_javascript_confirm assert_dom_equal( - "<a href=\"http://www.example.com\" data-confirm=\"Are you sure?\">Hello</a>", - link_to("Hello", "http://www.example.com", :data => { :confirm => "Are you sure?" }) + %{<a href="http://www.example.com" data-confirm="Are you sure?">Hello</a>}, + link_to("Hello", "http://www.example.com", data: { confirm: "Are you sure?" }) ) assert_dom_equal( - "<a href=\"http://www.example.com\" data-confirm=\"You cant possibly be sure, can you?\">Hello</a>", - link_to("Hello", "http://www.example.com", :data => { :confirm => "You cant possibly be sure, can you?" }) + %{<a href="http://www.example.com" data-confirm="You cant possibly be sure, can you?">Hello</a>}, + link_to("Hello", "http://www.example.com", data: { confirm: "You cant possibly be sure, can you?" }) ) assert_dom_equal( - "<a href=\"http://www.example.com\" data-confirm=\"You cant possibly be sure,\n can you?\">Hello</a>", - link_to("Hello", "http://www.example.com", :data => { :confirm => "You cant possibly be sure,\n can you?" }) + %{<a href="http://www.example.com" data-confirm="You cant possibly be sure,\n can you?">Hello</a>}, + link_to("Hello", "http://www.example.com", data: { confirm: "You cant possibly be sure,\n can you?" }) ) end def test_link_tag_with_deprecated_confirm assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<a href=\"http://www.example.com\" data-confirm=\"Are you sure?\">Hello</a>", - link_to("Hello", "http://www.example.com", :confirm => "Are you sure?") + %{<a href="http://www.example.com" data-confirm="Are you sure?">Hello</a>}, + link_to("Hello", "http://www.example.com", confirm: "Are you sure?") ) end assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<a href=\"http://www.example.com\" data-confirm=\"You cant possibly be sure, can you?\">Hello</a>", - link_to("Hello", "http://www.example.com", :confirm => "You cant possibly be sure, can you?") + %{<a href="http://www.example.com" data-confirm="You cant possibly be sure, can you?">Hello</a>}, + link_to("Hello", "http://www.example.com", confirm: "You cant possibly be sure, can you?") ) end assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<a href=\"http://www.example.com\" data-confirm=\"You cant possibly be sure,\n can you?\">Hello</a>", - link_to("Hello", "http://www.example.com", :confirm => "You cant possibly be sure,\n can you?") + %{<a href="http://www.example.com" data-confirm="You cant possibly be sure,\n can you?">Hello</a>}, + link_to("Hello", "http://www.example.com", confirm: "You cant possibly be sure,\n can you?") ) end end def test_link_to_with_remote assert_dom_equal( - "<a href=\"http://www.example.com\" data-remote=\"true\">Hello</a>", - link_to("Hello", "http://www.example.com", :remote => true) + %{<a href="http://www.example.com" data-remote="true">Hello</a>}, + link_to("Hello", "http://www.example.com", remote: true) ) end def test_link_to_with_remote_false assert_dom_equal( - "<a href=\"http://www.example.com\">Hello</a>", - link_to("Hello", "http://www.example.com", :remote => false) + %{<a href="http://www.example.com">Hello</a>}, + link_to("Hello", "http://www.example.com", remote: false) + ) + end + + def test_link_to_with_symbolic_remote_in_non_html_options + assert_dom_equal( + %{<a href="/" data-remote="true">Hello</a>}, + link_to("Hello", hash_for(remote: true), {}) + ) + end + + def test_link_to_with_string_remote_in_non_html_options + assert_dom_equal( + %{<a href="/" data-remote="true">Hello</a>}, + link_to("Hello", hash_for('remote' => true), {}) ) end def test_link_tag_using_post_javascript assert_dom_equal( - "<a href='http://www.example.com' data-method=\"post\" rel=\"nofollow\">Hello</a>", - link_to("Hello", "http://www.example.com", :method => :post) + %{<a href="http://www.example.com" data-method="post" rel="nofollow">Hello</a>}, + link_to("Hello", "http://www.example.com", method: :post) ) end def test_link_tag_using_delete_javascript assert_dom_equal( - "<a href='http://www.example.com' rel=\"nofollow\" data-method=\"delete\">Destroy</a>", - link_to("Destroy", "http://www.example.com", :method => :delete) + %{<a href="http://www.example.com" rel="nofollow" data-method="delete">Destroy</a>}, + link_to("Destroy", "http://www.example.com", method: :delete) ) end def test_link_tag_using_delete_javascript_and_href assert_dom_equal( - "<a href='\#' rel=\"nofollow\" data-method=\"delete\">Destroy</a>", - link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#') + %{<a href="\#" rel="nofollow" data-method="delete">Destroy</a>}, + link_to("Destroy", "http://www.example.com", method: :delete, href: '#') ) end def test_link_tag_using_post_javascript_and_rel assert_dom_equal( - "<a href='http://www.example.com' data-method=\"post\" rel=\"example nofollow\">Hello</a>", - link_to("Hello", "http://www.example.com", :method => :post, :rel => 'example') + %{<a href="http://www.example.com" data-method="post" rel="example nofollow">Hello</a>}, + link_to("Hello", "http://www.example.com", method: :post, rel: 'example') ) end def test_link_tag_using_post_javascript_and_confirm assert_dom_equal( - "<a href=\"http://www.example.com\" data-method=\"post\" rel=\"nofollow\" data-confirm=\"Are you serious?\">Hello</a>", - link_to("Hello", "http://www.example.com", :method => :post, :data => { :confirm => "Are you serious?" }) + %{<a href="http://www.example.com" data-method="post" rel="nofollow" data-confirm="Are you serious?">Hello</a>}, + link_to("Hello", "http://www.example.com", method: :post, data: { confirm: "Are you serious?" }) ) end def test_link_tag_using_post_javascript_and_with_deprecated_confirm assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<a href=\"http://www.example.com\" data-method=\"post\" rel=\"nofollow\" data-confirm=\"Are you serious?\">Hello</a>", - link_to("Hello", "http://www.example.com", :method => :post, :confirm => "Are you serious?") + %{<a href="http://www.example.com" data-method="post" rel="nofollow" data-confirm="Are you serious?">Hello</a>}, + link_to("Hello", "http://www.example.com", method: :post, confirm: "Are you serious?") ) end end def test_link_tag_using_delete_javascript_and_href_and_confirm assert_dom_equal( - "<a href='\#' rel=\"nofollow\" data-confirm=\"Are you serious?\" data-method=\"delete\">Destroy</a>", - link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#', :data => { :confirm => "Are you serious?" }), - "When specifying url, form should be generated with it, but not this.href" + %{<a href="\#" rel="nofollow" data-confirm="Are you serious?" data-method="delete">Destroy</a>}, + link_to("Destroy", "http://www.example.com", method: :delete, href: '#', data: { confirm: "Are you serious?" }) ) end def test_link_tag_using_delete_javascript_and_href_and_with_deprecated_confirm assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use ':data => { :confirm => \'Text\' }' instead" do assert_dom_equal( - "<a href='\#' rel=\"nofollow\" data-confirm=\"Are you serious?\" data-method=\"delete\">Destroy</a>", - link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#', :confirm => "Are you serious?"), - "When specifying url, form should be generated with it, but not this.href" + %{<a href="\#" rel="nofollow" data-confirm="Are you serious?" data-method="delete">Destroy</a>}, + link_to("Destroy", "http://www.example.com", method: :delete, href: '#', confirm: "Are you serious?") ) end end def test_link_tag_with_block - assert_dom_equal '<a href="/"><span>Example site</span></a>', + assert_dom_equal %{<a href="/"><span>Example site</span></a>}, link_to('/') { content_tag(:span, 'Example site') } end def test_link_tag_with_block_and_html_options - assert_dom_equal '<a class="special" href="/"><span>Example site</span></a>', - link_to('/', :class => "special") { content_tag(:span, 'Example site') } + assert_dom_equal %{<a class="special" href="/"><span>Example site</span></a>}, + link_to('/', class: "special") { content_tag(:span, 'Example site') } end def test_link_tag_using_block_in_erb @@ -377,18 +392,18 @@ class UrlHelperTest < ActiveSupport::TestCase def test_link_tag_with_html_safe_string assert_dom_equal( - "<a href=\"/article/Gerd_M%C3%BCller\">Gerd Müller</a>", + %{<a href="/article/Gerd_M%C3%BCller">Gerd Müller</a>}, link_to("Gerd Müller", article_path("Gerd_Müller".html_safe)) ) end def test_link_tag_escapes_content - assert_dom_equal '<a href="/">Malicious <script>content</script></a>', + assert_dom_equal %{<a href="/">Malicious <script>content</script></a>}, link_to("Malicious <script>content</script>", "/") end def test_link_tag_does_not_escape_html_safe_content - assert_dom_equal '<a href="/">Malicious <script>content</script></a>', + assert_dom_equal %{<a href="/">Malicious <script>content</script></a>}, link_to("Malicious <script>content</script>".html_safe, "/") end @@ -438,12 +453,12 @@ class UrlHelperTest < ActiveSupport::TestCase def test_current_page_with_params_that_match @request = request_for_url("/?order=desc&page=1") - assert current_page?(hash_for(:order => "desc", :page => "1")) + assert current_page?(hash_for(order: "desc", page: "1")) assert current_page?("http://www.example.com/?order=desc&page=1") end def test_current_page_with_not_get_verb - @request = request_for_url("/events", :method => :post) + @request = request_for_url("/events", method: :post) assert !current_page?('/events') end @@ -466,20 +481,20 @@ class UrlHelperTest < ActiveSupport::TestCase @request = request_for_url("/?order=desc&page=1") assert_equal "Showing", - link_to_unless_current("Showing", hash_for(:order => 'desc', :page => '1')) + link_to_unless_current("Showing", hash_for(order: 'desc', page: '1')) assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=1") @request = request_for_url("/?order=desc") assert_equal %{<a href="/?order=asc">Showing</a>}, - link_to_unless_current("Showing", hash_for(:order => :asc)) + link_to_unless_current("Showing", hash_for(order: :asc)) assert_equal %{<a href="http://www.example.com/?order=asc">Showing</a>}, link_to_unless_current("Showing", "http://www.example.com/?order=asc") @request = request_for_url("/?order=desc") assert_equal %{<a href="/?order=desc&page=2\">Showing</a>}, - link_to_unless_current("Showing", hash_for(:order => "desc", :page => 2)) + link_to_unless_current("Showing", hash_for(order: "desc", page: 2)) assert_equal %{<a href="http://www.example.com/?order=desc&page=2">Showing</a>}, link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=2") @@ -492,56 +507,86 @@ class UrlHelperTest < ActiveSupport::TestCase end def test_mail_to - assert_dom_equal "<a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>", mail_to("david@loudthinking.com") - assert_dom_equal "<a href=\"mailto:david@loudthinking.com\">David Heinemeier Hansson</a>", mail_to("david@loudthinking.com", "David Heinemeier Hansson") + assert_dom_equal %{<a href="mailto:david@loudthinking.com">david@loudthinking.com</a>}, mail_to("david@loudthinking.com") + assert_dom_equal %{<a href="mailto:david@loudthinking.com">David Heinemeier Hansson</a>}, mail_to("david@loudthinking.com", "David Heinemeier Hansson") assert_dom_equal( - "<a class=\"admin\" href=\"mailto:david@loudthinking.com\">David Heinemeier Hansson</a>", + %{<a class="admin" href="mailto:david@loudthinking.com">David Heinemeier Hansson</a>}, mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin") ) assert_equal mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin"), - mail_to("david@loudthinking.com", "David Heinemeier Hansson", :class => "admin") + mail_to("david@loudthinking.com", "David Heinemeier Hansson", class: "admin") end def test_mail_to_with_javascript - snippet = mail_to("me@domain.com", "My email", :encode => "javascript") + snippet = mail_to("me@domain.com", "My email", encode: "javascript") assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet end def test_mail_to_with_javascript_unicode - snippet = mail_to("unicode@example.com", "únicode", :encode => "javascript") + snippet = mail_to("unicode@example.com", "únicode", encode: "javascript") assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%5c%22%3e%c3%ba%6e%69%63%6f%64%65%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet end def test_mail_with_options assert_dom_equal( - %(<a href="mailto:me@example.com?cc=ccaddress%40example.com&bcc=bccaddress%40example.com&body=This%20is%20the%20body%20of%20the%20message.&subject=This%20is%20an%20example%20email">My email</a>), - mail_to("me@example.com", "My email", :cc => "ccaddress@example.com", :bcc => "bccaddress@example.com", :subject => "This is an example email", :body => "This is the body of the message.") + %{<a href="mailto:me@example.com?cc=ccaddress%40example.com&bcc=bccaddress%40example.com&body=This%20is%20the%20body%20of%20the%20message.&subject=This%20is%20an%20example%20email">My email</a>}, + mail_to("me@example.com", "My email", cc: "ccaddress@example.com", bcc: "bccaddress@example.com", subject: "This is an example email", body: "This is the body of the message.") ) end def test_mail_to_with_img - assert_dom_equal %(<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>), + assert_dom_equal %{<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>}, mail_to('feedback@example.com', '<img src="/feedback.png" />'.html_safe) end def test_mail_to_with_hex - assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex") - assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me@domain.com</a>", mail_to("me@domain.com", nil, :encode => "hex") + assert_dom_equal( + %{<a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>}, + mail_to("me@domain.com", "My email", encode: "hex") + ) + + assert_dom_equal( + %{<a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">me@domain.com</a>}, + mail_to("me@domain.com", nil, encode: "hex") + ) end def test_mail_to_with_replace_options - assert_dom_equal "<a href=\"mailto:wolfgang@stufenlos.net\">wolfgang(at)stufenlos(dot)net</a>", mail_to("wolfgang@stufenlos.net", nil, :replace_at => "(at)", :replace_dot => "(dot)") - assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain.com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)") - assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)") - assert_dom_equal "<a href=\"mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">me(at)domain(dot)com</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)") - assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") - assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") + assert_dom_equal( + %{<a href="mailto:wolfgang@stufenlos.net">wolfgang(at)stufenlos(dot)net</a>}, + mail_to("wolfgang@stufenlos.net", nil, replace_at: "(at)", replace_dot: "(dot)") + ) + + assert_dom_equal( + %{<a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">me(at)domain.com</a>}, + mail_to("me@domain.com", nil, encode: "hex", replace_at: "(at)") + ) + + assert_dom_equal( + %{<a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>}, + mail_to("me@domain.com", "My email", encode: "hex", replace_at: "(at)") + ) + + assert_dom_equal( + %{<a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">me(at)domain(dot)com</a>}, + mail_to("me@domain.com", nil, encode: "hex", replace_at: "(at)", replace_dot: "(dot)") + ) + + assert_dom_equal( + %{<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>}, + mail_to("me@domain.com", "My email", encode: "javascript", replace_at: "(at)", replace_dot: "(dot)") + ) + + assert_dom_equal( + %{<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>}, + mail_to("me@domain.com", nil, encode: "javascript", replace_at: "(at)", replace_dot: "(dot)") + ) end def test_mail_to_returns_html_safe_string assert mail_to("david@loudthinking.com").html_safe? - assert mail_to("me@domain.com", "My email", :encode => "javascript").html_safe? - assert mail_to("me@domain.com", "My email", :encode => "hex").html_safe? + assert mail_to("me@domain.com", "My email", encode: "javascript").html_safe? + assert mail_to("me@domain.com", "My email", encode: "hex").html_safe? end def protect_against_forgery? @@ -568,62 +613,62 @@ class UrlHelperControllerTest < ActionController::TestCase class UrlHelperController < ActionController::Base test_routes do get 'url_helper_controller_test/url_helper/show/:id', - :to => 'url_helper_controller_test/url_helper#show', - :as => :show + to: 'url_helper_controller_test/url_helper#show', + as: :show get 'url_helper_controller_test/url_helper/profile/:name', - :to => 'url_helper_controller_test/url_helper#show', - :as => :profile + to: 'url_helper_controller_test/url_helper#show', + as: :profile get 'url_helper_controller_test/url_helper/show_named_route', - :to => 'url_helper_controller_test/url_helper#show_named_route', - :as => :show_named_route + to: 'url_helper_controller_test/url_helper#show_named_route', + as: :show_named_route get "/:controller(/:action(/:id))" get 'url_helper_controller_test/url_helper/normalize_recall_params', - :to => UrlHelperController.action(:normalize_recall), - :as => :normalize_recall_params + to: UrlHelperController.action(:normalize_recall), + as: :normalize_recall_params get '/url_helper_controller_test/url_helper/override_url_helper/default', - :to => 'url_helper_controller_test/url_helper#override_url_helper', - :as => :override_url_helper + to: 'url_helper_controller_test/url_helper#override_url_helper', + as: :override_url_helper end def show if params[:name] - render :inline => 'ok' + render inline: 'ok' else redirect_to profile_path(params[:id]) end end def show_url_for - render :inline => "<%= url_for :controller => 'url_helper_controller_test/url_helper', :action => 'show_url_for' %>" + render inline: "<%= url_for controller: 'url_helper_controller_test/url_helper', action: 'show_url_for' %>" end def show_overriden_url_for - render :inline => "<%= url_for params.merge(:controller => 'url_helper_controller_test/url_helper', :action => 'show_url_for') %>" + render inline: "<%= url_for params.merge(controller: 'url_helper_controller_test/url_helper', action: 'show_url_for') %>" end def show_named_route - render :inline => "<%= show_named_route_#{params[:kind]} %>" + render inline: "<%= show_named_route_#{params[:kind]} %>" end def nil_url_for - render :inline => '<%= url_for(nil) %>' + render inline: '<%= url_for(nil) %>' end def normalize_recall_params - render :inline => '<%= normalize_recall_params_path %>' + render inline: '<%= normalize_recall_params_path %>' end def recall_params_not_changed - render :inline => '<%= url_for(:action => :show_url_for) %>' + render inline: '<%= url_for(action: :show_url_for) %>' end def override_url_helper - render :inline => '<%= override_url_helper_path %>' + render inline: '<%= override_url_helper_path %>' end def override_url_helper_path @@ -645,13 +690,13 @@ class UrlHelperControllerTest < ActionController::TestCase end def test_named_route_url_shows_host_and_path - get :show_named_route, :kind => 'url' + get :show_named_route, kind: 'url' assert_equal 'http://test.host/url_helper_controller_test/url_helper/show_named_route', @response.body end def test_named_route_path_shows_only_path - get :show_named_route, :kind => 'path' + get :show_named_route, kind: 'path' assert_equal '/url_helper_controller_test/url_helper/show_named_route', @response.body end @@ -663,11 +708,11 @@ class UrlHelperControllerTest < ActionController::TestCase def test_named_route_should_show_host_and_path_using_controller_default_url_options class << @controller def default_url_options - {:host => 'testtwo.host'} + { host: 'testtwo.host' } end end - get :show_named_route, :kind => 'url' + get :show_named_route, kind: 'url' assert_equal 'http://testtwo.host/url_helper_controller_test/url_helper/show_named_route', @response.body end @@ -682,11 +727,11 @@ class UrlHelperControllerTest < ActionController::TestCase end def test_recall_params_should_normalize_id - get :show, :id => '123' + get :show, id: '123' assert_equal 302, @response.status assert_equal 'http://test.host/url_helper_controller_test/url_helper/profile/123', @response.location - get :show, :name => '123' + get :show, name: '123' assert_equal 'ok', @response.body end @@ -711,9 +756,8 @@ class TasksController < ActionController::Base protected def render_default - render :inline => - "<%= link_to_unless_current(\"tasks\", tasks_path) %>\n" + - "<%= link_to_unless_current(\"tasks\", tasks_url) %>" + render inline: "<%= link_to_unless_current('tasks', tasks_path) %>\n" + + "<%= link_to_unless_current('tasks', tasks_url) %>" end end @@ -726,9 +770,9 @@ class LinkToUnlessCurrentWithControllerTest < ActionController::TestCase end def test_link_to_unless_current_shows_link - get :show, :id => 1 - assert_equal "<a href=\"/tasks\">tasks</a>\n" + - "<a href=\"#{@request.protocol}#{@request.host_with_port}/tasks\">tasks</a>", + get :show, id: 1 + assert_equal %{<a href="/tasks">tasks</a>\n} + + %{<a href="#{@request.protocol}#{@request.host_with_port}/tasks">tasks</a>}, @response.body end end @@ -760,12 +804,12 @@ class WorkshopsController < ActionController::Base def index @workshop = Workshop.new(nil) - render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" + render inline: "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" end def show @workshop = Workshop.new(params[:id]) - render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" + render inline: "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" end end @@ -779,13 +823,13 @@ class SessionsController < ActionController::Base def index @workshop = Workshop.new(params[:workshop_id]) @session = Session.new(nil) - render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>" + render inline: "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>" end def show @workshop = Workshop.new(params[:workshop_id]) @session = Session.new(params[:id]) - render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>" + render inline: "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>" end end @@ -794,27 +838,27 @@ class PolymorphicControllerTest < ActionController::TestCase @controller = WorkshopsController.new get :index - assert_equal "/workshops\n<a href=\"/workshops\">Workshop</a>", @response.body + assert_equal %{/workshops\n<a href="/workshops">Workshop</a>}, @response.body end def test_existing_resource @controller = WorkshopsController.new - get :show, :id => 1 - assert_equal "/workshops/1\n<a href=\"/workshops/1\">Workshop</a>", @response.body + get :show, id: 1 + assert_equal %{/workshops/1\n<a href="/workshops/1">Workshop</a>}, @response.body end def test_new_nested_resource @controller = SessionsController.new - get :index, :workshop_id => 1 - assert_equal "/workshops/1/sessions\n<a href=\"/workshops/1/sessions\">Session</a>", @response.body + get :index, workshop_id: 1 + assert_equal %{/workshops/1/sessions\n<a href="/workshops/1/sessions">Session</a>}, @response.body end def test_existing_nested_resource @controller = SessionsController.new - get :show, :workshop_id => 1, :id => 1 - assert_equal "/workshops/1/sessions/1\n<a href=\"/workshops/1/sessions/1\">Session</a>", @response.body + get :show, workshop_id: 1, id: 1 + assert_equal %{/workshops/1/sessions/1\n<a href="/workshops/1/sessions/1">Session</a>}, @response.body end end 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 67bec5f38e..9abfe2e6fd 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,44 @@ ## Rails 4.0.0 (unreleased) ## +* Rename `ActiveRecord::Fixtures` class to `ActiveRecord::FixtureSet`. + Instances of this class normally hold a collection of fixtures (records) + loaded either from a single YAML file, or from a file and a folder + with the same name. This change make the class name singular and makes + the class easier to distinguish from the modules like + `ActiveRecord::TestFixtures`, which operates on multiple fixture sets, + or `DelegatingFixtures`, `::Fixtures`, etc., + and from the class `ActiveRecord::Fixture`, which corresponds to a single + fixture. + + *Alexey Muranov* + +* 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. + + *lulalala* + * Support for partial inserts. When inserting new records, only the fields which have been changed @@ -608,7 +647,7 @@ *Michael Pearson* * Added default order to `first` to assure consistent results among - diferent database engines. Introduced `take` as a replacement to + different database engines. Introduced `take` as a replacement to the old behavior of `first`. *Marcelo Silveira* 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 c877079b25..57838ff984 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -22,6 +22,10 @@ module ActiveRecord counters.each do |association| has_many_association = reflect_on_association(association.to_sym) + if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection + 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) diff --git a/activerecord/lib/active_record/fixtures/file.rb b/activerecord/lib/active_record/fixture_set/file.rb index 0f6ab3e396..11b53275e1 100644 --- a/activerecord/lib/active_record/fixtures/file.rb +++ b/activerecord/lib/active_record/fixture_set/file.rb @@ -2,7 +2,7 @@ require 'erb' require 'yaml' module ActiveRecord - class Fixtures + class FixtureSet class File # :nodoc: include Enumerable diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 60fc653735..899e89a6f5 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -2,9 +2,11 @@ require 'erb' require 'yaml' require 'zlib' require 'active_support/dependencies' -require 'active_record/fixtures/file' +require 'active_record/fixture_set/file' require 'active_record/errors' +require 'active_support/deprecation' # temporary + module ActiveRecord class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc: end @@ -350,8 +352,8 @@ module ActiveRecord # to the rescue: # # george_reginald: - # monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %> - # pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %> + # monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %> + # pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %> # # == Support for YAML defaults # @@ -370,10 +372,11 @@ module ActiveRecord # *DEFAULTS # # Any fixture labeled "DEFAULTS" is safely ignored. - class Fixtures + class FixtureSet #-- - # NOTE: an instance of Fixtures can be called fixture_set, it is normally stored in a single YAML file and possibly in a folder with the same name. + # An instance of FixtureSet is normally stored in a single YAML file and possibly in a folder with the same name. #++ + MAX_ID = 2 ** 30 - 1 @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} } @@ -451,7 +454,7 @@ module ActiveRecord fixtures_map = {} fixture_sets = files_to_read.map do |fs_name| - fixtures_map[fs_name] = new( # ActiveRecord::Fixtures.new + fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new connection, fs_name, class_names[fs_name] || default_fixture_model_name(fs_name), @@ -565,7 +568,7 @@ module ActiveRecord # generate a primary key if necessary if has_primary_key_column? && !row.include?(primary_key_name) - row[primary_key_name] = ActiveRecord::Fixtures.identify(label) + row[primary_key_name] = ActiveRecord::FixtureSet.identify(label) end # If STI is used, find the correct subclass for association reflection @@ -588,7 +591,7 @@ module ActiveRecord row[association.foreign_type] = $1 end - row[fk_name] = ActiveRecord::Fixtures.identify(value) + row[fk_name] = ActiveRecord::FixtureSet.identify(value) end when :has_and_belongs_to_many if (targets = row.delete(association.name.to_s)) @@ -596,7 +599,7 @@ module ActiveRecord table_name = association.join_table rows[table_name].concat targets.map { |target| { association.foreign_key => row[primary_key_name], - association.association_foreign_key => ActiveRecord::Fixtures.identify(target) } + association.association_foreign_key => ActiveRecord::FixtureSet.identify(target) } } end end @@ -637,7 +640,7 @@ module ActiveRecord } + [yaml_file_path] yaml_files.each do |file| - Fixtures::File.open(file) do |fh| + FixtureSet::File.open(file) do |fh| fh.each do |fixture_name, row| fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class) end @@ -651,6 +654,11 @@ module ActiveRecord end + #-- + # Deprecate 'Fixtures' in favor of 'FixtureSet'. + #++ + Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::Fixtures', '::ActiveRecord::FixtureSet') + class Fixture #:nodoc: include Enumerable @@ -712,7 +720,7 @@ module ActiveRecord self.pre_loaded_fixtures = false self.fixture_class_names = Hash.new do |h, fixture_set_name| - h[fixture_set_name] = ActiveRecord::Fixtures.default_fixture_model_name(fixture_set_name) + h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name) end end @@ -847,7 +855,7 @@ module ActiveRecord end # Load fixtures for every test. else - ActiveRecord::Fixtures.reset_cache + ActiveRecord::FixtureSet.reset_cache @@already_loaded_fixtures[self.class] = nil @loaded_fixtures = load_fixtures end @@ -860,7 +868,7 @@ module ActiveRecord return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank? unless run_in_transaction? - ActiveRecord::Fixtures.reset_cache + ActiveRecord::FixtureSet.reset_cache end # Rollback changes if a transaction is active. @@ -879,7 +887,7 @@ module ActiveRecord private def load_fixtures - fixtures = ActiveRecord::Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names) + fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names) Hash[fixtures.map { |f| [f.name, f] }] end @@ -888,16 +896,16 @@ module ActiveRecord def instantiate_fixtures if pre_loaded_fixtures - raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::Fixtures.all_loaded_fixtures.empty? + raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::FixtureSet.all_loaded_fixtures.empty? unless @@required_fixture_classes - self.class.require_fixture_classes ActiveRecord::Fixtures.all_loaded_fixtures.keys + self.class.require_fixture_classes ActiveRecord::FixtureSet.all_loaded_fixtures.keys @@required_fixture_classes = true end - ActiveRecord::Fixtures.instantiate_all_loaded_fixtures(self, load_instances?) + ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?) else raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil? @loaded_fixtures.each_value do |fixture_set| - ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_set, load_instances?) + ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?) end end end 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/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index b11483de8c..d7e35fb771 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -129,7 +129,8 @@ module ActiveRecord end initializer "active_record.add_watchable_files" do |app| - config.watchable_files.concat ["#{app.root}/db/schema.rb", "#{app.root}/db/structure.sql"] + path = app.paths["db"].first + config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"] end config.after_initialize do |app| diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index d134978128..69a9526fcc 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -196,7 +196,7 @@ db_namespace = namespace :db do fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file| - ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file) + ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_file) end end @@ -207,13 +207,13 @@ db_namespace = namespace :db do label, id = ENV['LABEL'], ENV['ID'] raise 'LABEL or ID required' if label.blank? && id.blank? - puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::Fixtures.identify(label)}.) if label + puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures') Dir["#{base_dir}/**/*.yml"].each do |file| if data = YAML::load(ERB.new(IO.read(file)).result) data.keys.each do |key| - key_id = ActiveRecord::Fixtures.identify(key) + key_id = ActiveRecord::FixtureSet.identify(key) if key == label || key_id == id.to_i puts "#{file}: #{key} (#{key_id})" diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 42b4cff4b8..f3e47a958e 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -141,23 +141,6 @@ module ActiveRecord end end - def expand_range_bind_variables(bind_vars) #:nodoc: - expanded = [] - - bind_vars.each do |var| - next if var.is_a?(Hash) - - if var.is_a?(Range) - expanded << var.first - expanded << var.last - else - expanded << var - end - end - - expanded - end - def quote_bound_value(value, c = connection) #:nodoc: if value.respond_to?(:map) && !value.acts_like?(:string) if value.respond_to?(:empty?) && value.empty? diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb index aff971a955..5164acf77f 100644 --- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -136,9 +136,9 @@ class MysqlReservedWordTest < ActiveRecord::TestCase #the following functions were added to DRY test cases private - # custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path + # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path def create_test_fixtures(*fixture_names) - ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) + ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) end # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 9fd07f014e..1017b0758d 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -136,9 +136,9 @@ class MysqlReservedWordTest < ActiveRecord::TestCase #the following functions were added to DRY test cases private - # custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path + # custom fixture loader, uses FixtureSet#create_fixtures and appends base_path to the current file's path def create_test_fixtures(*fixture_names) - ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) + ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names) end # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name 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/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index ee443741ca..fc46a249c8 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -10,9 +10,12 @@ require 'models/dog' require 'models/dog_lover' require 'models/person' require 'models/friendship' +require 'models/subscriber' +require 'models/subscription' +require 'models/book' class CounterCacheTest < ActiveRecord::TestCase - fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers, :people, :friendships + fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers, :people, :friendships, :subscribers, :subscriptions, :books class ::SpecialTopic < ::Topic has_many :special_replies, :foreign_key => 'parent_id' @@ -118,4 +121,14 @@ class CounterCacheTest < ActiveRecord::TestCase Person.reset_counters(michael.id, :followers) end end + + test "reset counter of has_many :through association" do + subscriber = subscribers('second') + Subscriber.reset_counters(subscriber.id, 'books') + Subscriber.increment_counter('books_count', subscriber.id) + + assert_difference 'subscriber.reload.books_count', -1 do + Subscriber.reset_counters(subscriber.id, 'books') + end + end end diff --git a/activerecord/test/cases/fixtures/file_test.rb b/activerecord/test/cases/fixture_set/file_test.rb index e623fbe4d1..a029fedbd3 100644 --- a/activerecord/test/cases/fixtures/file_test.rb +++ b/activerecord/test/cases/fixture_set/file_test.rb @@ -2,7 +2,7 @@ require 'cases/helper' require 'tempfile' module ActiveRecord - class Fixtures + class FixtureSet class FileTest < ActiveRecord::TestCase def test_open fh = File.open(::File.join(FIXTURES_ROOT, "accounts.yml")) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index c28f8de682..b0b29f5f42 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -40,7 +40,7 @@ class FixturesTest < ActiveRecord::TestCase FIXTURES.each do |name| fixtures = nil assert_nothing_raised { fixtures = create_fixtures(name).first } - assert_kind_of(ActiveRecord::Fixtures, fixtures) + assert_kind_of(ActiveRecord::FixtureSet, fixtures) fixtures.each { |_name, fixture| fixture.each { |key, value| assert_match(MATCH_ATTRIBUTE_NAME, key) @@ -57,7 +57,7 @@ class FixturesTest < ActiveRecord::TestCase dir = File.dirname badyaml.path name = File.basename badyaml.path, '.yml' assert_raises(ActiveRecord::Fixture::FormatError) do - ActiveRecord::Fixtures.create_fixtures(dir, name) + ActiveRecord::FixtureSet.create_fixtures(dir, name) end ensure badyaml.close @@ -65,7 +65,7 @@ class FixturesTest < ActiveRecord::TestCase end def test_create_fixtures - fixtures = ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, "parrots") + fixtures = ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, "parrots") assert Parrot.find_by_name('Curious George'), 'George is not in the database' assert fixtures.detect { |f| f.name == 'parrots' }, "no fixtures named 'parrots' in #{fixtures.map(&:name).inspect}" end @@ -74,11 +74,11 @@ class FixturesTest < ActiveRecord::TestCase fixtures_array = nil assert_nothing_raised { fixtures_array = create_fixtures(*FIXTURES) } assert_kind_of(Array, fixtures_array) - fixtures_array.each { |fixtures| assert_kind_of(ActiveRecord::Fixtures, fixtures) } + fixtures_array.each { |fixtures| assert_kind_of(ActiveRecord::FixtureSet, fixtures) } end def test_create_symbol_fixtures - fixtures = ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, :collections, :collections => Course) { Course.connection } + fixtures = ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, :collections, :collections => Course) { Course.connection } assert Course.find_by_name('Collection'), 'course is not in the database' assert fixtures.detect { |f| f.name == 'collections' }, "no fixtures named 'collections' in #{fixtures.map(&:name).inspect}" @@ -102,7 +102,7 @@ class FixturesTest < ActiveRecord::TestCase if ActiveRecord::Base.connection.supports_migrations? def test_inserts_with_pre_and_suffix # Reset cache to make finds on the new table work - ActiveRecord::Fixtures.reset_cache + ActiveRecord::FixtureSet.reset_cache ActiveRecord::Base.connection.create_table :prefix_other_topics_suffix do |t| t.column :title, :string @@ -190,11 +190,11 @@ class FixturesTest < ActiveRecord::TestCase end def test_empty_yaml_fixture - assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts") + assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts") end def test_empty_yaml_fixture_with_a_comment_in_it - assert_not_nil ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies") + assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies") end def test_nonexistent_fixture_file @@ -204,19 +204,19 @@ class FixturesTest < ActiveRecord::TestCase assert Dir[nonexistent_fixture_path+"*"].empty? assert_raise(Errno::ENOENT) do - ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', nonexistent_fixture_path) + ActiveRecord::FixtureSet.new( Account.connection, "companies", 'Company', nonexistent_fixture_path) end end def test_dirty_dirty_yaml_file assert_raise(ActiveRecord::Fixture::FormatError) do - ActiveRecord::Fixtures.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses") + ActiveRecord::FixtureSet.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses") end end def test_omap_fixtures assert_nothing_raised do - fixtures = ActiveRecord::Fixtures.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered") + fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered") fixtures.each.with_index do |(name, fixture), i| assert_equal "fixture_no_#{i}", name @@ -254,7 +254,7 @@ if Account.connection.respond_to?(:reset_pk_sequence!) def setup @instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting')] - ActiveRecord::Fixtures.reset_cache # make sure tables get reinitialized + ActiveRecord::FixtureSet.reset_cache # make sure tables get reinitialized end def test_resets_to_min_pk_with_specified_pk_and_sequence @@ -582,13 +582,13 @@ class FasterFixturesTest < ActiveRecord::TestCase def load_extra_fixture(name) fixture = create_fixtures(name).first - assert fixture.is_a?(ActiveRecord::Fixtures) + assert fixture.is_a?(ActiveRecord::FixtureSet) @loaded_fixtures[fixture.table_name] = fixture end def test_cache - assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'categories') - assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'authors') + assert ActiveRecord::FixtureSet.fixture_is_cached?(ActiveRecord::Base.connection, 'categories') + assert ActiveRecord::FixtureSet.fixture_is_cached?(ActiveRecord::Base.connection, 'authors') assert_no_queries do create_fixtures('categories') @@ -596,7 +596,7 @@ class FasterFixturesTest < ActiveRecord::TestCase end load_extra_fixture('posts') - assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts') + assert ActiveRecord::FixtureSet.fixture_is_cached?(ActiveRecord::Base.connection, 'posts') self.class.setup_fixture_accessors :posts assert_equal 'Welcome to the weblog', posts(:welcome).title end @@ -606,17 +606,17 @@ class FoxyFixturesTest < ActiveRecord::TestCase fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers, :"admin/accounts", :"admin/users" def test_identifies_strings - assert_equal(ActiveRecord::Fixtures.identify("foo"), ActiveRecord::Fixtures.identify("foo")) - assert_not_equal(ActiveRecord::Fixtures.identify("foo"), ActiveRecord::Fixtures.identify("FOO")) + assert_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("foo")) + assert_not_equal(ActiveRecord::FixtureSet.identify("foo"), ActiveRecord::FixtureSet.identify("FOO")) end def test_identifies_symbols - assert_equal(ActiveRecord::Fixtures.identify(:foo), ActiveRecord::Fixtures.identify(:foo)) + assert_equal(ActiveRecord::FixtureSet.identify(:foo), ActiveRecord::FixtureSet.identify(:foo)) end def test_identifies_consistently - assert_equal 207281424, ActiveRecord::Fixtures.identify(:ruby) - assert_equal 1066363776, ActiveRecord::Fixtures.identify(:sapphire_2) + assert_equal 207281424, ActiveRecord::FixtureSet.identify(:ruby) + assert_equal 1066363776, ActiveRecord::FixtureSet.identify(:sapphire_2) end TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on) @@ -757,7 +757,7 @@ class FixtureLoadingTest < ActiveRecord::TestCase end class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase - ActiveRecord::Fixtures.reset_cache + ActiveRecord::FixtureSet.reset_cache set_fixture_class :randomly_named_a9 => ClassNameThatDoesNotFollowCONVENTIONS, @@ -782,7 +782,7 @@ class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase end def test_table_name_is_defined_in_the_model - assert_equal 'randomly_named_table', ActiveRecord::Fixtures::all_loaded_fixtures["admin/randomly_named_a9"].table_name + assert_equal 'randomly_named_table', ActiveRecord::FixtureSet::all_loaded_fixtures["admin/randomly_named_a9"].table_name assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name end end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index f39111ba77..cff6689c15 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -80,7 +80,7 @@ class ActiveSupport::TestCase self.use_transactional_fixtures = true def create_fixtures(*fixture_set_names, &block) - ActiveRecord::Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, fixture_class_names, &block) + ActiveRecord::FixtureSet.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, fixture_class_names, &block) end end 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/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index dc47d40f41..562ca8d9ff 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -136,7 +136,7 @@ class StoreTest < ActiveRecord::TestCase end test "all stored attributes are returned" do - assert_equal [:color, :homepage, :favorite_food, :phone_number], Admin::User.stored_attributes[:settings] + assert_equal [:color, :homepage, :favorite_food], Admin::User.stored_attributes[:settings] end test "stores_attributes are class level settings" do diff --git a/activerecord/test/fixtures/mateys.yml b/activerecord/test/fixtures/mateys.yml index d3690955fc..3f0405aaf8 100644 --- a/activerecord/test/fixtures/mateys.yml +++ b/activerecord/test/fixtures/mateys.yml @@ -1,4 +1,4 @@ blackbeard_to_redbeard: - pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %> - target_id: <%= ActiveRecord::Fixtures.identify(:redbeard) %> + pirate_id: <%= ActiveRecord::FixtureSet.identify(:blackbeard) %> + target_id: <%= ActiveRecord::FixtureSet.identify(:redbeard) %> weight: 10 diff --git a/activerecord/test/fixtures/parrots_pirates.yml b/activerecord/test/fixtures/parrots_pirates.yml index 66472243c7..e1a301b91a 100644 --- a/activerecord/test/fixtures/parrots_pirates.yml +++ b/activerecord/test/fixtures/parrots_pirates.yml @@ -1,7 +1,7 @@ george_blackbeard: - parrot_id: <%= ActiveRecord::Fixtures.identify(:george) %> - pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %> + parrot_id: <%= ActiveRecord::FixtureSet.identify(:george) %> + pirate_id: <%= ActiveRecord::FixtureSet.identify(:blackbeard) %> louis_blackbeard: - parrot_id: <%= ActiveRecord::Fixtures.identify(:louis) %> - pirate_id: <%= ActiveRecord::Fixtures.identify(:blackbeard) %> + parrot_id: <%= ActiveRecord::FixtureSet.identify(:louis) %> + pirate_id: <%= ActiveRecord::FixtureSet.identify(:blackbeard) %> diff --git a/activerecord/test/fixtures/peoples_treasures.yml b/activerecord/test/fixtures/peoples_treasures.yml index a72b190d0c..46abe50e6c 100644 --- a/activerecord/test/fixtures/peoples_treasures.yml +++ b/activerecord/test/fixtures/peoples_treasures.yml @@ -1,3 +1,3 @@ michael_diamond: - rich_person_id: <%= ActiveRecord::Fixtures.identify(:michael) %> - treasure_id: <%= ActiveRecord::Fixtures.identify(:diamond) %> + rich_person_id: <%= ActiveRecord::FixtureSet.identify(:michael) %> + treasure_id: <%= ActiveRecord::FixtureSet.identify(:diamond) %> diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb index 35170faa76..467f3ccd39 100644 --- a/activerecord/test/models/admin/user.rb +++ b/activerecord/test/models/admin/user.rb @@ -1,7 +1,7 @@ class Admin::User < ActiveRecord::Base belongs_to :account store :settings, :accessors => [ :color, :homepage ] - store_accessor :settings, :favorite_food, :phone_number + store_accessor :settings, :favorite_food store :preferences, :accessors => [ :remember_login ] store :json_data, :accessors => [ :height, :weight ], :coder => JSON store :json_data_empty, :accessors => [ :is_a_good_guy ], :coder => JSON diff --git a/activerecord/test/models/subscription.rb b/activerecord/test/models/subscription.rb index 4bdb36ea46..bcac4738a3 100644 --- a/activerecord/test/models/subscription.rb +++ b/activerecord/test/models/subscription.rb @@ -1,4 +1,4 @@ class Subscription < ActiveRecord::Base - belongs_to :subscriber + belongs_to :subscriber, :counter_cache => :books_count belongs_to :book end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 798ea20efc..2e4ec96933 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -621,6 +621,7 @@ ActiveRecord::Schema.define do create_table :subscribers, :force => true, :id => false do |t| t.string :nick, :null => false t.string :name + t.column :books_count, :integer, :null => false, :default => 0 end add_index :subscribers, :nick, :unique => true 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/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index b3f5fde335..6c15fffc0f 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -1,15 +1,24 @@ -require 'active_support/core_ext/module/deprecation' -require 'active_support/deprecation/instance_delegator' -require 'active_support/deprecation/behaviors' -require 'active_support/deprecation/reporting' -require 'active_support/deprecation/method_wrappers' -require 'active_support/deprecation/proxy_wrappers' require 'singleton' module ActiveSupport # \Deprecation specifies the API used by Rails to deprecate methods, instance # variables, objects and constants. class Deprecation + # active_support.rb sets an autoload for ActiveSupport::Deprecation. + # + # If these requires were at the top of the file the constant would not be + # defined by the time their files were loaded. Since some of them reopen + # ActiveSupport::Deprecation its autoload would be triggered, resulting in + # a circular require warning for active_support/deprecation.rb. + # + # So, we define the constant first, and load dependencies later. + require 'active_support/deprecation/instance_delegator' + require 'active_support/deprecation/behaviors' + require 'active_support/deprecation/reporting' + require 'active_support/deprecation/method_wrappers' + require 'active_support/deprecation/proxy_wrappers' + require 'active_support/core_ext/module/deprecation' + include Singleton include InstanceDelegator include Behavior 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/code/getting_started/app/assets/javascripts/application.js b/guides/code/getting_started/app/assets/javascripts/application.js index 9097d830e2..93cdae76ca 100644 --- a/guides/code/getting_started/app/assets/javascripts/application.js +++ b/guides/code/getting_started/app/assets/javascripts/application.js @@ -5,7 +5,7 @@ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// the compiled file. +// compiled file. // // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD // GO AFTER THE REQUIRES BELOW. 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..44c6507b08 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -154,10 +154,8 @@ module Rails GEMFILE else <<-GEMFILE.strip_heredoc + # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '#{Rails::VERSION::STRING}' - - # Bundle edge Rails instead: - # gem 'rails', github: 'rails/rails' GEMFILE end end @@ -226,7 +224,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..7342bffd9d 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 @@ -5,7 +5,7 @@ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// the compiled file. +// compiled file. // // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD // GO AFTER THE REQUIRES BELOW. @@ -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/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 39275e4285..fb43c90e21 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -11,12 +11,8 @@ require "action_mailer/railtie" <%= comment_if :skip_test_unit %>require "rails/test_unit/railtie" <% end -%> -if defined?(Bundler) - # If you precompile assets before deploying to production, use this line. - Bundler.require(*Rails.groups(assets: %w(development test))) - # If you want your assets lazily compiled in production, use this line. - # Bundler.require(:default, :assets, Rails.env) -end +# Assets should be precompiled for production (so we don't need the gems loaded then) +Bundler.require(*Rails.groups(assets: %w(development test))) module <%= app_const_base %> class Application < Rails::Application @@ -27,17 +23,6 @@ module <%= app_const_base %> # Custom directories with classes and modules you want to be autoloadable. # config.autoload_paths += %W(#{config.root}/extras) - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. - # config.time_zone = 'Central Time (US & Canada)' - - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. - # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] - # config.i18n.default_locale = :de - - # Configure the default encoding used in templates for Ruby 1.9. - config.encoding = "utf-8" - # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] 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/generators/rails/app/templates/config/initializers/locale.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb new file mode 100644 index 0000000000..9b41300e7e --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/locale.rb @@ -0,0 +1,7 @@ +# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. +# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. +# Rails.config.time_zone = 'Central Time (US & Canada)' + +# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. +# Rails.config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] +# Rails.config.i18n.default_locale = :de diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index f6b1ef1feb..631543c705 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -1,23 +1,20 @@ <%= app_const %>.routes.draw do - # The priority is based upon order of creation: - # first created -> highest priority. + # The priority is based upon order of creation: first created -> highest priority. + # See how all your routes lay out with "rake routes". - # You can have the root of your site routed with "root" - # just remember to delete public/index.html. + # You can have the root of your site routed with "root" just remember to delete public/index.html. # root to: 'welcome#index' - # Sample of regular route: + # Example of regular route: # get 'products/:id' => 'catalog#view' - # Keep in mind you can assign values other than :controller and :action. - # Sample of named route: + # Example of named route that can be invoked with purchase_url(id: product.id) # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase - # This route can be invoked with purchase_url(id: product.id). - # Sample resource route (maps HTTP verbs to controller actions automatically): + # Example resource route (maps HTTP verbs to controller actions automatically): # resources :products - # Sample resource route with options: + # Example resource route with options: # resources :products do # member do # get 'short' @@ -29,13 +26,13 @@ # end # end - # Sample resource route with sub-resources: + # Example resource route with sub-resources: # resources :products do # resources :comments, :sales # resource :seller # end - # Sample resource route with more complex sub-resources: + # Example resource route with more complex sub-resources: # resources :products do # resources :comments # resources :sales do @@ -43,13 +40,10 @@ # end # end - # Sample resource route within a namespace: + # Example resource route within a namespace: # namespace :admin do # # Directs /admin/products/* to Admin::ProductsController # # (app/controllers/admin/products_controller.rb) # resources :products # end - - - # See how all your routes lay out with "rake routes". end 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..aed7fd4b14 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' @@ -25,8 +29,8 @@ if defined?(ActiveRecord::Base) ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path - def create_fixtures(*table_names, &block) - Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, {}, &block) + def create_fixtures(*fixture_set_names, &block) + FixtureSet.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, {}, &block) end end 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' |