diff options
178 files changed, 2682 insertions, 3855 deletions
@@ -6,8 +6,11 @@ gem 'arel', github: 'rails/arel', branch: 'master' gem 'mocha', '>= 0.11.2', :require => false 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/CHANGELOG.md b/actionmailer/CHANGELOG.md index e29890f2d8..68b34805ff 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -27,4 +27,8 @@ * Asynchronously send messages via the Rails Queue *Brian Cardarella* +* Allow callbacks to be defined in mailers similar to `ActionController::Base`. You can configure default + settings, headers, attachments, delivery settings or change delivery using + `before_filter`, `after_filter` etc. *Justin S. Leitgeb* + Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/actionmailer/CHANGELOG.md) for previous changes. 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/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb index a6c163832e..3fe64759ac 100644 --- a/actionmailer/lib/action_mailer/log_subscriber.rb +++ b/actionmailer/lib/action_mailer/log_subscriber.rb @@ -1,13 +1,15 @@ module ActionMailer class LogSubscriber < ActiveSupport::LogSubscriber def deliver(event) + return unless logger.info? recipients = Array(event.payload[:to]).join(', ') - info("\nSent mail to #{recipients} (%1.fms)" % event.duration) + info("\nSent mail to #{recipients} (#{event.duration.round(1)}ms)") debug(event.payload[:mail]) end def receive(event) - info("\nReceived mail (%.1fms)" % event.duration) + return unless logger.info? + info("\nReceived mail (#{event.duration.round(1)}ms)") debug(event.payload[:mail]) end diff --git a/actionmailer/lib/rails/generators/mailer/USAGE b/actionmailer/lib/rails/generators/mailer/USAGE index 9f1d6b182e..7470289fd3 100644 --- a/actionmailer/lib/rails/generators/mailer/USAGE +++ b/actionmailer/lib/rails/generators/mailer/USAGE @@ -13,6 +13,6 @@ Example: creates a Notifications mailer class, views, test, and fixtures: Mailer: app/mailers/notifications.rb Views: app/views/notifications/signup.erb [...] - Test: test/functional/notifications_test.rb + Test: test/mailers/notifications_test.rb Fixtures: test/fixtures/notifications/signup [...] 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 248677688f..fd02492834 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,80 @@ ## Rails 4.0.0 (unreleased) ## +* Remove old asset tag concatenation (no longer needed now that we have the asset pipeline). *Josh Peek* + +* Accept :remote as symbolic option for `link_to` helper. *Riley Lynch* + +* Warn when the `:locals` option is passed to `assert_template` outside of a view test case + Fix #3415 + + *Yves Senn* + +* The `Rack::Cache` middleware is now disabled by default. To enable it, + set `config.action_dispatch.rack_cache = true` and add `gem rack-cache` to your Gemfile. + + *Guillermo Iguaran* + +* `ActionController::Base.page_cache_extension` option is deprecated + in favour of `ActionController::Base.default_static_extension`. + + *Francesco Rodriguez* + +* Action and Page caching has been extracted from Action Dispatch + as `actionpack-action_caching` and `actionpack-page_caching` gems. + Please read the `README.md` file on both gems for the usage. + + *Francesco Rodriguez* + +* Failsafe exception returns text/plain. *Steve Klabnik* + +* Remove `rack-cache` dependency from Action Pack and declare it on Gemfile + + *Guillermo Iguaran* + +* Rename internal variables on ActionController::TemplateAssertions to prevent + naming collisions. @partials, @templates and @layouts are now prefixed with an underscore. + Fix #7459 + + *Yves Senn* + +* `resource` and `resources` don't modify the passed options hash + Fix #7777 + + *Yves Senn* + +* Precompiled assets include aliases from foo.js to foo/index.js and vice versa. + + # Precompiles phone-<digest>.css and aliases phone/index.css to phone.css. + config.assets.precompile = [ 'phone.css' ] + + # Precompiles phone/index-<digest>.css and aliases phone.css to phone/index.css. + config.assets.precompile = [ 'phone/index.css' ] + + # Both of these work with either precompile thanks to their aliases. + <%= stylesheet_link_tag 'phone', media: 'all' %> + <%= stylesheet_link_tag 'phone/index', media: 'all' %> + + *Jeremy Kemper* + +* `assert_template` is no more passing with what ever string that matches + with the template name. + + Before when we have a template `/layout/hello.html.erb`, `assert_template` + was passing with any string that matches. This behavior allowed false + positive like: + + assert_template "layout" + assert_template "out/hello" + + Now it only passes with: + + assert_template "layout/hello" + assert_template "hello" + + Fixes #3849. + + *Hugolnx* + * `image_tag` will set the same width and height for image if numerical value passed to `size` option. @@ -44,9 +119,8 @@ *Luiz Felipe Garcia Pereira* -* Sprockets integration has been extracted from Action Pack and the `sprockets-rails` - gem should be added to Gemfile (under the assets group) in order to use Rails asset - pipeline in future versions of Rails. +* Sprockets integration has been extracted from Action Pack to the `sprockets-rails` + gem. `rails` gem is depending on `sprockets-rails` by default. *Guillermo Iguaran* diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index fd09d3b55b..7d292ac17c 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -18,7 +18,6 @@ Gem::Specification.new do |s| s.requirements << 'none' s.add_dependency('activesupport', version) - s.add_dependency('rack-cache', '~> 1.2') s.add_dependency('builder', '~> 3.1.0') s.add_dependency('rack', '~> 1.4.1') s.add_dependency('rack-test', '~> 0.6.1') diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index fc27a0774b..462f147371 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -2,11 +2,9 @@ require 'fileutils' require 'uri' require 'set' -module ActionController #:nodoc: +module ActionController # \Caching is a cheap way of speeding up slow applications by keeping the result of # calculations, renderings, and database calls around for subsequent requests. - # Action Controller affords you three approaches in varying levels of granularity: - # Page, Action, Fragment. # # You can read more about each approach and the sweeping assistance by clicking the # modules below. @@ -17,8 +15,7 @@ module ActionController #:nodoc: # == \Caching stores # # All the caching stores from ActiveSupport::Cache are available to be used as backends - # for Action Controller caching. This setting only affects action and fragment caching - # as page caching is always written to disk. + # for Action Controller caching. # # Configuration examples (MemoryStore is the default): # @@ -32,9 +29,7 @@ module ActionController #:nodoc: extend ActiveSupport::Autoload eager_autoload do - autoload :Actions autoload :Fragments - autoload :Pages autoload :Sweeper, 'action_controller/caching/sweeping' autoload :Sweeping, 'action_controller/caching/sweeping' end @@ -58,12 +53,25 @@ module ActionController #:nodoc: include AbstractController::Callbacks include ConfigMethods - include Pages, Actions, Fragments + include Fragments include Sweeping if defined?(ActiveRecord) included do extend ConfigMethods + config_accessor :default_static_extension + self.default_static_extension ||= '.html' + + def self.page_cache_extension=(extension) + ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension) + self.default_static_extension = extension + end + + def self.page_cache_extension + ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension) + default_static_extension + end + config_accessor :perform_caching self.perform_caching = true if perform_caching.nil? end diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb deleted file mode 100644 index bf16fe267c..0000000000 --- a/actionpack/lib/action_controller/caching/actions.rb +++ /dev/null @@ -1,189 +0,0 @@ -require 'set' - -module ActionController - module Caching - # Action caching is similar to page caching by the fact that the entire - # output of the response is cached, but unlike page caching, every - # request still goes through Action Pack. The key benefit of this is - # that filters run before the cache is served, which allows for - # authentication and other restrictions on whether someone is allowed - # to execute such action. - # - # class ListsController < ApplicationController - # before_filter :authenticate, except: :public - # - # caches_page :public - # caches_action :index, :show - # end - # - # In this example, the +public+ action doesn't require authentication - # so it's possible to use the faster page caching. On the other hand - # +index+ and +show+ require authentication. They can still be cached, - # but we need action caching for them. - # - # Action caching uses fragment caching internally and an around - # filter to do the job. The fragment cache is named according to - # the host and path of the request. A page that is accessed at - # <tt>http://david.example.com/lists/show/1</tt> will result in a fragment named - # <tt>david.example.com/lists/show/1</tt>. This allows the cacher to - # differentiate between <tt>david.example.com/lists/</tt> and - # <tt>jamis.example.com/lists/</tt> -- which is a helpful way of assisting - # the subdomain-as-account-key pattern. - # - # Different representations of the same resource, e.g. - # <tt>http://david.example.com/lists</tt> and - # <tt>http://david.example.com/lists.xml</tt> - # are treated like separate requests and so are cached separately. - # Keep in mind when expiring an action cache that - # <tt>action: 'lists'</tt> is not the same as - # <tt>action: 'list', format: :xml</tt>. - # - # You can modify the default action cache path by passing a - # <tt>:cache_path</tt> option. This will be passed directly to - # <tt>ActionCachePath.new</tt>. This is handy for actions with - # multiple possible routes that should be cached differently. If a - # block is given, it is called with the current controller instance. - # - # And you can also use <tt>:if</tt> (or <tt>:unless</tt>) to pass a - # proc that specifies when the action should be cached. - # - # As of Rails 3.0, you can also pass <tt>:expires_in</tt> with a time - # interval (in seconds) to schedule expiration of the cached item. - # - # The following example depicts some of the points made above: - # - # class ListsController < ApplicationController - # before_filter :authenticate, except: :public - # - # caches_page :public - # - # caches_action :index, if: Proc.new do - # !request.format.json? # cache if is not a JSON request - # end - # - # caches_action :show, cache_path: { project: 1 }, - # expires_in: 1.hour - # - # caches_action :feed, cache_path: Proc.new do - # if params[:user_id] - # user_list_url(params[:user_id, params[:id]) - # else - # list_url(params[:id]) - # end - # end - # end - # - # If you pass <tt>layout: false</tt>, it will only cache your action - # content. That's useful when your layout has dynamic information. - # - # Warning: If the format of the request is determined by the Accept HTTP - # header the Content-Type of the cached response could be wrong because - # no information about the MIME type is stored in the cache key. So, if - # you first ask for MIME type M in the Accept header, a cache entry is - # created, and then perform a second request to the same resource asking - # for a different MIME type, you'd get the content cached for M. - # - # The <tt>:format</tt> parameter is taken into account though. The safest - # way to cache by MIME type is to pass the format in the route. - module Actions - extend ActiveSupport::Concern - - module ClassMethods - # Declares that +actions+ should be cached. - # See ActionController::Caching::Actions for details. - def caches_action(*actions) - return unless cache_configured? - options = actions.extract_options! - options[:layout] = true unless options.key?(:layout) - filter_options = options.extract!(:if, :unless).merge(:only => actions) - cache_options = options.extract!(:layout, :cache_path).merge(:store_options => options) - - around_filter ActionCacheFilter.new(cache_options), filter_options - end - end - - def _save_fragment(name, options) - content = "" - response_body.each do |parts| - content << parts - end - - if caching_allowed? - write_fragment(name, content, options) - else - content - end - end - - protected - def expire_action(options = {}) - return unless cache_configured? - - if options.is_a?(Hash) && options[:action].is_a?(Array) - options[:action].each {|action| expire_action(options.merge(:action => action)) } - else - expire_fragment(ActionCachePath.new(self, options, false).path) - end - end - - class ActionCacheFilter #:nodoc: - def initialize(options, &block) - @cache_path, @store_options, @cache_layout = - options.values_at(:cache_path, :store_options, :layout) - end - - def around(controller) - cache_layout = @cache_layout.respond_to?(:call) ? @cache_layout.call(controller) : @cache_layout - - path_options = if @cache_path.respond_to?(:call) - controller.instance_exec(controller, &@cache_path) - else - @cache_path - end - - cache_path = ActionCachePath.new(controller, path_options || {}) - - body = controller.read_fragment(cache_path.path, @store_options) - - unless body - controller.action_has_layout = false unless cache_layout - yield - controller.action_has_layout = true - body = controller._save_fragment(cache_path.path, @store_options) - end - - body = controller.render_to_string(:text => body, :layout => true) unless cache_layout - - controller.response_body = body - controller.content_type = Mime[cache_path.extension || :html] - end - end - - class ActionCachePath - attr_reader :path, :extension - - # If +infer_extension+ is +true+, the cache path extension is looked up from the request's - # path and format. This is desirable when reading and writing the cache, but not when - # expiring the cache - +expire_action+ should expire the same files regardless of the - # request format. - def initialize(controller, options = {}, infer_extension = true) - if infer_extension - @extension = controller.params[:format] - options.reverse_merge!(:format => @extension) if options.is_a?(Hash) - end - - path = controller.url_for(options).split('://', 2).last - @path = normalize!(path) - end - - private - def normalize!(path) - ext = URI.parser.escape(extension) if extension - path << 'index' if path[-1] == ?/ - path << ".#{ext}" if extension and !path.split('?', 2).first.ends_with?(".#{ext}") - URI.parser.unescape(path) - end - end - end - end -end diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb deleted file mode 100644 index 3cf8d965ff..0000000000 --- a/actionpack/lib/action_controller/caching/pages.rb +++ /dev/null @@ -1,202 +0,0 @@ -require 'fileutils' -require 'active_support/core_ext/class/attribute_accessors' - -module ActionController - module Caching - # Page caching is an approach to caching where the entire action output of is - # stored as a HTML file that the web server can serve without going through - # Action Pack. This is the fastest way to cache your content as opposed to going - # dynamically through the process of generating the content. Unfortunately, this - # incredible speed-up is only available to stateless pages where all visitors are - # treated the same. Content management systems -- including weblogs and wikis -- - # have many pages that are a great fit for this approach, but account-based systems - # where people log in and manipulate their own data are often less likely candidates. - # - # Specifying which actions to cache is done through the +caches_page+ class method: - # - # class WeblogController < ActionController::Base - # caches_page :show, :new - # end - # - # This will generate cache files such as <tt>weblog/show/5.html</tt> and - # <tt>weblog/new.html</tt>, which match the URLs used that would normally trigger - # dynamic page generation. Page caching works by configuring a web server to first - # check for the existence of files on disk, and to serve them directly when found, - # without passing the request through to Action Pack. This is much faster than - # handling the full dynamic request in the usual way. - # - # Expiration of the cache is handled by deleting the cached file, which results - # in a lazy regeneration approach where the cache is not restored before another - # hit is made against it. The API for doing so mimics the options from +url_for+ and friends: - # - # class WeblogController < ActionController::Base - # def update - # List.update(params[:list][:id], params[:list]) - # expire_page action: 'show', id: params[:list][:id] - # redirect_to action: 'show', id: params[:list][:id] - # end - # end - # - # Additionally, you can expire caches using Sweepers that act on changes in - # the model to determine when a cache is supposed to be expired. - module Pages - extend ActiveSupport::Concern - - included do - # The cache directory should be the document root for the web server and is - # set using <tt>Base.page_cache_directory = "/document/root"</tt>. For Rails, - # this directory has already been set to Rails.public_path (which is usually - # set to <tt>Rails.root + "/public"</tt>). Changing this setting can be useful - # to avoid naming conflicts with files in <tt>public/</tt>, but doing so will - # likely require configuring your web server to look in the new location for - # cached files. - class_attribute :page_cache_directory - self.page_cache_directory ||= '' - - # Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. - # In these cases, the page caching mechanism will add one in order to make it - # easy for the cached files to be picked up properly by the web server. By - # default, this cache extension is <tt>.html</tt>. If you want something else, - # like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. - # In cases where a request already has an extension, such as <tt>.xml</tt> - # or <tt>.rss</tt>, page caching will not add an extension. This allows it - # to work well with RESTful apps. - class_attribute :page_cache_extension - self.page_cache_extension ||= '.html' - - # The compression used for gzip. If +false+ (default), the page is not compressed. - # If can be a symbol showing the ZLib compression method, for example, <tt>:best_compression</tt> - # or <tt>:best_speed</tt> or an integer configuring the compression level. - class_attribute :page_cache_compression - self.page_cache_compression ||= false - end - - module ClassMethods - # Expires the page that was cached with the +path+ as a key. - # - # expire_page '/lists/show' - def expire_page(path) - return unless perform_caching - path = page_cache_path(path) - - instrument_page_cache :expire_page, path do - File.delete(path) if File.exist?(path) - File.delete(path + '.gz') if File.exist?(path + '.gz') - end - end - - # Manually cache the +content+ in the key determined by +path+. - # - # cache_page "I'm the cached content", '/lists/show' - def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION) - return unless perform_caching - path = page_cache_path(path, extension) - - instrument_page_cache :write_page, path do - FileUtils.makedirs(File.dirname(path)) - File.open(path, "wb+") { |f| f.write(content) } - if gzip - Zlib::GzipWriter.open(path + '.gz', gzip) { |f| f.write(content) } - end - end - end - - # Caches the +actions+ using the page-caching approach that'll store - # the cache in a path within the +page_cache_directory+ that - # matches the triggering url. - # - # You can also pass a <tt>:gzip</tt> option to override the class configuration one. - # - # # cache the index action - # caches_page :index - # - # # cache the index action except for JSON requests - # caches_page :index, if: Proc.new { !request.format.json? } - # - # # don't gzip images - # caches_page :image, gzip: false - def caches_page(*actions) - return unless perform_caching - options = actions.extract_options! - - gzip_level = options.fetch(:gzip, page_cache_compression) - gzip_level = case gzip_level - when Symbol - Zlib.const_get(gzip_level.upcase) - when Fixnum - gzip_level - when false - nil - else - Zlib::BEST_COMPRESSION - end - - after_filter({:only => actions}.merge(options)) do |c| - c.cache_page(nil, nil, gzip_level) - end - end - - private - def page_cache_file(path, extension) - name = (path.empty? || path == "/") ? "/index" : URI.parser.unescape(path.chomp('/')) - unless (name.split('/').last || name).include? '.' - name << (extension || self.page_cache_extension) - end - return name - end - - def page_cache_path(path, extension = nil) - page_cache_directory.to_s + page_cache_file(path, extension) - end - - def instrument_page_cache(name, path) - ActiveSupport::Notifications.instrument("#{name}.action_controller", :path => path){ yield } - end - end - - # Expires the page that was cached with the +options+ as a key. - # - # expire_page controller: 'lists', action: 'show' - def expire_page(options = {}) - return unless self.class.perform_caching - - if options.is_a?(Hash) - if options[:action].is_a?(Array) - options[:action].each do |action| - self.class.expire_page(url_for(options.merge(:only_path => true, :action => action))) - end - else - self.class.expire_page(url_for(options.merge(:only_path => true))) - end - else - self.class.expire_page(options) - end - end - - # Manually cache the +content+ in the key determined by +options+. If no content is provided, - # the contents of response.body is used. If no options are provided, the url of the current - # request being handled is used. - # - # cache_page "I'm the cached content", controller: 'lists', action: 'show' - def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION) - return unless self.class.perform_caching && caching_allowed? - - path = case options - when Hash - url_for(options.merge(:only_path => true, :format => params[:format])) - when String - options - else - request.path - end - - if (type = Mime::LOOKUP[self.content_type]) && (type_symbol = type.symbol).present? - extension = ".#{type_symbol}" - end - - self.class.cache_page(content || response.body, path, extension, gzip) - end - - end - end -end diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index f41d1bb4b9..3d274e7dd7 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -4,6 +4,8 @@ module ActionController INTERNAL_PARAMS = %w(controller action format _method only_path) def start_processing(event) + return unless logger.info? + payload = event.payload params = payload[:params].except(*INTERNAL_PARAMS) format = payload[:format] @@ -14,6 +16,8 @@ module ActionController end def process_action(event) + return unless logger.info? + payload = event.payload additions = ActionController::Base.log_process_action(payload) @@ -22,35 +26,36 @@ module ActionController exception_class_name = payload[:exception].first status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name) end - message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration + message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" message << " (#{additions.join(" | ")})" unless additions.blank? info(message) end def halted_callback(event) - info "Filter chain halted as #{event.payload[:filter]} rendered or redirected" + info("Filter chain halted as #{event.payload[:filter]} rendered or redirected") end def send_file(event) - info("Sent file %s (%.1fms)" % [event.payload[:path], event.duration]) + info("Sent file #{event.payload[:path]} (#{event.duration.round(1)}ms)") end def redirect_to(event) - info "Redirected to #{event.payload[:location]}" + info("Redirected to #{event.payload[:location]}") end def send_data(event) - info("Sent data %s (%.1fms)" % [event.payload[:filename], event.duration]) + info("Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)") end %w(write_fragment read_fragment exist_fragment? expire_fragment expire_page write_page).each do |method| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{method}(event) + return unless logger.info? key_or_path = event.payload[:key] || event.payload[:path] human_name = #{method.to_s.humanize.inspect} - info("\#{human_name} \#{key_or_path} \#{"(%.1fms)" % event.duration}") + info("\#{human_name} \#{key_or_path} (\#{event.duration.round(1)}ms)") end METHOD end diff --git a/actionpack/lib/action_controller/metal/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 0caeef3192..ace5a2c822 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -12,16 +12,16 @@ module ActionController end def setup_subscriptions - @partials = Hash.new(0) - @templates = Hash.new(0) - @layouts = Hash.new(0) + @_partials = Hash.new(0) + @_templates = Hash.new(0) + @_layouts = Hash.new(0) ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload| path = payload[:layout] if path - @layouts[path] += 1 + @_layouts[path] += 1 if path =~ /^layouts\/(.*)/ - @layouts[$1] += 1 + @_layouts[$1] += 1 end end end @@ -32,11 +32,11 @@ module ActionController partial = path =~ /^.*\/_[^\/]*$/ if partial - @partials[path] += 1 - @partials[path.split("/").last] += 1 + @_partials[path] += 1 + @_partials[path.split("/").last] += 1 end - @templates[path] += 1 + @_templates[path] += 1 end end @@ -46,9 +46,9 @@ module ActionController end def process(*args) - @partials = Hash.new(0) - @templates = Hash.new(0) - @layouts = Hash.new(0) + @_partials = Hash.new(0) + @_templates = Hash.new(0) + @_layouts = Hash.new(0) super end @@ -86,52 +86,63 @@ module ActionController response.body case options - when NilClass, String, Symbol, Regexp + when NilClass, Regexp, String, Symbol options = options.to_s if Symbol === options - rendered = @templates + rendered = @_templates msg = message || sprintf("expecting <%s> but rendering with <%s>", options.inspect, rendered.keys) matches_template = - if options + case options + when String + rendered.any? do |t, num| + options_splited = options.split(File::SEPARATOR) + t_splited = t.split(File::SEPARATOR) + t_splited.last(options_splited.size) == options_splited + end + when Regexp rendered.any? { |t,num| t.match(options) } - else - @templates.blank? + when NilClass + rendered.blank? end assert matches_template, msg when Hash if options.key?(:layout) expected_layout = options[:layout] msg = message || sprintf("expecting layout <%s> but action rendered <%s>", - expected_layout, @layouts.keys) + expected_layout, @_layouts.keys) case expected_layout when String, Symbol - assert_includes @layouts.keys, expected_layout.to_s, msg + assert_includes @_layouts.keys, expected_layout.to_s, msg when Regexp - assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg) + assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg) when nil, false - assert(@layouts.empty?, msg) + assert(@_layouts.empty?, msg) end end if expected_partial = options[:partial] if expected_locals = options[:locals] - actual_locals = @locals[expected_partial.to_s.sub(/^_/,'')] - expected_locals.each_pair do |k,v| - assert_equal(v, actual_locals[k]) + if defined?(@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] + actual_count = @_partials[expected_partial] msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)", expected_partial, expected_count, actual_count) assert(actual_count == expected_count.to_i, msg) else msg = message || sprintf("expecting partial <%s> but action rendered <%s>", - options[:partial], @partials.keys) - assert_includes @partials, expected_partial, msg + options[:partial], @_partials.keys) + assert_includes @_partials, expected_partial, msg end elsif options.key?(:partial) - assert @partials.empty?, + assert @_partials.empty?, "Expected no partials to be rendered" end else diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index e4b3f53f07..0de10695e0 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -15,11 +15,11 @@ module ActionDispatch # If any exception happens inside the exceptions app, this middleware # catches the exceptions and returns a FAILSAFE_RESPONSE. class ShowExceptions - FAILSAFE_RESPONSE = [500, {'Content-Type' => 'text/html'}, - ["<html><body><h1>500 Internal Server Error</h1>" << + FAILSAFE_RESPONSE = [500, { 'Content-Type' => 'text/plain' }, + ["500 Internal Server Error\n" << "If you are the administrator of this website, then please read this web " << "application's log file and/or the web server's log file to find out what " << - "went wrong.</body></html>"]] + "went wrong."]] def initialize(app, exceptions_app) @app = app diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 9073e6582d..e3b15b43b9 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -29,7 +29,7 @@ module ActionDispatch def ext @ext ||= begin - ext = ::ActionController::Base.page_cache_extension + ext = ::ActionController::Base.default_static_extension "{,#{ext},/index#{ext}}" end end diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index ccc0435a39..284dd180db 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -12,12 +12,7 @@ module ActionDispatch config.action_dispatch.rescue_templates = { } config.action_dispatch.rescue_responses = { } config.action_dispatch.default_charset = nil - - config.action_dispatch.rack_cache = { - :metastore => "rails:/", - :entitystore => "rails:/", - :verbose => false - } + config.action_dispatch.rack_cache = false config.action_dispatch.default_headers = { 'X-Frame-Options' => 'SAMEORIGIN', diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 49afa01d25..c5cf413c8f 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1038,7 +1038,7 @@ module ActionDispatch # === Options # Takes same options as +resources+. def resource(*resources, &block) - options = resources.extract_options! + options = resources.extract_options!.dup if apply_common_behavior_for(:resource, resources, options, &block) return self @@ -1204,7 +1204,7 @@ module ActionDispatch # # resource actions are at /admin/posts. # resources :posts, :path => "admin/posts" def resources(*resources, &block) - options = resources.extract_options! + options = resources.extract_options!.dup if apply_common_behavior_for(:resources, resources, options, &block) return self diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb deleted file mode 100644 index e42e49fb04..0000000000 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +++ /dev/null @@ -1,145 +0,0 @@ -require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/file' -require 'action_view/helpers/tag_helper' - -module ActionView - module Helpers - module AssetTagHelper - - class AssetIncludeTag #:nodoc: - include TagHelper - - attr_reader :config, :asset_paths - class_attribute :expansions - - def self.inherited(base) - base.expansions = { } - end - - def initialize(config, asset_paths) - @config = config - @asset_paths = asset_paths - end - - def asset_name - raise NotImplementedError - end - - def extension - raise NotImplementedError - end - - def custom_dir - raise NotImplementedError - end - - def asset_tag(source, options) - raise NotImplementedError - end - - def include_tag(*sources) - options = sources.extract_options!.stringify_keys - concat = options.delete("concat") - cache = concat || options.delete("cache") - recursive = options.delete("recursive") - - if concat || (config.perform_caching && cache) - joined_name = (cache == true ? "all" : cache) + ".#{extension}" - joined_path = File.join((joined_name[/^#{File::SEPARATOR}/] ? config.assets_dir : custom_dir), joined_name) - unless config.perform_caching && File.exists?(joined_path) - write_asset_file_contents(joined_path, compute_paths(sources, recursive)) - end - asset_tag(joined_name, options) - else - sources = expand_sources(sources, recursive) - ensure_sources!(sources) if cache - sources.collect { |source| asset_tag(source, options) }.join("\n").html_safe - end - end - - private - - def path_to_asset(source, options = {}) - asset_paths.compute_public_path(source, asset_name.to_s.pluralize, options.merge(:ext => extension)) - end - - def path_to_asset_source(source) - asset_paths.compute_source_path(source, asset_name.to_s.pluralize, extension) - end - - def compute_paths(*args) - expand_sources(*args).collect { |source| path_to_asset_source(source) } - end - - def expand_sources(sources, recursive) - if sources.first == :all - collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - else - sources.inject([]) do |list, source| - determined_source = determine_source(source, expansions) - update_source_list(list, determined_source) - end - end - end - - def update_source_list(list, source) - case source - when String - list.delete(source) - list << source - when Array - updated_sources = source - list - list.concat(updated_sources) - end - end - - def ensure_sources!(sources) - sources.each do |source| - asset_file_path!(path_to_asset_source(source)) - end - end - - def collect_asset_files(*path) - dir = path.first - - Dir[File.join(*path.compact)].collect do |file| - file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '') - end.sort - end - - def determine_source(source, collection) - case source - when Symbol - collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}") - else - source - end - end - - def join_asset_file_contents(paths) - paths.collect { |path| File.read(asset_file_path!(path, true)) }.join("\n\n") - end - - def write_asset_file_contents(joined_asset_path, asset_paths) - FileUtils.mkdir_p(File.dirname(joined_asset_path)) - File.atomic_write(joined_asset_path) { |cache| cache.write(join_asset_file_contents(asset_paths)) } - - # Set mtime to the latest of the combined files to allow for - # consistent ETag without a shared filesystem. - mt = asset_paths.map { |p| File.mtime(asset_file_path!(p)) }.max - File.utime(mt, mt, joined_asset_path) - end - - def asset_file_path!(absolute_path, error_if_file_is_uri = false) - if asset_paths.is_uri?(absolute_path) - raise(Errno::ENOENT, "Asset file #{path} is uri and cannot be merged into single file") if error_if_file_is_uri - else - raise(Errno::ENOENT, "Asset file not found at '#{absolute_path}'" ) unless File.exist?(absolute_path) - return absolute_path - end - end - end - - end - end -end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index 139f4d19ab..b6b9eadc88 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -1,76 +1,11 @@ require 'active_support/core_ext/file' -require 'action_view/helpers/asset_tag_helpers/asset_include_tag' module ActionView module Helpers module AssetTagHelper - - class JavascriptIncludeTag < AssetIncludeTag #:nodoc: - def asset_name - 'javascript' - end - - def extension - 'js' - end - - def asset_tag(source, options) - content_tag("script", "", { "src" => path_to_asset(source) }.merge(options)) - end - - def custom_dir - config.javascripts_dir - end - - private - - def expand_sources(sources, recursive = false) - if sources.include?(:all) - all_asset_files = (collect_asset_files(custom_dir, ('**' if recursive), "*.#{extension}") - ['application']) - add_application_js(all_asset_files, sources) - ((determine_source(:defaults, expansions).dup & all_asset_files) + all_asset_files).uniq - else - expanded_sources = sources.inject([]) do |list, source| - determined_source = determine_source(source, expansions) - update_source_list(list, determined_source) - end - add_application_js(expanded_sources, sources) - expanded_sources - end - end - - def add_application_js(expanded_sources, sources) - if (sources.include?(:defaults) || sources.include?(:all)) && File.exist?(File.join(custom_dir, "application.#{extension}")) - expanded_sources.delete('application') - expanded_sources << "application" - end - end - end - - module JavascriptTagHelpers extend ActiveSupport::Concern - module ClassMethods - # Register one or more javascript files to be included when <tt>symbol</tt> - # is passed to <tt>javascript_include_tag</tt>. This method is typically intended - # to be called from plugin initialization to register javascript files - # that the plugin installed in <tt>public/javascripts</tt>. - # - # ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"] - # - # javascript_include_tag :monkey # => - # <script src="/javascripts/head.js"></script> - # <script src="/javascripts/body.js"></script> - # <script src="/javascripts/tail.js"></script> - def register_javascript_expansion(expansions) - js_expansions = JavascriptIncludeTag.expansions - expansions.each do |key, values| - js_expansions[key] = (js_expansions[key] || []) | Array(values) - end - end - end - # Computes the path to a javascript asset in the public javascripts directory. # If the +source+ filename has no extension, .js will be appended (except for explicit URIs) # Full paths from the document root will be passed through. @@ -101,15 +36,6 @@ module ActionView # # When passing paths, the ".js" extension is optional. # - # If the application is not using the asset pipeline, to include the default JavaScript - # expansion pass <tt>:defaults</tt> as source. By default, <tt>:defaults</tt> loads jQuery, - # and that can be overridden in <tt>config/application.rb</tt>: - # - # config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js) - # - # When using <tt>:defaults</tt> or <tt>:all</tt>, if an <tt>application.js</tt> file exists - # in <tt>public/javascripts</tt> it will be included as well at the end. - # # You can modify the HTML attributes of the script tag by passing a hash as the # last argument. # @@ -129,65 +55,14 @@ module ActionView # javascript_include_tag "http://www.example.com/xmlhr.js" # # => <script src="http://www.example.com/xmlhr.js"></script> # - # javascript_include_tag :defaults - # # => <script src="/javascripts/jquery.js?1284139606"></script> - # # <script src="/javascripts/rails.js?1284139606"></script> - # # <script src="/javascripts/application.js?1284139606"></script> - # - # Note: The application.js file is only referenced if it exists - # - # You can also include all JavaScripts in the +javascripts+ directory using <tt>:all</tt> as the source: - # - # javascript_include_tag :all - # # => <script src="/javascripts/jquery.js?1284139606"></script> - # # <script src="/javascripts/rails.js?1284139606"></script> - # # <script src="/javascripts/shop.js?1284139606"></script> - # # <script src="/javascripts/checkout.js?1284139606"></script> - # # <script src="/javascripts/application.js?1284139606"></script> - # - # Note that your defaults of choice will be included first, so they will be available to all subsequently - # included files. - # - # If you want Rails to search in all the subdirectories under <tt>public/javascripts</tt>, you should - # explicitly set <tt>:recursive</tt>: - # - # javascript_include_tag :all, :recursive => true - # - # == Caching multiple JavaScripts into one - # - # You can also cache multiple JavaScripts into one file, which requires less HTTP connections to download - # and can better be compressed by gzip (leading to faster transfers). Caching will only happen if - # <tt>config.perform_caching</tt> is set to true (which is the case by default for the Rails - # production environment, but not for the development environment). - # - # # assuming config.perform_caching is false - # javascript_include_tag :all, :cache => true - # # => <script src="/javascripts/jquery.js?1284139606"></script> - # # <script src="/javascripts/rails.js?1284139606"></script> - # # <script src="/javascripts/shop.js?1284139606"></script> - # # <script src="/javascripts/checkout.js?1284139606"></script> - # # <script src="/javascripts/application.js?1284139606"></script> - # - # # assuming config.perform_caching is true - # javascript_include_tag :all, :cache => true - # # => <script src="/javascripts/all.js?1344139789"></script> - # - # # assuming config.perform_caching is false - # javascript_include_tag "jquery", "cart", "checkout", :cache => "shop" - # # => <script src="/javascripts/jquery.js?1284139606"></script> - # # <script src="/javascripts/cart.js?1289139157"></script> - # # <script src="/javascripts/checkout.js?1299139816"></script> - # - # # assuming config.perform_caching is true - # javascript_include_tag "jquery", "cart", "checkout", :cache => "shop" - # # => <script src="/javascripts/shop.js?1299139816"></script> - # - # The <tt>:recursive</tt> option is also available for caching: - # - # javascript_include_tag :all, :cache => true, :recursive => true def javascript_include_tag(*sources) - @javascript_include ||= JavascriptIncludeTag.new(config, asset_paths) - @javascript_include.include_tag(*sources) + options = sources.extract_options!.stringify_keys + sources.dup.map { |source| + tag_options = { + "src" => path_to_javascript(source) + }.merge(options) + content_tag(:script, "", tag_options) + }.join("\n").html_safe end end end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index e3a86a8889..bf59f8e81a 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -1,53 +1,11 @@ require 'active_support/core_ext/file' -require 'action_view/helpers/asset_tag_helpers/asset_include_tag' module ActionView module Helpers module AssetTagHelper - - class StylesheetIncludeTag < AssetIncludeTag #:nodoc: - def asset_name - 'stylesheet' - end - - def extension - 'css' - end - - def asset_tag(source, options) - # We force the :request protocol here to avoid a double-download bug in IE7 and IE8 - tag("link", { "rel" => "stylesheet", "media" => "screen", "href" => path_to_asset(source, :protocol => :request) }.merge(options)) - end - - def custom_dir - config.stylesheets_dir - end - end - - module StylesheetTagHelpers extend ActiveSupport::Concern - module ClassMethods - # Register one or more stylesheet files to be included when <tt>symbol</tt> - # is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended - # to be called from plugin initialization to register stylesheet files - # that the plugin installed in <tt>public/stylesheets</tt>. - # - # ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"] - # - # stylesheet_link_tag :monkey # => - # <link href="/stylesheets/head.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/body.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" /> - def register_stylesheet_expansion(expansions) - style_expansions = StylesheetIncludeTag.expansions - expansions.each do |key, values| - style_expansions[key] = (style_expansions[key] || []) | Array(values) - end - end - end - # Computes the path to a stylesheet asset in the public stylesheets directory. # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs). # Full paths from the document root will be passed through. @@ -96,56 +54,18 @@ module ActionView # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" /> # <link href="/css/stylish.css" media="screen" rel="stylesheet" /> # - # You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source: - # - # stylesheet_link_tag :all # => - # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" /> - # - # If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>: - # - # stylesheet_link_tag :all, :recursive => true - # - # == Caching multiple stylesheets into one - # - # You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be - # compressed by gzip (leading to faster transfers). Caching will only happen if +config.perform_caching+ - # is set to true (which is the case by default for the Rails production environment, but not for the development - # environment). Examples: - # - # stylesheet_link_tag :all, :cache => true # when config.perform_caching is false => - # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" /> - # - # stylesheet_link_tag :all, :cache => true # when config.perform_caching is true => - # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" /> - # - # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is false => - # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" /> - # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" /> - # - # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is true => - # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" /> - # - # The <tt>:recursive</tt> option is also available for caching: - # - # stylesheet_link_tag :all, :cache => true, :recursive => true - # - # To force concatenation (even in development mode) set <tt>:concat</tt> to true. This is useful if - # you have too many stylesheets for IE to load. - # - # stylesheet_link_tag :all, :concat => true - # def stylesheet_link_tag(*sources) - @stylesheet_include ||= StylesheetIncludeTag.new(config, asset_paths) - @stylesheet_include.include_tag(*sources) + options = sources.extract_options!.stringify_keys + sources.uniq.map { |source| + tag_options = { + "rel" => "stylesheet", + "media" => "screen", + "href" => path_to_stylesheet(source) + }.merge(options) + tag(:link, tag_options) + }.join("\n").html_safe end - end - end end end diff --git a/actionpack/lib/action_view/helpers/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/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb index cc3a871576..fd9a543e0a 100644 --- a/actionpack/lib/action_view/log_subscriber.rb +++ b/actionpack/lib/action_view/log_subscriber.rb @@ -3,10 +3,13 @@ module ActionView # # Provides functionality so that Rails can output logs from Action View. class LogSubscriber < ActiveSupport::LogSubscriber + VIEWS_PATTERN = /^app\/views\//.freeze + def render_template(event) + return unless logger.info? message = " Rendered #{from_rails_root(event.payload[:identifier])}" message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] - message << (" (%.1fms)" % event.duration) + message << " (#{event.duration.round(1)}ms)" info(message) end alias :render_partial :render_template @@ -19,7 +22,7 @@ module ActionView protected def from_rails_root(string) - string.sub("#{Rails.root}/", "").sub(/^app\/views\//, "") + string.sub("#{Rails.root}/", "").sub(VIEWS_PATTERN, "") end end end diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb index 2d36deaa78..55f6ea5522 100644 --- a/actionpack/lib/action_view/railtie.rb +++ b/actionpack/lib/action_view/railtie.rb @@ -5,8 +5,6 @@ module ActionView # = Action View Railtie class Railtie < Rails::Railtie config.action_view = ActiveSupport::OrderedOptions.new - config.action_view.stylesheet_expansions = {} - config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) } config.action_view.embed_authenticity_token_in_remote_forms = false config.eager_load_namespaces << ActionView @@ -30,18 +28,6 @@ module ActionView end end - initializer "action_view.javascript_expansions" do |app| - ActiveSupport.on_load(:action_view) do - ActionView::Helpers::AssetTagHelper.register_javascript_expansion( - app.config.action_view.delete(:javascript_expansions) - ) - - ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion( - app.config.action_view.delete(:stylesheet_expansions) - ) - end - end - initializer "action_view.set_configs" do |app| ActiveSupport.on_load(:action_view) do app.config.action_view.each do |k,v| diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index b7fd6ec7e1..5434b3421e 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -196,17 +196,17 @@ module ActionView :@_result, :@_routes, :@controller, - :@layouts, + :@_layouts, :@locals, :@method_name, :@output_buffer, - :@partials, + :@_partials, :@passed, :@rendered, :@request, :@routes, :@tagged_logger, - :@templates, + :@_templates, :@options, :@test_passed, :@view, 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/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 4ab5d92a2b..ca542eb7e2 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -7,6 +7,7 @@ class ActionPackAssertionsController < ActionController::Base def nothing() head :ok end def hello_world() render :template => "test/hello_world"; end + def hello_repeating_in_path() render :template => "test/hello/hello"; end def hello_xml_world() render :template => "test/hello_xml_world"; end @@ -464,6 +465,20 @@ class AssertTemplateTest < ActionController::TestCase end end + def test_fails_with_incorrect_string_that_matches + get :hello_world + assert_raise(ActiveSupport::TestCase::Assertion) do + assert_template 'est/he' + end + end + + def test_fails_with_repeated_name_in_path + get :hello_repeating_in_path + assert_raise(ActiveSupport::TestCase::Assertion) do + assert_template 'test/hello' + end + end + def test_fails_with_incorrect_symbol get :hello_world assert_raise(ActiveSupport::TestCase::Assertion) do @@ -471,6 +486,13 @@ class AssertTemplateTest < ActionController::TestCase end end + def test_fails_with_incorrect_symbol_that_matches + get :hello_world + assert_raise(ActiveSupport::TestCase::Assertion) do + assert_template :"est/he" + end + end + def test_fails_with_wrong_layout get :render_with_layout assert_raise(ActiveSupport::TestCase::Assertion) do 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/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb index 351b9c4cfa..ab1bd0e3b6 100644 --- a/actionpack/test/controller/show_exceptions_test.rb +++ b/actionpack/test/controller/show_exceptions_test.rb @@ -93,4 +93,20 @@ module ShowExceptions assert_equal 'text/html', response.content_type.to_s end end + + class ShowFailsafeExceptionsTest < ActionDispatch::IntegrationTest + def test_render_failsafe_exception + @app = ShowExceptionsOverridenController.action(:boom) + @exceptions_app = @app.instance_variable_get(:@exceptions_app) + @app.instance_variable_set(:@exceptions_app, nil) + $stderr = StringIO.new + + get '/', {}, 'HTTP_ACCEPT' => 'text/json' + assert_response :internal_server_error + assert_equal 'text/plain', response.content_type.to_s + + @app.instance_variable_set(:@exceptions_app, @exceptions_app) + $stderr = STDERR + end + end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 4e83ad16d7..93d89f7568 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1124,6 +1124,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/sheep/1/_it', _it_sheep_path(1) end + def test_resource_does_not_modify_passed_options + options = {:id => /.+?/, :format => /json|xml/} + self.class.stub_controllers do |routes| + routes.draw do + resource :user, options + end + end + assert_equal({:id => /.+?/, :format => /json|xml/}, options) + end + + def test_resources_does_not_modify_passed_options + options = {:id => /.+?/, :format => /json|xml/} + self.class.stub_controllers do |routes| + routes.draw do + resources :users, options + end + end + assert_equal({:id => /.+?/, :format => /json|xml/}, options) + end + def test_path_names get '/pt/projetos' assert_equal 'projects#index', @response.body diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb index a74e165826..b8479e8836 100644 --- a/actionpack/test/dispatch/session/cache_store_test.rb +++ b/actionpack/test/dispatch/session/cache_store_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'fixtures/session_autoload_test/session_autoload_test/foo' class CacheStoreTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base diff --git a/actionpack/test/fixtures/test/hello/hello.erb b/actionpack/test/fixtures/test/hello/hello.erb new file mode 100644 index 0000000000..6769dd60bd --- /dev/null +++ b/actionpack/test/fixtures/test/hello/hello.erb @@ -0,0 +1 @@ +Hello world!
\ No newline at end of file diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index a04694714d..754622c883 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -45,8 +45,6 @@ class AssetTagHelperTest < ActionView::TestCase end.new @controller.request = @request - - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :defaults => ['prototype', 'effects', 'dragdrop', 'controls', 'rails'] end def url_for(*args) @@ -54,11 +52,7 @@ class AssetTagHelperTest < ActionView::TestCase end def teardown - config.perform_caching = false ENV.delete('RAILS_ASSET_ID') - - JavascriptIncludeTag.expansions.clear - StylesheetIncludeTag.expansions.clear end AutoDiscoveryToTag = { @@ -105,12 +99,6 @@ class AssetTagHelperTest < ActionView::TestCase %(javascript_include_tag("bank.js")) => %(<script src="/javascripts/bank.js" ></script>), %(javascript_include_tag("bank", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/bank.js" ></script>), %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" ></script>\n<script src="/elsewhere/cools.js" ></script>), - %(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" ></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), - %(javascript_include_tag(:all)) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>), - %(javascript_include_tag(:all, :recursive => true)) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/subdir/subdir.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>), - %(javascript_include_tag(:defaults, "bank")) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/application.js"></script>), - %(javascript_include_tag(:defaults, "application")) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), - %(javascript_include_tag("bank", :defaults)) => %(<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), %(javascript_include_tag("http://example.com/all")) => %(<script src="http://example.com/all"></script>), %(javascript_include_tag("http://example.com/all.js")) => %(<script src="http://example.com/all.js"></script>), @@ -151,9 +139,6 @@ class AssetTagHelperTest < ActionView::TestCase %(stylesheet_link_tag("/elsewhere/file")) => %(<link href="/elsewhere/file.css" media="screen" rel="stylesheet" />), %(stylesheet_link_tag("subdir/subdir")) => %(<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />), %(stylesheet_link_tag("bank", :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" />), - %(stylesheet_link_tag(:all)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />), - %(stylesheet_link_tag(:all, :recursive => true)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />), - %(stylesheet_link_tag(:all, :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="all" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="all" rel="stylesheet" />), %(stylesheet_link_tag("random.styles", "/elsewhere/file")) => %(<link href="/stylesheets/random.styles" media="screen" rel="stylesheet" />\n<link href="/elsewhere/file.css" media="screen" rel="stylesheet" />), %(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style" media="screen" rel="stylesheet" />), @@ -339,93 +324,14 @@ class AssetTagHelperTest < ActionView::TestCase } assert_nothing_raised { - javascript_include_tag(:defaults, 'missing_security_guard') - } - - assert_nothing_raised { javascript_include_tag('http://example.com/css/missing_security_guard') } end - def test_javascript_include_tag_with_given_asset_id - ENV["RAILS_ASSET_ID"] = "1" - assert_dom_equal(%(<script src="/javascripts/prototype.js?1"></script>\n<script src="/javascripts/effects.js?1"></script>\n<script src="/javascripts/dragdrop.js?1"></script>\n<script src="/javascripts/controls.js?1"></script>\n<script src="/javascripts/rails.js?1"></script>\n<script src="/javascripts/application.js?1"></script>), javascript_include_tag(:defaults)) - end - def test_javascript_include_tag_is_html_safe - assert javascript_include_tag(:defaults).html_safe? assert javascript_include_tag("prototype").html_safe? end - def test_custom_javascript_expansions - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"] - assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/effects.js"></script>), javascript_include_tag('controls', :robbery, 'effects') - end - - def test_custom_javascript_expansions_return_unique_set - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :defaults => %w(prototype effects dragdrop controls rails application) - assert_dom_equal %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag(:defaults) - end - - def test_custom_javascript_expansions_and_defaults_puts_application_js_at_the_end - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"] - assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('controls',:defaults, :robbery, 'effects') - end - - def test_javascript_include_tag_should_not_output_the_same_asset_twice - ENV["RAILS_ASSET_ID"] = "" - assert_dom_equal %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('prototype', 'effects', :defaults) - end - - def test_javascript_include_tag_should_not_output_the_same_expansion_twice - ENV["RAILS_ASSET_ID"] = "" - assert_dom_equal %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag(:defaults, :defaults) - end - - def test_single_javascript_asset_keys_should_take_precedence_over_expansions - ENV["RAILS_ASSET_ID"] = "" - assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('controls', :defaults, 'effects') - assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('controls', 'effects', :defaults) - end - - def test_registering_javascript_expansions_merges_with_existing_expansions - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :can_merge => ['bank'] - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :can_merge => ['robber'] - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :can_merge => ['bank'] - assert_dom_equal %(<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>), javascript_include_tag(:can_merge) - end - - def test_custom_javascript_expansions_with_undefined_symbol - assert_raise(ArgumentError) { javascript_include_tag('first', :unknown, 'last') } - end - - def test_custom_javascript_expansions_with_nil_value - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :monkey => nil - assert_dom_equal %(<script src="/javascripts/first.js"></script>\n<script src="/javascripts/last.js"></script>), javascript_include_tag('first', :monkey, 'last') - end - - def test_custom_javascript_expansions_with_empty_array_value - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :monkey => [] - assert_dom_equal %(<script src="/javascripts/first.js"></script>\n<script src="/javascripts/last.js"></script>), javascript_include_tag('first', :monkey, 'last') - end - - def test_custom_javascript_and_stylesheet_expansion_with_same_name - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"] - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :robbery => ["money", "security"] - assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/effects.js"></script>), javascript_include_tag('controls', :robbery, 'effects') - assert_dom_equal %(<link href="/stylesheets/style.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/money.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/security.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/print.css" rel="stylesheet" media="screen" />), stylesheet_link_tag('style', :robbery, 'print') - end - - def test_reset_javascript_expansions - JavascriptIncludeTag.expansions.clear - assert_raise(ArgumentError) { javascript_include_tag(:defaults) } - end - def test_all_javascript_expansion_not_include_application_js_if_not_exists FileUtils.mv(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'application.js'), File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'application.bak')) @@ -478,57 +384,11 @@ class AssetTagHelperTest < ActionView::TestCase assert_dom_equal %(<link href="/file.css" media="<script>" rel="stylesheet" />), stylesheet_link_tag('/file', :media => '<script>') end - def test_custom_stylesheet_expansions - ENV["RAILS_ASSET_ID"] = '' - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :robbery => ["bank", "robber"] - assert_dom_equal %(<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('version.1.0', :robbery, 'subdir/subdir') - end - - def test_custom_stylesheet_expansions_return_unique_set - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :cities => %w(wellington amsterdam london) - assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/london.css" media="screen" rel="stylesheet" />), stylesheet_link_tag(:cities) - end - def test_stylesheet_link_tag_should_not_output_the_same_asset_twice ENV["RAILS_ASSET_ID"] = "" assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington', 'wellington', 'amsterdam') end - def test_stylesheet_link_tag_should_not_output_the_same_expansion_twice - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :cities => %w(wellington amsterdam london) - assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/london.css" media="screen" rel="stylesheet" />), stylesheet_link_tag(:cities, :cities) - end - - def test_single_stylesheet_asset_keys_should_take_precedence_over_expansions - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :cities => %w(wellington amsterdam london) - assert_dom_equal %(<link href="/stylesheets/london.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('london', :cities) - end - - def test_custom_stylesheet_expansions_with_unknown_symbol - assert_raise(ArgumentError) { stylesheet_link_tag('first', :unknown, 'last') } - end - - def test_custom_stylesheet_expansions_with_nil_value - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :monkey => nil - assert_dom_equal %(<link href="/stylesheets/first.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/last.css" rel="stylesheet" media="screen" />), stylesheet_link_tag('first', :monkey, 'last') - end - - def test_custom_stylesheet_expansions_with_empty_array_value - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :monkey => [] - assert_dom_equal %(<link href="/stylesheets/first.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/last.css" rel="stylesheet" media="screen" />), stylesheet_link_tag('first', :monkey, 'last') - end - - def test_registering_stylesheet_expansions_merges_with_existing_expansions - ENV["RAILS_ASSET_ID"] = "" - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :can_merge => ['bank'] - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :can_merge => ['robber'] - ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :can_merge => ['bank'] - assert_dom_equal %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />), stylesheet_link_tag(:can_merge) - end - def test_image_path ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -714,616 +574,6 @@ class AssetTagHelperTest < ActionView::TestCase @controller.request.stubs(:ssl?).returns(true) assert_equal "http://localhost/images/xml.png", image_path("xml.png") end - - def test_caching_javascript_include_tag_when_caching_on - ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = 'http://a0.example.com' - config.perform_caching = true - - assert_dom_equal( - %(<script src="http://a0.example.com/javascripts/all.js"></script>), - javascript_include_tag(:all, :cache => true) - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_dom_equal( - %(<script src="http://a0.example.com/javascripts/money.js"></script>), - javascript_include_tag(:all, :cache => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - - assert_dom_equal( - %(<script src="http://a0.example.com/absolute/test.js"></script>), - javascript_include_tag(:all, :cache => "/absolute/test") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::ASSETS_DIR, 'absolute', 'test.js')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::ASSETS_DIR, 'absolute')) - end - - def test_caching_javascript_include_tag_when_caching_on_with_proc_asset_host - ENV['RAILS_ASSET_ID'] = '' - @controller.config.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } - config.perform_caching = true - - assert_equal '/javascripts/scripts.js'.length, 23 - assert_dom_equal( - %(<script src="http://a23.example.com/javascripts/scripts.js"></script>), - javascript_include_tag(:all, :cache => 'scripts') - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'scripts.js')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'scripts.js')) - end - - def test_caching_javascript_include_tag_when_caching_on_with_2_argument_proc_asset_host - ENV['RAILS_ASSET_ID'] = '' - @controller.config.asset_host = Proc.new { |source, request| - if request.ssl? - "#{request.protocol}#{request.host_with_port}" - else - "#{request.protocol}assets#{source.length}.example.com" - end - } - config.perform_caching = true - - assert_equal '/javascripts/vanilla.js'.length, 23 - assert_dom_equal( - %(<script src="http://assets23.example.com/javascripts/vanilla.js"></script>), - javascript_include_tag(:all, :cache => 'vanilla') - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'vanilla.js')) - - class << @controller.request - def protocol() 'https://' end - def ssl?() true end - end - - assert_equal '/javascripts/secure.js'.length, 22 - assert_dom_equal( - %(<script src="https://localhost/javascripts/secure.js"></script>), - javascript_include_tag(:all, :cache => 'secure') - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'vanilla.js')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) - end - - def test_caching_javascript_include_tag_when_caching_on_with_2_argument_object_asset_host - ENV['RAILS_ASSET_ID'] = '' - @controller.config.asset_host = Class.new do - def call(source, request) - if request.ssl? - "#{request.protocol}#{request.host_with_port}" - else - "#{request.protocol}assets#{source.length}.example.com" - end - end - end.new - - config.perform_caching = true - - assert_equal '/javascripts/vanilla.js'.length, 23 - assert_dom_equal( - %(<script src="http://assets23.example.com/javascripts/vanilla.js"></script>), - javascript_include_tag(:all, :cache => 'vanilla') - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'vanilla.js')) - - class << @controller.request - def protocol() 'https://' end - def ssl?() true end - end - - assert_equal '/javascripts/secure.js'.length, 22 - assert_dom_equal( - %(<script src="https://localhost/javascripts/secure.js"></script>), - javascript_include_tag(:all, :cache => 'secure') - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'vanilla.js')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'secure.js')) - end - - def test_caching_javascript_include_tag_when_caching_on_and_using_subdirectory - ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = 'http://a%d.example.com' - config.perform_caching = true - - number = Zlib.crc32('/javascripts/cache/money.js') % 4 - assert_dom_equal( - %(<script src="http://a#{number}.example.com/javascripts/cache/money.js"></script>), - javascript_include_tag(:all, :cache => "cache/money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'cache', 'money.js')) - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'cache', 'money.js')) - end - - def test_caching_javascript_include_tag_with_all_and_recursive_puts_defaults_at_the_start_of_the_file - ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = 'http://a0.example.com' - config.perform_caching = true - - assert_dom_equal( - %(<script src="http://a0.example.com/javascripts/combined.js"></script>), - javascript_include_tag(:all, :cache => "combined", :recursive => true) - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js')) - - assert_equal( - %(// prototype js\n\n// effects js\n\n// dragdrop js\n\n// controls js\n\n// bank js\n\n// robber js\n\n// subdir js\n\n\n// version.1.0 js\n\n// application js), - IO.read(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js')) - ) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js')) - end - - def test_caching_javascript_include_tag_with_all_puts_defaults_at_the_start_of_the_file - ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = 'http://a0.example.com' - config.perform_caching = true - - assert_dom_equal( - %(<script src="http://a0.example.com/javascripts/combined.js"></script>), - javascript_include_tag(:all, :cache => "combined") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js')) - - assert_equal( - %(// prototype js\n\n// effects js\n\n// dragdrop js\n\n// controls js\n\n// bank js\n\n// robber js\n\n// version.1.0 js\n\n// application js), - IO.read(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js')) - ) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js')) - end - - def test_caching_javascript_include_tag_with_relative_url_root - ENV["RAILS_ASSET_ID"] = "" - @controller.config.relative_url_root = "/collaboration/hieraki" - config.perform_caching = true - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/all.js"></script>), - javascript_include_tag(:all, :cache => true) - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/money.js"></script>), - javascript_include_tag(:all, :cache => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - # Same as above, but with script_name - def test_caching_javascript_include_tag_with_script_name - ENV["RAILS_ASSET_ID"] = "" - @request.script_name = "/collaboration/hieraki" - config.perform_caching = true - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/all.js"></script>), - javascript_include_tag(:all, :cache => true) - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/money.js"></script>), - javascript_include_tag(:all, :cache => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - def test_caching_javascript_include_tag_with_named_paths_and_relative_url_root_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - @controller.config.relative_url_root = "/collaboration/hieraki" - config.perform_caching = false - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>), - javascript_include_tag('robber', :cache => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>), - javascript_include_tag('robber', :cache => "money", :recursive => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - # Same as above, but with script_name - def test_caching_javascript_include_tag_with_named_paths_and_script_name_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - @request.script_name = "/collaboration/hieraki" - config.perform_caching = false - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>), - javascript_include_tag('robber', :cache => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_dom_equal( - %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>), - javascript_include_tag('robber', :cache => "money", :recursive => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - def test_caching_javascript_include_tag_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = false - - assert_dom_equal( - %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>), - javascript_include_tag(:all, :cache => true) - ) - - assert_dom_equal( - %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/subdir/subdir.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>), - javascript_include_tag(:all, :cache => true, :recursive => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_dom_equal( - %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>), - javascript_include_tag(:all, :cache => "money") - ) - - assert_dom_equal( - %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/subdir/subdir.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>), - javascript_include_tag(:all, :cache => "money", :recursive => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - def test_caching_javascript_include_tag_when_caching_on_and_missing_javascript_file - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = true - - assert_raise(Errno::ENOENT) { - javascript_include_tag('bank', 'robber', 'missing_security_guard', :cache => true) - } - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_raise(Errno::ENOENT) { - javascript_include_tag('bank', 'robber', 'missing_security_guard', :cache => "money") - } - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - def test_caching_javascript_include_tag_when_caching_on_and_javascript_file_is_uri - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = true - - assert_raise(Errno::ENOENT) { - javascript_include_tag('bank', 'robber', 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.js', :cache => true) - } - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - end - - def test_caching_javascript_include_tag_when_caching_off_and_missing_javascript_file - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = false - - assert_raise(Errno::ENOENT) { - javascript_include_tag('bank', 'robber', 'missing_security_guard', :cache => true) - } - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) - - assert_raise(Errno::ENOENT) { - javascript_include_tag('bank', 'robber', 'missing_security_guard', :cache => "money") - } - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) - end - - def test_caching_stylesheet_link_tag_when_caching_on - ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = 'a0.example.com' - config.perform_caching = true - - assert_dom_equal( - %(<link href="http://a0.example.com/stylesheets/all.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => true) - ) - - files_to_be_joined = Dir["#{ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR}/[^all]*.css"] - - expected_mtime = files_to_be_joined.map { |p| File.mtime(p) }.max - assert_equal expected_mtime, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - bytes_added_by_join = "\n\n".size * files_to_be_joined.size - "\n\n".size - expected_size = files_to_be_joined.sum { |p| File.size(p) } + bytes_added_by_join - assert_equal expected_size, File.size(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="http://a0.example.com/stylesheets/money.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - - assert_dom_equal( - %(<link href="http://a0.example.com/absolute/test.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => "/absolute/test") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::ASSETS_DIR, 'absolute', 'test.css')) - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::ASSETS_DIR, 'absolute')) - end - - def test_concat_stylesheet_link_tag_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - - assert_dom_equal( - %(<link href="/stylesheets/all.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :concat => true) - ) - - expected = Dir["#{ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR}/*.css"].map { |p| File.mtime(p) }.max - assert_equal expected, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="/stylesheets/money.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :concat => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - - assert_dom_equal( - %(<link href="/absolute/test.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :concat => "/absolute/test") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::ASSETS_DIR, 'absolute', 'test.css')) - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::ASSETS_DIR, 'absolute')) - end - - def test_caching_stylesheet_link_tag_when_caching_on_and_missing_css_file - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = true - - assert_raise(Errno::ENOENT) { - stylesheet_link_tag('bank', 'robber', 'missing_security_guard', :cache => true) - } - - assert ! File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_raise(Errno::ENOENT) { - stylesheet_link_tag('bank', 'robber', 'missing_security_guard', :cache => "money") - } - - assert ! File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - def test_caching_stylesheet_link_tag_when_caching_off_and_missing_css_file - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = false - - assert_raise(Errno::ENOENT) { - stylesheet_link_tag('bank', 'robber', 'missing_security_guard', :cache => true) - } - - assert ! File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_raise(Errno::ENOENT) { - stylesheet_link_tag('bank', 'robber', 'missing_security_guard', :cache => "money") - } - - assert ! File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - def test_caching_stylesheet_link_tag_when_caching_on_with_proc_asset_host - ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = Proc.new { |source| "a#{source.length}.example.com" } - config.perform_caching = true - - assert_equal '/stylesheets/styles.css'.length, 23 - assert_dom_equal( - %(<link href="http://a23.example.com/stylesheets/styles.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => 'styles') - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'styles.css')) - - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'styles.css')) - end - - def test_caching_stylesheet_link_tag_with_relative_url_root - ENV["RAILS_ASSET_ID"] = "" - @controller.config.relative_url_root = "/collaboration/hieraki" - config.perform_caching = true - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/all.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => true) - ) - - files_to_be_joined = Dir["#{ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR}/[^all]*.css"] - - expected_mtime = files_to_be_joined.map { |p| File.mtime(p) }.max - assert_equal expected_mtime, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/money.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - # Same as above, but with script_name - def test_caching_stylesheet_link_tag_with_script_name - ENV["RAILS_ASSET_ID"] = "" - @request.script_name = "/collaboration/hieraki" - config.perform_caching = true - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/all.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => true) - ) - - files_to_be_joined = Dir["#{ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR}/[^all]*.css"] - - expected_mtime = files_to_be_joined.map { |p| File.mtime(p) }.max - assert_equal expected_mtime, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/money.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => "money") - ) - - assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - ensure - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - - def test_caching_stylesheet_link_tag_with_named_paths_and_relative_url_root_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - @controller.config.relative_url_root = "/collaboration/hieraki" - config.perform_caching = false - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag('robber', :cache => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag('robber', :cache => "money") - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - # Same as above, but with script_name - def test_caching_stylesheet_link_tag_with_named_paths_and_script_name_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - @request.script_name = "/collaboration/hieraki" - config.perform_caching = false - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag('robber', :cache => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag('robber', :cache => "money") - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - def test_caching_stylesheet_include_tag_when_caching_off - ENV["RAILS_ASSET_ID"] = "" - config.perform_caching = false - - assert_dom_equal( - %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => true) - ) - - assert_dom_equal( - %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => true, :recursive => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - - assert_dom_equal( - %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => "money") - ) - - assert_dom_equal( - %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag(:all, :cache => "money", :recursive => true) - ) - - assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) - end - - def test_caching_stylesheet_include_tag_with_absolute_uri - ENV["RAILS_ASSET_ID"] = "" - - assert_dom_equal( - %(<link href="/stylesheets/all.css" media="screen" rel="stylesheet" />), - stylesheet_link_tag("/foo/baz", :cache => true) - ) - - FileUtils.rm(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) - end end class AssetTagHelperNonVhostTest < ActionView::TestCase @@ -1336,8 +586,6 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase @request = Struct.new(:protocol).new("gopher://") @controller.request = @request - - JavascriptIncludeTag.expansions.clear end def url_for(options) 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..7f39d3083e 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] @@ -412,7 +414,7 @@ module ActiveRecord persisted.map! do |record| if mem_record = memory.delete(record) - (record.attribute_names - mem_record.changes.keys).each do |name| + ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name| mem_record[name] = record[name] end 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/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index a25f2c7bca..ca79950049 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -1,11 +1,13 @@ module ActiveRecord class LogSubscriber < ActiveSupport::LogSubscriber + IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"] + def self.runtime=(value) - Thread.current["active_record_sql_runtime"] = value + Thread.current[:active_record_sql_runtime] = value end def self.runtime - Thread.current["active_record_sql_runtime"] ||= 0 + Thread.current[:active_record_sql_runtime] ||= 0 end def self.reset_runtime @@ -24,9 +26,9 @@ module ActiveRecord payload = event.payload - return if 'SCHEMA' == payload[:name] + return if IGNORE_PAYLOAD_NAMES.include?(payload[:name]) - name = '%s (%.1fms)' % [payload[:name], event.duration] + name = "#{payload[:name]} (#{event.duration.round(1)}ms)" sql = payload[:sql].squeeze(' ') binds = nil 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/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 019290725d..3706885881 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -10,7 +10,7 @@ module ActiveRecord # puts invalid.record.errors # end class RecordInvalid < ActiveRecordError - attr_reader :record # :nodoc: + attr_reader :record # :nodoc: def initialize(record) # :nodoc: @record = record errors = @record.errors.full_messages.join(", ") diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index f3620c1324..5dece1cb36 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -64,14 +64,12 @@ module ActiveRecord end def build_relation(klass, table, attribute, value) #:nodoc: - reflection = klass.reflect_on_association(attribute) - if reflection - column = klass.columns_hash[reflection.foreign_key] + if reflection = klass.reflect_on_association(attribute) attribute = reflection.foreign_key value = value.attributes[reflection.primary_key_column.name] - else - column = klass.columns_hash[attribute.to_s] end + + column = klass.columns_hash[attribute.to_s] value = column.limit ? value.to_s[0, column.limit] : value.to_s if !value.nil? && column.text? if !options[:case_sensitive] && value && column.text? 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/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 86893ec4b3..9b00c21b52 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -231,6 +231,14 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase assert_equal "2", categories(:sti_test).authors_with_select.first.post_id.to_s end + def test_create_through_has_many_with_piggyback + category = categories(:sti_test) + ernie = category.authors_with_select.create(:name => 'Ernie') + assert_nothing_raised do + assert_equal ernie, category.authors_with_select.detect {|a| a.name == 'Ernie'} + end + end + def test_include_has_many_through posts = Post.all.merge!(:order => 'posts.id').to_a posts_with_authors = Post.all.merge!(:includes => :authors, :order => 'posts.id').to_a 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/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 75a52544d3..7334514f9a 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -554,7 +554,7 @@ class DirtyTest < ActiveRecord::TestCase test "partial insert" do with_partial_updates Person do jon = nil - assert_sql(/first_name/) do + assert_sql(/first_name/i) do jon = Person.create! first_name: 'Jon' 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/CHANGELOG.md b/activesupport/CHANGELOG.md index 2df3d1f69b..0a12ba6cdd 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 4.0.0 (unreleased) ## +* Optimize ActiveSupport::Cache::Entry to reduce memory and processing overhead. *Brian Durand* + * Tests tag the Rails log with the current test class and test case: [SessionsControllerTest] [test_0002_sign in] Processing by SessionsController#create as HTML @@ -237,4 +239,6 @@ * Remove deprecated ActiveSupport::JSON::Variable. *Erich Menge* +* Optimize log subscribers to check log level before doing any processing. *Brian Durand* + Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 836bc2f9cf..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', '~> 3.2') + 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/cache.rb b/activesupport/lib/active_support/cache.rb index f98ba16cdd..732ab4b7df 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -284,7 +284,9 @@ module ActiveSupport end if entry && entry.expired? race_ttl = options[:race_condition_ttl].to_i - if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl + if race_ttl && (Time.now - entry.expires_at <= race_ttl) + # When an entry has :race_condition_ttl defined, put the stale entry back into the cache + # for a brief period while the entry is begin recalculated. entry.expires_at = Time.now + race_ttl write_entry(key, entry, :expires_in => race_ttl * 2) else @@ -532,102 +534,122 @@ module ActiveSupport end end - # Entry that is put into caches. It supports expiration time on entries and - # can compress values to save space in the cache. - class Entry - attr_reader :created_at, :expires_in - + # This class is used to represent cache entries. Cache entries have a value and an optional + # expiration time. The expiration time is used to support the :race_condition_ttl option + # on the cache. + # + # Since cache entries in most instances will be serialized, the internals of this class are highly optimized + # using short instance variable names that are lazily defined. + class Entry # :nodoc: DEFAULT_COMPRESS_LIMIT = 16.kilobytes - class << self - # Create an entry with internal attributes set. This method is intended - # to be used by implementations that store cache entries in a native - # format instead of as serialized Ruby objects. - def create(raw_value, created_at, options = {}) - entry = new(nil) - entry.instance_variable_set(:@value, raw_value) - entry.instance_variable_set(:@created_at, created_at.to_f) - entry.instance_variable_set(:@compressed, options[:compressed]) - entry.instance_variable_set(:@expires_in, options[:expires_in]) - entry - end - end - # Create a new cache entry for the specified value. Options supported are # +:compress+, +:compress_threshold+, and +:expires_in+. def initialize(value, options = {}) - @compressed = false - @expires_in = options[:expires_in] - @expires_in = @expires_in.to_f if @expires_in - @created_at = Time.now.to_f - if value.nil? - @value = nil + if should_compress?(value, options) + @v = compress(value) + @c = true else - @value = Marshal.dump(value) - if should_compress?(@value, options) - @value = Zlib::Deflate.deflate(@value) - @compressed = true - end + @v = value end - end - - # Get the raw value. This value may be serialized and compressed. - def raw_value - @value - end - - # Get the value stored in the cache. - def value - # If the original value was exactly false @value is still true because - # it is marshalled and eventually compressed. Both operations yield - # strings. - if @value - Marshal.load(compressed? ? Zlib::Inflate.inflate(@value) : @value) + if expires_in = options[:expires_in] + @x = (Time.now + expires_in).to_i end end - def compressed? - @compressed + def value + convert_version_3_entry! if defined?(@value) + compressed? ? uncompress(@v) : @v end # Check if the entry is expired. The +expires_in+ parameter can override # the value set when the entry was created. def expired? - @expires_in && @created_at + @expires_in <= Time.now.to_f - end - - # Set a new time when the entry will expire. - def expires_at=(time) - if time - @expires_in = time.to_f - @created_at + convert_version_3_entry! if defined?(@value) + if defined?(@x) + @x && @x < Time.now.to_i else - @expires_in = nil + false end end - # Seconds since the epoch when the entry will expire. def expires_at - @expires_in ? @created_at + @expires_in : nil + Time.at(@x) if defined?(@x) + end + + def expires_at=(value) + @x = value.to_i end # Returns the size of the cached value. This could be less than # <tt>value.size</tt> if the data is compressed. def size - if @value.nil? - 0 + if defined?(@s) + @s else - @value.bytesize + case value + when NilClass + 0 + when String + @v.bytesize + else + @s = Marshal.dump(@v).bytesize + end + end + end + + # Duplicate the value in a class. This is used by cache implementations that don't natively + # serialize entries to protect against accidental cache modifications. + def dup_value! + convert_version_3_entry! if defined?(@value) + if @v && !compressed? && !(@v.is_a?(Numeric) || @v == true || @v == false) + if @v.is_a?(String) + @v = @v.dup + else + @v = Marshal.load(Marshal.dump(@v)) + end end end private - def should_compress?(serialized_value, options) - if options[:compress] + def should_compress?(value, options) + if value && options[:compress] compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT - return true if serialized_value.size >= compress_threshold + serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize + return true if serialized_value_size >= compress_threshold end false end + + def compressed? + defined?(@c) ? @c : false + end + + def compress(value) + Zlib::Deflate.deflate(Marshal.dump(value)) + end + + def uncompress(value) + Marshal.load(Zlib::Inflate.inflate(value)) + end + + # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue + # to ensure that cache entries created under the old version still work with the new class definition. + def convert_version_3_entry! + if defined?(@value) + @v = @value + remove_instance_variable(:@value) + end + if defined?(@compressed) + @c = @compressed + remove_instance_variable(:@compressed) + end + if defined?(@expires_in) && defined?(@created_at) && @expires_in && @created_at + @x = (@created_at + @expires_in).to_i + remove_instance_variable(:@created_at) + remove_instance_variable(:@expires_in) + end + end end end end diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 7fd5e3b53d..4d26fb7e42 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -135,6 +135,7 @@ module ActiveSupport end def write_entry(key, entry, options) # :nodoc: + entry.dup_value! synchronize do old_entry = @data[key] return false if @data.key?(key) && options[:unless_exist] diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 7f37c459c1..6a0c4a015a 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -1,6 +1,8 @@ require 'active_support/xml_mini' require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/object/to_param' +require 'active_support/core_ext/object/to_query' class Array # Converts the array to a comma-separated sentence where the last element is diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index a7551d9c64..02ae57b4a6 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -28,7 +28,7 @@ class Date # Returns week start day symbol (e.g. :monday), or raises an ArgumentError for invalid day symbol. def find_beginning_of_week!(week_start) - raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.keys.include?(week_start) + raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start) week_start end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 5ba8197006..828f03ad62 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -1,8 +1,10 @@ require 'active_support/xml_mini' require 'active_support/time' +require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/object/to_param' +require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/hash/reverse_merge' -require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/inflections' class Hash diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index ec2157221f..ab01d7787a 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -1,14 +1,11 @@ require 'active_support/core_ext/object/acts_like' require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/object/deep_dup' -require 'active_support/core_ext/object/try' +require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/object/inclusion' - -require 'active_support/core_ext/object/conversions' require 'active_support/core_ext/object/instance_variables' - require 'active_support/core_ext/object/to_json' require 'active_support/core_ext/object/to_param' require 'active_support/core_ext/object/to_query' +require 'active_support/core_ext/object/try' require 'active_support/core_ext/object/with_options' diff --git a/activesupport/lib/active_support/core_ext/object/conversions.rb b/activesupport/lib/active_support/core_ext/object/conversions.rb deleted file mode 100644 index 540f7aadb0..0000000000 --- a/activesupport/lib/active_support/core_ext/object/conversions.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'active_support/core_ext/object/to_param' -require 'active_support/core_ext/object/to_query' -require 'active_support/core_ext/array/conversions' -require 'active_support/core_ext/hash/conversions' 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/caching_test.rb b/activesupport/test/caching_test.rb index 71cd9d81b3..febf0eeeff 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -224,25 +224,22 @@ module CacheStoreBehavior end def test_read_multi_with_expires - @cache.write('foo', 'bar', :expires_in => 0.001) + time = Time.now + @cache.write('foo', 'bar', :expires_in => 10) @cache.write('fu', 'baz') @cache.write('fud', 'biz') - sleep(0.002) + Time.stubs(:now).returns(time + 11) assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu')) end def test_read_and_write_compressed_small_data @cache.write('foo', 'bar', :compress => true) - raw_value = @cache.send(:read_entry, 'foo', {}).raw_value assert_equal 'bar', @cache.read('foo') - assert_equal 'bar', Marshal.load(raw_value) end def test_read_and_write_compressed_large_data @cache.write('foo', 'bar', :compress => true, :compress_threshold => 2) - raw_value = @cache.send(:read_entry, 'foo', {}).raw_value assert_equal 'bar', @cache.read('foo') - assert_equal 'bar', Marshal.load(Zlib::Inflate.inflate(raw_value)) end def test_read_and_write_compressed_nil @@ -301,14 +298,6 @@ module CacheStoreBehavior assert !@cache.exist?('foo') end - def test_read_should_return_a_different_object_id_each_time_it_is_called - @cache.write('foo', 'bar') - assert_not_equal @cache.read('foo').object_id, @cache.read('foo').object_id - value = @cache.read('foo') - value << 'bingo' - assert_not_equal value, @cache.read('foo') - end - def test_original_store_objects_should_not_be_immutable bar = 'bar' @cache.write('foo', bar) @@ -363,7 +352,7 @@ module CacheStoreBehavior rescue ArgumentError end assert_equal "bar", @cache.read('foo') - Time.stubs(:now).returns(time + 71) + Time.stubs(:now).returns(time + 91) assert_nil @cache.read('foo') end @@ -627,7 +616,7 @@ end class MemoryStoreTest < ActiveSupport::TestCase def setup - @record_size = Marshal.dump("aaaaaaaaaa").bytesize + @record_size = ActiveSupport::Cache::Entry.new("aaaaaaaaaa").size @cache = ActiveSupport::Cache.lookup_store(:memory_store, :expires_in => 60, :size => @record_size * 10) end @@ -646,9 +635,9 @@ class MemoryStoreTest < ActiveSupport::TestCase @cache.prune(@record_size * 3) assert @cache.exist?(5) assert @cache.exist?(4) - assert !@cache.exist?(3) + assert !@cache.exist?(3), "no entry" assert @cache.exist?(2) - assert !@cache.exist?(1) + assert !@cache.exist?(1), "no entry" end def test_prune_size_on_write @@ -670,12 +659,12 @@ class MemoryStoreTest < ActiveSupport::TestCase assert @cache.exist?(9) assert @cache.exist?(8) assert @cache.exist?(7) - assert !@cache.exist?(6) - assert !@cache.exist?(5) + assert !@cache.exist?(6), "no entry" + assert !@cache.exist?(5), "no entry" assert @cache.exist?(4) - assert !@cache.exist?(3) + assert !@cache.exist?(3), "no entry" assert @cache.exist?(2) - assert !@cache.exist?(1) + assert !@cache.exist?(1), "no entry" end def test_pruning_is_capped_at_a_max_time @@ -764,6 +753,14 @@ class MemCacheStoreTest < ActiveSupport::TestCase assert_equal [], cache.read("foo") end end + + def test_read_should_return_a_different_object_id_each_time_it_is_called + @cache.write('foo', 'bar') + assert_not_equal @cache.read('foo').object_id, @cache.read('foo').object_id + value = @cache.read('foo') + value << 'bingo' + assert_not_equal value, @cache.read('foo') + end end class NullStoreTest < ActiveSupport::TestCase @@ -844,15 +841,6 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase end class CacheEntryTest < ActiveSupport::TestCase - def test_create_raw_entry - time = Time.now - entry = ActiveSupport::Cache::Entry.create("raw", time, :compress => false, :expires_in => 300) - assert_equal "raw", entry.raw_value - assert_equal time.to_f, entry.created_at - assert !entry.compressed? - assert_equal 300, entry.expires_in - end - def test_expired entry = ActiveSupport::Cache::Entry.new("value") assert !entry.expired?, 'entry not expired' @@ -864,16 +852,43 @@ class CacheEntryTest < ActiveSupport::TestCase end def test_compress_values - entry = ActiveSupport::Cache::Entry.new("value", :compress => true, :compress_threshold => 1) - assert_equal "value", entry.value - assert entry.compressed? - assert_equal "value", Marshal.load(Zlib::Inflate.inflate(entry.raw_value)) + value = "value" * 100 + entry = ActiveSupport::Cache::Entry.new(value, :compress => true, :compress_threshold => 1) + assert_equal value, entry.value + assert(value.bytesize > entry.size, "value is compressed") end def test_non_compress_values - entry = ActiveSupport::Cache::Entry.new("value") - assert_equal "value", entry.value - assert_equal "value", Marshal.load(entry.raw_value) - assert !entry.compressed? + value = "value" * 100 + entry = ActiveSupport::Cache::Entry.new(value) + assert_equal value, entry.value + assert_equal value.bytesize, entry.size + end + + def test_restoring_version_3_entries + version_3_entry = ActiveSupport::Cache::Entry.allocate + version_3_entry.instance_variable_set(:@value, "hello") + version_3_entry.instance_variable_set(:@created_at, Time.now - 60) + entry = Marshal.load(Marshal.dump(version_3_entry)) + assert_equal "hello", entry.value + assert_equal false, entry.expired? + end + + def test_restoring_compressed_version_3_entries + version_3_entry = ActiveSupport::Cache::Entry.allocate + version_3_entry.instance_variable_set(:@value, Zlib::Deflate.deflate(Marshal.dump("hello"))) + version_3_entry.instance_variable_set(:@compressed, true) + entry = Marshal.load(Marshal.dump(version_3_entry)) + assert_equal "hello", entry.value + end + + def test_restoring_expired_version_3_entries + version_3_entry = ActiveSupport::Cache::Entry.allocate + version_3_entry.instance_variable_set(:@value, "hello") + version_3_entry.instance_variable_set(:@created_at, Time.now - 60) + version_3_entry.instance_variable_set(:@expires_in, 58.9) + entry = Marshal.load(Marshal.dump(version_3_entry)) + assert_equal "hello", entry.value + assert_equal true, entry.expired? end end diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 9dfa2cbf11..0b33a63460 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -1,7 +1,6 @@ require 'abstract_unit' require 'active_support/core_ext/array' require 'active_support/core_ext/big_decimal' -require 'active_support/core_ext/object/conversions' require 'active_support/core_ext' # FIXME: pulling in all to_xml extensions require 'active_support/hash_with_indifferent_access' diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 01934dd2c3..53ea2aad16 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -3,7 +3,6 @@ require 'active_support/core_ext/hash' require 'bigdecimal' require 'active_support/core_ext/string/access' require 'active_support/ordered_hash' -require 'active_support/core_ext/object/conversions' require 'active_support/core_ext/object/deep_dup' require 'active_support/inflections' 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/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index d19aab1438..9516556844 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -100,11 +100,11 @@ class AssertPresentTest < ActiveSupport::TestCase BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", [], {} ] NOT_BLANK = [ EmptyFalse.new, Object.new, true, 0, 1, 'x', [nil], { nil => 0 } ] - def test_assert_blank_true + def test_assert_present_true NOT_BLANK.each { |v| assert_present v } end - def test_assert_blank_false + def test_assert_present_false BLANK.each { |v| begin assert_present v diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md new file mode 100644 index 0000000000..a1a480e911 --- /dev/null +++ b/guides/CHANGELOG.md @@ -0,0 +1,5 @@ +## Rails 4.0.0 (unreleased) ## + +* Guides updated to reflect new test locations. *Mike Moore* + +* 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/4_0_release_notes.md b/guides/source/4_0_release_notes.md index cce5bc5331..54fe49252f 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -141,6 +141,8 @@ Action Mailer end ``` +* Allow for callbacks in mailers similar to ActionController::Base. You can now set up headers/attachments using `before_filter` or `after_filter`. You could also change delivery settings or prevent delivery in an after filter based on instance variables set in your mailer action. You have access to `ActionMailer::Base` instance methods like `message`, `attachments`, `headers`. + Action Pack ----------- 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/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 3c3ab83c37..a938db6265 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -27,7 +27,7 @@ create app/mailers/user_mailer.rb invoke erb create app/views/user_mailer invoke test_unit -create test/functional/user_mailer_test.rb +create test/mailers/user_mailer_test.rb ``` So we got the mailer, the views, and the tests. diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 2a84242b9c..645498437d 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -3097,7 +3097,8 @@ Time.local(2000, 11, 31).next_quarter # => Wed, 28 Feb 2001 The methods `beginning_of_week` and `end_of_week` return the dates for the beginning and end of the week, respectively. Weeks are assumed to start on -Monday, but that can be changed passing an argument. +Monday, but that can be changed passing an argument, setting thread local +`Date.beginning_of_week` or `config.beginning_of_week`. ```ruby d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 @@ -3111,18 +3112,24 @@ d.end_of_week(:sunday) # => Sat, 08 May 2010 ##### `monday`, `sunday` -The methods `monday` and `sunday` return the dates for the beginning and -end of the week, respectively. Weeks are assumed to start on Monday. +The methods `monday` and `sunday` return the dates for the previous Monday and +next Sunday, respectively. ```ruby d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 d.monday # => Mon, 03 May 2010 d.sunday # => Sun, 09 May 2010 + +d = Date.new(2012, 9, 10) # => Mon, 10 Sep 2012 +d.monday # => Mon, 10 Sep 2012 + +d = Date.new(2012, 9, 16) # => Sun, 16 Sep 2012 +d.sunday # => Sun, 16 Sep 2012 ``` ##### `prev_week`, `next_week` -The method `next_week` receives a symbol with a day name in English (in lowercase, default is `:monday`) and it returns the date corresponding to that day: +The method `next_week` receives a symbol with a day name in English (default is the thread local `Date.beginning_of_week`, or `config.beginning_of_week`, or `:monday`) and it returns the date corresponding to that day. ```ruby d = Date.new(2010, 5, 9) # => Sun, 09 May 2010 @@ -3140,6 +3147,8 @@ d.prev_week(:friday) # => Fri, 30 Apr 2010 `prev_week` is aliased to `last_week`. +Both `next_week` and `prev_week` work as expected when `Date.beginning_of_week` or `config.beginning_of_week` are set. + ##### `beginning_of_month`, `end_of_month` The methods `beginning_of_month` and `end_of_month` return the dates for the beginning and end of the month: @@ -3617,6 +3626,8 @@ now = Time.current # => Mon, 09 Aug 2010 23:20:05 UTC +00:00 now.all_week # => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Sun, 15 Aug 2010 23:59:59 UTC +00:00 +now.all_week(:sunday) +# => Sun, 16 Sep 2012 00:00:00 UTC +00:00..Sat, 22 Sep 2012 23:59:59 UTC +00:00 now.all_month # => Sat, 01 Aug 2010 00:00:00 UTC +00:00..Tue, 31 Aug 2010 23:59:59 UTC +00:00 now.all_quarter diff --git a/guides/source/command_line.md b/guides/source/command_line.md index b66b30a117..0338ef5ad0 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -134,10 +134,10 @@ Example: `rails generate controller CreditCard open debit credit close` Credit card controller with URLs like /credit_card/debit. - Controller: app/controllers/credit_card_controller.rb - Functional Test: test/functional/credit_card_controller_test.rb - Views: app/views/credit_card/debit.html.erb [...] - Helper: app/helpers/credit_card_helper.rb + Controller: app/controllers/credit_card_controller.rb + Test: test/controllers/credit_card_controller_test.rb + Views: app/views/credit_card/debit.html.erb [...] + Helper: app/helpers/credit_card_helper.rb ``` The controller generator is expecting parameters in the form of `generate controller ControllerName action1 action2`. Let's make a `Greetings` controller with an action of **hello**, which will say something nice to us. @@ -150,11 +150,11 @@ $ rails generate controller Greetings hello create app/views/greetings create app/views/greetings/hello.html.erb invoke test_unit - create test/functional/greetings_controller_test.rb + create test/controllers/greetings_controller_test.rb invoke helper create app/helpers/greetings_helper.rb invoke test_unit - create test/unit/helpers/greetings_helper_test.rb + create test/helpers/greetings_helper_test.rb invoke assets invoke coffee create app/assets/javascripts/greetings.js.coffee @@ -223,7 +223,7 @@ $ rails generate scaffold HighScore game:string score:integer create db/migrate/20120528060026_create_high_scores.rb create app/models/high_score.rb invoke test_unit - create test/unit/high_score_test.rb + create test/models/high_score_test.rb create test/fixtures/high_scores.yml route resources :high_scores invoke scaffold_controller @@ -236,11 +236,11 @@ $ rails generate scaffold HighScore game:string score:integer create app/views/high_scores/new.html.erb create app/views/high_scores/_form.html.erb invoke test_unit - create test/functional/high_scores_controller_test.rb + create test/controllers/high_scores_controller_test.rb invoke helper create app/helpers/high_scores_helper.rb invoke test_unit - create test/unit/helpers/high_scores_helper_test.rb + create test/helpers/high_scores_helper_test.rb invoke assets invoke coffee create app/assets/javascripts/high_scores.js.coffee @@ -327,7 +327,7 @@ $ rails generate model Oops create db/migrate/20120528062523_create_oops.rb create app/models/oops.rb invoke test_unit - create test/unit/oops_test.rb + create test/models/oops_test.rb create test/fixtures/oops.yml ``` ```bash @@ -336,7 +336,7 @@ $ rails destroy model Oops remove db/migrate/20120528062523_create_oops.rb remove app/models/oops.rb invoke test_unit - remove test/unit/oops_test.rb + remove test/models/oops_test.rb remove test/fixtures/oops.yml ``` @@ -480,15 +480,9 @@ The `tmp:` namespaced tasks will help you clear the `Rails.root/tmp` directory: * `rake secret` will give you a pseudo-random key to use for your session secret. * `rake time:zones:all` lists all the timezones Rails knows about. -### Writing Rake Tasks +### Custom Rake Tasks -If you have (or want to write) any automation scripts outside your app (data import, checks, etc), you can make them as rake tasks. It's easy. - -INFO: [Complete guide about how to write tasks](http://rake.rubyforge.org/files/doc/rakefile_rdoc.html) is available in the official documentation. - -Tasks should be placed in `Rails.root/lib/tasks` and should have a `.rake` extension. - -Each task should be defined in next format (dependencies are optional): +Custom rake tasks have a `.rake` extension and are placed in `Rails.root/lib/tasks`. ```ruby desc "I am short, but comprehensive description for my cool task" @@ -498,7 +492,7 @@ task :task_name => [:prerequisite_task, :another_task_we_depend_on] do end ``` -If you need to pass parameters, you can use next format (both arguments and dependencies are optional): +To pass arguments to your custom rake task: ```ruby task :task_name, [:arg_1] => [:pre_1, :pre_2] do |t, args| @@ -509,7 +503,7 @@ end You can group tasks by placing them in namespaces: ```ruby -namespace :do +namespace :db do desc "This task does nothing" task :nothing do # Seriously, nothing @@ -517,13 +511,13 @@ namespace :do end ``` -You can see your tasks to be listed by `rake -T` command. And, according to the examples above, you can invoke them as follows: +Invocation of the tasks will look like: ```bash rake task_name rake "task_name[value 1]" # entire argument string should be quoted -rake do:nothing -``` +rake db:nothing +``` NOTE: If your need to interact with your application models, perform database queries and so on, your task should depend on the `environment` task, which will load your application code. diff --git a/guides/source/engines.md b/guides/source/engines.md index 9adb5a1725..97af423f3e 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -169,7 +169,7 @@ end This line mounts the engine at the path `/blorgh`, which will make it accessible through the application only at that path. -Also in the test directory is the `test/integration` directory, where integration tests for the engine should be placed. Other directories can be created in the `test` directory also. For example, you may wish to create a `test/unit` directory for your unit tests. +Also in the test directory is the `test/integration` directory, where integration tests for the engine should be placed. Other directories can be created in the `test` directory also. For example, you may wish to create a `test/models` directory for your models tests. Providing engine functionality ------------------------------ @@ -191,7 +191,7 @@ invoke active_record create db/migrate/[timestamp]_create_blorgh_posts.rb create app/models/blorgh/post.rb invoke test_unit -create test/unit/blorgh/post_test.rb +create test/models/blorgh/post_test.rb create test/fixtures/blorgh/posts.yml route resources :posts invoke scaffold_controller @@ -204,11 +204,11 @@ create app/views/blorgh/posts/show.html.erb create app/views/blorgh/posts/new.html.erb create app/views/blorgh/posts/_form.html.erb invoke test_unit -create test/functional/blorgh/posts_controller_test.rb +create test/controllers/blorgh/posts_controller_test.rb invoke helper create app/helpers/blorgh/posts_helper.rb invoke test_unit -create test/unit/helpers/blorgh/posts_helper_test.rb +create test/helpers/blorgh/posts_helper_test.rb invoke assets invoke js create app/assets/javascripts/blorgh/posts.js @@ -220,7 +220,7 @@ create app/assets/stylesheets/scaffold.css The first thing that the scaffold generator does is invoke the `active_record` generator, which generates a migration and a model for the resource. Note here, however, that the migration is called `create_blorgh_posts` rather than the usual `create_posts`. This is due to the `isolate_namespace` method called in the `Blorgh::Engine` class's definition. The model here is also namespaced, being placed at `app/models/blorgh/post.rb` rather than `app/models/post.rb` due to the `isolate_namespace` call within the `Engine` class. -Next, the `test_unit` generator is invoked for this model, generating a unit test at `test/unit/blorgh/post_test.rb` (rather than `test/unit/post_test.rb`) and a fixture at `test/fixtures/blorgh/posts.yml` (rather than `test/fixtures/posts.yml`). +Next, the `test_unit` generator is invoked for this model, generating a model test at `test/models/blorgh/post_test.rb` (rather than `test/models/post_test.rb`) and a fixture at `test/fixtures/blorgh/posts.yml` (rather than `test/fixtures/posts.yml`). After that, a line for the resource is inserted into the `config/routes.rb` file for the engine. This line is simply `resources :posts`, turning the `config/routes.rb` file for the engine into this: @@ -232,7 +232,7 @@ end Note here that the routes are drawn upon the `Blorgh::Engine` object rather than the `YourApp::Application` class. This is so that the engine routes are confined to the engine itself and can be mounted at a specific point as shown in the [test directory](#test-directory) section. This is also what causes the engine's routes to be isolated from those routes that are within the application. This is discussed further in the [Routes](#routes) section of this guide. -Next, the `scaffold_controller` generator is invoked, generating a controller called `Blorgh::PostsController` (at `app/controllers/blorgh/posts_controller.rb`) and its related views at `app/views/blorgh/posts`. This generator also generates a functional test for the controller (`test/functional/blorgh/posts_controller_test.rb`) and a helper (`app/helpers/blorgh/posts_controller.rb`). +Next, the `scaffold_controller` generator is invoked, generating a controller called `Blorgh::PostsController` (at `app/controllers/blorgh/posts_controller.rb`) and its related views at `app/views/blorgh/posts`. This generator also generates a test for the controller (`test/controllers/blorgh/posts_controller_test.rb`) and a helper (`app/helpers/blorgh/posts_controller.rb`). Everything this generator has created is neatly namespaced. The controller's class is defined within the `Blorgh` module: @@ -300,7 +300,7 @@ invoke active_record create db/migrate/[timestamp]_create_blorgh_comments.rb create app/models/blorgh/comment.rb invoke test_unit -create test/unit/blorgh/comment_test.rb +create test/models/blorgh/comment_test.rb create test/fixtures/blorgh/comments.yml ``` @@ -373,11 +373,11 @@ create app/controllers/blorgh/comments_controller.rb invoke erb exist app/views/blorgh/comments invoke test_unit -create test/functional/blorgh/comments_controller_test.rb +create test/controllers/blorgh/comments_controller_test.rb invoke helper create app/helpers/blorgh/comments_helper.rb invoke test_unit -create test/unit/helpers/blorgh/comments_helper_test.rb +create test/helpers/blorgh/comments_helper_test.rb invoke assets invoke js create app/assets/javascripts/blorgh/comments.js diff --git a/guides/source/generators.md b/guides/source/generators.md index 0bcfa1dc68..d56bbe853c 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -176,7 +176,7 @@ $ rails generate scaffold User name:string create db/migrate/20091120125558_create_users.rb create app/models/user.rb invoke test_unit - create test/unit/user_test.rb + create test/models/user_test.rb create test/fixtures/users.yml route resources :users invoke scaffold_controller @@ -189,11 +189,11 @@ $ rails generate scaffold User name:string create app/views/users/new.html.erb create app/views/users/_form.html.erb invoke test_unit - create test/functional/users_controller_test.rb + create test/controllers/users_controller_test.rb invoke helper create app/helpers/users_helper.rb invoke test_unit - create test/unit/helpers/users_helper_test.rb + create test/helpers/users_helper_test.rb invoke stylesheets create app/assets/stylesheets/scaffold.css ``` @@ -350,7 +350,7 @@ $ rails generate scaffold Comment body:text create db/migrate/20091120151323_create_comments.rb create app/models/comment.rb invoke shoulda - create test/unit/comment_test.rb + create test/models/comment_test.rb create test/fixtures/comments.yml route resources :comments invoke scaffold_controller @@ -364,11 +364,11 @@ $ rails generate scaffold Comment body:text create app/views/comments/_form.html.erb create app/views/layouts/comments.html.erb invoke shoulda - create test/functional/comments_controller_test.rb + create test/controllers/comments_controller_test.rb invoke my_helper create app/helpers/comments_helper.rb invoke shoulda - create test/unit/helpers/comments_helper_test.rb + create test/helpers/comments_helper_test.rb ``` Fallbacks allow your generators to have a single responsibility, increasing code reuse and reducing the amount of duplication. diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index c0760915ba..9f23f9fc42 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -192,11 +192,11 @@ invoke erb create app/views/welcome create app/views/welcome/index.html.erb invoke test_unit -create test/functional/welcome_controller_test.rb +create test/controllers/welcome_controller_test.rb invoke helper create app/helpers/welcome_helper.rb invoke test_unit -create test/unit/helpers/welcome_helper_test.rb +create test/helpers/welcome_helper_test.rb invoke assets invoke coffee create app/assets/javascripts/welcome.js.coffee @@ -1246,7 +1246,7 @@ This command will generate four files: | -------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | db/migrate/20100207235629_create_comments.rb | Migration to create the comments table in your database (your name will include a different timestamp) | | app/models/comment.rb | The Comment model | -| test/unit/comment_test.rb | Unit testing harness for the comments model | +| test/models/comment_test.rb | Testing harness for the comments model | | test/fixtures/comments.yml | Sample comments for use in testing | First, take a look at `comment.rb`: @@ -1367,15 +1367,15 @@ $ rails generate controller Comments This creates six files and one empty directory: -| File/Directory | Purpose | -| ------------------------------------------- | ---------------------------------------- | -| app/controllers/comments_controller.rb | The Comments controller | -| app/views/comments/ | Views of the controller are stored here | -| test/functional/comments_controller_test.rb | The functional tests for the controller | -| app/helpers/comments_helper.rb | A view helper file | -| test/unit/helpers/comments_helper_test.rb | The unit tests for the helper | -| app/assets/javascripts/comment.js.coffee | CoffeeScript for the controller | -| app/assets/stylesheets/comment.css.scss | Cascading style sheet for the controller | +| File/Directory | Purpose | +| -------------------------------------------- | ---------------------------------------- | +| app/controllers/comments_controller.rb | The Comments controller | +| app/views/comments/ | Views of the controller are stored here | +| test/controllers/comments_controller_test.rb | The test for the controller | +| app/helpers/comments_helper.rb | A view helper file | +| test/helpers/comments_helper_test.rb | The test for the helper | +| app/assets/javascripts/comment.js.coffee | CoffeeScript for the controller | +| app/assets/stylesheets/comment.css.scss | Cascading style sheet for the controller | Like with any blog, our readers will create their comments directly after reading the post, and once they have added their comment, will be sent back to diff --git a/guides/source/initialization.md b/guides/source/initialization.md index c760a18dda..c582acd3a3 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -367,7 +367,7 @@ on your needs. ### `Rails::Server#start` -After `congif/application` is loaded, `server.start` is called. This method is defined like this: +After `config/application` is loaded, `server.start` is called. This method is defined like this: ```ruby def start 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 f02e86b420..67538b85e8 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -332,7 +332,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/testing.md b/guides/source/testing.md index 2680525928..b45aba8d55 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -121,18 +121,18 @@ For this guide we will be using Rails _scaffolding_. It will create the model, a NOTE: For more information on Rails <i>scaffolding</i>, refer to [Getting Started with Rails](getting_started.html) -When you use `rails generate scaffold`, for a resource among other things it creates a test stub in the `test/unit` folder: +When you use `rails generate scaffold`, for a resource among other things it creates a test stub in the `test/models` folder: ```bash $ rails generate scaffold post title:string body:text ... create app/models/post.rb -create test/unit/post_test.rb +create test/models/post_test.rb create test/fixtures/posts.yml ... ``` -The default test stub in `test/unit/post_test.rb` looks like this: +The default test stub in `test/models/post_test.rb` looks like this: ```ruby require 'test_helper' @@ -225,9 +225,9 @@ TIP: You can see all these rake tasks and their descriptions by running `rake -- Running a test is as simple as invoking the file containing the test cases through Ruby: ```bash -$ ruby -Itest test/unit/post_test.rb +$ ruby -Itest test/models/post_test.rb -Loaded suite unit/post_test +Loaded suite models/post_test Started . Finished in 0.023513 seconds. @@ -240,9 +240,9 @@ This will run all the test methods from the test case. Note that `test_helper.rb You can also run a particular test method from the test case by using the `-n` switch with the `test method name`. ```bash -$ ruby -Itest test/unit/post_test.rb -n test_the_truth +$ ruby -Itest test/models/post_test.rb -n test_the_truth -Loaded suite unit/post_test +Loaded suite models/post_test Started . Finished in 0.023513 seconds. @@ -271,7 +271,7 @@ F Finished in 0.102072 seconds. 1) Failure: -test_should_not_save_post_without_title(PostTest) [/test/unit/post_test.rb:6]: +test_should_not_save_post_without_title(PostTest) [/test/models/post_test.rb:6]: <false> is not true. 1 tests, 1 assertions, 1 failures, 0 errors @@ -290,7 +290,7 @@ Running this test shows the friendlier assertion message: ```bash 1) Failure: -test_should_not_save_post_without_title(PostTest) [/test/unit/post_test.rb:6]: +test_should_not_save_post_without_title(PostTest) [/test/models/post_test.rb:6]: Saved the post without a title. <false> is not true. ``` @@ -341,7 +341,7 @@ Finished in 0.082603 seconds. 1) Error: test_should_report_error(PostTest): NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x249d354> - /test/unit/post_test.rb:6:in `test_should_report_error' + /test/models/post_test.rb:6:in `test_should_report_error' 1 tests, 0 assertions, 0 failures, 1 errors ``` @@ -420,7 +420,7 @@ You should test for things such as: * was the correct object stored in the response template? * was the appropriate message displayed to the user in the view? -Now that we have used Rails scaffold generator for our `Post` resource, it has already created the controller code and functional tests. You can take look at the file `posts_controller_test.rb` in the `test/functional` directory. +Now that we have used Rails scaffold generator for our `Post` resource, it has already created the controller code and tests. You can take look at the file `posts_controller_test.rb` in the `test/controllers` directory. Let me take you through one such test, `test_should_get_index` from the file `posts_controller_test.rb`. @@ -762,12 +762,16 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai | ------------------------------- | ----------- | | `rake test` | Runs all unit, functional and integration tests. You can also simply run `rake` as the _test_ target is the default.| | `rake test:benchmark` | Benchmark the performance tests| -| `rake test:functionals` | Runs all the functional tests from `test/functional`| +| `rake test:controllers` | Runs all the controller tests from `test/controllers`| +| `rake test:functionals` | Runs all the functional tests from `test/controllers`, `test/mailers`, and `test/functional`| +| `rake test:helpers` | Runs all the helper tests from `test/helpers`| | `rake test:integration` | Runs all the integration tests from `test/integration`| +| `rake test:mailers` | Runs all the mailer tests from `test/mailers`| +| `rake test:models` | Runs all the model tests from `test/models`| | `rake test:profile` | Profile the performance tests| | `rake test:recent` | Tests recent changes| | `rake test:uncommitted` | Runs all the tests which are uncommitted. Supports Subversion and Git| -| `rake test:units` | Runs all the unit tests from `test/unit`| +| `rake test:units` | Runs all the unit tests from `test/models`, `test/helpers`, and `test/unit`| Brief Note About `Test::Unit` diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 68fe509a33..148316d170 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..c9fa541db8 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,7 +1,17 @@ ## Rails 4.0.0 (unreleased) ## +* New test locations `test/models`, `test/helpers`, `test/controllers`, and + `test/mailers`. Corresponding rake tasks added as well. *Mike Moore* + +* Set a different cache per environment for assets pipeline + through `config.assets.cache`. + + *Guillermo Iguaran* + +* `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. + This option is now unsupported in sprockets-rails. *Guillermo Iguaran & Dmitry Vorotilin* @@ -10,7 +20,7 @@ *Guillermo Iguaran* -* Remove `config.active_record.whitelist_attributes` and +* Remove `config.active_record.whitelist_attributes` and `config.active_record.mass_assignment_sanitizer` from new applications since MassAssignmentSecurity has been extracted from 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 050190cba6..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 @@ -282,6 +291,21 @@ module Rails ActionDispatch::MiddlewareStack.new.tap do |middleware| app = self if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache + begin + require 'rack/cache' + rescue LoadError => error + 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/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 613c5b25f0..a7a35c2685 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -57,7 +57,7 @@ module Rails @assets.debug = false @assets.compile = true @assets.digest = false - @assets.cache_store = [ :file_store, "#{root}/tmp/cache/assets/" ] + @assets.cache_store = [ :file_store, "#{root}/tmp/cache/assets/#{Rails.env}/" ] @assets.js_compressor = nil @assets.css_compressor = nil @assets.initialize_on_precompile = true diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 4829f3fd45..e761e26b04 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/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 417bddaa9d..67e7c4d54f 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -113,9 +113,11 @@ module Rails def test empty_directory_with_keep_file 'test/fixtures' - empty_directory_with_keep_file 'test/functional' + empty_directory_with_keep_file 'test/controllers' + empty_directory_with_keep_file 'test/mailers' + empty_directory_with_keep_file 'test/models' + empty_directory_with_keep_file 'test/helpers' empty_directory_with_keep_file 'test/integration' - empty_directory_with_keep_file 'test/unit' template 'test/performance/browsing_test.rb' template 'test/test_helper.rb' diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 55a6b3f4f2..30f8a5f75e 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -12,7 +12,7 @@ source 'https://rubygems.org' # 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: https://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 ddfdd9720d..74457b0efd 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..a8285f88ca --- /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.application.config.time_zone = 'Central Time (US & Canada)' + +# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. +# Rails.application.config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] +# Rails.application.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/generators/rails/controller/USAGE b/railties/lib/rails/generators/rails/controller/USAGE index b658777b12..9def4af65c 100644 --- a/railties/lib/rails/generators/rails/controller/USAGE +++ b/railties/lib/rails/generators/rails/controller/USAGE @@ -12,7 +12,7 @@ Example: `rails generate controller CreditCards open debit credit close` CreditCards controller with URLs like /credit_cards/debit. - Controller: app/controllers/credit_cards_controller.rb - Functional Test: test/functional/credit_cards_controller_test.rb - Views: app/views/credit_cards/debit.html.erb [...] - Helper: app/helpers/credit_cards_helper.rb + Controller: app/controllers/credit_cards_controller.rb + Test: test/controllers/credit_cards_controller_test.rb + Views: app/views/credit_cards/debit.html.erb [...] + Helper: app/helpers/credit_cards_helper.rb diff --git a/railties/lib/rails/generators/rails/helper/USAGE b/railties/lib/rails/generators/rails/helper/USAGE index c0ddb0f606..30e323a858 100644 --- a/railties/lib/rails/generators/rails/helper/USAGE +++ b/railties/lib/rails/generators/rails/helper/USAGE @@ -13,5 +13,5 @@ Example: Credit card helper. Helper: app/helpers/credit_card_helper.rb - Test: test/unit/helpers/credit_card_helper_test.rb + Test: test/helpers/credit_card_helper_test.rb diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE index c46c86076e..e29e19490e 100644 --- a/railties/lib/rails/generators/rails/model/USAGE +++ b/railties/lib/rails/generators/rails/model/USAGE @@ -74,7 +74,7 @@ Examples: For ActiveRecord and TestUnit it creates: Model: app/models/account.rb - Test: test/unit/account_test.rb + Test: test/models/account_test.rb Fixtures: test/fixtures/accounts.yml Migration: db/migrate/XXX_add_accounts.rb @@ -88,7 +88,7 @@ Examples: Module: app/models/admin.rb Model: app/models/admin/account.rb - Test: test/unit/admin/account_test.rb + Test: test/models/admin/account_test.rb Fixtures: test/fixtures/admin/accounts.yml Migration: db/migrate/XXX_add_admin_accounts.rb diff --git a/railties/lib/rails/generators/rails/observer/USAGE b/railties/lib/rails/generators/rails/observer/USAGE index d8f32a6a48..177ff49e4a 100644 --- a/railties/lib/rails/generators/rails/observer/USAGE +++ b/railties/lib/rails/generators/rails/observer/USAGE @@ -9,4 +9,4 @@ Example: For ActiveRecord and TestUnit it creates: Observer: app/models/account_observer.rb - TestUnit: test/unit/account_observer_test.rb + TestUnit: test/models/account_observer_test.rb diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb index d5c2cf4c93..121205b254 100644 --- a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb +++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb @@ -1,13 +1,50 @@ module Rails module Generators class ResourceRouteGenerator < NamedBase # :nodoc: + + # Properly nests namespaces passed into a generator + # + # $ rails generate resource admin/users/products + # + # should give you + # + # namespace :admin do + # namespace :users + # resources :products + # end + # end def add_resource_route return if options[:actions].present? - route_config = regular_class_path.collect{ |namespace| "namespace :#{namespace} do " }.join(" ") - route_config << "resources :#{file_name.pluralize}" - route_config << " end" * regular_class_path.size - route route_config + + # iterates over all namespaces and opens up blocks + regular_class_path.each_with_index do |namespace, index| + write("namespace :#{namespace} do", index + 1) + end + + # inserts the primary resource + write("resources :#{file_name.pluralize}", route_length + 1) + + # ends blocks + regular_class_path.each_index do |index| + write("end", route_length - index) + end + + # route prepends two spaces onto the front of the string that is passed, this corrects that + route route_string[2..-1] end + + private + def route_string + @route_string ||= "" + end + + def write(str, indent) + route_string << "#{" " * indent}#{str}\n" + end + + def route_length + regular_class_path.length + end end end end diff --git a/railties/lib/rails/generators/rails/scaffold_controller/USAGE b/railties/lib/rails/generators/rails/scaffold_controller/USAGE index 5cd51b62d4..8ba4c5ccbc 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/USAGE +++ b/railties/lib/rails/generators/rails/scaffold_controller/USAGE @@ -13,7 +13,7 @@ Example: `rails generate scaffold_controller CreditCard` Credit card controller with URLs like /credit_card/debit. - Controller: app/controllers/credit_cards_controller.rb - Functional Test: test/functional/credit_cards_controller_test.rb - Views: app/views/credit_cards/index.html.erb [...] - Helper: app/helpers/credit_cards_helper.rb + Controller: app/controllers/credit_cards_controller.rb + Test: test/controllers/credit_cards_controller_test.rb + Views: app/views/credit_cards/index.html.erb [...] + Helper: app/helpers/credit_cards_helper.rb diff --git a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb index 2611d61b7a..c53930f994 100644 --- a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb +++ b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb @@ -8,7 +8,7 @@ module TestUnit # :nodoc: def create_test_files template 'functional_test.rb', - File.join('test/functional', class_path, "#{file_name}_controller_test.rb") + File.join('test/controllers', class_path, "#{file_name}_controller_test.rb") end end end diff --git a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb index 44de759a5b..bcd370098e 100644 --- a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb +++ b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb @@ -6,7 +6,7 @@ module TestUnit # :nodoc: check_class_collision :suffix => "HelperTest" def create_helper_files - template 'helper_test.rb', File.join('test/unit/helpers', class_path, "#{file_name}_helper_test.rb") + template 'helper_test.rb', File.join('test/helpers', class_path, "#{file_name}_helper_test.rb") end end end diff --git a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb index ddc639ae7e..570a733227 100644 --- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb +++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb @@ -7,7 +7,7 @@ module TestUnit # :nodoc: check_class_collision :suffix => "Test" def create_test_files - template "functional_test.rb", File.join('test/functional', class_path, "#{file_name}_test.rb") + template "functional_test.rb", File.join('test/mailers', class_path, "#{file_name}_test.rb") end end end diff --git a/railties/lib/rails/generators/test_unit/model/model_generator.rb b/railties/lib/rails/generators/test_unit/model/model_generator.rb index f373f43f11..9b73f3561f 100644 --- a/railties/lib/rails/generators/test_unit/model/model_generator.rb +++ b/railties/lib/rails/generators/test_unit/model/model_generator.rb @@ -9,7 +9,7 @@ module TestUnit # :nodoc: check_class_collision :suffix => "Test" def create_test_file - template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb") + template 'unit_test.rb', File.join('test/models', class_path, "#{file_name}_test.rb") end hook_for :fixture_replacement diff --git a/railties/lib/rails/generators/test_unit/observer/observer_generator.rb b/railties/lib/rails/generators/test_unit/observer/observer_generator.rb index f9b0234b11..8bfb749743 100644 --- a/railties/lib/rails/generators/test_unit/observer/observer_generator.rb +++ b/railties/lib/rails/generators/test_unit/observer/observer_generator.rb @@ -6,7 +6,7 @@ module TestUnit # :nodoc: check_class_collision :suffix => "ObserverTest" def create_test_files - template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_observer_test.rb") + template 'unit_test.rb', File.join('test/models', class_path, "#{file_name}_observer_test.rb") end end end diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index a9946b9791..0462c15224 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -12,7 +12,7 @@ module TestUnit # :nodoc: def create_test_files template "functional_test.rb", - File.join("test/functional", controller_class_path, "#{controller_file_name}_controller_test.rb") + File.join("test/controllers", controller_class_path, "#{controller_file_name}_controller_test.rb") end private 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/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake index 67a6d2d2ac..c1674c72ad 100644 --- a/railties/lib/rails/tasks/statistics.rake +++ b/railties/lib/rails/tasks/statistics.rake @@ -6,9 +6,13 @@ STATS_DIRECTORIES = [ %w(Javascripts app/assets/javascripts), %w(Libraries lib/), %w(APIs app/apis), + %w(Controller\ tests test/controllers), + %w(Helper\ tests test/helpers), + %w(Model\ tests test/models), + %w(Mailer\ tests test/mailers), %w(Integration\ tests test/integration), - %w(Functional\ tests test/functional), - %w(Unit\ tests test/unit) + %w(Functional\ tests\ (old) test/functional), + %w(Unit\ tests \ (old) test/unit) ].collect { |name, dir| [ name, "#{Rails.root}/#{dir}" ] }.select { |name, dir| File.directory?(dir) } desc "Report code statistics (KLOCs, etc) from the application" diff --git a/railties/lib/rails/tasks/tmp.rake b/railties/lib/rails/tasks/tmp.rake index 0968765b4f..0bb64ced95 100644 --- a/railties/lib/rails/tasks/tmp.rake +++ b/railties/lib/rails/tasks/tmp.rake @@ -6,7 +6,9 @@ namespace :tmp do 'tmp/cache', 'tmp/sockets', 'tmp/pids', - 'tmp/cache/assets' ] + 'tmp/cache/assets/development', + 'tmp/cache/assets/test', + 'tmp/cache/assets/production' ] tmp_dirs.each { |d| directory d } 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/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 0de4afe905..63cb955d44 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -15,11 +15,11 @@ def recent_tests(source_pattern, test_path, touched_since = 10.minutes.ago) # Support subdirs in app/models and app/controllers modified_test_path = source_dir.length > 2 ? "#{test_path}/" << source_dir[1..source_dir.length].join('/') : test_path - # For modified files in app/ run the tests for it. ex. /test/functional/account_controller.rb + # For modified files in app/ run the tests for it. ex. /test/controllers/account_controller.rb test = "#{modified_test_path}/#{source_file}_test.rb" tests.push test if File.exist?(test) - # For modified files in app, run tests in subdirs too. ex. /test/functional/account/*_test.rb + # For modified files in app, run tests in subdirs too. ex. /test/controllers/account/*_test.rb test = "#{modified_test_path}/#{File.basename(path, '.rb').sub("_controller","")}" FileList["#{test}/*_test.rb"].each { |f| tests.push f } if File.exist?(test) @@ -74,7 +74,9 @@ namespace :test do Rake::TestTask.new(:recent => "test:prepare") do |t| since = TEST_CHANGES_SINCE touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } + + recent_tests('app/models/**/*.rb', 'test/models', since) + recent_tests('app/models/**/*.rb', 'test/unit', since) + + recent_tests('app/controllers/**/*.rb', 'test/controllers', since) + recent_tests('app/controllers/**/*.rb', 'test/functional', since) t.libs << 'test' @@ -95,8 +97,10 @@ namespace :test do models = changed_since_checkin.select { |path| path =~ /app[\\\/]models[\\\/].*\.rb$/ } controllers = changed_since_checkin.select { |path| path =~ /app[\\\/]controllers[\\\/].*\.rb$/ } - unit_tests = models.map { |model| "test/unit/#{File.basename(model, '.rb')}_test.rb" } - functional_tests = controllers.map { |controller| "test/functional/#{File.basename(controller, '.rb')}_test.rb" } + unit_tests = models.map { |model| "test/models/#{File.basename(model, '.rb')}_test.rb" } + + models.map { |model| "test/unit/#{File.basename(model, '.rb')}_test.rb" } + + functional_tests = controllers.map { |controller| "test/controllers/#{File.basename(controller, '.rb')}_test.rb" } + + controllers.map { |controller| "test/functional/#{File.basename(controller, '.rb')}_test.rb" } (unit_tests + functional_tests).uniq.select { |file| File.exist?(file) } end @@ -108,14 +112,34 @@ namespace :test do t.libs << "test" end + Rails::SubTestTask.new(:models => "test:prepare") do |t| + t.libs << "test" + t.pattern = 'test/models/**/*_test.rb' + end + + Rails::SubTestTask.new(:helpers => "test:prepare") do |t| + t.libs << "test" + t.pattern = 'test/helpers/**/*_test.rb' + end + Rails::SubTestTask.new(:units => "test:prepare") do |t| t.libs << "test" - t.pattern = 'test/unit/**/*_test.rb' + t.pattern = 'test/{models,helpers,unit}/**/*_test.rb' + end + + Rails::SubTestTask.new(:controllers => "test:prepare") do |t| + t.libs << "test" + t.pattern = 'test/controllers/**/*_test.rb' + end + + Rails::SubTestTask.new(:mailers => "test:prepare") do |t| + t.libs << "test" + t.pattern = 'test/mailers/**/*_test.rb' end Rails::SubTestTask.new(:functionals => "test:prepare") do |t| t.libs << "test" - t.pattern = 'test/functional/**/*_test.rb' + t.pattern = 'test/{controllers,mailers,functional}/**/*_test.rb' end Rails::SubTestTask.new(:integration => "test:prepare") do |t| diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb index ecacb34cb2..ebafc7d670 100644 --- a/railties/test/application/asset_debugging_test.rb +++ b/railties/test/application/asset_debugging_test.rb @@ -50,7 +50,7 @@ module ApplicationTests end test "assets aren't concatened when compile is true is on and debug_assets params is true" do - app_file "config/initializers/compile.rb", "Rails.application.config.assets.compile = true" + add_to_env_config "production", "config.assets.compile = true" ENV["RAILS_ENV"] = "production" require "#{app_path}/config/environment" diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 6e9ae6b74e..be97855e1a 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -323,7 +323,8 @@ module ApplicationTests clean_assets! - files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/assets/*"] + files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/assets/development/*", + "#{app_path}/tmp/cache/assets/test/*", "#{app_path}/tmp/cache/assets/production/*"] assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}" end @@ -395,21 +396,6 @@ module ApplicationTests assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body) end - test "assets aren't concatened when compile is true is on and debug_assets params is true" do - app_with_assets_in_view - add_to_env_config "production", "config.assets.compile = true" - add_to_env_config "production", "config.assets.allow_debugging = true" - - ENV["RAILS_ENV"] = "production" - require "#{app_path}/config/environment" - - class ::PostsController < ActionController::Base ; end - - get '/posts?debug_assets=true' - assert_match(/<script src="\/assets\/application-([0-z]+)\.js\?body=1"><\/script>/, last_response.body) - assert_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js\?body=1"><\/script>/, last_response.body) - end - test "assets can access model information when precompiling" do app_file "app/models/post.rb", "class Post; end" app_file "app/assets/javascripts/application.js", "//= require_tree ." 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/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index b05fe3aed5..2e7426150c 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -86,12 +86,12 @@ module ApplicationTests def test_rake_test_error_output Dir.chdir(app_path){ `rake db:migrate` } - app_file "test/unit/one_unit_test.rb", <<-RUBY - raise 'unit' + app_file "test/models/one_model_test.rb", <<-RUBY + raise 'models' RUBY - app_file "test/functional/one_functional_test.rb", <<-RUBY - raise 'functional' + app_file "test/controllers/one_controller_test.rb", <<-RUBY + raise 'controllers' RUBY app_file "test/integration/one_integration_test.rb", <<-RUBY @@ -100,8 +100,8 @@ module ApplicationTests silence_stderr do output = Dir.chdir(app_path) { `rake test 2>&1` } - assert_match 'unit', output - assert_match 'functional', output + assert_match 'models', output + assert_match 'controllers', output assert_match 'integration', output end end diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb index 91ede1cb68..69e89d87ae 100644 --- a/railties/test/commands/console_test.rb +++ b/railties/test/commands/console_test.rb @@ -68,40 +68,40 @@ class Rails::ConsoleTest < ActiveSupport::TestCase def test_default_environment_with_no_rails_env with_rails_env nil do start - assert_match /\sdevelopment\s/, output + assert_match(/\sdevelopment\s/, output) end end def test_default_environment_with_rails_env with_rails_env 'special-production' do start - assert_match /\sspecial-production\s/, output + assert_match(/\sspecial-production\s/, output) end end def test_e_option start ['-e', 'special-production'] - assert_match /\sspecial-production\s/, output + assert_match(/\sspecial-production\s/, output) end def test_environment_option start ['--environment=special-production'] - assert_match /\sspecial-production\s/, output + assert_match(/\sspecial-production\s/, output) end def test_rails_env_is_production_when_first_argument_is_p start ['p'] - assert_match /\sproduction\s/, output + assert_match(/\sproduction\s/, output) end def test_rails_env_is_test_when_first_argument_is_t start ['t'] - assert_match /\stest\s/, output + assert_match(/\stest\s/, output) end def test_rails_env_is_development_when_argument_is_d start ['d'] - assert_match /\sdevelopment\s/, output + assert_match(/\sdevelopment\s/, output) end private diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 856a0e163b..91575a38b6 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -26,10 +26,12 @@ DEFAULT_APP_FILES = %w( log script/rails test/fixtures - test/functional + test/controllers + test/models + test/helpers + test/mailers test/integration test/performance - test/unit vendor vendor/assets tmp/cache @@ -300,6 +302,11 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile", /# gem 'debugger'/ end + def test_inclusion_of_rack_cache + run_generator + assert_file "Gemfile", /gem 'rack-cache'/ + end + def test_template_from_dir_pwd FileUtils.cd(Rails.root) assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"])) diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index c3fa9ebb03..5205deafd9 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -28,13 +28,13 @@ class ControllerGeneratorTest < Rails::Generators::TestCase def test_invokes_helper run_generator assert_file "app/helpers/account_helper.rb" - assert_file "test/unit/helpers/account_helper_test.rb" + assert_file "test/helpers/account_helper_test.rb" end def test_does_not_invoke_helper_if_required run_generator ["account", "--skip-helper"] assert_no_file "app/helpers/account_helper.rb" - assert_no_file "test/unit/helpers/account_helper_test.rb" + assert_no_file "test/helpers/account_helper_test.rb" end def test_invokes_assets @@ -45,12 +45,12 @@ class ControllerGeneratorTest < Rails::Generators::TestCase def test_invokes_default_test_framework run_generator - assert_file "test/functional/account_controller_test.rb" + assert_file "test/controllers/account_controller_test.rb" end def test_does_not_invoke_test_framework_if_required run_generator ["account", "--no-test-framework"] - assert_no_file "test/functional/account_controller_test.rb" + assert_no_file "test/controllers/account_controller_test.rb" end def test_invokes_default_template_engine diff --git a/railties/test/generators/helper_generator_test.rb b/railties/test/generators/helper_generator_test.rb index 8da3aa61a4..81d4fcb129 100644 --- a/railties/test/generators/helper_generator_test.rb +++ b/railties/test/generators/helper_generator_test.rb @@ -15,7 +15,7 @@ class HelperGeneratorTest < Rails::Generators::TestCase def test_invokes_default_test_framework run_generator - assert_file "test/unit/helpers/admin_helper_test.rb", /class AdminHelperTest < ActionView::TestCase/ + assert_file "test/helpers/admin_helper_test.rb", /class AdminHelperTest < ActionView::TestCase/ end def test_logs_if_the_test_framework_cannot_be_found diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index c501780e7f..6b2351fc1a 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -29,7 +29,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase def test_invokes_default_test_framework run_generator - assert_file "test/functional/notifier_test.rb" do |test| + assert_file "test/mailers/notifier_test.rb" do |test| assert_match(/class NotifierTest < ActionMailer::TestCase/, test) assert_match(/test "foo"/, test) assert_match(/test "bar"/, test) diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 436de26826..a90ad5cde0 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -157,7 +157,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_match(/create_table :products/, up) assert_match(/t\.string :name/, up) assert_match(/t\.integer :supplier_id/, up) - + assert_match(/add_index :products, :name/, up) assert_match(/add_index :products, :supplier_id/, up) assert_no_match(/add_index :products, :year/, up) @@ -181,7 +181,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_match(/add_index :products, :discount, unique: true/, content) end end - + def test_migration_without_timestamps ActiveRecord::Base.timestamped_migrations = false run_generator ["account"] @@ -269,7 +269,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_invokes_default_test_framework run_generator - assert_file "test/unit/account_test.rb", /class AccountTest < ActiveSupport::TestCase/ + assert_file "test/models/account_test.rb", /class AccountTest < ActiveSupport::TestCase/ assert_file "test/fixtures/accounts.yml", /name: MyString/, /age: 1/ end diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index db2b8af217..d48712e51f 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -25,7 +25,7 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase /module TestApp/, / class AccountController < ApplicationController/ - assert_file "test/functional/test_app/account_controller_test.rb", + assert_file "test/controllers/test_app/account_controller_test.rb", /module TestApp/, / class AccountControllerTest/ end @@ -46,12 +46,12 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase def test_helpr_is_also_namespaced run_generator assert_file "app/helpers/test_app/account_helper.rb", /module TestApp/, / module AccountHelper/ - assert_file "test/unit/helpers/test_app/account_helper_test.rb", /module TestApp/, / class AccountHelperTest/ + assert_file "test/helpers/test_app/account_helper_test.rb", /module TestApp/, / class AccountHelperTest/ end def test_invokes_default_test_framework run_generator - assert_file "test/functional/test_app/account_controller_test.rb" + assert_file "test/controllers/test_app/account_controller_test.rb" end def test_invokes_default_template_engine @@ -136,7 +136,7 @@ class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase def test_invokes_default_test_framework run_generator - assert_file "test/unit/test_app/account_test.rb", /module TestApp/, /class AccountTest < ActiveSupport::TestCase/ + assert_file "test/models/test_app/account_test.rb", /module TestApp/, /class AccountTest < ActiveSupport::TestCase/ assert_file "test/fixtures/test_app/accounts.yml", /name: MyString/, /age: 1/ end end @@ -158,7 +158,7 @@ class NamespacedObserverGeneratorTest < NamespacedGeneratorTestCase def test_invokes_default_test_framework run_generator - assert_file "test/unit/test_app/account_observer_test.rb", /module TestApp/, / class AccountObserverTest < ActiveSupport::TestCase/ + assert_file "test/models/test_app/account_observer_test.rb", /module TestApp/, / class AccountObserverTest < ActiveSupport::TestCase/ end end @@ -186,7 +186,7 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase def test_invokes_default_test_framework run_generator - assert_file "test/functional/test_app/notifier_test.rb" do |test| + assert_file "test/mailers/test_app/notifier_test.rb" do |test| assert_match(/module TestApp/, test) assert_match(/class NotifierTest < ActionMailer::TestCase/, test) assert_match(/test "foo"/, test) @@ -225,7 +225,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Model assert_file "app/models/test_app/product_line.rb", /module TestApp\n class ProductLine < ActiveRecord::Base/ - assert_file "test/unit/test_app/product_line_test.rb", /module TestApp\n class ProductLineTest < ActiveSupport::TestCase/ + assert_file "test/models/test_app/product_line_test.rb", /module TestApp\n class ProductLineTest < ActiveSupport::TestCase/ assert_file "test/fixtures/test_app/product_lines.yml" assert_migration "db/migrate/create_test_app_product_lines.rb" @@ -240,7 +240,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase /module TestApp/, /class ProductLinesController < ApplicationController/ - assert_file "test/functional/test_app/product_lines_controller_test.rb", + assert_file "test/controllers/test_app/product_lines_controller_test.rb", /module TestApp\n class ProductLinesControllerTest < ActionController::TestCase/ # Views @@ -255,7 +255,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Helpers assert_file "app/helpers/test_app/product_lines_helper.rb" - assert_file "test/unit/helpers/test_app/product_lines_helper_test.rb" + assert_file "test/helpers/test_app/product_lines_helper_test.rb" # Stylesheets assert_file "app/assets/stylesheets/scaffold.css" @@ -267,7 +267,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Model assert_no_file "app/models/test_app/product_line.rb" - assert_no_file "test/unit/test_app/product_line_test.rb" + assert_no_file "test/models/test_app/product_line_test.rb" assert_no_file "test/fixtures/test_app/product_lines.yml" assert_no_migration "db/migrate/create_test_app_product_lines.rb" @@ -278,7 +278,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Controller assert_no_file "app/controllers/test_app/product_lines_controller.rb" - assert_no_file "test/functional/test_app/product_lines_controller_test.rb" + assert_no_file "test/controllers/test_app/product_lines_controller_test.rb" # Views assert_no_file "app/views/test_app/product_lines" @@ -286,7 +286,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Helpers assert_no_file "app/helpers/test_app/product_lines_helper.rb" - assert_no_file "test/unit/helpers/test_app/product_lines_helper_test.rb" + assert_no_file "test/helpers/test_app/product_lines_helper_test.rb" # Stylesheets (should not be removed) assert_file "app/assets/stylesheets/scaffold.css" @@ -298,13 +298,13 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Model assert_file "app/models/test_app/admin.rb", /module TestApp\n module Admin/ assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ActiveRecord::Base/ - assert_file "test/unit/test_app/admin/role_test.rb", /module TestApp\n class Admin::RoleTest < ActiveSupport::TestCase/ + assert_file "test/models/test_app/admin/role_test.rb", /module TestApp\n class Admin::RoleTest < ActiveSupport::TestCase/ assert_file "test/fixtures/test_app/admin/roles.yml" assert_migration "db/migrate/create_test_app_admin_roles.rb" # Route assert_file "config/routes.rb" do |route| - assert_match(/namespace :admin do resources :roles end$/, route) + assert_match(/^ namespace :admin do\n resources :roles\n end$/, route) end # Controller @@ -312,7 +312,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase assert_match(/module TestApp\n class Admin::RolesController < ApplicationController/, content) end - assert_file "test/functional/test_app/admin/roles_controller_test.rb", + assert_file "test/controllers/test_app/admin/roles_controller_test.rb", /module TestApp\n class Admin::RolesControllerTest < ActionController::TestCase/ # Views @@ -327,7 +327,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Helpers assert_file "app/helpers/test_app/admin/roles_helper.rb" - assert_file "test/unit/helpers/test_app/admin/roles_helper_test.rb" + assert_file "test/helpers/test_app/admin/roles_helper_test.rb" # Stylesheets assert_file "app/assets/stylesheets/scaffold.css" @@ -340,18 +340,18 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Model assert_file "app/models/test_app/admin.rb" # ( should not be remove ) assert_no_file "app/models/test_app/admin/role.rb" - assert_no_file "test/unit/test_app/admin/role_test.rb" + assert_no_file "test/models/test_app/admin/role_test.rb" assert_no_file "test/fixtures/test_app/admin/roles.yml" assert_no_migration "db/migrate/create_test_app_admin_roles.rb" # Route assert_file "config/routes.rb" do |route| - assert_no_match(/namespace :admin do resources :roles end$/, route) + assert_no_match(/^ namespace :admin do\n resources :roles\n end$$/, route) end # Controller assert_no_file "app/controllers/test_app/admin/roles_controller.rb" - assert_no_file "test/functional/test_app/admin/roles_controller_test.rb" + assert_no_file "test/controllers/test_app/admin/roles_controller_test.rb" # Views assert_no_file "app/views/test_app/admin/roles" @@ -359,7 +359,79 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Helpers assert_no_file "app/helpers/test_app/admin/roles_helper.rb" - assert_no_file "test/unit/helpers/test_app/admin/roles_helper_test.rb" + assert_no_file "test/helpers/test_app/admin/roles_helper_test.rb" + + # Stylesheets (should not be removed) + assert_file "app/assets/stylesheets/scaffold.css" + end + + def test_scaffold_with_nested_namespace_on_invoke + run_generator [ "admin/user/special/role", "name:string", "description:string" ] + + # Model + assert_file "app/models/test_app/admin/user/special.rb", /module TestApp\n module Admin/ + assert_file "app/models/test_app/admin/user/special/role.rb", /module TestApp\n class Admin::User::Special::Role < ActiveRecord::Base/ + assert_file "test/models/test_app/admin/user/special/role_test.rb", /module TestApp\n class Admin::User::Special::RoleTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/test_app/admin/user/special/roles.yml" + assert_migration "db/migrate/create_test_app_admin_user_special_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/^ namespace :admin do\n namespace :user do\n namespace :special do\n resources :roles\n end\n end\n end$/, route) + end + + # Controller + assert_file "app/controllers/test_app/admin/user/special/roles_controller.rb" do |content| + assert_match(/module TestApp\n class Admin::User::Special::RolesController < ApplicationController/, content) + end + + assert_file "test/controllers/test_app/admin/user/special/roles_controller_test.rb", + /module TestApp\n class Admin::User::Special::RolesControllerTest < ActionController::TestCase/ + + # Views + %w( + index + edit + new + show + _form + ).each { |view| assert_file "app/views/test_app/admin/user/special/roles/#{view}.html.erb" } + assert_no_file "app/views/layouts/admin/user/special/roles.html.erb" + + # Helpers + assert_file "app/helpers/test_app/admin/user/special/roles_helper.rb" + assert_file "test/helpers/test_app/admin/user/special/roles_helper_test.rb" + + # Stylesheets + assert_file "app/assets/stylesheets/scaffold.css" + end + + def test_scaffold_with_nested_namespace_on_revoke + run_generator [ "admin/user/special/role", "name:string", "description:string" ] + run_generator [ "admin/user/special/role" ], :behavior => :revoke + + # Model + assert_file "app/models/test_app/admin/user/special.rb" # ( should not be remove ) + assert_no_file "app/models/test_app/admin/user/special/role.rb" + assert_no_file "test/models/test_app/admin/user/special/role_test.rb" + assert_no_file "test/fixtures/test_app/admin/user/special/roles.yml" + assert_no_migration "db/migrate/create_test_app_admin_user_special_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_no_match(/^ namespace :admin do\n namespace :user do\n namespace :special do\n resources :roles\n end\n end\n end$/, route) + end + + # Controller + assert_no_file "app/controllers/test_app/admin/user/special/roles_controller.rb" + assert_no_file "test/controllers/test_app/admin/user/special/roles_controller_test.rb" + + # Views + assert_no_file "app/views/test_app/admin/user/special/roles" + + # Helpers + assert_no_file "app/helpers/test_app/admin/user/special/roles_helper.rb" + assert_no_file "test/helpers/test_app/admin/user/special/roles_helper_test.rb" # Stylesheets (should not be removed) assert_file "app/assets/stylesheets/scaffold.css" diff --git a/railties/test/generators/observer_generator_test.rb b/railties/test/generators/observer_generator_test.rb index afcee0a2dc..1231827466 100644 --- a/railties/test/generators/observer_generator_test.rb +++ b/railties/test/generators/observer_generator_test.rb @@ -17,7 +17,7 @@ class ObserverGeneratorTest < Rails::Generators::TestCase def test_invokes_default_test_framework run_generator - assert_file "test/unit/account_observer_test.rb", /class AccountObserverTest < ActiveSupport::TestCase/ + assert_file "test/models/account_observer_test.rb", /class AccountObserverTest < ActiveSupport::TestCase/ end def test_logs_if_the_test_framework_cannot_be_found diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb index 73804dae45..0ae0841442 100644 --- a/railties/test/generators/resource_generator_test.rb +++ b/railties/test/generators/resource_generator_test.rb @@ -18,7 +18,7 @@ class ResourceGeneratorTest < Rails::Generators::TestCase %w( app/models/account.rb - test/unit/account_test.rb + test/models/account_test.rb test/fixtures/accounts.yml ).each { |path| assert_file path } @@ -33,10 +33,10 @@ class ResourceGeneratorTest < Rails::Generators::TestCase def test_resource_controller_with_pluralized_class_name run_generator assert_file "app/controllers/accounts_controller.rb", /class AccountsController < ApplicationController/ - assert_file "test/functional/accounts_controller_test.rb", /class AccountsControllerTest < ActionController::TestCase/ + assert_file "test/controllers/accounts_controller_test.rb", /class AccountsControllerTest < ActionController::TestCase/ assert_file "app/helpers/accounts_helper.rb", /module AccountsHelper/ - assert_file "test/unit/helpers/accounts_helper_test.rb", /class AccountsHelperTest < ActionView::TestCase/ + assert_file "test/helpers/accounts_helper_test.rb", /class AccountsHelperTest < ActionView::TestCase/ end def test_resource_controller_with_actions @@ -62,14 +62,14 @@ class ResourceGeneratorTest < Rails::Generators::TestCase def test_plural_names_are_singularized content = run_generator ["accounts".freeze] assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/ - assert_file "test/unit/account_test.rb", /class AccountTest/ + assert_file "test/models/account_test.rb", /class AccountTest/ assert_match(/Plural version of the model detected, using singularized version. Override with --force-plural./, content) end def test_plural_names_can_be_forced content = run_generator ["accounts", "--force-plural"] assert_file "app/models/accounts.rb", /class Accounts < ActiveRecord::Base/ - assert_file "test/unit/accounts_test.rb", /class AccountsTest/ + assert_file "test/models/accounts_test.rb", /class AccountsTest/ assert_no_match(/Plural version of the model detected/, content) end diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index aa09343346..38454dfb8b 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -57,7 +57,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase def test_helper_are_invoked_with_a_pluralized_name run_generator assert_file "app/helpers/users_helper.rb", /module UsersHelper/ - assert_file "test/unit/helpers/users_helper_test.rb", /class UsersHelperTest < ActionView::TestCase/ + assert_file "test/helpers/users_helper_test.rb", /class UsersHelperTest < ActionView::TestCase/ end def test_views_are_generated @@ -75,7 +75,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase def test_functional_tests run_generator - assert_file "test/functional/users_controller_test.rb" do |content| + assert_file "test/controllers/users_controller_test.rb" do |content| assert_match(/class UsersControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) assert_match(/post :create, user: \{ age: @user.age, name: @user.name \}/, content) @@ -86,7 +86,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase def test_functional_tests_without_attributes run_generator ["User"] - assert_file "test/functional/users_controller_test.rb" do |content| + assert_file "test/controllers/users_controller_test.rb" do |content| assert_match(/class UsersControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) assert_match(/post :create, user: \{ \}/, content) @@ -97,7 +97,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase def test_skip_helper_if_required run_generator ["User", "name:string", "age:integer", "--no-helper"] assert_no_file "app/helpers/users_helper.rb" - assert_no_file "test/unit/helpers/users_helper_test.rb" + assert_no_file "test/helpers/users_helper_test.rb" end def test_skip_layout_if_required diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 40c5188042..dc825c7c99 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -12,7 +12,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Model assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/ - assert_file "test/unit/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/ + assert_file "test/models/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/ assert_file "test/fixtures/product_lines.yml" assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product, index: true/ assert_migration "db/migrate/create_product_lines.rb", /references :user, index: true/ @@ -60,7 +60,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end end - assert_file "test/functional/product_lines_controller_test.rb" do |test| + assert_file "test/controllers/product_lines_controller_test.rb" do |test| assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test) assert_match(/post :create, product_line: \{ title: @product_line.title \}/, test) assert_match(/put :update, id: @product_line, product_line: \{ title: @product_line.title \}/, test) @@ -78,7 +78,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Helpers assert_file "app/helpers/product_lines_helper.rb" - assert_file "test/unit/helpers/product_lines_helper_test.rb" + assert_file "test/helpers/product_lines_helper_test.rb" # Assets assert_file "app/assets/stylesheets/scaffold.css" @@ -89,7 +89,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase def test_functional_tests_without_attributes run_generator ["product_line"] - assert_file "test/functional/product_lines_controller_test.rb" do |content| + assert_file "test/controllers/product_lines_controller_test.rb" do |content| assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, content) assert_match(/test "should get index"/, content) assert_match(/post :create, product_line: \{ \}/, content) @@ -103,7 +103,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Model assert_no_file "app/models/product_line.rb" - assert_no_file "test/unit/product_line_test.rb" + assert_no_file "test/models/product_line_test.rb" assert_no_file "test/fixtures/product_lines.yml" assert_no_migration "db/migrate/create_product_lines.rb" @@ -114,7 +114,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Controller assert_no_file "app/controllers/product_lines_controller.rb" - assert_no_file "test/functional/product_lines_controller_test.rb" + assert_no_file "test/controllers/product_lines_controller_test.rb" # Views assert_no_file "app/views/product_lines" @@ -122,7 +122,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Helpers assert_no_file "app/helpers/product_lines_helper.rb" - assert_no_file "test/unit/helpers/product_lines_helper_test.rb" + assert_no_file "test/helpers/product_lines_helper_test.rb" # Assets assert_file "app/assets/stylesheets/scaffold.css", /:visited/ @@ -136,13 +136,13 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Model assert_file "app/models/admin.rb", /module Admin/ assert_file "app/models/admin/role.rb", /class Admin::Role < ActiveRecord::Base/ - assert_file "test/unit/admin/role_test.rb", /class Admin::RoleTest < ActiveSupport::TestCase/ + assert_file "test/models/admin/role_test.rb", /class Admin::RoleTest < ActiveSupport::TestCase/ assert_file "test/fixtures/admin/roles.yml" assert_migration "db/migrate/create_admin_roles.rb" # Route assert_file "config/routes.rb" do |route| - assert_match(/namespace :admin do resources :roles end$/, route) + assert_match(/^ namespace :admin do\n resources :roles\n end$/, route) end # Controller @@ -183,7 +183,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end end - assert_file "test/functional/admin/roles_controller_test.rb", + assert_file "test/controllers/admin/roles_controller_test.rb", /class Admin::RolesControllerTest < ActionController::TestCase/ # Views @@ -198,7 +198,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Helpers assert_file "app/helpers/admin/roles_helper.rb" - assert_file "test/unit/helpers/admin/roles_helper_test.rb" + assert_file "test/helpers/admin/roles_helper_test.rb" # Assets assert_file "app/assets/stylesheets/scaffold.css", /:visited/ @@ -213,7 +213,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Model assert_file "app/models/admin.rb" # ( should not be remove ) assert_no_file "app/models/admin/role.rb" - assert_no_file "test/unit/admin/role_test.rb" + assert_no_file "test/models/admin/role_test.rb" assert_no_file "test/fixtures/admin/roles.yml" assert_no_migration "db/migrate/create_admin_roles.rb" @@ -224,7 +224,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Controller assert_no_file "app/controllers/admin/roles_controller.rb" - assert_no_file "test/functional/admin/roles_controller_test.rb" + assert_no_file "test/controllers/admin/roles_controller_test.rb" # Views assert_no_file "app/views/admin/roles" @@ -232,7 +232,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Helpers assert_no_file "app/helpers/admin/roles_helper.rb" - assert_no_file "test/unit/helpers/admin/roles_helper_test.rb" + assert_no_file "test/helpers/admin/roles_helper_test.rb" # Assets assert_file "app/assets/stylesheets/scaffold.css" 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' |