diff options
361 files changed, 5293 insertions, 4154 deletions
diff --git a/.gitignore b/.gitignore index 715d0bc4bc..9d68f4860f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +*.gem +pkg +.bundle debug.log doc/rdoc activemodel/doc @@ -16,6 +19,3 @@ railties/doc/guides/html/images railties/doc/guides/html/stylesheets railties/guides/output railties/tmp -bin -.bundle -pkg @@ -1,6 +1,7 @@ path File.dirname(__FILE__) -source 'http://gemcutter.org' +source 'http://rubygems.org' +gem "arel", :git => "git://github.com/rails/arel.git" gem "rails", "3.0.0.beta1" gem "rake", ">= 0.8.7" @@ -14,7 +15,7 @@ end gem "sqlite3-ruby", ">= 1.2.5", :require => 'sqlite3' group :test do - gem "pg", ">= 0.8.0" + gem "pg", ">= 0.9.0" gem "mysql", ">= 2.8.1" end @@ -22,6 +23,10 @@ end gem "rack-test", "0.5.3", :require => 'rack/test' gem "RedCloth", ">= 4.2.2" +group :documentation do + gem 'rdoc', '2.1' +end + if ENV['CI'] gem "nokogiri", ">= 1.4.0" diff --git a/RAILS_VERSION b/RAILS_VERSION new file mode 100644 index 0000000000..efcd59e268 --- /dev/null +++ b/RAILS_VERSION @@ -0,0 +1 @@ +3.0.0.beta1 @@ -2,23 +2,17 @@ require 'rake' require 'rake/rdoctask' require 'rake/gempackagetask' -env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD'] - PROJECTS = %w(activesupport activemodel actionpack actionmailer activeresource activerecord railties) -Dir["#{File.dirname(__FILE__)}/*/lib/*/version.rb"].each do |version_path| - require version_path -end - desc 'Run all tests by default' task :default => %w(test test:isolated) -%w(test test:isolated rdoc pgem package gem gemspec).each do |task_name| +%w(test test:isolated rdoc package gem).each do |task_name| desc "Run #{task_name} task for all projects" task task_name do errors = [] PROJECTS.each do |project| - system(%(cd #{project} && #{env} #{$0} #{task_name})) || errors << project + system(%(cd #{project} && #{$0} #{task_name})) || errors << project end fail("Errors in #{errors.join(', ')}") unless errors.empty? end @@ -27,9 +21,9 @@ end desc "Smoke-test all projects" task :smoke do (PROJECTS - %w(activerecord)).each do |project| - system %(cd #{project} && #{env} #{$0} test:isolated) + system %(cd #{project} && #{$0} test:isolated) end - system %(cd activerecord && #{env} #{$0} sqlite3:isolated_test) + system %(cd activerecord && #{$0} sqlite3:isolated_test) end spec = eval(File.read('rails.gemspec')) @@ -48,12 +42,14 @@ desc "Release all components to gemcutter." task :release_projects => :package do errors = [] PROJECTS.each do |project| - system(%(cd #{project} && #{env} #{$0} release)) || errors << project + system(%(cd #{project} && #{$0} release)) || errors << project end fail("Errors in #{errors.join(', ')}") unless errors.empty? end +desc "Install gems for all projects." task :install => :gem do + require File.expand_path("../actionpack/lib/action_pack/version", __FILE__) (PROJECTS - ["railties"]).each do |project| puts "INSTALLING #{project}" system("gem install #{project}/pkg/#{project}-#{ActionPack::VERSION::STRING}.gem --no-ri --no-rdoc") @@ -121,6 +117,34 @@ task :pdoc => :rdoc do require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/api", "doc/rdoc").upload PROJECTS.each do |project| - system %(cd #{project} && #{env} #{$0} pdoc) + system %(cd #{project} && #{$0} pdoc) + end +end + +task :update_versions do + require File.dirname(__FILE__) + "/version" + + File.open("RAILS_VERSION", "w") do |f| + f.write Rails::VERSION::STRING + "\n" + end + + constants = { + "activesupport" => "ActiveSupport", + "activemodel" => "ActiveModel", + "actionpack" => "ActionPack", + "actionmailer" => "ActionMailer", + "activeresource" => "ActiveResource", + "activerecord" => "ActiveRecord", + "railties" => "Rails" + } + + version_file = File.read("version.rb") + + PROJECTS.each do |project| + Dir["#{project}/lib/*/version.rb"].each do |file| + File.open(file, "w") do |f| + f.write version_file.gsub(/Rails/, constants[project]) + end + end end end diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index baea591b97..5b72843f5e 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -4,17 +4,6 @@ require 'rake/testtask' require 'rake/rdoctask' require 'rake/packagetask' require 'rake/gempackagetask' -require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version') - -PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' -PKG_NAME = 'actionmailer' -PKG_VERSION = ActionMailer::VERSION::STRING + PKG_BUILD -PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" - -RELEASE_NAME = "REL #{PKG_VERSION}" - -RUBY_FORGE_PROJECT = "actionmailer" -RUBY_FORGE_USER = "webster132" desc "Default Task" task :default => [ :test ] diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index 31d8efc7bf..a7a2599105 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -1,9 +1,11 @@ +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip + Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'actionmailer' - s.version = '3.0.0.beta1' - s.summary = 'Email composition, delivery, and recieval framework (part of Rails).' - s.description = 'Email composition, delivery, and recieval framework (part of Rails).' + s.version = version + s.summary = 'Email composition, delivery, and receiving framework (part of Rails).' + s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.' s.required_ruby_version = '>= 1.8.7' s.author = 'David Heinemeier Hansson' @@ -17,7 +19,7 @@ Gem::Specification.new do |s| s.has_rdoc = true - s.add_dependency('actionpack', '= 3.0.0.beta1') - s.add_dependency('mail', '~> 2.1.2') + s.add_dependency('actionpack', version) + s.add_dependency('mail', '~> 2.1.3') s.add_dependency('text-format', '~> 1.0.0') end diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 7f5bcad922..43d73e71b9 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -34,6 +34,7 @@ require 'active_support/core_ext/array/uniq_by' require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/string/inflections' +require 'active_support/lazy_load_hooks' module ActionMailer extend ::ActiveSupport::Autoload diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 4e89c1ea0c..ef3820ad67 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -181,6 +181,18 @@ module ActionMailer #:nodoc: # and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book # with the filename +free_book.pdf+. # + # = Observing and Intercepting Mails + # + # ActionMailer provides hooks into the Mail observer and interceptor methods. These allow you to + # register objects that are called during the mail delivery life cycle. + # + # An observer object must implement the <tt>:delivered_email(message)</tt> method which will be + # called once for every email sent after the email has been sent. + # + # An interceptor object must implement the <tt>:delivering_email(message)</tt> method which will be + # called before the email is sent, allowing you to make modifications to the email before it hits + # the delivery agents. Your object should make and needed modifications directly to the passed + # in Mail::Message instance. # # = Configuration options # @@ -255,16 +267,17 @@ module ActionMailer #:nodoc: include AbstractController::Logger include AbstractController::Rendering - include AbstractController::LocalizedCache include AbstractController::Layouts include AbstractController::Helpers include AbstractController::Translation - include AbstractController::Compatibility helper ActionMailer::MailHelper include ActionMailer::OldApi include ActionMailer::DeprecatedApi + + delegate :register_observer, :to => Mail + delegate :register_interceptor, :to => Mail private_class_method :new #:nodoc: @@ -276,6 +289,8 @@ module ActionMailer #:nodoc: :parts_order => [ "text/plain", "text/enriched", "text/html" ] }.freeze + ActionMailer.run_base_hooks(self) + class << self def mailer_name @@ -452,10 +467,27 @@ module ActionMailer #:nodoc: # field for the 'envelope from' value. # # If you do not pass a block to the +mail+ method, it will find all templates in the - # template path that match the method name that it is being called from, it will then - # create parts for each of these templates intelligently, making educated guesses - # on correct content type and sequence, and return a fully prepared Mail::Message - # ready to call <tt>:deliver</tt> on to send. + # view paths using by default the mailer name and the method name that it is being + # called from, it will then create parts for each of these templates intelligently, + # making educated guesses on correct content type and sequence, and return a fully + # prepared Mail::Message ready to call <tt>:deliver</tt> on to send. + # + # For example: + # + # class Notifier < ActionMailer::Base + # default :from => 'no-reply@test.lindsaar.net', + # + # def welcome + # mail(:to => 'mikel@test.lindsaar.net') + # end + # end + # + # Will look for all templates at "app/views/notifier" with name "welcome". However, those + # can be customized: + # + # 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: # @@ -493,7 +525,7 @@ module ActionMailer #:nodoc: # Merge defaults from class headers = headers.reverse_merge(self.class.default) - charset = headers[:charset] + charset = headers.delete(:charset) # Quote fields headers[:subject] ||= default_i18n_subject @@ -514,13 +546,11 @@ module ActionMailer #:nodoc: end # Set configure delivery behavior - wrap_delivery_behavior!(headers[:delivery_method]) + wrap_delivery_behavior!(headers.delete(:delivery_method)) - # Remove headers already treated and assign all others - headers.except!(:subject, :to, :from, :cc, :bcc, :reply_to) - headers.except!(:body, :parts_order, :content_type, :charset, :delivery_method) + # Remove any missing configuration header and assign all others + headers.except!(:parts_order, :content_type) headers.each { |k, v| m[k] = v } - m end @@ -548,12 +578,12 @@ module ActionMailer #:nodoc: # TODO: Move this into Mail def quote_fields!(headers, charset) #:nodoc: m = @_message - m.subject ||= quote_if_necessary(headers[:subject], charset) if headers[:subject] - m.to ||= quote_address_if_necessary(headers[:to], charset) if headers[:to] - m.from ||= quote_address_if_necessary(headers[:from], charset) if headers[:from] - m.cc ||= quote_address_if_necessary(headers[:cc], charset) if headers[:cc] - m.bcc ||= quote_address_if_necessary(headers[:bcc], charset) if headers[:bcc] - m.reply_to ||= quote_address_if_necessary(headers[:reply_to], charset) if headers[:reply_to] + m.subject ||= quote_if_necessary(headers.delete(:subject), charset) if headers[:subject] + m.to ||= quote_address_if_necessary(headers.delete(:to), charset) if headers[:to] + m.from ||= quote_address_if_necessary(headers.delete(:from), charset) if headers[:from] + m.cc ||= quote_address_if_necessary(headers.delete(:cc), charset) if headers[:cc] + m.bcc ||= quote_address_if_necessary(headers.delete(:bcc), charset) if headers[:bcc] + m.reply_to ||= quote_address_if_necessary(headers.delete(:reply_to), charset) if headers[:reply_to] end def collect_responses_and_parts_order(headers) #:nodoc: @@ -566,13 +596,16 @@ module ActionMailer #:nodoc: responses = collector.responses elsif headers[:body] responses << { - :body => headers[:body], + :body => headers.delete(:body), :content_type => self.class.default[:content_type] || "text/plain" } else - each_template do |template| + templates_path = headers.delete(:template_path) || self.class.mailer_name + templates_name = headers.delete(:template_name) || action_name + + each_template(templates_path, templates_name) do |template| responses << { - :body => render_to_body(:_template => template), + :body => render(:_template => template), :content_type => template.mime_type.to_s } end @@ -581,10 +614,10 @@ module ActionMailer #:nodoc: [responses, parts_order] end - def each_template(&block) #:nodoc: - self.class.view_paths.each do |load_paths| - templates = load_paths.find_all(action_name, {}, self.class.mailer_name) - templates = templates.uniq_by { |t| t.details[:formats] } + def each_template(paths, name, &block) #:nodoc: + Array(paths).each do |path| + templates = lookup_context.find_all(name, path) + templates = templates.uniq_by { |t| t.formats } unless templates.empty? templates.each(&block) diff --git a/actionmailer/lib/action_mailer/old_api.rb b/actionmailer/lib/action_mailer/old_api.rb index 936ceb0dd6..aeb653c5db 100644 --- a/actionmailer/lib/action_mailer/old_api.rb +++ b/actionmailer/lib/action_mailer/old_api.rb @@ -206,8 +206,8 @@ module ActionMailer if String === @body @parts.unshift create_inline_part(@body) elsif @parts.empty? || @parts.all? { |p| p.content_disposition =~ /^attachment/ } - self.class.view_paths.first.find_all(@template, {}, @mailer_name).each do |template| - @parts << create_inline_part(render_to_body(:_template => template), template.mime_type) + lookup_context.find_all(@template, @mailer_name).each do |template| + @parts << create_inline_part(render(:_template => template), template.mime_type) end if @parts.size > 1 diff --git a/actionmailer/lib/action_mailer/quoting.rb b/actionmailer/lib/action_mailer/quoting.rb index 70f636bf69..2b55c0f0ab 100644 --- a/actionmailer/lib/action_mailer/quoting.rb +++ b/actionmailer/lib/action_mailer/quoting.rb @@ -22,7 +22,7 @@ module ActionMailer # A quick-and-dirty regexp for determining whether a string contains any # characters that need escaping. if !defined?(CHARS_NEEDING_QUOTING) - CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/ + CHARS_NEEDING_QUOTING = Regexp.new('[\000-\011\013\014\016-\037\177-\377]', nil, 'n') end # Quote the given text if it contains any "illegal" characters diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index a3afc23e6a..0182e48425 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -6,19 +6,21 @@ module ActionMailer railtie_name :action_mailer initializer "action_mailer.url_for", :before => :load_environment_config do |app| - ActionMailer::Base.send(:include, ActionController::UrlFor) if defined?(ActionController) + ActionMailer.base_hook { include app.routes.url_helpers } end - require "action_mailer/railties/subscriber" - subscriber ActionMailer::Railties::Subscriber.new + require "action_mailer/railties/log_subscriber" + log_subscriber ActionMailer::Railties::LogSubscriber.new initializer "action_mailer.logger" do - ActionMailer::Base.logger ||= Rails.logger + ActionMailer.base_hook { self.logger ||= Rails.logger } end initializer "action_mailer.set_configs" do |app| - app.config.action_mailer.each do |k,v| - ActionMailer::Base.send "#{k}=", v + ActionMailer.base_hook do + app.config.action_mailer.each do |k,v| + send "#{k}=", v + end end end end diff --git a/actionmailer/lib/action_mailer/railties/subscriber.rb b/actionmailer/lib/action_mailer/railties/log_subscriber.rb index cff852055c..d1b3dd33af 100644 --- a/actionmailer/lib/action_mailer/railties/subscriber.rb +++ b/actionmailer/lib/action_mailer/railties/log_subscriber.rb @@ -1,6 +1,6 @@ module ActionMailer module Railties - class Subscriber < Rails::Subscriber + class LogSubscriber < Rails::LogSubscriber def deliver(event) recipients = Array(event.payload[:to]).join(', ') info("\nSent mail to #{recipients} (%1.fms)" % event.duration) diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index 56af531d01..7328a7dc80 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -2,8 +2,9 @@ module ActionMailer module VERSION #:nodoc: MAJOR = 3 MINOR = 0 - TINY = "0.beta1" + TINY = 0 + BUILD = "beta1" - STRING = [MAJOR, MINOR, TINY].join('.') + STRING = [MAJOR, MINOR, TINY, BUILD].join('.') end end diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index f6baa4a9e8..16fef3a9a4 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -1,5 +1,8 @@ require File.expand_path('../../../load_paths', __FILE__) +lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) + require 'test/unit' require 'action_mailer' require 'action_mailer/test_case' @@ -14,7 +17,7 @@ ActionView::Template.register_template_handler :bak, lambda { |template| "Lame b FIXTURE_LOAD_PATH = File.expand_path('fixtures', File.dirname(__FILE__)) ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH -class MockSMTP +class MockSMTP def self.deliveries @@deliveries end diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 222db66aaa..c1cf1f0157 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -14,8 +14,13 @@ class BaseTest < ActiveSupport::TestCase mail({:subject => "The first email on new API!"}.merge!(hash)) end - def simple(hash = {}) - mail(hash) + def welcome_with_headers(hash = {}) + headers hash + mail + end + + def welcome_from_another_path(path) + mail(:template_name => "welcome", :template_path => path) end def html_only(hash = {}) @@ -25,11 +30,6 @@ class BaseTest < ActiveSupport::TestCase def plain_text_only(hash = {}) mail(hash) end - - def simple_with_headers(hash = {}) - headers hash - mail - end def attachment_with_content(hash = {}) attachments['invoice.pdf'] = 'This is test File content' @@ -78,8 +78,12 @@ class BaseTest < ActiveSupport::TestCase format.html{ render "welcome" } if include_html end end - - def different_template(template_name='') + + def implicit_different_template(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}" } @@ -88,13 +92,10 @@ class BaseTest < ActiveSupport::TestCase def different_layout(layout_name='') mail do |format| - format.text { - render :layout => layout_name - } + format.text { render :layout => layout_name } format.html { render :layout => layout_name } end end - end test "method call to mail does not raise error" do @@ -154,7 +155,7 @@ class BaseTest < ActiveSupport::TestCase test "can pass random headers in as a hash to mail" do hash = {'X-Special-Domain-Specific-Header' => "SecretValue", 'In-Reply-To' => '1234@mikel.me.com' } - mail = BaseMailer.simple(hash) + mail = BaseMailer.welcome(hash) assert_equal('SecretValue', mail['X-Special-Domain-Specific-Header'].decoded) assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded) end @@ -162,7 +163,7 @@ class BaseTest < ActiveSupport::TestCase test "can pass random headers in as a hash" do hash = {'X-Special-Domain-Specific-Header' => "SecretValue", 'In-Reply-To' => '1234@mikel.me.com' } - mail = BaseMailer.simple_with_headers(hash) + mail = BaseMailer.welcome_with_headers(hash) assert_equal('SecretValue', mail['X-Special-Domain-Specific-Header'].decoded) assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded) end @@ -247,9 +248,9 @@ class BaseTest < ActiveSupport::TestCase end test "uses random default headers from class" do - with_default BaseMailer, "X-SPAM" => "Not spam" do - email = BaseMailer.simple - assert_equal("Not spam", email["X-SPAM"].decoded) + with_default BaseMailer, "X-Custom" => "Custom" do + email = BaseMailer.welcome + assert_equal("Custom", email["X-Custom"].decoded) end end @@ -476,18 +477,58 @@ class BaseTest < ActiveSupport::TestCase end # Rendering - test "that you can specify a different template" do - mail = BaseMailer.different_template('explicit_multipart_templates') + test "you can specify a different template for implicit render" do + mail = BaseMailer.implicit_different_template('implicit_multipart') + assert_equal("HTML Implicit Multipart", mail.html_part.body.decoded) + assert_equal("TEXT Implicit Multipart", mail.text_part.body.decoded) + end + + test "you can specify a different template for explicit render" do + mail = BaseMailer.explicit_different_template('explicit_multipart_templates') assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded) assert_equal("TEXT Explicit Multipart Templates", mail.text_part.body.decoded) end - test "that you can specify a different layout" do + test "you can specify a different layout" do mail = BaseMailer.different_layout('different_layout') assert_equal("HTML -- HTML", mail.html_part.body.decoded) assert_equal("PLAIN -- PLAIN", mail.text_part.body.decoded) end + test "you can specify the template path for implicit lookup" do + mail = BaseMailer.welcome_from_another_path('another.path/base_mailer') + assert_equal("Welcome from another path", mail.body.encoded) + + mail = BaseMailer.welcome_from_another_path(['unknown/invalid', 'another.path/base_mailer']) + assert_equal("Welcome from another path", mail.body.encoded) + end + + # Before and After hooks + + class MyObserver + def self.delivered_email(mail) + end + end + + test "you can register an observer to the mail object that gets informed on email delivery" do + ActionMailer::Base.register_observer(MyObserver) + mail = BaseMailer.welcome + MyObserver.expects(:delivered_email).with(mail) + mail.deliver + end + + class MyInterceptor + def self.delivering_email(mail) + end + end + + test "you can register an interceptor to the mail object that gets passed the mail object before delivery" do + ActionMailer::Base.register_interceptor(MyInterceptor) + mail = BaseMailer.welcome + MyInterceptor.expects(:delivering_email).with(mail) + mail.deliver + end + protected # Execute the block setting the given values and restoring old values after diff --git a/actionmailer/test/subscriber_test.rb b/actionmailer/test/log_subscriber_test.rb index 3d1736d64f..c08c34b4a2 100644 --- a/actionmailer/test/subscriber_test.rb +++ b/actionmailer/test/log_subscriber_test.rb @@ -1,10 +1,14 @@ require "abstract_unit" -require "rails/subscriber/test_helper" -require "action_mailer/railties/subscriber" +require "rails/log_subscriber/test_helper" +require "action_mailer/railties/log_subscriber" -class AMSubscriberTest < ActionMailer::TestCase - include Rails::Subscriber::TestHelper - Rails::Subscriber.add(:action_mailer, ActionMailer::Railties::Subscriber.new) +class AMLogSubscriberTest < ActionMailer::TestCase + include Rails::LogSubscriber::TestHelper + + def setup + super + Rails::LogSubscriber.add(:action_mailer, ActionMailer::Railties::LogSubscriber.new) + end class TestMailer < ActionMailer::Base def basic diff --git a/actionmailer/test/old_base/asset_host_test.rb b/actionmailer/test/old_base/asset_host_test.rb index aa9cfd88dd..0ec0242f55 100644 --- a/actionmailer/test/old_base/asset_host_test.rb +++ b/actionmailer/test/old_base/asset_host_test.rb @@ -14,6 +14,10 @@ class AssetHostTest < Test::Unit::TestCase set_delivery_method :test ActionMailer::Base.perform_deliveries = true ActionMailer::Base.deliveries.clear + AssetHostMailer.configure do |c| + c.asset_host = "http://www.example.com" + c.assets_dir = '' + end end def teardown @@ -21,13 +25,12 @@ class AssetHostTest < Test::Unit::TestCase end def test_asset_host_as_string - ActionController::Base.asset_host = "http://www.example.com" mail = AssetHostMailer.email_with_asset assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.to_s.strip end def test_asset_host_as_one_arguement_proc - ActionController::Base.asset_host = Proc.new { |source| + AssetHostMailer.config.asset_host = Proc.new { |source| if source.starts_with?('/images') "http://images.example.com" else @@ -39,7 +42,7 @@ class AssetHostTest < Test::Unit::TestCase end def test_asset_host_as_two_arguement_proc - ActionController::Base.asset_host = Proc.new {|source,request| + ActionController::Base.config.asset_host = Proc.new {|source,request| if request && request.ssl? "https://www.example.com" else diff --git a/actionmailer/test/old_base/url_test.rb b/actionmailer/test/old_base/url_test.rb index d851431c7a..60740d6b0b 100644 --- a/actionmailer/test/old_base/url_test.rb +++ b/actionmailer/test/old_base/url_test.rb @@ -4,13 +4,19 @@ require 'action_controller' class WelcomeController < ActionController::Base end +AppRoutes = ActionDispatch::Routing::RouteSet.new + class ActionMailer::Base - include ActionController::UrlFor + include AppRoutes.url_helpers end class TestMailer < ActionMailer::Base default_url_options[:host] = 'www.basecamphq.com' + configure do |c| + c.assets_dir = '' # To get the tests to pass + end + def signed_up_with_url(recipient) @recipients = recipient @subject = "[Signed up] Welcome #{recipient}" @@ -61,7 +67,7 @@ class ActionMailerUrlTest < Test::Unit::TestCase def test_signed_up_with_url TestMailer.delivery_method = :test - ActionController::Routing::Routes.draw do |map| + AppRoutes.draw do |map| map.connect ':controller/:action/:id' map.welcome 'welcome', :controller=>"foo", :action=>"bar" end diff --git a/actionpack/Rakefile b/actionpack/Rakefile index c45f88ed04..b9ace8658a 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -4,17 +4,6 @@ require 'rake/testtask' require 'rake/rdoctask' require 'rake/packagetask' require 'rake/gempackagetask' -require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version') - -PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' -PKG_NAME = 'actionpack' -PKG_VERSION = ActionPack::VERSION::STRING + PKG_BUILD -PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" - -RELEASE_NAME = "REL #{PKG_VERSION}" - -RUBY_FORGE_PROJECT = "actionpack" -RUBY_FORGE_USER = "webster132" desc "Default Task" task :default => :test @@ -115,26 +104,7 @@ task :update_js => [ :update_scriptaculous ] # Publishing ------------------------------------------------------ desc "Publish the API documentation" -task :pgem => [:package] do - require 'rake/contrib/sshpublisher' - Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload - `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` -end - -desc "Publish the API documentation" task :pdoc => [:rdoc] do require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload -end - -desc "Publish the release files to RubyForge." -task :release => [ :package ] do - require 'rubyforge' - require 'rake/contrib/rubyforgepublisher' - - packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" } - - rubyforge = RubyForge.new - rubyforge.login - rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages) -end +end
\ No newline at end of file diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 8a7169bfa1..ed5b7e1e93 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -1,9 +1,11 @@ +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip + Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'actionpack' - s.version = '3.0.0.beta1' + s.version = version s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).' - s.description = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).' + s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.' s.required_ruby_version = '>= 1.8.7' s.author = 'David Heinemeier Hansson' @@ -17,10 +19,10 @@ Gem::Specification.new do |s| s.has_rdoc = true - s.add_dependency('activesupport', '= 3.0.0.beta1') - s.add_dependency('activemodel', '= 3.0.0.beta1') + s.add_dependency('activesupport', version) + s.add_dependency('activemodel', version) s.add_dependency('rack', '~> 1.1.0') s.add_dependency('rack-test', '~> 0.5.0') - s.add_dependency('rack-mount', '~> 0.4.7') + s.add_dependency('rack-mount', '~> 0.6.0') s.add_dependency('erubis', '~> 2.6.5') end diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 1e15ab090c..2da4dc052c 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -3,8 +3,11 @@ $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.inc require 'active_support/ruby/shim' require 'active_support/dependencies/autoload' +require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/module/anonymous' +require 'active_support/i18n' module AbstractController extend ActiveSupport::Autoload @@ -12,11 +15,10 @@ module AbstractController autoload :Base autoload :Callbacks autoload :Collector - autoload :Compatibility autoload :Helpers autoload :Layouts - autoload :LocalizedCache autoload :Logger autoload :Rendering autoload :Translation + autoload :ViewPaths end diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 3119ee498b..c12b584144 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -1,3 +1,5 @@ +require 'active_support/ordered_options' + module AbstractController class Error < StandardError; end class ActionNotFound < StandardError; end @@ -5,7 +7,6 @@ module AbstractController class Base attr_internal :response_body attr_internal :action_name - attr_internal :formats class << self attr_reader :abstract @@ -28,6 +29,14 @@ module AbstractController @descendants ||= [] end + def config + @config ||= ActiveSupport::InheritableOptions.new(superclass < Base ? superclass.config : {}) + end + + def configure + yield config + end + # A list of all internal methods for a controller. This finds the first # abstract superclass of a controller, and gets a list of all public # instance methods on that abstract class. Public instance methods of @@ -84,15 +93,14 @@ module AbstractController # ==== Returns # String def controller_path - @controller_path ||= name && name.sub(/Controller$/, '').underscore + @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous? end end abstract! - # Initialize controller with nil formats. - def initialize #:nodoc: - @_formats = nil + def config + @config ||= ActiveSupport::InheritableOptions.new(self.class.config) end # Calls the action going through the entire action dispatch stack. diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index d429333661..81fb514770 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -1,3 +1,5 @@ +require "action_dispatch/http/mime_type" + module AbstractController module Collector def self.generate_method_for_mime(mime) diff --git a/actionpack/lib/abstract_controller/compatibility.rb b/actionpack/lib/abstract_controller/compatibility.rb deleted file mode 100644 index 7fb93a0eb5..0000000000 --- a/actionpack/lib/abstract_controller/compatibility.rb +++ /dev/null @@ -1,18 +0,0 @@ -module AbstractController - module Compatibility - extend ActiveSupport::Concern - - def _find_layout(name, details) - details[:prefix] = nil if name =~ /\blayouts/ - super - end - - # Move this into a "don't run in production" module - def _default_layout(details, require_layout = false) - super - rescue ActionView::MissingTemplate - _find_layout(_layout({}), {}) - nil - end - end -end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 578b884a4d..f875213afb 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -1,6 +1,4 @@ require 'active_support/dependencies' -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/module/delegation' module AbstractController module Helpers @@ -27,7 +25,7 @@ module AbstractController def inherited(klass) helpers = _helpers klass._helpers = Module.new { include helpers } - klass.class_eval { default_helper_module! unless name.blank? } + klass.class_eval { default_helper_module! unless anonymous? } super end @@ -99,7 +97,7 @@ module AbstractController def helper(*args, &block) self._helper_serial = AbstractController::Helpers.next_serial + 1 - _modules_for_helpers(args).each do |mod| + modules_for_helpers(args).each do |mod| add_template_helper(mod) end @@ -134,7 +132,7 @@ module AbstractController # ==== Returns # Array[Module]:: A normalized list of modules for the list of # helpers provided. - def _modules_for_helpers(args) + def modules_for_helpers(args) args.flatten.map! do |arg| case arg when String, Symbol diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 0d214396aa..2f9616124a 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -1,6 +1,3 @@ -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/module/delegation' - module AbstractController # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in # repeated setups. The inclusion pattern has pages that look like this: @@ -173,27 +170,7 @@ module AbstractController module ClassMethods def inherited(klass) super - klass.class_eval do - _write_layout_method - @found_layouts = {} - end - end - - def clear_template_caches! - @found_layouts.clear if defined? @found_layouts - super - end - - def cache_layout(details) - layout = @found_layouts - key = Thread.current[:format_locale_key] - - # Cache nil - if layout.key?(key) - return layout[key] - else - layout[key] = yield - end + klass._write_layout_method end # This module is mixed in if layout conditions are provided. This means @@ -262,10 +239,10 @@ module AbstractController def _write_layout_method case defined?(@_layout) ? @_layout : nil when String - self.class_eval %{def _layout(details) #{@_layout.inspect} end} + self.class_eval %{def _layout; #{@_layout.inspect} end} when Symbol self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 - def _layout(details) + def _layout #{@_layout}.tap do |layout| unless layout.is_a?(String) || !layout raise ArgumentError, "Your layout method :#{@_layout} returned \#{layout}. It " \ @@ -276,21 +253,21 @@ module AbstractController ruby_eval when Proc define_method :_layout_from_proc, &@_layout - self.class_eval %{def _layout(details) _layout_from_proc(self) end} + self.class_eval %{def _layout; _layout_from_proc(self) end} when false - self.class_eval %{def _layout(details) end} + self.class_eval %{def _layout; end} when true raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil" when nil if name + _prefix = "layouts" unless _implied_layout_name =~ /\blayouts/ + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def _layout(details) - self.class.cache_layout(details) do - if template_exists?("#{_implied_layout_name}", details, :_prefix => "layouts") - "#{_implied_layout_name}" - else - super - end + def _layout + if template_exists?("#{_implied_layout_name}", #{_prefix.inspect}) + "#{_implied_layout_name}" + else + super end end RUBY @@ -300,42 +277,20 @@ module AbstractController end end - def render_to_body(options = {}) - # In the case of a partial with a layout, handle the layout - # here, and make sure the view does not try to handle it - layout = options.delete(:layout) if options.key?(:partial) - - response = super + def _normalize_options(options) + super - # This is a little bit messy. We need to explicitly handle partial - # layouts here since the core lookup logic is in the view, but - # we need to determine the layout based on the controller - # - # TODO: An easier way to handle this would probably be to override - # render_template - if layout - layout = _layout_for_option(layout, options[:_template].details) - response = layout.render(view_context, options[:locals] || {}) { response } + if _include_layout?(options) + layout = options.key?(:layout) ? options.delete(:layout) : :default + value = _layout_for_option(layout) + options[:layout] = (value =~ /\blayouts/ ? value : "layouts/#{value}") if value end - - response end private # This will be overwritten by _write_layout_method - def _layout(details) end - - # Determine the layout for a given name and details. - # - # ==== Parameters - # name<String>:: The name of the template - # details<Hash{Symbol => Object}>:: A list of details to restrict - # the lookup to. By default, layout lookup is limited to the - # formats specified for the current request. - def _layout_for_name(name, details) - name && _find_layout(name, details) - end + def _layout; end # Determine the layout for a given name and details, taking into account # the name type. @@ -345,11 +300,11 @@ module AbstractController # details<Hash{Symbol => Object}>:: A list of details to restrict # the lookup to. By default, layout lookup is limited to the # formats specified for the current request. - def _layout_for_option(name, details) + def _layout_for_option(name) case name - when String then _layout_for_name(name, details) - when true then _default_layout(details, true) - when :default then _default_layout(details, false) + when String then name + when true then _default_layout(true) + when :default then _default_layout(false) when false, nil then nil else raise ArgumentError, @@ -357,29 +312,6 @@ module AbstractController end end - def _determine_template(options) - super - - return unless (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) - layout = options.key?(:layout) ? options[:layout] : :default - options[:_layout] = _layout_for_option(layout, options[:_template].details) - end - - # Take in the name and details and find a Template. - # - # ==== Parameters - # name<String>:: The name of the template to retrieve - # details<Hash>:: A list of details to restrict the search by. This - # might include details like the format or locale of the template. - # - # ==== Returns - # Template:: A template object matching the name and details - def _find_layout(name, details) - # TODO: Make prefix actually part of details in ViewPath#find_by_parts - prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts" - find_template(name, details, :_prefix => prefix) - end - # Returns the default layout for this controller and a given set of details. # Optionally raises an exception if the layout could not be found. # @@ -392,18 +324,24 @@ module AbstractController # # ==== Returns # Template:: The template object for the default layout (or nil) - def _default_layout(details, require_layout = false) - if require_layout && _action_has_layout? && !_layout(details) - raise ArgumentError, - "There was no default layout for #{self.class} in #{view_paths.inspect}" - end - + def _default_layout(require_layout = false) begin - _layout_for_name(_layout(details), details) if _action_has_layout? + layout_name = _layout if _action_has_layout? rescue NameError => e raise NoMethodError, "You specified #{@_layout.inspect} as the layout, but no such method was found" end + + if require_layout && _action_has_layout? && !layout_name + raise ArgumentError, + "There was no default layout for #{self.class} in #{view_paths.inspect}" + end + + layout_name + end + + def _include_layout?(options) + (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) end def _action_has_layout? diff --git a/actionpack/lib/abstract_controller/localized_cache.rb b/actionpack/lib/abstract_controller/localized_cache.rb deleted file mode 100644 index 5e3efa002c..0000000000 --- a/actionpack/lib/abstract_controller/localized_cache.rb +++ /dev/null @@ -1,49 +0,0 @@ -module AbstractController - class HashKey - @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|sh,sk| sh[sk] = {} } } - - def self.get(klass, formats, locale) - @hash_keys[klass][formats][locale] ||= new(klass, formats, locale) - end - - attr_accessor :hash - def initialize(klass, formats, locale) - @formats, @locale = formats, locale - @hash = [formats, locale].hash - end - - alias_method :eql?, :equal? - - def inspect - "#<HashKey -- formats: #{@formats.inspect} locale: #{@locale.inspect}>" - end - end - - module LocalizedCache - extend ActiveSupport::Concern - - module ClassMethods - def clear_template_caches! - ActionView::Partials::PartialRenderer::TEMPLATES.clear - template_cache.clear - super - end - - def template_cache - @template_cache ||= Hash.new {|h,k| h[k] = {} } - end - end - - def render(*args) - Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale) - super - end - - private - - def with_template_cache(name) - self.class.template_cache[Thread.current[:format_locale_key]][name] ||= super - end - - end -end diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb index a23a13e1d6..9318f5e369 100644 --- a/actionpack/lib/abstract_controller/logger.rb +++ b/actionpack/lib/abstract_controller/logger.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/logger' -require 'active_support/benchmarkable' +require "active_support/core_ext/logger" +require "active_support/benchmarkable" module AbstractController module Logger diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 619a49571b..42f4939108 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,7 +1,4 @@ require "abstract_controller/base" -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/array/wrap' module AbstractController class DoubleRenderError < Error @@ -12,32 +9,47 @@ module AbstractController end end + # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request, + # it will trigger the lookup_context and consequently expire the cache. + # TODO Add some deprecation warnings to remove I18n.locale from controllers + class I18nProxy < ::I18n::Config #:nodoc: + attr_reader :i18n_config, :lookup_context + + def initialize(i18n_config, lookup_context) + @i18n_config, @lookup_context = i18n_config, lookup_context + end + + def locale + @i18n_config.locale + end + + def locale=(value) + @i18n_config.locale = value + @lookup_context.update_details(:locale => @i18n_config.locale) + end + end + module Rendering extend ActiveSupport::Concern + include AbstractController::ViewPaths - included do - class_attribute :_view_paths - delegate :_view_paths, :to => :'self.class' - self._view_paths = ActionView::PathSet.new + # Overwrite process to setup I18n proxy. + def process(*) #:nodoc: + old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) + super + ensure + I18n.config = old_config end # An instance of a view class. The default view class is ActionView::Base # # The view class must have the following methods: - # View.for_controller[controller] Create a new ActionView instance for a - # controller - # View#render_partial[options] - # - responsible for setting options[:_template] - # - Returns String with the rendered partial - # options<Hash>:: see _render_partial in ActionView::Base - # View#render_template[template, layout, options, partial] - # - Returns String with the rendered template - # template<ActionView::Template>:: The template to render - # layout<ActionView::Template>:: The layout to render around the template - # options<Hash>:: See _render_template_with_layout in ActionView::Base - # partial<Boolean>:: Whether or not the template to render is a partial + # View.for_controller[controller] + # Create a new ActionView instance for a controller + # View#render_template[options] + # Returns String with the rendered template # - # Override this method in a to change the default behavior. + # Override this method in a module to change the default behavior. def view_context @_view_context ||= ActionView::Base.for_controller(self) end @@ -45,57 +57,29 @@ module AbstractController # Mostly abstracts the fact that calling render twice is a DoubleRenderError. # Delegates render_to_body and sticks the result in self.response_body. def render(*args, &block) - options = _normalize_options(*args, &block) + options = _normalize_args(*args, &block) + _normalize_options(options) self.response_body = render_to_body(options) end # Raw rendering of a template to a Rack-compatible body. - # - # ==== Options - # _partial_object<Object>:: The object that is being rendered. If this - # exists, we are in the special case of rendering an object as a partial. - # # :api: plugin def render_to_body(options = {}) - # TODO: Refactor so we can just use the normal template logic for this - if options.key?(:partial) - _render_partial(options) - else - _determine_template(options) - _render_template(options) - end + _process_options(options) + _render_template(options) end # Raw rendering of a template to a string. Just convert the results of # render_to_body into a String. - # # :api: plugin - def render_to_string(*args) - options = _normalize_options(*args) + def render_to_string(options={}) + _normalize_options(options) AbstractController::Rendering.body_to_s(render_to_body(options)) end - # Renders the template from an object. - # - # ==== Options - # _template<ActionView::Template>:: The template to render - # _layout<ActionView::Template>:: The layout to wrap the template in (optional) + # Find and renders a template based on the options given. def _render_template(options) - view_context.render_template(options) - end - - # Renders the given partial. - # - # ==== Options - # partial<String|Object>:: The partial name or the object to be rendered - def _render_partial(options) - view_context.render_partial(options) - end - - # The list of view paths for this controller. See ActionView::ViewPathSet for - # more details about writing custom view paths. - def view_paths - _view_paths + view_context.render_template(options) { |template| _with_template_hook(template) } end # The prefix used in render "foo" shortcuts. @@ -117,122 +101,42 @@ module AbstractController private - # Normalize options, by converting render "foo" to render :template => "prefix/foo" - # and render "/foo" to render :file => "/foo". - def _normalize_options(action=nil, options={}) + # Normalize options by converting render "foo" to render :action => "foo" and + # render "foo/bar" to render :file => "foo/bar". + def _normalize_args(action=nil, options={}) case action + when NilClass when Hash options, action = action, nil when String, Symbol action = action.to_s - case action.index("/") - when NilClass - options[:_prefix] = _prefix - options[:_template_name] = action - when 0 - options[:file] = action - else - options[:template] = action - end + key = action.include?(?/) ? :file : :action + options[key] = action + else + options.merge!(:partial => action) end options end - # Take in a set of options and determine the template to render - # - # ==== Options - # _template<ActionView::Template>:: If this is provided, the search is over - # _template_name<#to_s>:: The name of the template to look up. Otherwise, - # use the current action name. - # _prefix<String>:: The prefix to look inside of. In a file system, this corresponds - # to a directory. - # _partial<TrueClass, FalseClass>:: Whether or not the file to look up is a partial - def _determine_template(options) - if options.key?(:text) - options[:_template] = ActionView::Template::Text.new(options[:text], format_for_text) - elsif options.key?(:inline) - handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb") - template = ActionView::Template.new(options[:inline], "inline template", handler, {}) - options[:_template] = template - elsif options.key?(:template) - options[:_template_name] = options[:template] - elsif options.key?(:file) - options[:_template_name] = options[:file] + def _normalize_options(options) + if options[:partial] == true + options[:partial] = action_name end - name = (options[:_template_name] || options[:action] || action_name).to_s - options[:_prefix] ||= _prefix if (options.keys & [:partial, :file, :template]).empty? - - details = _normalize_details(options) - options[:_template] ||= with_template_cache(name) do - find_template(name, details, options) + if (options.keys & [:partial, :file, :template]).empty? + options[:prefix] ||= _prefix end - end - - def _normalize_details(options) - details = { :formats => formats } - details[:formats] = Array(options[:format]) if options[:format] - details[:locale] = Array(options[:locale]) if options[:locale] - details - end - def find_template(name, details, options) - view_paths.find(name, details, options[:_prefix], options[:_partial]) - end - - def template_exists?(name, details, options) - view_paths.exists?(name, details, options[:_prefix], options[:_partial]) - end - - def with_template_cache(name) - yield + options[:template] ||= (options[:action] || action_name).to_s + options end - def format_for_text - Mime[:text] + def _process_options(options) end - module ClassMethods - def clear_template_caches! - end - - # Append a path to the list of view paths for this controller. - # - # ==== Parameters - # path<String, ViewPath>:: If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::ViewPathSet for more information) - def append_view_path(path) - self.view_paths = view_paths.dup + Array.wrap(path) - end - - # Prepend a path to the list of view paths for this controller. - # - # ==== Parameters - # path<String, ViewPath>:: If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::ViewPathSet for more information) - def prepend_view_path(path) - clear_template_caches! - self.view_paths = Array.wrap(path) + view_paths.dup - end - - # A list of all of the default view paths for this controller. - def view_paths - _view_paths - end - - # Set the view paths. - # - # ==== Parameters - # paths<ViewPathSet, Object>:: If a ViewPathSet is provided, use that; - # otherwise, process the parameter into a ViewPathSet. - def view_paths=(paths) - clear_template_caches! - self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths) - _view_paths.freeze - end + def _with_template_hook(template) + self.formats = template.formats end end end diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb new file mode 100644 index 0000000000..c2a9f6336d --- /dev/null +++ b/actionpack/lib/abstract_controller/view_paths.rb @@ -0,0 +1,73 @@ +module AbstractController + module ViewPaths + extend ActiveSupport::Concern + + included do + class_attribute :_view_paths + self._view_paths = ActionView::PathSet.new + end + + delegate :template_exists?, :view_paths, :formats, :formats=, + :locale, :locale=, :to => :lookup_context + + # LookupContext is the object responsible to hold all information required to lookup + # templates, i.e. view paths and details. Check ActionView::LookupContext for more + # information. + def lookup_context + @lookup_context ||= ActionView::LookupContext.new(self.class._view_paths, details_for_lookup) + end + + def details_for_lookup + { } + end + + def append_view_path(path) + lookup_context.view_paths.push(*path) + end + + def prepend_view_path(path) + lookup_context.view_paths.unshift(*path) + end + + def template_exists?(*args) + lookup_context.exists?(*args) + end + + module ClassMethods + # Append a path to the list of view paths for this controller. + # + # ==== Parameters + # path<String, ViewPath>:: If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::ViewPathSet for more information) + def append_view_path(path) + self.view_paths = view_paths.dup + Array(path) + end + + # Prepend a path to the list of view paths for this controller. + # + # ==== Parameters + # path<String, ViewPath>:: If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::ViewPathSet for more information) + def prepend_view_path(path) + self.view_paths = Array(path) + view_paths.dup + end + + # A list of all of the default view paths for this controller. + def view_paths + _view_paths + end + + # Set the view paths. + # + # ==== Parameters + # paths<ViewPathSet, Object>:: If a ViewPathSet is provided, use that; + # otherwise, process the parameter into a ViewPathSet. + def view_paths=(paths) + self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths) + self._view_paths.freeze + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 759e52b135..536154fc6b 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -13,13 +13,13 @@ module ActionController autoload_under "metal" do autoload :Compatibility autoload :ConditionalGet - autoload :Configuration autoload :Cookies autoload :Flash autoload :Head autoload :Helpers autoload :HideActions autoload :HttpAuthentication + autoload :ImplicitRender autoload :Instrumentation autoload :MimeResponds autoload :RackDelegation @@ -46,7 +46,6 @@ module ActionController eager_autoload do autoload :RecordIdentifier - autoload :UrlRewriter # TODO: Don't autoload exceptions, setup explicit # requires for files that need them diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 10244f8216..fcd3cb9bd3 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -15,7 +15,6 @@ module ActionController include ActionController::Renderers::All include ActionController::ConditionalGet include ActionController::RackDelegation - include ActionController::Configuration # Legacy modules include SessionManagement @@ -30,35 +29,13 @@ module ActionController include ActionController::Verification include ActionController::RequestForgeryProtection include ActionController::Streaming + include ActionController::RecordIdentifier include ActionController::HttpAuthentication::Basic::ControllerMethods include ActionController::HttpAuthentication::Digest::ControllerMethods # Add instrumentations hooks at the bottom, to ensure they instrument # all the methods properly. include ActionController::Instrumentation - - # TODO: Extract into its own module - # This should be moved together with other normalizing behavior - module ImplicitRender - def send_action(*) - ret = super - default_render unless response_body - ret - end - - def default_render - render - end - - def method_for_action(action_name) - super || begin - if template_exists?(action_name.to_s, {:formats => formats}, :_prefix => controller_path) - "default_render" - end - end - end - end - include ImplicitRender include ActionController::Rescue @@ -82,12 +59,9 @@ module ActionController filter end - protected + ActionController.run_base_hooks(self) - # Overwrite url rewriter to use request. - def _url_rewriter - return ActionController::UrlRewriter unless request - @_url_rewriter ||= ActionController::UrlRewriter.new(request, params) - end end end + +require "action_controller/deprecated/base" diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index d784138ebe..b3fa129929 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -40,28 +40,34 @@ module ActionController #:nodoc: autoload :Sweeping, 'action_controller/caching/sweeping' end - included do - @@cache_store = nil - cattr_reader :cache_store - - # Defines the storage option for cached fragments - def self.cache_store=(store_option) - @@cache_store = ActiveSupport::Cache.lookup_store(store_option) + module ConfigMethods + def cache_store + config.cache_store end - include Pages, Actions, Fragments - include Sweeping if defined?(ActiveRecord) + def cache_store=(store) + config.cache_store = ActiveSupport::Cache.lookup_store(store) + end - @@perform_caching = true - cattr_accessor :perform_caching - end + private - module ClassMethods def cache_configured? perform_caching && cache_store end end + include ConfigMethods + include Pages, Actions, Fragments + include Sweeping if defined?(ActiveRecord) + + included do + extend ConfigMethods + + @@perform_caching = true + cattr_accessor :perform_caching + end + + def caching_allowed? request.get? && response.status == 200 end @@ -75,10 +81,5 @@ module ActionController #:nodoc: yield end end - - private - def cache_configured? - self.class.cache_configured? - end end end diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 00a7f034d3..bb5ff95a67 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -41,7 +41,9 @@ module ActionController #:nodoc: else pos = buffer.length block.call - write_fragment(name, buffer[pos..-1], options) + content = buffer[pos..-1] + content = content.as_str if content.respond_to?(:as_str) + write_fragment(name, content, options) end else block.call diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 5797eeebd6..fe95f0e0d7 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -1,5 +1,6 @@ require 'fileutils' require 'uri' +require 'active_support/core_ext/class/attribute_accessors' module ActionController #:nodoc: module Caching @@ -34,27 +35,26 @@ module ActionController #:nodoc: # 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 - def self.included(base) #:nodoc: - base.extend(ClassMethods) - base.class_eval do - @@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : "" - ## - # :singleton-method: - # 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. - cattr_accessor :page_cache_directory - - @@page_cache_extension = '.html' - ## - # :singleton-method: - # 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. - cattr_accessor :page_cache_extension - end + extend ActiveSupport::Concern + + included do + @@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : "" + ## + # :singleton-method: + # 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. + cattr_accessor :page_cache_directory + + @@page_cache_extension = '.html' + ## + # :singleton-method: + # 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. + cattr_accessor :page_cache_extension end module ClassMethods @@ -121,10 +121,10 @@ module ActionController #:nodoc: if options.is_a?(Hash) if options[:action].is_a?(Array) options[:action].dup.each do |action| - self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => 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, :skip_relative_url_root => true))) + self.class.expire_page(url_for(options.merge(:only_path => true))) end else self.class.expire_page(options) @@ -139,7 +139,7 @@ module ActionController #:nodoc: path = case options when Hash - url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format])) + url_for(options.merge(:only_path => true, :format => params[:format])) when String options else diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index 871f41bfdd..cf16417e84 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -30,9 +30,7 @@ module ActionController #:nodoc: # cache_sweeper OpenBar::Sweeper, :only => [ :edit, :destroy, :share ] # end module Sweeping - def self.included(base) #:nodoc: - base.extend(ClassMethods) - end + extend ActiveSupport::Concern module ClassMethods #:nodoc: def cache_sweeper(*sweepers) diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb index a4eef07841..9f2de57033 100644 --- a/actionpack/lib/action_controller/deprecated.rb +++ b/actionpack/lib/action_controller/deprecated.rb @@ -1,5 +1,3 @@ ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response ActionController::Routing = ActionDispatch::Routing -ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new -ActionController::UrlWriter = ActionController::UrlFor diff --git a/actionpack/lib/action_controller/deprecated/base.rb b/actionpack/lib/action_controller/deprecated/base.rb new file mode 100644 index 0000000000..1d05b3fbd6 --- /dev/null +++ b/actionpack/lib/action_controller/deprecated/base.rb @@ -0,0 +1,131 @@ +module ActionController + class Base + class << self + def deprecated_config_accessor(option, message = nil) + deprecated_config_reader(option, message) + deprecated_config_writer(option, message) + end + + def deprecated_config_reader(option, message = nil) + message ||= "Reading #{option} directly from ActionController::Base is deprecated. " \ + "Please read it from config.#{option}" + + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{option} + ActiveSupport::Deprecation.warn #{message.inspect}, caller + config.#{option} + end + RUBY + end + + def deprecated_config_writer(option, message = nil) + message ||= "Setting #{option} directly on ActionController::Base is deprecated. " \ + "Please set it on config.action_controller.#{option}" + + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{option}=(val) + ActiveSupport::Deprecation.warn #{message.inspect}, caller + config.#{option} = val + end + RUBY + end + + def consider_all_requests_local + ActiveSupport::Deprecation.warn "ActionController::Base.consider_all_requests_local is deprecated, " << + "use Rails.application.config.consider_all_requests_local instead", caller + Rails.application.config.consider_all_requests_local + end + + def consider_all_requests_local=(value) + ActiveSupport::Deprecation.warn "ActionController::Base.consider_all_requests_local= is deprecated. " << + "Please configure it on your application with config.consider_all_requests_local=", caller + Rails.application.config.consider_all_requests_local = value + end + + def allow_concurrency + ActiveSupport::Deprecation.warn "ActionController::Base.allow_concurrency is deprecated, " << + "use Rails.application.config.allow_concurrency instead", caller + Rails.application.config.allow_concurrency + end + + def allow_concurrency=(value) + ActiveSupport::Deprecation.warn "ActionController::Base.allow_concurrency= is deprecated. " << + "Please configure it on your application with config.allow_concurrency=", caller + Rails.application.config.allow_concurrency = value + end + + def ip_spoofing_check=(value) + ActiveSupport::Deprecation.warn "ActionController::Base.ip_spoofing_check= is deprecated. " << + "Please configure it on your application with config.action_dispatch.ip_spoofing_check=", caller + Rails.application.config.action_dispatch.ip_spoofing_check = value + end + + def ip_spoofing_check + ActiveSupport::Deprecation.warn "ActionController::Base.ip_spoofing_check is deprecated. " << + "Configuring ip_spoofing_check on the application configures a middleware.", caller + Rails.application.config.action_disaptch.ip_spoofing_check + end + + def trusted_proxies=(value) + ActiveSupport::Deprecation.warn "ActionController::Base.trusted_proxies= is deprecated. " << + "Please configure it on your application with config.action_dispatch.trusted_proxies=", caller + Rails.application.config.action_dispatch.ip_spoofing_check = value + end + + def trusted_proxies + ActiveSupport::Deprecation.warn "ActionController::Base.trusted_proxies is deprecated. " << + "Configuring trusted_proxies on the application configures a middleware.", caller + Rails.application.config.action_dispatch.ip_spoofing_check = value + end + + def session(*args) + ActiveSupport::Deprecation.warn( + "Disabling sessions for a single controller has been deprecated. " + + "Sessions are now lazy loaded. So if you don't access them, " + + "consider them off. You can still modify the session cookie " + + "options with request.session_options.", caller) + end + + def session=(value) + ActiveSupport::Deprecation.warn "ActionController::Base.session= is deprecated. " << + "Please configure it on your application with config.session_store :cookie_store, :key => '....'", caller + if value.delete(:disabled) + Rails.application.config.session_store :disabled + else + store = Rails.application.config.session_store + Rails.application.config.session_store store, value + end + end + + # Controls the resource action separator + def resource_action_separator + @resource_action_separator ||= "/" + end + + def resource_action_separator=(val) + ActiveSupport::Deprecation.warn "ActionController::Base.resource_action_separator is deprecated and only " \ + "works with the deprecated router DSL." + @resource_action_separator = val + end + + def use_accept_header + ActiveSupport::Deprecation.warn "ActionController::Base.use_accept_header doesn't do anything anymore. " \ + "The accept header is always taken into account." + end + + def use_accept_header=(val) + use_accept_header + end + end + + deprecated_config_writer :session_store + deprecated_config_writer :session_options + deprecated_config_accessor :relative_url_root, "relative_url_root is ineffective. Please stop using it" + deprecated_config_accessor :assets_dir + deprecated_config_accessor :javascripts_dir + deprecated_config_accessor :stylesheets_dir + + delegate :consider_all_requests_local, :consider_all_requests_local=, + :allow_concurrency, :allow_concurrency=, :to => :"self.class" + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 2b35e111ec..eebd2c943a 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -34,7 +34,8 @@ module ActionController # and response object available. You might wish to control the # environment and response manually for performance reasons. - attr_internal :status, :headers, :content_type, :response + attr_internal :status, :headers, :content_type, :response, :request + delegate :session, :to => "@_request" def initialize(*) @_headers = {} @@ -49,6 +50,14 @@ module ActionController headers["Content-Type"] = type.to_s end + def content_type + headers["Content-Type"] + end + + def location + headers["Location"] + end + def location=(url) headers["Location"] = url end @@ -58,8 +67,9 @@ module ActionController end # :api: private - def dispatch(name, env) - @_env = env + def dispatch(name, request) + @_request = request + @_env = request.env @_env['action_controller.instance'] = self process(name) to_a @@ -70,31 +80,12 @@ module ActionController response ? response.to_a : [status, headers, response_body] end - class ActionEndpoint - @@endpoints = Hash.new {|h,k| h[k] = Hash.new {|sh,sk| sh[sk] = {} } } - - def self.for(controller, action, stack) - @@endpoints[controller][action][stack] ||= begin - endpoint = new(controller, action) - stack.build(endpoint) - end - end - - def initialize(controller, action) - @controller, @action = controller, action - @_formats = [Mime::HTML] - end - - def call(env) - @controller.new.dispatch(@action, env) - end - end - class_attribute :middleware_stack self.middleware_stack = ActionDispatch::MiddlewareStack.new def self.inherited(base) self.middleware_stack = base.middleware_stack.dup + super end def self.use(*args) @@ -118,8 +109,10 @@ module ActionController # # ==== Returns # Proc:: A rack application - def self.action(name) - ActionEndpoint.for(self, name, middleware_stack) + def self.action(name, klass = ActionDispatch::Request) + middleware_stack.build do |env| + new.dispatch(name, klass.new(env)) + end end end end diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index a1cfa32d4d..ab8d87b2c4 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -2,26 +2,23 @@ module ActionController module Compatibility extend ActiveSupport::Concern - include AbstractController::Compatibility - class ::ActionController::ActionControllerError < StandardError #:nodoc: end + module ClassMethods + end + # Temporary hax included do ::ActionController::UnknownAction = ::AbstractController::ActionNotFound ::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError - cattr_accessor :session_options - self.session_options = {} - - cattr_accessor :relative_url_root - self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT'] + # ROUTES TODO: This should be handled by a middleware and route generation + # should be able to handle SCRIPT_NAME + self.config.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT'] class << self delegate :default_charset=, :to => "ActionDispatch::Response" - delegate :resources_path_names, :to => "ActionController::Routing::Routes" - delegate :resources_path_names=, :to => "ActionController::Routing::Routes" end # cattr_reader :protected_instance_variables @@ -32,31 +29,17 @@ module ActionController @before_filter_chain_aborted @_headers @_params @_response) - # Controls the resource action separator - cattr_accessor :resource_action_separator - self.resource_action_separator = "/" - - cattr_accessor :use_accept_header - self.use_accept_header = true + def rescue_action(env) + raise env["action_dispatch.rescue.exception"] + end self.page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : "" - - # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, - # and images to a dedicated asset server away from the main web server. Example: - # ActionController::Base.asset_host = "http://assets.example.com" - cattr_accessor :asset_host - - cattr_accessor :ip_spoofing_check - self.ip_spoofing_check = true - - cattr_accessor :trusted_proxies end # For old tests def initialize_template_class(*) end def assign_shortcuts(*) end - # TODO: Remove this after we flip def template @template ||= view_context end @@ -66,52 +49,20 @@ module ActionController super end - module ClassMethods - def consider_all_requests_local - ActiveSupport::Deprecation.warn "ActionController::Base.consider_all_requests_local is deprecated, " << - "use Rails.application.config.consider_all_requests_local instead" - Rails.application.config.consider_all_requests_local - end - - def consider_all_requests_local=(value) - ActiveSupport::Deprecation.warn "ActionController::Base.consider_all_requests_local= is no longer effective. " << - "Please configure it on your application with config.consider_all_requests_local=" - Rails.application.config.consider_all_requests_local = value - end - - def allow_concurrency - ActiveSupport::Deprecation.warn "ActionController::Base.allow_concurrency is deprecated, " << - "use Rails.application.config.allow_concurrency instead" - Rails.application.config.allow_concurrency - end - - def allow_concurrency=(value) - ActiveSupport::Deprecation.warn "ActionController::Base.allow_concurrency= is no longer effective. " << - "Please configure it on your application with config.allow_concurrency=" - Rails.application.config.allow_concurrency = value - end - - def rescue_action(env) - raise env["action_dispatch.rescue.exception"] - end - - # Defines the storage option for cached fragments - def cache_store=(store_option) - @@cache_store = ActiveSupport::Cache.lookup_store(store_option) - end - end - - delegate :consider_all_requests_local, :consider_all_requests_local=, - :allow_concurrency, :allow_concurrency=, :to => :"self.class" - - def render_to_body(options) - if options.is_a?(Hash) && options.key?(:template) - options[:template].sub!(/^\//, '') + def _normalize_options(options) + if options[:action] && options[:action].to_s.include?(?/) + ActiveSupport::Deprecation.warn "Giving a path to render :action is deprecated. " << + "Please use render :template instead", caller + options[:template] = options.delete(:action) end options[:text] = nil if options.delete(:nothing) == true options[:text] = " " if options.key?(:text) && options[:text].nil? + super + end + def render_to_body(options) + options[:template].sub!(/^\//, '') if options.key?(:template) super || " " end @@ -126,18 +77,5 @@ module ActionController def performed? response_body end - - # ==== Request only view path switching ==== - def append_view_path(path) - view_paths.push(*path) - end - - def prepend_view_path(path) - view_paths.unshift(*path) - end - - def view_paths - view_context.view_paths - end end end diff --git a/actionpack/lib/action_controller/metal/configuration.rb b/actionpack/lib/action_controller/metal/configuration.rb deleted file mode 100644 index 5c829853b7..0000000000 --- a/actionpack/lib/action_controller/metal/configuration.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ActionController - module Configuration - extend ActiveSupport::Concern - - def config - @config ||= self.class.config - end - - def config=(config) - @config = config - end - - module ClassMethods - def default_config - @default_config ||= {} - end - - def config - self.config ||= default_config - end - - def config=(config) - @config = ActiveSupport::OrderedHash.new - @config.merge!(config) - end - end - end -end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 37be8b3999..a5c9910d68 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -1,6 +1,7 @@ module ActionController module Head extend ActiveSupport::Concern + include ActionController::UrlFor # Return a response that has no content (merely headers). The options diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 1b5a4c3080..8efe01e37b 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -86,7 +86,7 @@ module ActionController end private - # Overwrite _modules_for_helpers to accept :all as argument, which loads + # Overwrite modules_for_helpers to accept :all as argument, which loads # all helpers in helpers_dir. # # ==== Parameters @@ -95,7 +95,7 @@ module ActionController # ==== Returns # Array[Module]:: A normalized list of modules for the list of # helpers provided. - def _modules_for_helpers(args) + def modules_for_helpers(args) args += all_application_helpers if args.delete(:all) super(args) end diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb index e893acffdf..3358d80c35 100644 --- a/actionpack/lib/action_controller/metal/hide_actions.rb +++ b/actionpack/lib/action_controller/metal/hide_actions.rb @@ -15,10 +15,8 @@ module ActionController # Overrides AbstractController::Base#action_method? to return false if the # action name is in the list of hidden actions. - def action_method?(action_name) - self.class.visible_action?(action_name) do - !self.class.hidden_actions.include?(action_name) && super - end + def method_for_action(action_name) + self.class.visible_action?(action_name) && super end module ClassMethods @@ -31,13 +29,13 @@ module ActionController end def inherited(klass) - klass.instance_variable_set("@visible_actions", {}) + klass.class_eval { @visible_actions = {} } super end def visible_action?(action_name) return @visible_actions[action_name] if @visible_actions.key?(action_name) - @visible_actions[action_name] = yield + @visible_actions[action_name] = !hidden_actions.include?(action_name) end # Overrides AbstractController::Base#action_methods to remove any methods diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 0f35a7c040..6ec788f302 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -124,7 +124,7 @@ module ActionController end def authenticate(request, &login_procedure) - unless authorization(request).blank? + unless request.authorization.blank? login_procedure.call(*user_name_and_password(request)) end end @@ -133,15 +133,8 @@ module ActionController decode_credentials(request).split(/:/, 2) end - def authorization(request) - request.env['HTTP_AUTHORIZATION'] || - request.env['X-HTTP_AUTHORIZATION'] || - request.env['X_HTTP_AUTHORIZATION'] || - request.env['REDIRECT_X_HTTP_AUTHORIZATION'] - end - def decode_credentials(request) - ActiveSupport::Base64.decode64(authorization(request).split(' ', 2).last || '') + ActiveSupport::Base64.decode64(request.authorization.split(' ', 2).last || '') end def encode_credentials(user_name, password) @@ -165,7 +158,7 @@ module ActionController # Authenticate with HTTP Digest, returns true or false def authenticate_with_http_digest(realm = "Application", &password_procedure) - HttpAuthentication::Digest.authenticate(request, realm, &password_procedure) + HttpAuthentication::Digest.authenticate(config.secret, request, realm, &password_procedure) end # Render output including the HTTP Digest authentication header @@ -175,30 +168,23 @@ module ActionController end # Returns false on a valid response, true otherwise - def authenticate(request, realm, &password_procedure) - authorization(request) && validate_digest_response(request, realm, &password_procedure) - end - - def authorization(request) - request.env['HTTP_AUTHORIZATION'] || - request.env['X-HTTP_AUTHORIZATION'] || - request.env['X_HTTP_AUTHORIZATION'] || - request.env['REDIRECT_X_HTTP_AUTHORIZATION'] + def authenticate(secret_key, request, realm, &password_procedure) + request.authorization && validate_digest_response(secret_key, request, realm, &password_procedure) end # Returns false unless the request credentials response value matches the expected value. # First try the password as a ha1 digest password. If this fails, then try it as a plain # text password. - def validate_digest_response(request, realm, &password_procedure) + def validate_digest_response(secret_key, request, realm, &password_procedure) credentials = decode_credentials_header(request) - valid_nonce = validate_nonce(request, credentials[:nonce]) + valid_nonce = validate_nonce(secret_key, request, credentials[:nonce]) - if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque] + if valid_nonce && realm == credentials[:realm] && opaque(secret_key) == credentials[:opaque] password = password_procedure.call(credentials[:username]) return false unless password method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] - uri = credentials[:uri][0,1] == '/' ? request.request_uri : request.url + uri = credentials[:uri][0,1] == '/' ? request.fullpath : request.url [true, false].any? do |password_is_ha1| expected = expected_response(method, uri, credentials, password, password_is_ha1) @@ -226,7 +212,7 @@ module ActionController end def decode_credentials_header(request) - decode_credentials(authorization(request)) + decode_credentials(request.authorization) end def decode_credentials(header) @@ -238,6 +224,9 @@ module ActionController end def authentication_header(controller, realm) + secret_key = controller.config.secret + nonce = self.nonce(secret_key) + opaque = opaque(secret_key) controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}") end @@ -280,7 +269,7 @@ module ActionController # The nonce is opaque to the client. Composed of Time, and hash of Time with secret # key from the Rails session secret generated upon creation of project. Ensures # the time cannot be modified by client. - def nonce(time = Time.now) + def nonce(secret_key, time = Time.now) t = time.to_i hashed = [t, secret_key] digest = ::Digest::MD5.hexdigest(hashed.join(":")) @@ -292,21 +281,16 @@ module ActionController # Can be much shorter if the Stale directive is implemented. This would # allow a user to use new nonce without prompting user again for their # username and password. - def validate_nonce(request, value, seconds_to_timeout=5*60) + def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60) t = ActiveSupport::Base64.decode64(value).split(":").first.to_i - nonce(t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout + nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout end # Opaque based on random generation - but changing each request? - def opaque() + def opaque(secret_key) ::Digest::MD5.hexdigest(secret_key) end - # Set in /initializers/session_store.rb, and loaded even if sessions are not in use. - def secret_key - ActionController::Base.session_options[:secret] - end - end end end diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb new file mode 100644 index 0000000000..282dcf66b3 --- /dev/null +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -0,0 +1,21 @@ +module ActionController + module ImplicitRender + def send_action(*) + ret = super + default_render unless response_body + ret + end + + def default_render + render + end + + def method_for_action(action_name) + super || begin + if template_exists?(action_name.to_s, _prefix) + "default_render" + end + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 85035dc09c..d69de65f28 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -20,7 +20,7 @@ module ActionController :params => request.filtered_parameters, :formats => request.formats.map(&:to_sym), :method => request.method, - :path => (request.request_uri rescue "unknown") + :path => (request.fullpath rescue "unknown") } ActiveSupport::Notifications.instrument("action_controller.start_processing", raw_payload.dup) diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index bb55383631..37106733cb 100644 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -6,14 +6,11 @@ module ActionController extend ActiveSupport::Concern included do - delegate :session, :to => "@_request" delegate :headers, :status=, :location=, :content_type=, :status, :location, :content_type, :to => "@_response" - attr_internal :request end - def dispatch(action, env) - @_request = ActionDispatch::Request.new(env) + def dispatch(action, request) @_response = ActionDispatch::Response.new @_response.request = request super diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index faf0589fd2..25e4e18493 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -11,6 +11,7 @@ module ActionController extend ActiveSupport::Concern include AbstractController::Logger + include ActionController::RackDelegation include ActionController::UrlFor # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 639b508746..49d3d6b466 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -19,7 +19,7 @@ module ActionController <<-RUBY_EVAL if options.key?(:#{name}) _process_options(options) - return _render_option_#{name}(options[:#{name}], options) + return _render_option_#{name}(options.delete(:#{name}), options) end RUBY_EVAL end diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 0aae9f8579..f892bd9b91 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -2,68 +2,54 @@ module ActionController module Rendering extend ActiveSupport::Concern - included do - include AbstractController::Rendering - include AbstractController::LocalizedCache - end + include ActionController::RackDelegation + include AbstractController::Rendering - def process_action(*) - self.formats = request.formats.map {|x| x.to_sym} + def process(*) + self.formats = request.formats.map { |x| x.to_sym } super end def render(*args) - if response_body - raise ::AbstractController::DoubleRenderError - end - - args << {} unless args.last.is_a?(Hash) - super(*args) - self.content_type ||= args.last[:_template].mime_type.to_s - response_body - end - - def render_to_body(options) - _process_options(options) + raise ::AbstractController::DoubleRenderError if response_body super + response_body end private - def _render_partial(options) - options[:partial] = action_name if options[:partial] == true - options[:_details] = {:formats => formats} - super + def _normalize_args(action=nil, options={}, &blk) + options = super + options[:update] = blk if block_given? + options end - def format_for_text - formats.first + def _normalize_options(options) + if options.key?(:text) && options[:text].respond_to?(:to_text) + options[:text] = options[:text].to_text + end + + if options[:status] + options[:status] = Rack::Utils.status_code(options[:status]) + end + + super end def _process_options(options) status, content_type, location = options.values_at(:status, :content_type, :location) + self.status = status if status self.content_type = content_type if content_type self.headers["Location"] = url_for(location) if location - end - def _normalize_options(action=nil, options={}, &blk) - case action - when NilClass - when Hash - options = super(action.delete(:action), action) - when String, Symbol - options = super - else - options.merge! :partial => action - end - - if options[:status] - options[:status] = Rack::Utils.status_code(options[:status]) - end + super + end - options[:update] = blk if block_given? - options + def _with_template_hook(template) + super + self.content_type ||= template.mime_type.to_s end + end end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 276c703307..39a809657b 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -12,11 +12,10 @@ module ActionController #:nodoc: included do # Sets the token parameter name for RequestForgery. Calling +protect_from_forgery+ # sets it to <tt>:authenticity_token</tt> by default. - cattr_accessor :request_forgery_protection_token + config.request_forgery_protection_token ||= :authenticity_token # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode. - class_attribute :allow_forgery_protection - self.allow_forgery_protection = true + config.allow_forgery_protection ||= true helper_method :form_authenticity_token helper_method :protect_against_forgery? @@ -80,9 +79,47 @@ module ActionController #:nodoc: self.request_forgery_protection_token ||= :authenticity_token before_filter :verify_authenticity_token, options end + + def request_forgery_protection_token + config.request_forgery_protection_token + end + + def request_forgery_protection_token=(val) + config.request_forgery_protection_token = val + end + + def allow_forgery_protection + config.allow_forgery_protection + end + + def allow_forgery_protection=(val) + config.allow_forgery_protection = val + end end protected + + def protect_from_forgery(options = {}) + self.request_forgery_protection_token ||= :authenticity_token + before_filter :verify_authenticity_token, options + end + + def request_forgery_protection_token + config.request_forgery_protection_token + end + + def request_forgery_protection_token=(val) + config.request_forgery_protection_token = val + end + + def allow_forgery_protection + config.allow_forgery_protection + end + + def allow_forgery_protection=(val) + config.allow_forgery_protection = val + end + # The actual before_filter that is used. Modify this to change how you handle unverified requests. def verify_authenticity_token verified_request? || raise(ActionController::InvalidAuthenticityToken) @@ -109,7 +146,7 @@ module ActionController #:nodoc: end def protect_against_forgery? - self.class.allow_forgery_protection + config.allow_forgery_protection end end end diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb index d70f40ce7a..91d89ff9a4 100644 --- a/actionpack/lib/action_controller/metal/session_management.rb +++ b/actionpack/lib/action_controller/metal/session_management.rb @@ -2,44 +2,8 @@ module ActionController #:nodoc: module SessionManagement #:nodoc: extend ActiveSupport::Concern - include ActionController::Configuration - module ClassMethods - # Set the session store to be used for keeping the session data between requests. - # By default, sessions are stored in browser cookies (<tt>:cookie_store</tt>), - # but you can also specify one of the other included stores (<tt>:active_record_store</tt>, - # <tt>:mem_cache_store</tt>, or your own custom class. - def session_store=(store) - if store == :active_record_store - self.session_store = ActiveRecord::SessionStore - else - @@session_store = store.is_a?(Symbol) ? - ActionDispatch::Session.const_get(store.to_s.camelize) : - store - end - end - - # Returns the session store class currently used. - def session_store - if defined? @@session_store - @@session_store - else - ActionDispatch::Session::CookieStore - end - end - - def session=(options = {}) - self.session_store = nil if options.delete(:disabled) - session_options.merge!(options) - end - def session(*args) - ActiveSupport::Deprecation.warn( - "Disabling sessions for a single controller has been deprecated. " + - "Sessions are now lazy loaded. So if you don't access them, " + - "consider them off. You can still modify the session cookie " + - "options with request.session_options.", caller) - end end end end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 8f03b8bb17..753af3dc58 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -9,18 +9,13 @@ module ActionController #:nodoc: DEFAULT_SEND_FILE_OPTIONS = { :type => 'application/octet-stream'.freeze, :disposition => 'attachment'.freeze, - :stream => true, - :buffer_size => 4096, - :x_sendfile => false }.freeze - X_SENDFILE_HEADER = 'X-Sendfile'.freeze - protected - # Sends the file, by default streaming it 4096 bytes at a time. This way the - # whole file doesn't need to be read into memory at once. This makes it - # feasible to send even large files. You can optionally turn off streaming - # and send the whole file at once. + # Sends the file. This uses a server-appropriate method (such as X-Sendfile) + # via the Rack::Sendfile middleware. The header to use is set via + # config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile". + # Your server can also configure this for you by setting the X-Sendfile-Type header. # # Be careful to sanitize the path parameter if it is coming from a web # page. <tt>send_file(params[:path])</tt> allows a malicious user to @@ -31,24 +26,12 @@ module ActionController #:nodoc: # Defaults to <tt>File.basename(path)</tt>. # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json - # * <tt>:length</tt> - used to manually override the length (in bytes) of the content that - # is going to be sent to the client. Defaults to <tt>File.size(path)</tt>. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). - # * <tt>:stream</tt> - whether to send the file to the user agent as it is read (+true+) - # or to read the entire file before sending (+false+). Defaults to +true+. - # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file. - # Defaults to 4096. # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'. # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from # the URL, which is necessary for i18n filenames on certain browsers # (setting <tt>:filename</tt> overrides this option). - # * <tt>:x_sendfile</tt> - uses X-Sendfile to send the file when set to +true+. This is currently - # only available with Lighttpd/Apache2 and specific modules installed and activated. Since this - # uses the web server to send the file, this may lower memory consumption on your server and - # it will not block your application for further requests. - # See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and - # http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+. # # The default Content-Type and Content-Disposition headers are # set to download arbitrary binary files in as many browsers as @@ -81,29 +64,16 @@ module ActionController #:nodoc: def send_file(path, options = {}) #:doc: raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path) - options[:length] ||= File.size(path) options[:filename] ||= File.basename(path) unless options[:url_based_filename] send_file_headers! options - @performed_render = false - if options[:x_sendfile] - head options[:status], X_SENDFILE_HEADER => path - else - if options[:stream] - # TODO : Make render :text => proc {} work with the new base - render :status => options[:status], :text => Proc.new { |response, output| - len = options[:buffer_size] || 4096 - File.open(path, 'rb') do |file| - while buf = file.read(len) - output.write(buf) - end - end - } - else - File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read } - end + ActiveSupport::Deprecation.warn(":x_sendfile is no longer needed in send_file", caller) end + + self.status = options[:status] || 200 + self.content_type = options[:content_type] if options.key?(:content_type) + self.response_body = File.open(path, "rb") end # Sends the given binary data to the browser. This method is similar to @@ -138,32 +108,35 @@ module ActionController #:nodoc: # data to the browser, then use <tt>render :text => proc { ... }</tt> # instead. See ActionController::Base#render for more information. def send_data(data, options = {}) #:doc: - send_file_headers! options.merge(:length => data.bytesize) - render :status => options[:status], :text => data + send_file_headers! options.dup + render options.slice(:status, :content_type).merge(:text => data) end private def send_file_headers!(options) options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) - [:length, :type, :disposition].each do |arg| + [:type, :disposition].each do |arg| raise ArgumentError, ":#{arg} option required" if options[arg].nil? end - disposition = options[:disposition].dup || 'attachment' + if options.key?(:length) + ActiveSupport::Deprecation.warn("You do not need to provide the file's length", caller) + end - disposition <<= %(; filename="#{options[:filename]}") if options[:filename] + disposition = options[:disposition] + disposition += %(; filename="#{options[:filename]}") if options[:filename] content_type = options[:type] if content_type.is_a?(Symbol) - raise ArgumentError, "Unknown MIME type #{options[:type]}" unless Mime::EXTENSION_LOOKUP.key?(content_type.to_s) - self.content_type = Mime::Type.lookup_by_extension(content_type.to_s) + extension = Mime[content_type] + raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension + self.content_type = extension else self.content_type = content_type end headers.merge!( - 'Content-Length' => options[:length].to_s, 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary' ) diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index 707ad968f4..4b8c452d50 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -13,7 +13,6 @@ module ActionController if cookies = @_request.env['action_dispatch.cookies'] cookies.write(@_response) end - @_response.body ||= self.response_body @_response.prepare! set_test_assigns ret diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 4f3ad07be5..10c7ca9021 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -1,165 +1,20 @@ -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/module/attribute_accessors' - module ActionController - # In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse - # is also possible: an URL can be generated from one of your routing definitions. - # URL generation functionality is centralized in this module. - # - # See ActionController::Routing and ActionController::Resources for general - # information about routing and routes.rb. - # - # <b>Tip:</b> If you need to generate URLs from your models or some other place, - # then ActionController::UrlFor is what you're looking for. Read on for - # an introduction. - # - # == URL generation from parameters - # - # As you may know, some functions - such as ActionController::Base#url_for - # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set - # of parameters. For example, you've probably had the chance to write code - # like this in one of your views: - # - # <%= link_to('Click here', :controller => 'users', - # :action => 'new', :message => 'Welcome!') %> - # - # #=> Generates a link to: /users/new?message=Welcome%21 - # - # link_to, and all other functions that require URL generation functionality, - # actually use ActionController::UrlFor under the hood. And in particular, - # they use the ActionController::UrlFor#url_for method. One can generate - # the same path as the above example by using the following code: - # - # include UrlFor - # url_for(:controller => 'users', - # :action => 'new', - # :message => 'Welcome!', - # :only_path => true) - # # => "/users/new?message=Welcome%21" - # - # Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no - # information about the website hostname that your Rails app is serving. So if you - # want to include the hostname as well, then you must also pass the <tt>:host</tt> - # argument: - # - # include UrlFor - # url_for(:controller => 'users', - # :action => 'new', - # :message => 'Welcome!', - # :host => 'www.example.com') # Changed this. - # # => "http://www.example.com/users/new?message=Welcome%21" - # - # By default, all controllers and views have access to a special version of url_for, - # that already knows what the current hostname is. So if you use url_for in your - # controllers or your views, then you don't need to explicitly pass the <tt>:host</tt> - # argument. - # - # For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for. - # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for' - # in full. However, mailers don't have hostname information, and what's why you'll still - # have to specify the <tt>:host</tt> argument when generating URLs in mailers. - # - # - # == URL generation for named routes - # - # UrlFor also allows one to access methods that have been auto-generated from - # named routes. For example, suppose that you have a 'users' resource in your - # <b>routes.rb</b>: - # - # map.resources :users - # - # This generates, among other things, the method <tt>users_path</tt>. By default, - # this method is accessible from your controllers, views and mailers. If you need - # to access this auto-generated method from other places (such as a model), then - # you can do that by including ActionController::UrlFor in your class: - # - # class User < ActiveRecord::Base - # include ActionController::UrlFor - # - # def base_uri - # user_path(self) - # end - # end - # - # User.find(1).base_uri # => "/users/1" - # module UrlFor extend ActiveSupport::Concern - included do - ActionController::Routing::Routes.install_helpers(self) - - # Including in a class uses an inheritable hash. Modules get a plain hash. - if respond_to?(:class_attribute) - class_attribute :default_url_options - else - mattr_accessor :default_url_options - end + include ActionDispatch::Routing::UrlFor - self.default_url_options = {} + def url_options + super.reverse_merge( + :host => request.host_with_port, + :protocol => request.protocol, + :_path_segments => request.symbolized_path_parameters + ).merge(:script_name => request.script_name) end - # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in - # the form of a hash, just like the one you would use for url_for directly. Example: - # - # def default_url_options(options) - # { :project => @project.active? ? @project.url_name : "unknown" } - # end - # - # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the - # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set - # by this method. - def default_url_options(options = nil) - self.class.default_url_options - end - - def rewrite_options(options) #:nodoc: - if options.delete(:use_defaults) != false && (defaults = default_url_options(options)) - defaults.merge(options) - else - options - end - end - - # Generate a url based on the options provided, default_url_options and the - # routes defined in routes.rb. The following options are supported: - # - # * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+. - # * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'. - # * <tt>:host</tt> - Specifies the host the link should be targeted at. - # If <tt>:only_path</tt> is false, this option must be - # provided either explicitly, or via +default_url_options+. - # * <tt>:port</tt> - Optionally specify the port to connect to. - # * <tt>:anchor</tt> - An anchor name to be appended to the path. - # * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the - # +relative_url_root+ set in ActionController::Base.relative_url_root. - # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/" - # - # Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to - # +url_for+ is forwarded to the Routes module. - # - # Examples: - # - # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing' - # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok' - # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/' - # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33' - def url_for(options = {}) - options ||= {} - case options - when String - options - when Hash - _url_rewriter.rewrite(rewrite_options(options)) - else - polymorphic_url(options) - end - end - - protected - - def _url_rewriter - ActionController::UrlRewriter + def _router + raise "In order to use #url_for, you must include the helpers of a particular " \ + "router. For instance, `include Rails.application.routes.url_helpers" end end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/middleware.rb b/actionpack/lib/action_controller/middleware.rb index 17275793b7..2115b07b3e 100644 --- a/actionpack/lib/action_controller/middleware.rb +++ b/actionpack/lib/action_controller/middleware.rb @@ -6,7 +6,8 @@ module ActionController end def call(env) - @controller.build(@app).dispatch(:index, env) + request = ActionDispatch::Request.new(env) + @controller.build(@app).dispatch(:index, request) end end diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index eaed00cfb7..ae363e300c 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -92,8 +92,7 @@ module ActionController inflection = if options[:action].to_s == "new" args.pop :singular - elsif (record.respond_to?(:new_record?) && record.new_record?) || - (record.respond_to?(:destroyed?) && record.destroyed?) + elsif (record.respond_to?(:persisted?) && !record.persisted?) args.pop :plural elsif record.is_a?(Class) diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 55a5c22ac0..6a3afbb157 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -1,30 +1,80 @@ -require "action_controller" require "rails" +require "action_controller" require "action_view/railtie" +require "active_support/core_ext/class/subclasses" +require "active_support/deprecation/proxy_wrappers" +require "active_support/deprecation" module ActionController class Railtie < Rails::Railtie railtie_name :action_controller - require "action_controller/railties/subscriber" - subscriber ActionController::Railties::Subscriber.new + require "action_controller/railties/log_subscriber" + require "action_controller/railties/url_helpers" + + ad = config.action_dispatch + config.action_controller.singleton_class.send(:define_method, :session) do + ActiveSupport::Deprecation.warn "config.action_controller.session has been " \ + "renamed to config.action_dispatch.session.", caller + ad.session + end + + config.action_controller.singleton_class.send(:define_method, :session=) do |val| + ActiveSupport::Deprecation.warn "config.action_controller.session has been " \ + "renamed to config.action_dispatch.session.", caller + ad.session = val + end + + config.action_controller.singleton_class.send(:define_method, :session_store) do + ActiveSupport::Deprecation.warn "config.action_controller.session_store has been " \ + "renamed to config.action_dispatch.session_store.", caller + ad.session_store + end + + config.action_controller.singleton_class.send(:define_method, :session_store=) do |val| + ActiveSupport::Deprecation.warn "config.action_controller.session_store has been " \ + "renamed to config.action_dispatch.session_store.", caller + ad.session_store = val + end + + log_subscriber ActionController::Railties::LogSubscriber.new initializer "action_controller.logger" do - ActionController::Base.logger ||= Rails.logger + ActionController.base_hook { self.logger ||= Rails.logger } end initializer "action_controller.set_configs" do |app| - app.config.action_controller.each do |k,v| - ActionController::Base.send "#{k}=", v - end + paths = app.config.paths + ac = app.config.action_controller + + ac.assets_dir = paths.public.to_a.first + ac.javascripts_dir = paths.public.javascripts.to_a.first + ac.stylesheets_dir = paths.public.stylesheets.to_a.first + ac.secret = app.config.cookie_secret + + ActionController.base_hook { self.config.replace(ac) } end initializer "action_controller.initialize_framework_caches" do - ActionController::Base.cache_store ||= RAILS_CACHE + ActionController.base_hook { self.cache_store ||= RAILS_CACHE } end initializer "action_controller.set_helpers_path" do |app| - ActionController::Base.helpers_path = app.config.paths.app.helpers.to_a + ActionController.base_hook do + self.helpers_path = app.config.paths.app.helpers.to_a + end + end + + initializer "action_controller.url_helpers" do |app| + ActionController.base_hook do + extend ::ActionController::Railtie::UrlHelpers.with(app.routes) + end + + message = "ActionController::Routing::Routes is deprecated. " \ + "Instead, use Rails.application.routes" + + proxy = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(app.routes, message) + ActionController::Routing::Routes = proxy end end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/railties/subscriber.rb b/actionpack/lib/action_controller/railties/log_subscriber.rb index 4499e82292..c2299d0b05 100644 --- a/actionpack/lib/action_controller/railties/subscriber.rb +++ b/actionpack/lib/action_controller/railties/log_subscriber.rb @@ -1,6 +1,6 @@ module ActionController module Railties - class Subscriber < Rails::Subscriber + class LogSubscriber < Rails::LogSubscriber INTERNAL_PARAMS = %w(controller action format _method only_path) def start_processing(event) @@ -22,15 +22,7 @@ module ActionController end def send_file(event) - message = if event.payload[:x_sendfile] - header = ActionController::Streaming::X_SENDFILE_HEADER - "Sent #{header} header %s" - elsif event.payload[:stream] - "Streamed file %s" - else - "Sent file %s" - end - + message = "Sent file %s" message << " (%.1fms)" info(message % [event.payload[:path], event.duration]) end diff --git a/actionpack/lib/action_controller/railties/url_helpers.rb b/actionpack/lib/action_controller/railties/url_helpers.rb new file mode 100644 index 0000000000..ad2a8d4ef3 --- /dev/null +++ b/actionpack/lib/action_controller/railties/url_helpers.rb @@ -0,0 +1,14 @@ +module ActionController + class Railtie + module UrlHelpers + def self.with(router) + Module.new do + define_method(:inherited) do |klass| + super(klass) + klass.send(:include, router.url_helpers) + end + end + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 1165c3b7c5..3f966b1b64 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -60,13 +60,32 @@ module ActionController # # dom_id(Post.find(45), :edit) # => "edit_post_45" def dom_id(record, prefix = nil) - if record_id = record.id + if record_id = record_key_for_dom_id(record) "#{dom_class(record, prefix)}#{JOIN}#{record_id}" else dom_class(record, prefix || NEW) end end + # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id. + # This can be overwritten to customize the default generated string representation if desired. + # If you need to read back a key from a dom_id in order to query for the underlying database record, + # you should write a helper like 'person_record_from_dom_id' that will extract the key either based + # on the default implementation (which just joins all key attributes with '-') or on your own + # overwritten version of the method. By default, this implementation passes the key string through a + # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to + # make sure yourself that your dom ids are valid, in case you overwrite this method. + def record_key_for_dom_id(record) + return record.id unless record.respond_to?(:to_model) + key = record.to_model.to_key + key ? sanitize_dom_id(key.join('_')) : key + end + + # Replaces characters that are invalid in HTML DOM ids with valid ones. + def sanitize_dom_id(candidate_id) + candidate_id # TODO implement conversion to valid DOM id values + end + # Returns the plural class name of a record or class. Examples: # # plural_class_name(post) # => "posts" diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 14557ca782..cdb5db32aa 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -17,9 +17,9 @@ module ActionController end end - def assign_parameters(controller_path, action, parameters = {}) + def assign_parameters(router, controller_path, action, parameters = {}) parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) - extra_keys = ActionController::Routing::Routes.extra_keys(parameters) + extra_keys = router.extra_keys(parameters) non_path_parameters = get? ? query_parameters : request_parameters parameters.each do |key, value| if value.is_a? Fixnum @@ -220,7 +220,7 @@ module ActionController def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET') # Sanity check for required instance variables so we can give an # understandable error message. - %w(@controller @request @response).each do |iv_name| + %w(@router @controller @request @response).each do |iv_name| if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil? raise "#{iv_name} is nil: make sure you set it in your test's setup method." end @@ -236,7 +236,7 @@ module ActionController @request.env['REQUEST_METHOD'] = http_method parameters ||= {} - @request.assign_parameters(@controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters) + @request.assign_parameters(@router, @controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters) @request.session = ActionController::TestSession.new(session) unless session.nil? @request.session["flash"] = @request.flash.update(flash || {}) @@ -322,6 +322,8 @@ module ActionController @controller ||= klass.new rescue nil end + @request.env.delete('PATH_INFO') + if @controller @controller.request = @request @controller.params = {} @@ -335,13 +337,20 @@ module ActionController private def build_request_uri(action, parameters) - unless @request.env['REQUEST_URI'] - options = @controller.__send__(:rewrite_options, parameters) - options.update(:only_path => true, :action => action) - - url = ActionController::UrlRewriter.new(@request, parameters) - @request.request_uri = url.rewrite(options) + unless @request.env["PATH_INFO"] + options = @controller.__send__(:url_options).merge(parameters) + options.update( + :only_path => true, + :action => action, + :relative_url_root => nil, + :_path_segments => @request.symbolized_path_parameters) + + url, query_string = @router.url_for(options).split("?", 2) + + @request.env["SCRIPT_NAME"] = @controller.config.relative_url_root + @request.env["PATH_INFO"] = url + @request.env["QUERY_STRING"] = query_string || "" end end - end + end end diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb deleted file mode 100644 index 933a1fa8f9..0000000000 --- a/actionpack/lib/action_controller/url_rewriter.rb +++ /dev/null @@ -1,76 +0,0 @@ -require 'active_support/core_ext/hash/except' - -module ActionController - # Rewrites URLs for Base.redirect_to and Base.url_for in the controller. - class UrlRewriter #:nodoc: - RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :skip_relative_url_root] - - def initialize(request, parameters) - @request, @parameters = request, parameters - end - - def rewrite(options = {}) - options[:host] ||= @request.host_with_port - options[:protocol] ||= @request.protocol - - self.class.rewrite(options, @request.symbolized_path_parameters) do |options| - process_path_options(options) - end - end - - def to_str - "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}" - end - - alias_method :to_s, :to_str - - def self.rewrite(options, path_segments=nil) - rewritten_url = "" - - unless options[:only_path] - rewritten_url << (options[:protocol] || "http") - rewritten_url << "://" unless rewritten_url.match("://") - rewritten_url << rewrite_authentication(options) - - raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host] - - rewritten_url << options[:host] - rewritten_url << ":#{options.delete(:port)}" if options.key?(:port) - end - - path_options = options.except(*RESERVED_OPTIONS) - path_options = yield(path_options) if block_given? - path = Routing::Routes.generate(path_options, path_segments || {}) - - rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root] - rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) - rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor] - - rewritten_url - end - - protected - - def self.rewrite_authentication(options) - if options[:user] && options[:password] - "#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@" - else - "" - end - end - - # Given a Hash of options, generates a route - def process_path_options(options) - options = options.symbolize_keys - options.update(options[:params].symbolize_keys) if options[:params] - - if (overwrite = options.delete(:overwrite_params)) - options.update(@parameters.symbolize_keys) - options.update(overwrite.symbolize_keys) - end - - options - end - - end -end diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 479ea959e6..dfb8919561 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -48,6 +48,7 @@ module ActionDispatch autoload :Flash autoload :Head autoload :ParamsParser + autoload :RemoteIp autoload :Rescue autoload :ShowExceptions autoload :Static diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 428e62dc6b..d2404e63c5 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -37,8 +37,21 @@ module ActionDispatch end module Response - def cache_control - @cache_control ||= {} + attr_reader :cache_control + + def initialize(*) + status, header, body = super + + @cache_control = {} + @etag = self["ETag"] + + if cache_control = self["Cache-Control"] + cache_control.split(/,\s*/).each do |segment| + first, last = segment.split("=") + last ||= true + @cache_control[first.to_sym] = last + end + end end def last_modified @@ -65,7 +78,7 @@ module ActionDispatch def etag=(etag) key = ActiveSupport::Cache.expand_cache_key(etag) - @etag = %("#{Digest::MD5.hexdigest(key)}") + @etag = self["ETag"] = %("#{Digest::MD5.hexdigest(key)}") end private @@ -100,6 +113,8 @@ module ActionDispatch def set_conditional_cache_control! control = @cache_control + return if self["Cache-Control"].present? + if control.empty? headers["Cache-Control"] = DEFAULT_CACHE_CONTROL elsif @cache_control[:no_cache] diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 1958e1668d..451b79b190 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -25,9 +25,16 @@ module ActionDispatch module FilterParameters extend ActiveSupport::Concern + mattr_reader :compiled_parameter_filter_for + @@compiled_parameter_filter_for = {} + # Return a hash of parameters with all sensitive data replaced. def filtered_parameters - @filtered_parameters ||= process_parameter_filter(parameters) + @filtered_parameters ||= if filtering_parameters? + process_parameter_filter(parameters) + else + parameters.dup + end end alias :fitered_params :filtered_parameters @@ -46,10 +53,18 @@ module ActionDispatch protected - def compile_parameter_filter #:nodoc: + def filtering_parameters? #:nodoc: + @env["action_dispatch.parameter_filter"].present? + end + + def process_parameter_filter(params) #:nodoc: + compiled_parameter_filter_for(@env["action_dispatch.parameter_filter"]).call(params) + end + + def compile_parameter_filter(filters) #:nodoc: strings, regexps, blocks = [], [], [] - Array(@env["action_dispatch.parameter_filter"]).each do |item| + filters.each do |item| case item when NilClass when Proc @@ -65,34 +80,34 @@ module ActionDispatch [regexps, blocks] end - def filtering_parameters? #:nodoc: - @env["action_dispatch.parameter_filter"].present? - end + def compiled_parameter_filter_for(filters) #:nodoc: + @@compiled_parameter_filter_for[filters] ||= begin + regexps, blocks = compile_parameter_filter(filters) - def process_parameter_filter(original_params) #:nodoc: - return original_params.dup unless filtering_parameters? + lambda do |original_params| + filtered_params = {} - filtered_params = {} - regexps, blocks = compile_parameter_filter + original_params.each do |key, value| + if regexps.find { |r| key =~ r } + value = '[FILTERED]' + elsif value.is_a?(Hash) + value = process_parameter_filter(value) + elsif value.is_a?(Array) + value = value.map { |v| v.is_a?(Hash) ? process_parameter_filter(v) : v } + elsif blocks.present? + key = key.dup + value = value.dup if value.duplicable? + blocks.each { |b| b.call(key, value) } + end - original_params.each do |key, value| - if regexps.find { |r| key =~ r } - value = '[FILTERED]' - elsif value.is_a?(Hash) - value = process_parameter_filter(value) - elsif value.is_a?(Array) - value = value.map { |i| process_parameter_filter(i) } - elsif blocks.present? - key = key.dup - value = value.dup if value.duplicable? - blocks.each { |b| b.call(key, value) } - end + filtered_params[key] = value + end - filtered_params[key] = value + filtered_params + end end - - filtered_params end + end end end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 40617e239a..fec250e928 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -67,21 +67,6 @@ module ActionDispatch @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])] end - # Returns a symbolized version of the <tt>:format</tt> parameter of the request. - # If no \format is given it returns <tt>:js</tt>for Ajax requests and <tt>:html</tt> - # otherwise. - def template_format - parameter_format = parameters[:format] - - if parameter_format - parameter_format - elsif xhr? - :js - else - :html - end - end - # Receives an array of mimes and return the first user sent mime that # matches the order array. # diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 7a17023ed2..ea9f0f99c2 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -30,6 +30,14 @@ module ActionDispatch METHOD end + def self.new(env) + if request = env["action_dispatch.request"] && request.instance_of?(self) + return request + end + + super + end + def key?(key) @env.key?(key) end @@ -119,36 +127,7 @@ module ActionDispatch # delimited list in the case of multiple chained proxies; the last # address which is not trusted is the originating IP. def remote_ip - remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/) - - unless remote_addr_list.blank? - not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES || addr =~ ActionController::Base.trusted_proxies} - return not_trusted_addrs.first unless not_trusted_addrs.empty? - end - remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',') - - if @env.include? 'HTTP_CLIENT_IP' - if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP']) - # We don't know which came from the proxy, and which from the user - raise ActionController::ActionControllerError.new <<EOM -IP spoofing attack?! -HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} -HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect} -EOM - end - - return @env['HTTP_CLIENT_IP'] - end - - if remote_ips - while remote_ips.size > 1 && (TRUSTED_PROXIES =~ remote_ips.last.strip || ActionController::Base.trusted_proxies =~ remote_ips.last.strip) - remote_ips.pop - end - - return remote_ips.last.strip - end - - @env['REMOTE_ADDR'] + (@env["action_dispatch.remote_ip"] || ip).to_s end # Returns the lowercase name of the HTTP server software. diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index f299306ff4..9cfe5a5ea9 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -32,31 +32,38 @@ module ActionDispatch # :nodoc: # end # end class Response < Rack::Response - include ActionDispatch::Http::Cache::Response - attr_accessor :request, :blank attr_writer :header, :sending_file alias_method :headers=, :header= - def initialize - @status = 200 - @header = {} - @cache_control = {} + module Setup + def initialize(status = 200, header = {}, body = []) + @writer = lambda { |x| @body << x } + @block = nil + @length = 0 - @writer = lambda { |x| @body << x } - @block = nil - @length = 0 + @status, @header = status, header + self.body = body - @body, @cookie = [], [] - @sending_file = false + @cookie = [] + @sending_file = false - @blank = false - @etag = nil + @blank = false + + if content_type = self["Content-Type"] + type, charset = content_type.split(/;\s*charset=/) + @content_type = Mime::Type.lookup(type) + @charset = charset || "UTF-8" + end - yield self if block_given? + yield self if block_given? + end end + include Setup + include ActionDispatch::Http::Cache::Response + def status=(status) @status = Rack::Utils.status_code(status) end @@ -76,6 +83,18 @@ module ActionDispatch # :nodoc: end alias_method :status_message, :message + def respond_to?(method) + if method.to_sym == :to_path + @body.respond_to?(:to_path) + else + super + end + end + + def to_path + @body.to_path + end + def body str = '' each { |part| str << part.to_s } @@ -120,7 +139,7 @@ module ActionDispatch # :nodoc: assign_default_content_type_and_charset! handle_conditional_get! self["Set-Cookie"] = @cookie.join("\n") unless @cookie.blank? - self["ETag"] = @etag if @etag + self["ETag"] = @_etag if @_etag super end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 40ceb5a9b6..b64a83c62e 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -3,7 +3,7 @@ module ActionDispatch module URL # Returns the complete URL used for this request. def url - protocol + host_with_port + request_uri + protocol + host_with_port + fullpath end # Returns 'https://' if this is an SSL request and 'http://' otherwise. @@ -81,42 +81,15 @@ module ActionDispatch parts[0..-(tld_length+2)] end - # Returns the query string, accounting for server idiosyncrasies. - def query_string - @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].to_s.split('?', 2)[1] || '') + def subdomain(tld_length = 1) + subdomains(tld_length).join('.') end # Returns the request URI, accounting for server idiosyncrasies. # WEBrick includes the full URL. IIS leaves REQUEST_URI blank. def request_uri - if uri = @env['REQUEST_URI'] - # Remove domain, which webrick puts into the request_uri. - (%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri - else - # Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO. - uri = @env['PATH_INFO'].to_s - - if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$}) - uri = uri.sub(/#{script_filename}\//, '') - end - - env_qs = @env['QUERY_STRING'].to_s - uri += "?#{env_qs}" unless env_qs.empty? - - if uri.blank? - @env.delete('REQUEST_URI') - else - @env['REQUEST_URI'] = uri - end - end - end - - # Returns the interpreted \path to requested resource after all the installation - # directory of this application was taken into account. - def path - path = request_uri.to_s[/\A[^\?]*/] - path.sub!(/\A#{ActionController::Base.relative_url_root}/, '') - path + ActiveSupport::Deprecation.warn "Using #request_uri is deprecated. Use fullpath instead.", caller + fullpath end private diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 7cf75ffe63..d07841218a 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -37,7 +37,7 @@ module ActionDispatch def initialize(app, prepare_each_request = false) @app, @prepare_each_request = app, prepare_each_request - run_callbacks(:prepare) unless @prepare_each_request + run_callbacks(:prepare) end def call(env) diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 522982e202..f4c4324fb0 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -1,4 +1,3 @@ -require 'active_support/json' require 'action_dispatch/http/request' module ActionDispatch diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb new file mode 100644 index 0000000000..c7d710b98e --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -0,0 +1,51 @@ +module ActionDispatch + class RemoteIp + class IpSpoofAttackError < StandardError ; end + + class RemoteIpGetter + def initialize(env, check_ip_spoofing, trusted_proxies) + @env = env + @check_ip_spoofing = check_ip_spoofing + @trusted_proxies = trusted_proxies + end + + def remote_addrs + @remote_addrs ||= begin + list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : [] + list.reject { |addr| addr =~ @trusted_proxies } + end + end + + def to_s + return remote_addrs.first if remote_addrs.any? + + forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : [] + + if client_ip = @env['HTTP_CLIENT_IP'] + if @check_ip_spoofing && !forwarded_ips.include?(client_ip) + # We don't know which came from the proxy, and which from the user + raise IpSpoofAttackError, "IP spoofing attack?!" \ + "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \ + "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" + end + return client_ip + end + + return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"] + end + end + + def initialize(app, check_ip_spoofing = true, trusted_proxies = nil) + @app = app + @check_ip_spoofing = check_ip_spoofing + regex = '(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)' + regex << "|(#{trusted_proxies})" if trusted_proxies + @trusted_proxies = Regexp.new(regex, "i") + end + + def call(env) + env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies) + @app.call(env) + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 04a101dbb2..22da82479e 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/hash/keys' -require 'rack/request' module ActionDispatch module Session @@ -177,9 +176,8 @@ module ActionDispatch if key.blank? raise ArgumentError, 'A key is required to write a ' + 'cookie containing the session data. Use ' + - 'config.action_controller.session = { :key => ' + - '"_myapp_session", :secret => "some secret phrase" } in ' + - 'config/application.rb' + 'config.action_controller.session_store :cookie_store, { :key => ' + + '"_myapp_session" } in config/application.rb' end end @@ -193,10 +191,9 @@ module ActionDispatch if secret.blank? raise ArgumentError, "A secret is required to generate an " + "integrity hash for cookie session data. Use " + - "config.action_controller.session = { :key => " + - "\"_myapp_session\", :secret => \"some secret phrase of at " + - "least #{SECRET_MIN_LENGTH} characters\" } " + - "in config/environment.rb" + "config.cookie_secret = \"some secret phrase of at " + + "least #{SECRET_MIN_LENGTH} characters\"" + + "in config/application.rb" end if secret.length < SECRET_MIN_LENGTH diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 18a2922fa7..5c5362ce4a 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -58,7 +58,7 @@ module ActionDispatch if lazy_compare?(@klass) && lazy_compare?(middleware) normalize(@klass) == normalize(middleware) else - klass == ActiveSupport::Inflector.constantize(middleware.to_s) + klass.name == middleware.to_s end end end @@ -122,7 +122,11 @@ module ActionDispatch find_all { |middleware| middleware.active? } end - def build(app) + def build(app = nil, &blk) + app ||= blk + + raise "MiddlewareStack#build requires an app" unless app + active.reverse.inject(app) { |a, e| e.build(a) } end end diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 335daafc01..e486bd4079 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -5,6 +5,9 @@ module ActionDispatch class Railtie < Rails::Railtie railtie_name :action_dispatch + config.action_dispatch.x_sendfile_header = "X-Sendfile" + config.action_dispatch.ip_spoofing_check = true + # Prepare dispatcher callbacks and run 'prepare' callbacks initializer "action_dispatch.prepare_dispatcher" do |app| # TODO: This used to say unless defined?(Dispatcher). Find out why and fix. diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 335c9edb98..5bc3205c51 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -205,6 +205,7 @@ module ActionDispatch autoload :Mapper, 'action_dispatch/routing/mapper' autoload :Route, 'action_dispatch/routing/route' autoload :RouteSet, 'action_dispatch/routing/route_set' + autoload :UrlFor, 'action_dispatch/routing/url_for' SEPARATORS = %w( / . ? ) HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb index 8ce6b2f6d5..dd650e83d9 100644 --- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb +++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb @@ -1,5 +1,30 @@ module ActionDispatch module Routing + class RouteSet + attr_accessor :controller_namespaces + + CONTROLLER_REGEXP = /[_a-zA-Z0-9]+/ + + def controller_constraints + @controller_constraints ||= begin + namespaces = controller_namespaces + in_memory_controller_namespaces + source = namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" } + source << CONTROLLER_REGEXP.source + Regexp.compile(source.sort.reverse.join('|')) + end + end + + def in_memory_controller_namespaces + namespaces = Set.new + ActionController::Base.subclasses.each do |klass| + controller_name = klass.underscore + namespaces << controller_name.split('/')[0...-1].join('/') + end + namespaces.delete('') + namespaces + end + end + # Mapper instances are used to build routes. The object passed to the draw # block in config/routes.rb is a Mapper instance. # @@ -244,14 +269,15 @@ module ActionDispatch attr_reader :collection_methods, :member_methods, :new_methods attr_reader :path_prefix, :name_prefix, :path_segment attr_reader :plural, :singular - attr_reader :options + attr_reader :options, :defaults - def initialize(entities, options) + def initialize(entities, options, defaults) @plural ||= entities @singular ||= options[:singular] || plural.to_s.singularize @path_segment = options.delete(:as) || @plural @options = options + @defaults = defaults arrange_actions add_default_actions @@ -280,7 +306,7 @@ module ActionDispatch def new_path new_action = self.options[:path_names][:new] if self.options[:path_names] - new_action ||= ActionController::Base.resources_path_names[:new] + new_action ||= self.defaults[:path_names][:new] @new_path ||= "#{path}/#{new_action}" end @@ -370,7 +396,7 @@ module ActionDispatch end class SingletonResource < Resource #:nodoc: - def initialize(entity, options) + def initialize(entity, options, defaults) @singular = @plural = entity options[:controller] ||= @singular.to_s.pluralize super @@ -717,7 +743,7 @@ module ActionDispatch private def map_resource(entities, options = {}, &block) - resource = Resource.new(entities, options) + resource = Resource.new(entities, options, :path_names => @set.resources_path_names) with_options :controller => resource.controller do |map| map_associations(resource, options) @@ -734,7 +760,7 @@ module ActionDispatch end def map_singleton_resource(entities, options = {}, &block) - resource = SingletonResource.new(entities, options) + resource = SingletonResource.new(entities, options, :path_names => @set.resources_path_names) with_options :controller => resource.controller do |map| map_associations(resource, options) @@ -826,7 +852,7 @@ module ActionDispatch actions.each do |action| [method].flatten.each do |m| action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) - action_path ||= ActionController::Base.resources_path_names[action] || action + action_path ||= @set.resources_path_names[action] || action map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true }) end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 8de68b3174..0b7b09ee7a 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/hash/except' + module ActionDispatch module Routing class Mapper @@ -36,7 +38,7 @@ module ActionDispatch end def to_route - [ app, conditions, requirements, defaults, @options[:as] ] + [ app, conditions, requirements, defaults, @options[:as], @options[:anchor] ] end private @@ -64,7 +66,7 @@ module ActionDispatch # match "account/overview" def using_match_shorthand?(args, options) - args.present? && options.except(:via).empty? && !args.first.include?(':') + args.present? && options.except(:via, :anchor).empty? && !args.first.include?(':') end def normalize_path(path) @@ -85,10 +87,9 @@ module ActionDispatch end def requirements - @requirements ||= returning(@options[:constraints] || {}) do |requirements| + @requirements ||= (@options[:constraints] || {}).tap do |requirements| requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints] @options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) } - requirements[:controller] ||= @set.controller_constraints end end @@ -175,9 +176,15 @@ module ActionDispatch end def match(*args) - @set.add_route(*Mapping.new(@set, @scope, args).to_route) + mapping = Mapping.new(@set, @scope, args).to_route + @set.add_route(*mapping) self end + + def default_url_options=(options) + @set.default_url_options = options + end + alias_method :default_url_options, :default_url_options= end module HttpHelpers @@ -283,7 +290,7 @@ module ActionDispatch end def namespace(path) - scope(path.to_s, :name_prefix => path.to_s, :namespace => path.to_s) { yield } + scope(path.to_s, :name_prefix => path.to_s, :controller_namespace => path.to_s) { yield } end def constraints(constraints = {}) @@ -294,6 +301,7 @@ module ActionDispatch options = args.extract_options! options = (@scope[:options] || {}).merge(options) + options[:anchor] = true unless options.key?(:anchor) if @scope[:name_prefix] && !options[:as].blank? options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}" @@ -318,12 +326,12 @@ module ActionDispatch parent ? "#{parent}_#{child}" : child end - def merge_namespace_scope(parent, child) + def merge_controller_namespace_scope(parent, child) parent ? "#{parent}/#{child}" : child end def merge_controller_scope(parent, child) - @scope[:namespace] ? "#{@scope[:namespace]}/#{child}" : child + @scope[:controller_namespace] ? "#{@scope[:controller_namespace]}/#{child}" : child end def merge_resources_path_names_scope(parent, child) @@ -367,9 +375,9 @@ module ActionDispatch def actions if only = options[:only] - only.map(&:to_sym) + Array(only).map(&:to_sym) elsif except = options[:except] - default_actions - except.map(&:to_sym) + default_actions - Array(except).map(&:to_sym) else default_actions end @@ -443,7 +451,7 @@ module ActionDispatch def resource(*resources, &block) options = resources.extract_options! - if verify_common_behavior_for(:resource, resources, options, &block) + if apply_common_behavior_for(:resource, resources, options, &block) return self end @@ -451,7 +459,10 @@ module ActionDispatch scope(:path => resource.name.to_s, :controller => resource.controller) do with_scope_level(:resource, resource) do - yield if block_given? + + scope(:name_prefix => resource.name.to_s) do + yield if block_given? + end get :show if resource.actions.include?(:show) post :create if resource.actions.include?(:create) @@ -468,7 +479,7 @@ module ActionDispatch def resources(*resources, &block) options = resources.extract_options! - if verify_common_behavior_for(:resources, resources, options, &block) + if apply_common_behavior_for(:resources, resources, options, &block) return self end @@ -534,6 +545,21 @@ module ActionDispatch end end + def mount(app, options = nil) + if options + path = options.delete(:at) + else + options = app + app, path = options.find { |k, v| k.respond_to?(:call) } + options.delete(app) if app + end + + raise "A rack application must be specified" unless path + + match(path, options.merge(:to => app, :anchor => false)) + self + end + def match(*args) options = args.extract_options! @@ -591,7 +617,7 @@ module ActionDispatch path_names[name.to_sym] || name.to_s end - def verify_common_behavior_for(method, resources, options, &block) + def apply_common_behavior_for(method, resources, options, &block) if resources.length > 1 resources.each { |r| send(method, r, options, &block) } return true diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb index e6e44d3546..6f37eadd6b 100644 --- a/actionpack/lib/action_dispatch/routing/route.rb +++ b/actionpack/lib/action_dispatch/routing/route.rb @@ -4,7 +4,7 @@ module ActionDispatch attr_reader :app, :conditions, :defaults, :name attr_reader :path, :requirements - def initialize(app, conditions = {}, requirements = {}, defaults = {}, name = nil) + def initialize(app, conditions, requirements, defaults, name, anchor) @app = app @defaults = defaults @name = name @@ -17,7 +17,7 @@ module ActionDispatch if path = conditions[:path_info] @path = path - conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS) + conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS, anchor) end @conditions = conditions.inject({}) { |h, (k, v)| diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index dcf98b729b..722be432c7 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,5 +1,6 @@ require 'rack/mount' require 'forwardable' +require 'action_dispatch/routing/deprecated_mapper' module ActionDispatch module Routing @@ -11,8 +12,8 @@ module ActionDispatch PARAMETERS_KEY = 'action_dispatch.request.path_parameters' class Dispatcher - def initialize(options = {}) - defaults = options[:defaults] + def initialize(options={}) + @defaults = options[:defaults] @glob_param = options.delete(:glob) end @@ -20,7 +21,8 @@ module ActionDispatch params = env[PARAMETERS_KEY] prepare_params!(params) - unless controller = controller(params) + # Just raise undefined constant errors if a controller was specified as default. + unless controller = controller(params, @defaults.key?(:controller)) return [404, {'X-Cascade' => 'pass'}, []] end @@ -39,14 +41,13 @@ module ActionDispatch end end - def controller(params) + def controller(params, raise_error=true) if params && params.has_key?(:controller) controller = "#{params[:controller].camelize}Controller" ActiveSupport::Inflector.constantize(controller) end rescue NameError => e - raise unless e.message.include?(controller) - nil + raise ActionController::RoutingError, e.message, e.backtrace if raise_error end private @@ -59,7 +60,6 @@ module ActionDispatch end end - # A NamedRouteCollection instance is a collection of named routes, and also # maintains an anonymous module that can be used to install helpers for the # named routes. @@ -168,63 +168,25 @@ module ActionDispatch selector = url_helper_name(name, kind) hash_access_method = hash_access_name(name, kind) - # We use module_eval to avoid leaks. - # - # def users_url(*args) - # if args.empty? || Hash === args.first - # options = hash_for_users_url(args.first || {}) - # else - # options = hash_for_users_url(args.extract_options!) - # default = default_url_options(options) if self.respond_to?(:default_url_options, true) - # options = (default ||= {}).merge(options) - # - # keys = [] - # keys -= options.keys if args.size < keys.size - 1 - # - # args = args.zip(keys).inject({}) do |h, (v, k)| - # h[k] = v - # h - # end - # - # # Tell url_for to skip default_url_options - # options[:use_defaults] = false - # options.merge!(args) - # end - # - # url_for(options) - # end @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 def #{selector}(*args) - if args.empty? || Hash === args.first - options = #{hash_access_method}(args.first || {}) - else - options = #{hash_access_method}(args.extract_options!) - default = default_url_options(options) if self.respond_to?(:default_url_options, true) - options = (default ||= {}).merge(options) - - keys = #{route.segment_keys.inspect} - keys -= options.keys if args.size < keys.size - 1 # take format into account - - args = args.zip(keys).inject({}) do |h, (v, k)| - h[k] = v - h - end - - # Tell url_for to skip default_url_options - options[:use_defaults] = false - options.merge!(args) + options = #{hash_access_method}(args.extract_options!) + + if args.any? + options[:_positional_args] = args + options[:_positional_keys] = #{route.segment_keys.inspect} end url_for(options) end - protected :#{selector} END_EVAL helpers << selector end end - attr_accessor :routes, :named_routes, :controller_namespaces + attr_accessor :routes, :named_routes attr_accessor :disable_clear_and_finalize, :resources_path_names + attr_accessor :default_url_options def self.default_resources_path_names { :new => 'new', :edit => 'edit' } @@ -235,8 +197,10 @@ module ActionDispatch self.named_routes = NamedRouteCollection.new self.resources_path_names = self.class.default_resources_path_names.dup self.controller_namespaces = Set.new + self.default_url_options = {} @disable_clear_and_finalize = false + clear! end def draw(&block) @@ -273,61 +237,155 @@ module ActionDispatch named_routes.install(destinations, regenerate_code) end - def empty? - routes.empty? - end + def url_helpers + @url_helpers ||= begin + router = self - CONTROLLER_REGEXP = /[_a-zA-Z0-9]+/ + Module.new do + extend ActiveSupport::Concern + include UrlFor - def controller_constraints - @controller_constraints ||= begin - namespaces = controller_namespaces + in_memory_controller_namespaces - source = namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" } - source << CONTROLLER_REGEXP.source - Regexp.compile(source.sort.reverse.join('|')) + # ROUTES TODO: install_helpers isn't great... can we make a module with the stuff that + # we can include? + # Yes plz - JP + included do + router.install_helpers(self) + end + + define_method(:_router) { router } + end end end - def in_memory_controller_namespaces - namespaces = Set.new - ActionController::Base.subclasses.each do |klass| - controller_name = klass.underscore - namespaces << controller_name.split('/')[0...-1].join('/') - end - namespaces.delete('') - namespaces + def empty? + routes.empty? end - def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil) - route = Route.new(app, conditions, requirements, defaults, name) + def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true) + route = Route.new(app, conditions, requirements, defaults, name, anchor) @set.add_route(*route) named_routes[name] = route if name routes << route route end - def options_as_params(options) - # If an explicit :controller was given, always make :action explicit - # too, so that action expiry works as expected for things like - # - # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) - # - # (the above is from the unit tests). In the above case, because the - # controller was explicitly given, but no action, the action is implied to - # be "index", not the recalled action of "show". - # - # great fun, eh? - - options_as_params = options.clone - options_as_params[:action] ||= 'index' if options[:controller] - options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action] - options_as_params - end + class Generator + attr_reader :options, :recall, :set, :script_name, :named_route + + def initialize(options, recall, set, extras = false) + @script_name = options.delete(:script_name) + @named_route = options.delete(:use_route) + @options = options.dup + @recall = recall.dup + @set = set + @extras = extras + + normalize_options! + normalize_controller_action_id! + use_relative_controller! + controller.sub!(%r{^/}, '') if controller + handle_nil_action! + end + + def controller + @controller ||= @options[:controller] + end + + def current_controller + @recall[:controller] + end + + def use_recall_for(key) + if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key]) + @options[key] = @recall.delete(key) + end + end + + def normalize_options! + # If an explicit :controller was given, always make :action explicit + # too, so that action expiry works as expected for things like + # + # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) + # + # (the above is from the unit tests). In the above case, because the + # controller was explicitly given, but no action, the action is implied to + # be "index", not the recalled action of "show". + + if options[:controller] + options[:action] ||= 'index' + options[:controller] = options[:controller].to_s + end + + if options[:action] + options[:action] = options[:action].to_s + end + end + + # This pulls :controller, :action, and :id out of the recall. + # The recall key is only used if there is no key in the options + # or if the key in the options is identical. If any of + # :controller, :action or :id is not found, don't pull any + # more keys from the recall. + def normalize_controller_action_id! + @recall[:action] ||= 'index' if current_controller + + use_recall_for(:controller) or return + use_recall_for(:action) or return + use_recall_for(:id) + end + + # if the current controller is "foo/bar/baz" and :controller => "baz/bat" + # is specified, the controller becomes "foo/baz/bat" + def use_relative_controller! + if !named_route && different_controller? + old_parts = current_controller.split('/') + size = controller.count("/") + 1 + parts = old_parts[0...-size] << controller + @controller = @options[:controller] = parts.join("/") + end + end + + # This handles the case of :action => nil being explicitly passed. + # It is identical to :action => "index" + def handle_nil_action! + if options.has_key?(:action) && options[:action].nil? + options[:action] = 'index' + end + recall[:action] = options.delete(:action) if options[:action] == 'index' + end - def build_expiry(options, recall) - recall.inject({}) do |expiry, (key, recalled_value)| - expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param) - expiry + def generate + error = ActionController::RoutingError.new("No route matches #{options.inspect}") + path, params = @set.generate(:path_info, named_route, options, recall, opts) + + raise error unless path + + params.reject! {|k,v| !v } + + return [path, params.keys] if @extras + + path << "?#{params.to_query}" if params.any? + "#{script_name}#{path}" + rescue Rack::Mount::RoutingError + raise error + end + + def opts + parameterize = lambda do |name, value| + if name == :controller + value + elsif value.is_a?(Array) + value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/') + else + Rack::Mount::Utils.escape_uri(value.to_param) + end + end + {:parameterize => parameterize} + end + + def different_controller? + return false unless current_controller + controller.to_param != current_controller.to_param end end @@ -338,82 +396,44 @@ module ActionDispatch end def generate_extras(options, recall={}) - generate(options, recall, :generate_extras) + generate(options, recall, true) end - def generate(options, recall = {}, method = :generate) - options, recall = options.dup, recall.dup - named_route = options.delete(:use_route) + def generate(options, recall = {}, extras = false) + Generator.new(options, recall, @set, extras).generate + end - options = options_as_params(options) - expire_on = build_expiry(options, recall) + RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash] - recall[:action] ||= 'index' if options[:controller] || recall[:controller] + def url_for(options) + options = default_url_options.merge(options || {}) - if recall[:controller] && (!options.has_key?(:controller) || options[:controller] == recall[:controller]) - options[:controller] = recall.delete(:controller) + handle_positional_args(options) - if recall[:action] && (!options.has_key?(:action) || options[:action] == recall[:action]) - options[:action] = recall.delete(:action) + rewritten_url = "" - if recall[:id] && (!options.has_key?(:id) || options[:id] == recall[:id]) - options[:id] = recall.delete(:id) - end - end - end - - options[:controller] = options[:controller].to_s if options[:controller] + path_segments = options.delete(:_path_segments) - if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/ - old_parts = recall[:controller].split('/') - new_parts = options[:controller].split('/') - parts = old_parts[0..-(new_parts.length + 1)] + new_parts - options[:controller] = parts.join('/') - end + unless options[:only_path] + rewritten_url << (options[:protocol] || "http") + rewritten_url << "://" unless rewritten_url.match("://") + rewritten_url << rewrite_authentication(options) - options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/ + raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host] - merged = options.merge(recall) - if options.has_key?(:action) && options[:action].nil? - options.delete(:action) - recall[:action] = 'index' + rewritten_url << options[:host] + rewritten_url << ":#{options.delete(:port)}" if options.key?(:port) end - recall[:action] = options.delete(:action) if options[:action] == 'index' - - opts = {} - opts[:parameterize] = lambda { |name, value| - if name == :controller - value - elsif value.is_a?(Array) - value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/') - else - Rack::Mount::Utils.escape_uri(value.to_param) - end - } - unless result = @set.generate(:path_info, named_route, options, recall, opts) - raise ActionController::RoutingError, "No route matches #{options.inspect}" - end + path_options = options.except(*RESERVED_OPTIONS) + path_options = yield(path_options) if block_given? + path = generate(path_options, path_segments || {}) - path, params = result - params.each do |k, v| - if v - params[k] = v - else - params.delete(k) - end - end + # ROUTES TODO: This can be called directly, so script_name should probably be set in the router + rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) + rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor] - if path && method == :generate_extras - [path, params.keys] - elsif path - path << "?#{params.to_query}" if params.any? - path - else - raise ActionController::RoutingError, "No route matches #{options.inspect}" - end - rescue Rack::Mount::RoutingError - raise ActionController::RoutingError, "No route matches #{options.inspect}" + rewritten_url end def call(env) @@ -431,9 +451,9 @@ module ActionDispatch end req = Rack::Request.new(env) - @set.recognize(req) do |route, params| + @set.recognize(req) do |route, matches, params| dispatcher = route.app - if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params) + if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params, false) dispatcher.prepare_params!(params) return params end @@ -441,6 +461,30 @@ module ActionDispatch raise ActionController::RoutingError, "No route matches #{path.inspect}" end + + private + def handle_positional_args(options) + return unless args = options.delete(:_positional_args) + + keys = options.delete(:_positional_keys) + keys -= options.keys if args.size < keys.size - 1 # take format into account + + args = args.zip(keys).inject({}) do |h, (v, k)| + h[k] = v + h + end + + # Tell url_for to skip default_url_options + options.merge!(args) + end + + def rewrite_authentication(options) + if options[:user] && options[:password] + "#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@" + else + "" + end + end end end end diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb new file mode 100644 index 0000000000..ec78f53fa6 --- /dev/null +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -0,0 +1,139 @@ +module ActionDispatch + module Routing + # In <b>routes.rb</b> one defines URL-to-controller mappings, but the reverse + # is also possible: an URL can be generated from one of your routing definitions. + # URL generation functionality is centralized in this module. + # + # See ActionDispatch::Routing and ActionController::Resources for general + # information about routing and routes.rb. + # + # <b>Tip:</b> If you need to generate URLs from your models or some other place, + # then ActionController::UrlFor is what you're looking for. Read on for + # an introduction. + # + # == URL generation from parameters + # + # As you may know, some functions - such as ActionController::Base#url_for + # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set + # of parameters. For example, you've probably had the chance to write code + # like this in one of your views: + # + # <%= link_to('Click here', :controller => 'users', + # :action => 'new', :message => 'Welcome!') %> + # + # #=> Generates a link to: /users/new?message=Welcome%21 + # + # link_to, and all other functions that require URL generation functionality, + # actually use ActionController::UrlFor under the hood. And in particular, + # they use the ActionController::UrlFor#url_for method. One can generate + # the same path as the above example by using the following code: + # + # include UrlFor + # url_for(:controller => 'users', + # :action => 'new', + # :message => 'Welcome!', + # :only_path => true) + # # => "/users/new?message=Welcome%21" + # + # Notice the <tt>:only_path => true</tt> part. This is because UrlFor has no + # information about the website hostname that your Rails app is serving. So if you + # want to include the hostname as well, then you must also pass the <tt>:host</tt> + # argument: + # + # include UrlFor + # url_for(:controller => 'users', + # :action => 'new', + # :message => 'Welcome!', + # :host => 'www.example.com') # Changed this. + # # => "http://www.example.com/users/new?message=Welcome%21" + # + # By default, all controllers and views have access to a special version of url_for, + # that already knows what the current hostname is. So if you use url_for in your + # controllers or your views, then you don't need to explicitly pass the <tt>:host</tt> + # argument. + # + # For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for. + # So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for' + # in full. However, mailers don't have hostname information, and what's why you'll still + # have to specify the <tt>:host</tt> argument when generating URLs in mailers. + # + # + # == URL generation for named routes + # + # UrlFor also allows one to access methods that have been auto-generated from + # named routes. For example, suppose that you have a 'users' resource in your + # <b>routes.rb</b>: + # + # map.resources :users + # + # This generates, among other things, the method <tt>users_path</tt>. By default, + # this method is accessible from your controllers, views and mailers. If you need + # to access this auto-generated method from other places (such as a model), then + # you can do that by including ActionController::UrlFor in your class: + # + # class User < ActiveRecord::Base + # include ActionController::UrlFor + # + # def base_uri + # user_path(self) + # end + # end + # + # User.find(1).base_uri # => "/users/1" + # + module UrlFor + extend ActiveSupport::Concern + + included do + # TODO: with_routing extends @controller with url_helpers, trickling down to including this module which overrides its default_url_options + unless method_defined?(:default_url_options) + # Including in a class uses an inheritable hash. Modules get a plain hash. + if respond_to?(:class_attribute) + class_attribute :default_url_options + else + mattr_accessor :default_url_options + remove_method :default_url_options + end + + self.default_url_options = {} + end + end + + def url_options + default_url_options + end + + # Generate a url based on the options provided, default_url_options and the + # routes defined in routes.rb. The following options are supported: + # + # * <tt>:only_path</tt> - If true, the relative url is returned. Defaults to +false+. + # * <tt>:protocol</tt> - The protocol to connect to. Defaults to 'http'. + # * <tt>:host</tt> - Specifies the host the link should be targeted at. + # If <tt>:only_path</tt> is false, this option must be + # provided either explicitly, or via +default_url_options+. + # * <tt>:port</tt> - Optionally specify the port to connect to. + # * <tt>:anchor</tt> - An anchor name to be appended to the path. + # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/" + # + # Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to + # +url_for+ is forwarded to the Routes module. + # + # Examples: + # + # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :port=>'8080' # => 'http://somehost.org:8080/tasks/testing' + # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok' + # url_for :controller => 'tasks', :action => 'testing', :trailing_slash=>true # => 'http://somehost.org/tasks/testing/' + # url_for :controller => 'tasks', :action => 'testing', :host=>'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33' + def url_for(options = nil) + case options + when String + options + when nil, Hash + _router.url_for(url_options.merge(options || {})) + else + polymorphic_url(options) + end + end + end + end +end diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index c2486d3730..937c9f48d2 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -132,16 +132,21 @@ module ActionDispatch end def normalize_argument_to_redirection(fragment) - after_routing = @controller.url_for(fragment) - if after_routing =~ %r{^\w+://.*} - after_routing - else - # FIXME - this should probably get removed. - if after_routing.first != '/' - after_routing = '/' + after_routing + case fragment + when %r{^\w[\w\d+.-]*:.*} + fragment + when String + if fragment =~ %r{^\w[\w\d+.-]*:.*} + fragment + else + @request.protocol + @request.host_with_port + fragment end - @request.protocol + @request.host_with_port + after_routing - end + when :back + raise RedirectBackError unless refer = @request.headers["Referer"] + refer + else + @controller.url_for(fragment) + end.gsub(/[\r\n]/, '') end def validate_request! diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 0c33539b4a..1d7e8090e4 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -80,7 +80,7 @@ module ActionDispatch expected_path = "/#{expected_path}" unless expected_path[0] == ?/ # Load routes.rb if it hasn't been loaded. - generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, defaults) + generated_path, extra_keys = @router.generate_extras(options, defaults) found_extras = options.reject {|k, v| ! extra_keys.include? k} msg = build_message(message, "found extras <?>, not <?>", found_extras, extras) @@ -125,7 +125,7 @@ module ActionDispatch end # A helper to make it easier to test different route configurations. - # This method temporarily replaces ActionController::Routing::Routes + # This method temporarily replaces @router # with a new RouteSet instance. # # The new instance is yielded to the passed block. Typically the block @@ -142,22 +142,19 @@ module ActionDispatch # end # def with_routing - real_routes = ActionController::Routing::Routes - ActionController::Routing.module_eval { remove_const :Routes } - - temporary_routes = ActionController::Routing::RouteSet.new - ActionController::Routing.module_eval { const_set :Routes, temporary_routes } - - yield temporary_routes + old_routes, @router = @router, ActionDispatch::Routing::RouteSet.new + old_controller, @controller = @controller, @controller.clone if @controller + _router = @router + @controller.singleton_class.send(:send, :include, @router.url_helpers) if @controller + yield @router ensure - if ActionController::Routing.const_defined? :Routes - ActionController::Routing.module_eval { remove_const :Routes } - end - ActionController::Routing.const_set(:Routes, real_routes) if real_routes + @router = old_routes + @controller = old_controller if @controller end + # ROUTES TODO: These assertions should really work in an integration context def method_missing(selector, *args, &block) - if @controller && ActionController::Routing::Routes.named_routes.helpers.include?(selector) + if @controller && @router.named_routes.helpers.include?(selector) @controller.send(selector, *args, &block) else super @@ -174,7 +171,7 @@ module ActionDispatch request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method request.path = path - params = ActionController::Routing::Routes.recognize_path(path, { :method => request.method }) + params = @router.recognize_path(path, { :method => request.method }) request.path_parameters = params.with_indifferent_access request diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 2093bb3a0e..0aff4250c1 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -1,6 +1,6 @@ require 'stringio' require 'uri' -require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/object/singleton_class' require 'rack/test' module ActionDispatch @@ -162,12 +162,31 @@ module ActionDispatch # A running counter of the number of requests processed. attr_accessor :request_count + include ActionDispatch::Routing::UrlFor + # Create and initialize a new Session instance. def initialize(app) @app = app + + # If the app is a Rails app, make url_helpers available on the session + # This makes app.url_for and app.foo_path available in the console + if app.respond_to?(:routes) && app.routes.respond_to?(:url_helpers) + singleton_class.class_eval { include app.routes.url_helpers } + end + reset! end + def url_options + opts = super.reverse_merge( + :host => host, + :protocol => https? ? "https" : "http" + ) + + opts.merge!(:port => 443) if !opts.key?(:port) && https? + opts + end + # Resets the instance. This can be used to reset the state information # in an existing session instance, so it can be used from a clean-slate # condition. @@ -187,12 +206,10 @@ module ActionDispatch unless defined? @named_routes_configured # install the named routes in this session instance. - klass = metaclass - ActionController::Routing::Routes.install_helpers(klass) + klass = singleton_class # the helpers are made protected by default--we make them public for # easier access during testing and troubleshooting. - klass.module_eval { public *ActionController::Routing::Routes.named_routes.helpers } @named_routes_configured = true end end @@ -221,14 +238,6 @@ module ActionDispatch @host = name end - # Returns the URL for the given options, according to the rules specified - # in the application's routes. - def url_for(options) - controller ? - controller.url_for(options) : - generic_url_rewriter.rewrite(options) - end - private # Performs the actual request. @@ -273,26 +282,14 @@ module ActionDispatch @request_count += 1 @request = ActionDispatch::Request.new(session.last_request.env) - @response = ActionDispatch::TestResponse.from_response(@mock_session.last_response) + response = @mock_session.last_response + @response = ActionDispatch::TestResponse.new(response.status, response.headers, response.body) @html_document = nil @controller = session.last_request.env['action_controller.instance'] return response.status end - - # Get a temporary URL writer object - def generic_url_rewriter - env = { - 'REQUEST_METHOD' => "GET", - 'QUERY_STRING' => "", - "REQUEST_URI" => "/", - "HTTP_HOST" => host, - "SERVER_PORT" => https? ? "443" : "80", - "HTTPS" => https? ? "on" : "off" - } - ActionController::UrlRewriter.new(ActionDispatch::Request.new(env), {}) - end end module Runner @@ -364,6 +361,14 @@ module ActionDispatch end end + extend ActiveSupport::Concern + include ActionDispatch::Routing::UrlFor + + def url_options + reset! unless @integration_session + @integration_session.url_options + end + # Delegate unhandled messages to the current session instance. def method_missing(sym, *args, &block) reset! unless @integration_session diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index eae703e1b6..d4eecac2de 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -1,3 +1,5 @@ +require 'action_dispatch/middleware/flash' + module ActionDispatch module TestProcess def assigns(key = nil) diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 67f89e1627..72a1cd30ad 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,9 +1,10 @@ -module ActionPack #:nodoc: +module ActionPack module VERSION #:nodoc: MAJOR = 3 MINOR = 0 - TINY = "0.beta1" + TINY = 0 + BUILD = "beta1" - STRING = [MAJOR, MINOR, TINY].join('.') + STRING = [MAJOR, MINOR, TINY, BUILD].join('.') end end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index f5035fe45a..afe6386105 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -37,15 +37,18 @@ module ActionView autoload :Helpers autoload_under "render" do + autoload :Layouts autoload :Partials autoload :Rendering end - autoload :MissingTemplate, 'action_view/base' - autoload :Resolver, 'action_view/template/resolver' - autoload :PathResolver, 'action_view/template/resolver' - autoload :PathSet, 'action_view/paths' - autoload :FileSystemResolverWithFallback, 'action_view/template/resolver' + autoload :Base + autoload :LookupContext + autoload :MissingTemplate, 'action_view/base' + autoload :Resolver, 'action_view/template/resolver' + autoload :PathResolver, 'action_view/template/resolver' + autoload :FileSystemResolver, 'action_view/template/resolver' + autoload :PathSet, 'action_view/paths' autoload :TemplateError, 'action_view/template/error' autoload :TemplateHandler, 'action_view/template' @@ -55,7 +58,7 @@ module ActionView autoload :TestCase, 'action_view/test_case' end +require 'active_support/i18n' require 'active_support/core_ext/string/output_safety' -require 'action_view/base' I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml" diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 4096c296c3..ffe3060404 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -11,7 +11,7 @@ module ActionView #:nodoc: def initialize(paths, path, details, partial) @path = path - display_paths = paths.compact.join(":") + display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ") template_type = if partial "partial" elsif path =~ /layouts/i @@ -20,7 +20,7 @@ module ActionView #:nodoc: 'template' end - super("Missing #{template_type} #{path} with #{details.inspect} in view path #{display_paths}") + super("Missing #{template_type} #{path} with #{details.inspect} in view paths #{display_paths}") end end @@ -173,81 +173,43 @@ module ActionView #:nodoc: module Subclasses end - include Helpers, Rendering, Partials, ::ERB::Util + include Helpers, Rendering, Partials, Layouts, ::ERB::Util, Context + extend ActiveSupport::Memoizable - def config - self.config = DEFAULT_CONFIG unless @config - @config - end - - def config=(config) - @config = ActiveSupport::OrderedOptions.new.merge(config) - end - - extend ActiveSupport::Memoizable - - attr_accessor :base_path, :assigns, :template_extension, :formats - attr_internal :captures + ActionView.run_base_hooks(self) - def reset_formats(formats) - @formats = formats - - if defined?(AbstractController::HashKey) - # This is expensive, but we need to reset this when the format is updated, - # which currently only happens - Thread.current[:format_locale_key] = - AbstractController::HashKey.get(self.class, formats, I18n.locale) - end - end - - class << self - delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB' - delegate :logger, :to => 'ActionController::Base', :allow_nil => true - end - - @@debug_rjs = false - ## - # :singleton-method: # Specify whether RJS responses should be wrapped in a try/catch block # that alert()s the caught exception (and then re-raises it). cattr_accessor :debug_rjs + @@debug_rjs = false - # Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed. - # Automatically reloading templates are not thread safe and should only be used in development mode. - @@cache_template_loading = nil - cattr_accessor :cache_template_loading - - # :nodoc: - def self.xss_safe? - true - end + class_attribute :helpers + attr_reader :helpers - def self.cache_template_loading? - ActionController::Base.allow_concurrency || (cache_template_loading.nil? ? !ActiveSupport::Dependencies.load? : cache_template_loading) + class << self + delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB' + delegate :logger, :to => 'ActionController::Base', :allow_nil => true end - attr_internal :request, :layout + attr_accessor :base_path, :assigns, :template_extension, :lookup_context + attr_internal :captures, :request, :layout, :controller, :template, :config - def controller_path - @controller_path ||= controller && controller.controller_path - end + delegate :find, :exists?, :formats, :formats=, :locale, :locale=, + :view_paths, :view_paths=, :with_fallbacks, :update_details, :to => :lookup_context delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers, :flash, :action_name, :controller_name, :to => :controller delegate :logger, :to => :controller, :allow_nil => true - delegate :find, :to => :view_paths - - include Context + def self.xss_safe? #:nodoc: + true + end def self.process_view_paths(value) ActionView::PathSet.new(Array(value)) end - class_attribute :helpers - attr_reader :helpers - def self.for_controller(controller) @views ||= {} @@ -275,26 +237,26 @@ module ActionView #:nodoc: klass = self end - klass.new(controller.class.view_paths, {}, controller) + klass.new(controller.lookup_context, {}, controller) end - def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil, formats = nil)#:nodoc: + def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc: @config = nil - @formats = formats @assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) } @helpers = self.class.helpers || Module.new @_controller = controller - @_content_for = Hash.new {|h,k| h[k] = ActiveSupport::SafeBuffer.new } + @_config = ActiveSupport::InheritableOptions.new(controller.config) if controller + @_content_for = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new } @_virtual_path = nil - self.view_paths = view_paths - end - attr_internal :controller, :template - attr_reader :view_paths + @lookup_context = lookup_context.is_a?(ActionView::LookupContext) ? + lookup_context : ActionView::LookupContext.new(lookup_context) + @lookup_context.formats = formats if formats + end - def view_paths=(paths) - @view_paths = self.class.process_view_paths(paths) + def controller_path + @controller_path ||= controller && controller.controller_path end def punctuate_body!(part) diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index b4f649385a..e359b0bdac 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -10,6 +10,7 @@ module ActionView #:nodoc: autoload :CsrfHelper, 'action_view/helpers/csrf_helper' autoload :DateHelper, 'action_view/helpers/date_helper' autoload :DebugHelper, 'action_view/helpers/debug_helper' + autoload :DeprecatedBlockHelpers, 'action_view/helpers/deprecated_block_helpers' autoload :FormHelper, 'action_view/helpers/form_helper' autoload :FormOptionsHelper, 'action_view/helpers/form_options_helper' autoload :FormTagHelper, 'action_view/helpers/form_tag_helper' diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index e106bb0897..4e12cdab54 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -5,9 +5,11 @@ require 'active_support/core_ext/enumerable' require 'active_support/core_ext/kernel/reporting' module ActionView - class Base - @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>".html_safe } - cattr_accessor :field_error_proc + ActionView.base_hook do + class ActionView::Base + @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"fieldWithErrors\">#{html_tag}</div>".html_safe } + cattr_accessor :field_error_proc + end end module Helpers @@ -80,13 +82,13 @@ module ActionView record = convert_to_model(record) options = options.symbolize_keys - options[:action] ||= record.new_record? ? "create" : "update" + options[:action] ||= record.persisted? ? "update" : "create" action = url_for(:action => options[:action], :id => record) submit_value = options[:submit_value] || options[:action].gsub(/[^\w]/, '').capitalize contents = form_tag({:action => action}, :method =>(options[:method] || 'post'), :enctype => options[:multipart] ? 'multipart/form-data': nil) - contents.safe_concat hidden_field(record_name, :id) unless record.new_record? + contents.safe_concat hidden_field(record_name, :id) if record.persisted? contents.safe_concat all_input_tags(record, record_name, options) yield contents if block_given? contents.safe_concat submit_tag(submit_value) @@ -127,7 +129,7 @@ module ActionView if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) && (errors = obj.errors[method]) content_tag("div", - "#{options[:prepend_text]}#{ERB::Util.html_escape(errors.first)}#{options[:append_text]}", + (options[:prepend_text].html_safe << errors.first).safe_concat(options[:append_text]), :class => options[:css_class] ) else @@ -226,16 +228,16 @@ module ActionView error_messages = objects.sum do |object| object.errors.full_messages.map do |msg| - content_tag(:li, ERB::Util.html_escape(msg)) + content_tag(:li, msg) end - end.join + end.join.html_safe contents = '' contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank? contents << content_tag(:p, message) unless message.blank? contents << content_tag(:ul, error_messages) - content_tag(:div, contents, html) + content_tag(:div, contents.html_safe, html) end else '' diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 96976ce45f..0c488b6793 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -11,7 +11,7 @@ module ActionView # the assets exist before linking to them: # # image_tag("rails.png") - # # => <img alt="Rails src="/images/rails.png?1230601161" /> + # # => <img alt="Rails" src="/images/rails.png?1230601161" /> # stylesheet_link_tag("application") # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" /> # @@ -133,13 +133,6 @@ module ActionView # change. You can use something like Live HTTP Headers for Firefox to verify # that the cache is indeed working. module AssetTagHelper - assets_dir = defined?(Rails.public_path) ? Rails.public_path : "public" - ActionView::DEFAULT_CONFIG = { - :assets_dir => assets_dir, - :javascripts_dir => "#{assets_dir}/javascripts", - :stylesheets_dir => "#{assets_dir}/stylesheets", - } - JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls', 'rails'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES) # Returns a link tag that browsers and news readers can use to auto-detect @@ -530,7 +523,7 @@ module ActionView options.symbolize_keys! src = options[:src] = path_to_image(source) - options[:alt] ||= File.basename(src, '.*').split('.').first.to_s.capitalize + options[:alt] ||= File.basename(src, '.*').capitalize if size = options.delete(:size) options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} @@ -648,8 +641,8 @@ module ActionView source = rewrite_asset_path(source) if has_request && include_host - unless source =~ %r{^#{ActionController::Base.relative_url_root}/} - source = "#{ActionController::Base.relative_url_root}#{source}" + unless source =~ %r{^#{controller.config.relative_url_root}/} + source = "#{controller.config.relative_url_root}#{source}" end end end @@ -677,7 +670,7 @@ module ActionView # or the value returned from invoking the proc if it's a proc or the value from # invoking call if it's an object responding to call. def compute_asset_host(source) - if host = ActionController::Base.asset_host + if host = config.asset_host if host.is_a?(Proc) || host.respond_to?(:call) case host.is_a?(Proc) ? host.arity : host.method(:call).arity when 2 diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index 9951e11a37..58c3a8752e 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -8,7 +8,7 @@ module ActionView # Full usage example: # # config/routes.rb: - # ActionController::Routing::Routes.draw do |map| + # Basecamp::Application.routes.draw do |map| # map.resources :posts # map.root :controller => "posts" # end @@ -114,7 +114,7 @@ module ActionView feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)} xml.feed(feed_opts) do - xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.request_uri.split(".")[0]}") + xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}") xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port)) xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url) diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 8c48300ed3..75fc2fddeb 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -30,14 +30,10 @@ module ActionView # <b><%= @greeting %></b> # </body></html> # - def capture(*args, &block) - # Return captured buffer in erb. - if block_called_from_erb?(block) - with_output_buffer { block.call(*args) } - else - # Return block result otherwise, but protect buffer also. - with_output_buffer { return block.call(*args) } - end + def capture(*args) + value = nil + buffer = with_output_buffer { value = yield *args } + buffer.presence || value end # Calling content_for stores a block of markup in an identifier for later use. @@ -143,7 +139,7 @@ module ActionView # Defaults to a new empty string. def with_output_buffer(buf = nil) #:nodoc: unless buf - buf = ActiveSupport::SafeBuffer.new + buf = ActionView::OutputBuffer.new buf.force_encoding(output_buffer.encoding) if buf.respond_to?(:force_encoding) end self.output_buffer, old_buffer = buf, output_buffer diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 8be2f76bd6..42018ee261 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -1,5 +1,6 @@ require "date" require 'action_view/helpers/tag_helper' +require 'active_support/core_ext/hash/slice' module ActionView module Helpers @@ -815,7 +816,7 @@ module ActionView tag_options[:selected] = "selected" if selected == i select_options << content_tag(:option, value, tag_options) end - select_options.join("\n") + "\n" + (select_options.join("\n") + "\n").html_safe end # Builds select tag from date type and html select options @@ -833,9 +834,9 @@ module ActionView select_html = "\n" select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank] select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt] - select_html << select_options_as_html.to_s + select_html << select_options_as_html - (content_tag(:select, select_html, select_options) + "\n").html_safe + (content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe end # Builds a prompt option tag with supplied options or from default options @@ -865,7 +866,7 @@ module ActionView :id => input_id_from_type(type), :name => input_name_from_type(type), :value => value - }) + "\n").html_safe + }.merge(@html_options.slice(:disabled))) + "\n").html_safe end # Returns the name attribute for the input tag @@ -907,7 +908,7 @@ module ActionView when :hour (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator] when :minute - @options[:time_separator] + @options[:discard_minute] ? "" : @options[:time_separator] when :second @options[:include_seconds] ? @options[:time_separator] : "" end diff --git a/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb b/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb new file mode 100644 index 0000000000..3d0657e873 --- /dev/null +++ b/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb @@ -0,0 +1,52 @@ +module ActionView + module Helpers + module DeprecatedBlockHelpers + extend ActiveSupport::Concern + + include ActionView::Helpers::TagHelper + include ActionView::Helpers::TextHelper + include ActionView::Helpers::JavaScriptHelper + include ActionView::Helpers::FormHelper + + def content_tag(*, &block) + block_called_from_erb?(block) ? safe_concat(super) : super + end + + def javascript_tag(*, &block) + block_called_from_erb?(block) ? safe_concat(super) : super + end + + def form_for(*, &block) + block_called_from_erb?(block) ? safe_concat(super) : super + end + + def form_tag(*, &block) + block_called_from_erb?(block) ? safe_concat(super) : super + end + + def fields_for(*, &block) + block_called_from_erb?(block) ? safe_concat(super) : super + end + + def field_set_tag(*, &block) + block_called_from_erb?(block) ? safe_concat(super) : super + end + + BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template' + + if RUBY_VERSION < '1.9.0' + # Check whether we're called from an erb template. + # We'd return a string in any other case, but erb <%= ... %> + # can't take an <% end %> later on, so we have to use <% ... %> + # and implicitly concat. + def block_called_from_erb?(block) + block && eval(BLOCK_CALLED_FROM_ERB, block) + end + else + def block_called_from_erb?(block) + block && eval(BLOCK_CALLED_FROM_ERB, block.binding) + end + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 305d6b3128..7293145964 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -92,6 +92,10 @@ module ActionView # link:classes/ActionView/Helpers/DateHelper.html, and # link:classes/ActionView/Helpers/ActiveRecordHelper.html module FormHelper + extend ActiveSupport::Concern + + include FormTagHelper + # Creates a form and a scope around a specific model object that is used # as a base for questioning about values for the fields. # @@ -309,21 +313,20 @@ module ActionView options[:html][:remote] = true if options.delete(:remote) - safe_concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {})) - fields_for(object_name, *(args << options), &proc) - safe_concat('</form>') + output = form_tag(options.delete(:url) || {}, options.delete(:html) || {}) + output << fields_for(object_name, *(args << options), &proc) + output.safe_concat('</form>') end def apply_form_for_options!(object_or_array, options) #:nodoc: object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array - object = convert_to_model(object) html_options = - if object.respond_to?(:new_record?) && object.new_record? - { :class => dom_class(object, :new), :id => dom_id(object), :method => :post } - else + if object.respond_to?(:persisted?) && object.persisted? { :class => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put } + else + { :class => dom_class(object, :new), :id => dom_id(object), :method => :post } end options[:html] ||= {} @@ -529,7 +532,10 @@ module ActionView end builder = options[:builder] || ActionView::Base.default_form_builder - yield builder.new(object_name, object, self, options, block) + + with_output_buffer do + yield builder.new(object_name, object, self, options, block) + end end # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object @@ -1150,7 +1156,7 @@ module ActionView def submit_default_value object = @object.respond_to?(:to_model) ? @object.to_model : @object - key = object ? (object.new_record? ? :create : :update) : :submit + key = object ? (object.persisted? ? :update : :create) : :submit model = if object.class.respond_to?(:model_name) object.class.model_name.human @@ -1176,7 +1182,7 @@ module ActionView association = args.shift association = association.to_model if association.respond_to?(:to_model) - if association.respond_to?(:new_record?) + if association.respond_to?(:persisted?) association = [association] if @object.send(association_name).is_a?(Array) elsif !association.is_a?(Array) association = @object.send(association_name) @@ -1184,9 +1190,11 @@ module ActionView if association.is_a?(Array) explicit_child_index = options[:child_index] - association.map do |child| - fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, options, block) - end.join + output = ActiveSupport::SafeBuffer.new + association.each do |child| + output << fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, options, block) + end + output elsif association fields_for_nested_model(name, association, options, block) end @@ -1195,13 +1203,13 @@ module ActionView def fields_for_nested_model(name, object, options, block) object = object.to_model if object.respond_to?(:to_model) - if object.new_record? - @template.fields_for(name, object, options, &block) - else + if object.persisted? @template.fields_for(name, object, options) do |builder| block.call(builder) @template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id? end + else + @template.fields_for(name, object, options, &block) end end @@ -1212,8 +1220,10 @@ module ActionView end end - class Base - cattr_accessor :default_form_builder - @@default_form_builder = ::ActionView::Helpers::FormBuilder + ActionView.base_hook do + class ActionView::Base + cattr_accessor :default_form_builder + @@default_form_builder = ::ActionView::Helpers::FormBuilder + end end end diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 21acfbbee8..4c523d4b20 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -97,7 +97,9 @@ module ActionView # </select> # module FormOptionsHelper + # ERB::Util can mask some helpers like textilize. Make sure to include them. include ERB::Util + include TextHelper # Create a select tag and a series of contained option tags for the provided object and method. # The option currently held by the object will be selected, provided that the object is available. @@ -572,10 +574,9 @@ module ActionView end if value.blank? && options[:prompt] prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select') - "<option value=\"\">#{prompt}</option>\n" + option_tags - else - option_tags + option_tags = "<option value=\"\">#{prompt}</option>\n" + option_tags end + option_tags.html_safe end end diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 9d76870646..07694f5ebb 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -10,6 +10,11 @@ module ActionView # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying # <tt>:disabled => true</tt> will give <tt>disabled="disabled"</tt>. module FormTagHelper + extend ActiveSupport::Concern + + include UrlHelper + include TextHelper + # Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like # ActionController::Base#url_for. The method for the form defaults to POST. # @@ -90,9 +95,9 @@ module ActionView html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name if blank = options.delete(:include_blank) if blank.kind_of?(String) - option_tags = "<option value=\"\">#{blank}</option>" + option_tags + option_tags = "<option value=\"\">#{blank}</option>".html_safe + option_tags else - option_tags = "<option value=\"\"></option>" + option_tags + option_tags = "<option value=\"\"></option>".html_safe + option_tags end end content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) @@ -279,7 +284,7 @@ module ActionView escape = options.key?("escape") ? options.delete("escape") : true content = html_escape(content) if escape - content_tag :textarea, content, { "name" => name, "id" => sanitize_to_id(name) }.update(options) + content_tag :textarea, content.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options) end # Creates a check box form input tag. @@ -441,10 +446,10 @@ module ActionView # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset> def field_set_tag(legend = nil, options = nil, &block) content = capture(&block) - safe_concat(tag(:fieldset, options, true)) - safe_concat(content_tag(:legend, legend)) unless legend.blank? - concat(content) - safe_concat("</fieldset>") + output = tag(:fieldset, options, true) + output.safe_concat(content_tag(:legend, legend)) unless legend.blank? + output.concat(content) + output.safe_concat("</fieldset>") end private @@ -477,9 +482,10 @@ module ActionView def form_tag_in_block(html_options, &block) content = capture(&block) - safe_concat(form_tag_html(html_options)) - concat(content) - safe_concat("</form>") + output = ActiveSupport::SafeBuffer.new + output.safe_concat(form_tag_html(html_options)) + output << content + output.safe_concat("</form>") end def token_tag diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index 0a901b2d2b..07eee3b399 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -83,17 +83,11 @@ module ActionView content_or_options_with_block end - tag = content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS)) - - if block_called_from_erb?(block) - safe_concat(tag) - else - tag - end + content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS)) end def javascript_cdata_section(content) #:nodoc: - "\n//#{cdata_section("\n#{content}\n//")}\n" + "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe end end end diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 3d3502a08b..46e41bc406 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -28,27 +28,25 @@ module ActionView # number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".") # => +1.123.555.1234 x 1343 def number_to_phone(number, options = {}) - number = number.to_s.strip unless number.nil? + return nil if number.nil? + + number = number.to_s.strip options = options.symbolize_keys area_code = options[:area_code] || nil delimiter = options[:delimiter] || "-" extension = options[:extension].to_s.strip || nil country_code = options[:country_code] || nil - begin - str = "" - str << "+#{country_code}#{delimiter}" unless country_code.blank? - str << if area_code - number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4}$)/,"(\\1) \\2#{delimiter}\\3") - else - number.gsub!(/([0-9]{0,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") - number.starts_with?('-') ? number.slice!(1..-1) : number - end - str << " x #{extension}" unless extension.blank? - str - rescue - number + str = "" + str << "+#{country_code}#{delimiter}" unless country_code.blank? + str << if area_code + number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4}$)/,"(\\1) \\2#{delimiter}\\3") + else + number.gsub!(/([0-9]{0,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") + number.starts_with?('-') ? number.slice!(1..-1) : number end + str << " x #{extension}" unless extension.blank? + str end # Formats a +number+ into a currency string (e.g., $13.65). You can customize the format @@ -87,13 +85,14 @@ module ActionView format = options[:format] || defaults[:format] separator = '' if precision == 0 - begin - format.gsub(/%n/, number_with_precision(number, - :precision => precision, - :delimiter => delimiter, - :separator => separator) - ).gsub(/%u/, unit).html_safe - rescue + value = number_with_precision(number, + :precision => precision, + :delimiter => delimiter, + :separator => separator) + + if value + format.gsub(/%n/, value).gsub(/%u/, unit).html_safe + else number end end @@ -122,14 +121,11 @@ module ActionView separator = options[:separator] || defaults[:separator] delimiter = options[:delimiter] || defaults[:delimiter] - begin - number_with_precision(number, - :precision => precision, - :separator => separator, - :delimiter => delimiter) + "%" - rescue - number - end + value = number_with_precision(number, + :precision => precision, + :separator => separator, + :delimiter => delimiter) + value ? value + "%" : number end # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can @@ -168,11 +164,11 @@ module ActionView delimiter ||= (options[:delimiter] || defaults[:delimiter]) separator ||= (options[:separator] || defaults[:separator]) - begin - parts = number.to_s.split('.') + parts = number.to_s.split('.') + if parts[0] parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}") parts.join(separator) - rescue + else number end end @@ -216,11 +212,17 @@ module ActionView delimiter ||= (options[:delimiter] || defaults[:delimiter]) begin + value = Float(number) + rescue ArgumentError, TypeError + value = nil + end + + if value rounded_number = BigDecimal.new((Float(number) * (10 ** precision)).to_s).round.to_f / 10 ** precision number_with_delimiter("%01.#{precision}f" % rounded_number, :separator => separator, :delimiter => delimiter) - rescue + else number end end @@ -293,17 +295,13 @@ module ActionView unit_key = STORAGE_UNITS[exponent] unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true) - begin - escaped_separator = Regexp.escape(separator) - formatted_number = number_with_precision(number, - :precision => precision, - :separator => separator, - :delimiter => delimiter - ).sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') - storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit) - rescue - number - end + escaped_separator = Regexp.escape(separator) + formatted_number = number_with_precision(number, + :precision => precision, + :separator => separator, + :delimiter => delimiter + ).sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') + storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit) end end end diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 51510449bd..4d6ea7dfb2 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -182,9 +182,11 @@ module ActionView def initialize(context, &block) #:nodoc: context._evaluate_assigns_and_ivars @context, @lines = context, [] - include_helpers_from_context - @context.with_output_buffer(@lines) do - @context.instance_exec(self, &block) + @context.update_details(:formats => [:js, :html]) do + include_helpers_from_context + @context.with_output_buffer(@lines) do + @context.instance_exec(self, &block) + end end end @@ -569,15 +571,19 @@ module ActionView end end - def render(*options_for_render) - old_formats = @context && @context.formats + def render(*options) + with_formats(:html) do + case option = options.first + when Hash + @context.render(*options) + else + option.to_s + end + end + end - @context.reset_formats([:html]) if @context - Hash === options_for_render.first ? - @context.render(*options_for_render) : - options_for_render.first.to_s - ensure - @context.reset_formats(old_formats) if @context + def with_formats(*args) + @context ? @context.update_details(:formats => args) { yield } : yield end def javascript_object_for(object) diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index ee10f8f3bb..8ae2e5f28f 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -7,6 +7,9 @@ module ActionView module TagHelper include ERB::Util + extend ActiveSupport::Concern + include CaptureHelper + BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer autoplay controls loop selected hidden scoped async defer reversed ismap seemless muted required @@ -69,13 +72,7 @@ module ActionView def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) if block_given? options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) - content_tag = content_tag_string(name, capture(&block), options, escape) - - if block_called_from_erb?(block) - safe_concat(content_tag) - else - content_tag - end + content_tag_string(name, capture(&block), options, escape) else content_tag_string(name, content_or_options_with_block, options, escape) end @@ -109,25 +106,10 @@ module ActionView end private - BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template' - - if RUBY_VERSION < '1.9.0' - # Check whether we're called from an erb template. - # We'd return a string in any other case, but erb <%= ... %> - # can't take an <% end %> later on, so we have to use <% ... %> - # and implicitly concat. - def block_called_from_erb?(block) - block && eval(BLOCK_CALLED_FROM_ERB, block) - end - else - def block_called_from_erb?(block) - block && eval(BLOCK_CALLED_FROM_ERB, block.binding) - end - end def content_tag_string(name, content, options, escape = true) tag_options = tag_options(options, escape) if options - "<#{name}#{tag_options}>#{content}</#{name}>".html_safe + ("<#{name}#{tag_options}>".html_safe << content.to_s).safe_concat("</#{name}>") end def tag_options(options, escape = true) diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index c348ea7a0d..8a89ee58a0 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -12,9 +12,10 @@ module ActionView # prepend the key with a period, nothing is converted. def translate(key, options = {}) options[:raise] = true - I18n.translate(scope_key_by_partial(key), options).html_safe + translation = I18n.translate(scope_key_by_partial(key), options) + translation.is_a?(Array) ? translation.map { |entry| entry.html_safe } : translation.html_safe rescue I18n::MissingTranslationData => e - keys = I18n.send(:normalize_translation_keys, e.locale, e.key, e.options[:scope]) + keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) content_tag('span', keys.join(', '), :class => 'translation_missing') end alias :t :translate @@ -28,7 +29,7 @@ module ActionView private def scope_key_by_partial(key) - if key.to_s.first == "." + if (key.respond_to?(:join) ? key.join : key.to_s).first == "." if @_virtual_path @_virtual_path.gsub(%r{/_?}, ".") + key.to_s else @@ -40,4 +41,4 @@ module ActionView end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index fde4bfa4ce..ae1385f3b7 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -5,7 +5,7 @@ require 'active_support/core_ext/hash/keys' module ActionView module Helpers #:nodoc: # Provides a set of methods for making links and getting URLs that - # depend on the routing subsystem (see ActionController::Routing). + # depend on the routing subsystem (see ActionDispatch::Routing). # This allows you to use the same format for links in views # and controllers. module UrlHelper @@ -63,7 +63,7 @@ module ActionView # # => /testing/jump/#tax&ship # # <%= url_for(Workshop.new) %> - # # relies on Workshop answering a new_record? call (and in this case returning true) + # # relies on Workshop answering a persisted? call (and in this case returning false) # # => /workshops # # <%= url_for(@workshop) %> @@ -202,8 +202,6 @@ module ActionView # # link_to("Destroy", "http://www.example.com", :method => :delete, :confirm => "Are you sure?") # # => <a href='http://www.example.com' rel="nofollow" data-method="delete" data-confirm="Are you sure?">Destroy</a> - - # def link_to(*args, &block) if block_given? options = args.first || {} @@ -226,7 +224,7 @@ module ActionView end href_attr = "href=\"#{url}\"" unless href - "<a #{href_attr}#{tag_options}>#{ERB::Util.h(name || url)}</a>".html_safe + ("<a #{href_attr}#{tag_options}>".html_safe << (name || url)).safe_concat("</a>") end end @@ -469,14 +467,12 @@ module ActionView extras << "subject=#{Rack::Utils.escape(subject).gsub("+", "%20")}&" unless subject.nil? extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty? - email_address = email_address.to_s - - email_address_obfuscated = email_address.dup + email_address_obfuscated = html_escape(email_address) email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at") email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot") if encode == "javascript" - "document.write('#{content_tag("a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c| + "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c| string << sprintf("%%%x", c) end "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>" @@ -493,9 +489,9 @@ module ActionView char = c.chr string << (char =~ /\w/ ? sprintf("%%%x", c) : char) end - content_tag "a", name || email_address_encoded, html_options.merge({ "href" => "#{string}#{extras}" }) + content_tag "a", name || email_address_encoded.html_safe, html_options.merge({ "href" => "#{string}#{extras}" }) else - content_tag "a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" }) + content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" }) end end @@ -548,10 +544,11 @@ module ActionView # submitted url doesn't have any either. This lets the function # work with things like ?order=asc if url_string.index("?") - request_uri = request.request_uri + request_uri = request.fullpath else - request_uri = request.request_uri.split('?').first + request_uri = request.path end + if url_string =~ /^\w+:\/\// url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}" else diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb new file mode 100644 index 0000000000..27ee8b23c9 --- /dev/null +++ b/actionpack/lib/action_view/lookup_context.rb @@ -0,0 +1,154 @@ +require 'active_support/core_ext/object/try' +require 'active_support/core_ext/object/blank' + +module ActionView + # LookupContext is the object responsible to hold all information required to lookup + # templates, i.e. view paths and details. The LookupContext is also responsible to + # generate a key, given to view paths, used in the resolver cache lookup. Since + # this key is generated just once during the request, it speeds up all cache accesses. + class LookupContext #:nodoc: + mattr_accessor :fallbacks + @@fallbacks = [FileSystemResolver.new(""), FileSystemResolver.new("/")] + + mattr_accessor :registered_details + self.registered_details = [] + + def self.register_detail(name, options = {}, &block) + self.registered_details << name + + Setters.send :define_method, :"_#{name}_defaults", &block + Setters.module_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{name}=(value) + value = Array(value.presence || _#{name}_defaults) + #{"value << nil unless value.include?(nil)" unless options[:allow_nil] == false} + + unless value == @details[:#{name}] + @details_key, @details = nil, @details.merge(:#{name} => value) + @details.freeze + end + end + METHOD + end + + # Holds raw setters for the registered details. + module Setters #:nodoc: + end + + register_detail(:formats) { Mime::SET.symbols } + register_detail(:locale) { [I18n.locale] } + + class DetailsKey #:nodoc: + attr_reader :details + alias :eql? :equal? + + @details_keys = Hash.new + + def self.get(details) + @details_keys[details] ||= new(details) + end + + def initialize(details) + @details, @hash = details, details.hash + end + end + + def initialize(view_paths, details = {}) + @details, @details_key = {}, nil + self.view_paths = view_paths + self.details = details + end + + module ViewPaths + attr_reader :view_paths + + # Whenever setting view paths, makes a copy so we can manipulate then in + # instance objects as we wish. + def view_paths=(paths) + @view_paths = ActionView::Base.process_view_paths(paths) + end + + def find(name, prefix = nil, partial = false) + @view_paths.find(name, prefix, partial, details, details_key) + end + + def find_all(name, prefix = nil, partial = false) + @view_paths.find_all(name, prefix, partial, details, details_key) + end + + def exists?(name, prefix = nil, partial = false) + @view_paths.exists?(name, prefix, partial, details, details_key) + end + + # Add fallbacks to the view paths. Useful in cases you are rendering a :file. + def with_fallbacks + added_resolvers = 0 + self.class.fallbacks.each do |resolver| + next if view_paths.include?(resolver) + view_paths.push(resolver) + added_resolvers += 1 + end + yield + ensure + added_resolvers.times { view_paths.pop } + end + end + + module Details + attr_reader :details + + def details=(given_details) + registered_details.each { |key| send(:"#{key}=", given_details[key]) } + end + + def details_key + @details_key ||= DetailsKey.get(@details) + end + + # Shortcut to read formats from details. + def formats + @details[:formats].compact + end + + # Overload formats= to reject [:"*/*"] values. + def formats=(value, freeze=true) + value = nil if value == [:"*/*"] + super(value) + end + + # Shortcut to read locale. + def locale + I18n.locale + end + + # Overload locale= to also set the I18n.locale. If the current I18n.config object responds + # to i18n_config, it means that it's has a copy of the original I18n configuration and it's + # acting as proxy, which we need to skip. + def locale=(value) + value = value.first if value.is_a?(Array) + config = I18n.config.respond_to?(:i18n_config) ? I18n.config.i18n_config : I18n.config + config.locale = value if value + super(I18n.locale) + end + + # Update the details keys by merging the given hash into the current + # details hash. If a block is given, the details are modified just during + # the execution of the block and reverted to the previous value after. + def update_details(new_details) + old_details = @details + self.details = old_details.merge(new_details) + + if block_given? + begin + yield + ensure + @details = old_details + end + end + end + end + + include Setters + include Details + include ViewPaths + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index 6e55d69d00..35927d09d1 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -1,80 +1,39 @@ module ActionView #:nodoc: class PathSet < Array #:nodoc: - def self.type_cast(obj, cache = nil) - # TODO: Clean this up - if obj.is_a?(String) - if cache.nil? - cache = !defined?(Rails.application) || Rails.application.config.cache_classes + %w(initialize << concat insert push unshift).each do |method| + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{method}(*args) + super + typecast! end - FileSystemResolverWithFallback.new(obj, :cache => cache) - else - obj - end - end - - def initialize(*args) - super(*args).map! { |obj| self.class.type_cast(obj) } - end - - def <<(obj) - super(self.class.type_cast(obj)) - end - - def concat(array) - super(array.map! { |obj| self.class.type_cast(obj) }) - end - - def insert(index, obj) - super(index, self.class.type_cast(obj)) - end - - def push(*objs) - super(*objs.map { |obj| self.class.type_cast(obj) }) + METHOD end - def unshift(*objs) - super(*objs.map { |obj| self.class.type_cast(obj) }) + def find(path, prefix = nil, partial = false, details = {}, key = nil) + template = find_all(path, prefix, partial, details, key).first + raise MissingTemplate.new(self, "#{prefix}/#{path}", details, partial) unless template + template end - def find(path, details = {}, prefix = nil, partial = false) - template_path = path - - each do |load_path| - if template = load_path.find(template_path, details, prefix, partial) - return template - end + def find_all(*args) + each do |resolver| + templates = resolver.find_all(*args) + return templates unless templates.empty? end - - raise ActionView::MissingTemplate.new(self, "#{prefix}/#{path}", details, partial) + [] end - - def exists?(path, extension = nil, prefix = nil, partial = false) - template_path = path.sub(/^\//, '') - each do |load_path| - return true if template = load_path.find(template_path, extension, prefix, partial) - end - false + def exists?(*args) + find_all(*args).any? end - def find_template(original_template_path, format = nil, html_fallback = true) - return original_template_path if original_template_path.respond_to?(:render) - template_path = original_template_path.sub(/^\//, '') + protected - each do |load_path| - if template = load_path.find(template_path, format) - return template - # Try to find html version if the format is javascript - elsif format == :js && html_fallback && template = load_path["#{template_path}.#{I18n.locale}.html"] - return template - elsif format == :js && html_fallback && template = load_path["#{template_path}.html"] - return template - end + def typecast! + each_with_index do |path, i| + next unless path.is_a?(String) + self[i] = FileSystemResolver.new(path) end - - return Template.new(original_template_path, original_template_path.to_s =~ /\A\// ? "" : ".") if File.file?(original_template_path) - - raise MissingTemplate.new(self, original_template_path, format) end end end diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb index d9e2557d89..2e5d115630 100644 --- a/actionpack/lib/action_view/railtie.rb +++ b/actionpack/lib/action_view/railtie.rb @@ -5,12 +5,14 @@ module ActionView class Railtie < Rails::Railtie railtie_name :action_view - require "action_view/railties/subscriber" - subscriber ActionView::Railties::Subscriber.new + require "action_view/railties/log_subscriber" + log_subscriber ActionView::Railties::LogSubscriber.new initializer "action_view.cache_asset_timestamps" do |app| unless app.config.cache_classes - ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false + ActionView.base_hook do + ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false + end end end end diff --git a/actionpack/lib/action_view/railties/subscriber.rb b/actionpack/lib/action_view/railties/log_subscriber.rb index 803f19379c..9487a10706 100644 --- a/actionpack/lib/action_view/railties/subscriber.rb +++ b/actionpack/lib/action_view/railties/log_subscriber.rb @@ -1,6 +1,6 @@ module ActionView module Railties - class Subscriber < Rails::Subscriber + class LogSubscriber < Rails::LogSubscriber def render_template(event) message = "Rendered #{from_rails_root(event.payload[:identifier])}" message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] diff --git a/actionpack/lib/action_view/render/layouts.rb b/actionpack/lib/action_view/render/layouts.rb new file mode 100644 index 0000000000..8688de3d18 --- /dev/null +++ b/actionpack/lib/action_view/render/layouts.rb @@ -0,0 +1,65 @@ +require 'active_support/core_ext/object/try' + +module ActionView + module Layouts + + # You can think of a layout as a method that is called with a block. _layout_for + # returns the contents that are yielded to the layout. If the user calls yield + # :some_name, the block, by default, returns content_for(:some_name). If the user + # calls yield, the default block returns content_for(:layout). + # + # The user can override this default by passing a block to the layout. + # + # ==== Example + # + # # The template + # <% render :layout => "my_layout" do %>Content<% end %> + # + # # The layout + # <html><% yield %></html> + # + # In this case, instead of the default block, which would return content_for(:layout), + # this method returns the block that was passed in to render layout, and the response + # would be <html>Content</html>. + # + # Finally, the block can take block arguments, which can be passed in by yield. + # + # ==== Example + # + # # The template + # <% render :layout => "my_layout" do |customer| %>Hello <%= customer.name %><% end %> + # + # # The layout + # <html><% yield Struct.new(:name).new("David") %></html> + # + # In this case, the layout would receive the block passed into <tt>render :layout</tt>, + # and the Struct specified in the layout would be passed into the block. The result + # would be <html>Hello David</html>. + def _layout_for(name = nil, &block) #:nodoc: + if !block || name + @_content_for[name || :layout] + else + capture(&block) + end + end + + # This is the method which actually finds the layout using details in the lookup + # context object. If no layout is found, it checkes if at least a layout with + # the given name exists across all details before raising the error. + def _find_layout(layout) #:nodoc: + begin + layout =~ /^\// ? + with_fallbacks { find(layout) } : find(layout) + rescue ActionView::MissingTemplate => e + update_details(:formats => nil) do + raise unless exists?(layout) + end + end + end + + # Contains the logic that actually renders the layout. + def _render_layout(layout, locals, &block) #:nodoc: + layout.render(self, locals){ |*name| _layout_for(*name, &block) } + end + end +end diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 62d31662db..d34ab0354c 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -174,17 +174,11 @@ module ActionView class PartialRenderer PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} } - TEMPLATES = Hash.new {|h,k| h[k] = {} } - - attr_reader :template def initialize(view_context, options, block) @view = view_context @partial_names = PARTIAL_NAMES[@view.controller.class] - key = Thread.current[:format_locale_key] - @templates = TEMPLATES[key] if key - setup(options, block) end @@ -228,6 +222,7 @@ module ActionView if !@block && (layout = @options[:layout]) content = @view._render_layout(find_template(layout), @locals){ content } end + content end end @@ -244,9 +239,9 @@ module ActionView end def collection_with_template(template = @template) - segments, locals, as = [], @locals, @options[:as] || template.variable_name + segments, locals, as, template = [], @locals, @options[:as] || @template.variable_name, @template - counter_name = template.counter_name + counter_name = template.counter_name locals[counter_name] = -1 @collection.each do |object| @@ -256,7 +251,6 @@ module ActionView segments << template.render(@view, locals) end - @template = template segments end @@ -277,7 +271,7 @@ module ActionView end def render_partial(object = @object) - locals, view = @locals, @view + locals, view, template = @locals, @view, @template object ||= locals[template.variable_name] locals[@options[:as] || template.variable_name] = object @@ -288,6 +282,7 @@ module ActionView end private + def collection if @object.respond_to?(:to_ary) @object @@ -296,20 +291,10 @@ module ActionView end end - def find_template(path = @path) - unless @templates - path && _find_template(path) - else - path && @templates[path] ||= _find_template(path) - end - end - - def _find_template(path) - if controller = @view.controller - prefix = controller.controller_path unless path.include?(?/) - end - - @view.find(path, {:formats => @view.formats}, prefix, true) + def find_template(path=@path) + return path unless path.is_a?(String) + prefix = @view.controller_path unless path.include?(?/) + @view.find(path, prefix, true) end def partial_path(object = @object) @@ -324,21 +309,8 @@ module ActionView end end - def render_partial(options) - _evaluate_assigns_and_ivars - - details = options[:_details] - - # Is this needed - self.formats = details[:formats] if details - renderer = PartialRenderer.new(self, options, nil) - text = renderer.render - options[:_template] = renderer.template - text - end - def _render_partial(options, &block) #:nodoc: - if defined? @renderer + if defined?(@renderer) @renderer.setup(options, block) else @renderer = PartialRenderer.new(self, options, block) diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index abc7c09991..47ea70f5ad 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -15,27 +15,11 @@ module ActionView def render(options = {}, locals = {}, &block) #:nodoc: case options when Hash - layout = options[:layout] - options[:locals] ||= {} - if block_given? - return safe_concat(_render_partial(options.merge(:partial => layout), &block)) - elsif options.key?(:partial) - return _render_partial(options) - end - - template = if options[:file] - find(options[:file], {:formats => formats}) - elsif options[:inline] - handler = Template.handler_class_for_extension(options[:type] || "erb") - Template.new(options[:inline], "inline template", handler, {}) - elsif options[:text] - Template::Text.new(options[:text]) - end - - if template - layout = find(layout, {:formats => formats}) if layout - _render_template(template, layout, :locals => options[:locals]) + content = _render_partial(options.merge(:partial => options[:layout]), &block) + safe_concat(content) + else + _render(options) end when :update update_page(&block) @@ -44,61 +28,57 @@ module ActionView end end - # You can think of a layout as a method that is called with a block. _layout_for - # returns the contents that are yielded to the layout. If the user calls yield - # :some_name, the block, by default, returns content_for(:some_name). If the user - # calls yield, the default block returns content_for(:layout). - # - # The user can override this default by passing a block to the layout. - # - # ==== Example - # - # # The template - # <% render :layout => "my_layout" do %>Content<% end %> - # - # # The layout - # <html><% yield %></html> - # - # In this case, instead of the default block, which would return content_for(:layout), - # this method returns the block that was passed in to render layout, and the response - # would be <html>Content</html>. - # - # Finally, the block can take block arguments, which can be passed in by yield. - # - # ==== Example - # - # # The template - # <% render :layout => "my_layout" do |customer| %>Hello <%= customer.name %><% end %> - # - # # The layout - # <html><% yield Struct.new(:name).new("David") %></html> - # - # In this case, the layout would receive the block passed into <tt>render :layout</tt>, - # and the Struct specified in the layout would be passed into the block. The result - # would be <html>Hello David</html>. - def _layout_for(name = nil, &block) - return @_content_for[name || :layout] if !block_given? || name - capture(&block) - end - # This is the API to render a ViewContext's template from a controller. - # - # Internal Options: - # _template:: The Template object to render - # _layout:: The layout, if any, to wrap the Template in - def render_template(options) + def render_template(options, &block) _evaluate_assigns_and_ivars - template, layout = options.values_at(:_template, :_layout) - _render_template(template, layout, options) + + # TODO Layout for partials should be handled here, because inside the + # partial renderer it looks for the layout as a partial. + if options.key?(:partial) && options[:layout] + options[:layout] = _find_layout(options[:layout]) + end + + _render(options, &block) + end + + # This method holds the common render logic for both controllers and + # views rendering stacks. + def _render(options) #:nodoc: + if options.key?(:partial) + _render_partial(options) + else + template = _determine_template(options) + yield template if block_given? + _render_template(template, options[:layout], options) + end end - def _render_template(template, layout = nil, options = {}) + # Determine the template to be rendered using the given options. + def _determine_template(options) #:nodoc: + if options.key?(:inline) + handler = Template.handler_class_for_extension(options[:type] || "erb") + Template.new(options[:inline], "inline template", handler, {}) + elsif options.key?(:text) + Template::Text.new(options[:text], self.formats.try(:first)) + elsif options.key?(:_template) + options[:_template] + elsif options.key?(:file) + with_fallbacks { find(options[:file], options[:prefix]) } + elsif options.key?(:template) + find(options[:template], options[:prefix]) + end + end + + # Renders the given template. An string representing the layout can be + # supplied as well. + def _render_template(template, layout = nil, options = {}) #:nodoc: locals = options[:locals] || {} + layout = _find_layout(layout) if layout ActiveSupport::Notifications.instrument("action_view.render_template", :identifier => template.identifier, :layout => layout.try(:identifier)) do - content = template.render(self, locals) + content = template.render(self, locals) { |*name| _layout_for(*name) } @_content_for[:layout] = content if layout @@ -110,8 +90,5 @@ module ActionView end end - def _render_layout(layout, locals, &block) - layout.render(self, locals){ |*name| _layout_for(*name, &block) } - end end end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index cd6b1930a1..b4fdb49d3b 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -16,23 +16,22 @@ module ActionView end extend Template::Handlers - attr_reader :source, :identifier, :handler, :mime_type, :formats, :details + + attr_reader :source, :identifier, :handler, :virtual_path, :formats def initialize(source, identifier, handler, details) @source = source @identifier = identifier @handler = handler - @details = details + + @partial = details[:partial] + @virtual_path = details[:virtual_path] @method_names = {} - format = details.delete(:format) || begin - # TODO: Clean this up - handler.respond_to?(:default_format) ? handler.default_format.to_sym.to_s : "html" - end - @mime_type = Mime::Type.lookup_by_extension(format.to_s) - @formats = [format.to_sym] - @formats << :html if format == :js - @details[:formats] = Array.wrap(format.to_sym) + format = details[:format] + format ||= handler.default_format.to_sym if handler.respond_to?(:default_format) + format ||= :html + @formats = [format.to_sym] end def render(view, locals, &block) @@ -47,19 +46,20 @@ module ActionView end end - # TODO: Figure out how to abstract this + def mime_type + @mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first + end + def variable_name - @variable_name ||= identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym + @variable_name ||= @virtual_path[%r'_?(\w+)(\.\w+)*$', 1].to_sym end - # TODO: Figure out how to abstract this def counter_name @counter_name ||= "#{variable_name}_counter".to_sym end - # TODO: kill hax def partial? - @details[:partial] + @partial end def inspect @@ -87,7 +87,7 @@ module ActionView source = <<-end_src def #{method_name}(local_assigns) - _old_virtual_path, @_virtual_path = @_virtual_path, #{@details[:virtual_path].inspect};_old_output_buffer = output_buffer;#{locals_code};#{code} + _old_virtual_path, @_virtual_path = @_virtual_path, #{@virtual_path.inspect};_old_output_buffer = output_buffer;#{locals_code};#{code} ensure @_virtual_path, self.output_buffer = _old_virtual_path, _old_output_buffer end diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index 4573a440d1..ac5902cc0e 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -3,10 +3,17 @@ require 'active_support/core_ext/string/output_safety' require 'erubis' module ActionView + class OutputBuffer < ActiveSupport::SafeBuffer + def <<(value) + super(value.to_s) + end + alias :append= :<< + end + module Template::Handlers class Erubis < ::Erubis::Eruby def add_preamble(src) - src << "@output_buffer = ActiveSupport::SafeBuffer.new;" + src << "@output_buffer = ActionView::OutputBuffer.new;" end def add_text(src, text) @@ -15,15 +22,15 @@ module ActionView end def add_expr_literal(src, code) - if code =~ /\s*raw\s+(.*)/ - src << "@output_buffer.safe_concat((" << $1 << ").to_s);" + if code =~ /(do|\{)(\s*\|[^|]*\|)?\s*\Z/ + src << '@output_buffer.append= ' << code else - src << '@output_buffer << ((' << code << ').to_s);' + src << '@output_buffer.append= (' << code << ');' end end def add_expr_escaped(src, code) - src << '@output_buffer << ' << escaped_expr(code) << ';' + src << '@output_buffer.append= ' << escaped_expr(code) << ';' end def add_postamble(src) @@ -42,14 +49,14 @@ module ActionView self.erb_trim_mode = '-' self.default_format = Mime::HTML - - cattr_accessor :erubis_implementation - self.erubis_implementation = Erubis + + cattr_accessor :erb_implementation + self.erb_implementation = Erubis def compile(template) source = template.source.gsub(/\A(<%(#.*coding[:=]\s*(\S+)\s*)-?%>)\s*\n?/, '') erb = "<% __in_erb_template=true %>#{source}" - result = self.class.erubis_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src + result = self.class.erb_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src result = "#{$2}\n#{result}" if $2 result end diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 8acfe6cad0..a43597e728 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -4,64 +4,47 @@ require "active_support/core_ext/array/wrap" require "action_view/template" module ActionView - # Abstract superclass class Resolver - - class_inheritable_accessor(:registered_details) - self.registered_details = {} - - def self.register_detail(name, options = {}) - registered_details[name] = lambda do |val| - val = Array.wrap(val || yield) - val |= [nil] unless options[:allow_nil] == false - val - end - end - - register_detail(:locale) { [I18n.locale] } - register_detail(:formats) { Mime::SET.symbols } - register_detail(:handlers, :allow_nil => false) do - Template::Handlers.extensions + def initialize + @cached = Hash.new { |h1,k1| h1[k1] = + Hash.new { |h2,k2| h2[k2] = Hash.new { |h3, k3| h3[k3] = {} } } } end - def initialize(options = {}) - @cache = options[:cache] - @cached = {} + def clear_cache + @cached.clear end - # Normalizes the arguments and passes it on to find_template def find(*args) find_all(*args).first end - def find_all(name, details = {}, prefix = nil, partial = nil) - details = normalize_details(details) + # Normalizes the arguments and passes it on to find_template. + def find_all(name, prefix=nil, partial=false, details={}, key=nil) name, prefix = normalize_name(name, prefix) + details = details.merge(:handlers => default_handlers) - cached([name, details, prefix, partial]) do - find_templates(name, details, prefix, partial) + cached(key, prefix, name, partial) do + find_templates(name, prefix, partial, details) end end private + def caching? + @caching ||= !defined?(Rails.application) || Rails.application.config.cache_classes + end + + def default_handlers + Template::Handlers.extensions + [nil] + end + # This is what child classes implement. No defaults are needed # because Resolver guarantees that the arguments are present and # normalized. - def find_templates(name, details, prefix, partial) + def find_templates(name, prefix, partial, details) raise NotImplementedError end - def normalize_details(details) - details = details.dup - # TODO: Refactor this concern out of the resolver - details.delete(:formats) if details[:formats] == [:"*/*"] - registered_details.each do |k, v| - details[k] = v.call(details[k]) - end - details - end - # Support legacy foo.erb names even though we now ignore .erb # as well as incorrectly putting part of the path in the template # name instead of the prefix. @@ -73,92 +56,73 @@ module ActionView return parts.pop, [prefix, *parts].compact.join("/") end - def cached(key) - return yield unless @cache - return @cached[key] if @cached.key?(key) - @cached[key] = yield + def cached(key, prefix, name, partial) + return yield unless key && caching? + scope = @cached[key][prefix][name] + if scope.key?(partial) + scope[partial] + else + scope[partial] = yield + end end end class PathResolver < Resolver - EXTENSION_ORDER = [:locale, :formats, :handlers] def to_s @path.to_s end - alias to_path to_s - - def find_templates(name, details, prefix, partial) - path = build_path(name, details, prefix, partial) - query(path, EXTENSION_ORDER.map { |ext| details[ext] }) - end + alias :to_path :to_s private - def build_path(name, details, prefix, partial) + def find_templates(name, prefix, partial, details) + path = build_path(name, prefix, partial, details) + query(partial, path, EXTENSION_ORDER.map { |ext| details[ext] }) + end + + def build_path(name, prefix, partial, details) path = "" path << "#{prefix}/" unless prefix.empty? path << (partial ? "_#{name}" : name) path end - def query(path, exts) + def query(partial, path, exts) query = File.join(@path, path) + exts.each do |ext| query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << '}' end Dir[query].reject { |p| File.directory?(p) }.map do |p| - Template.new(File.read(p), File.expand_path(p), *path_to_details(p)) + handler, format = extract_handler_and_format(p) + Template.new(File.read(p), File.expand_path(p), handler, + :partial => partial, :virtual_path => path, :format => format) end end - # # TODO: fix me - # # :api: plugin - def path_to_details(path) - # [:erb, :format => :html, :locale => :en, :partial => true/false] - if m = path.match(%r'((^|.*/)(_)?[\w-]+)((?:\.[\w-]+)*)\.(\w+)$') - partial = m[3] == '_' - details = (m[4]||"").split('.').reject { |e| e.empty? } - handler = Template.handler_class_for_extension(m[5]) + def extract_handler_and_format(path) + pieces = File.basename(path).split(".") + pieces.shift - format = Mime[details.last] && details.pop.to_sym - locale = details.last && details.pop.to_sym - - virtual_path = (m[1].gsub("#{@path}/", "") << details.join(".")) - - return handler, :format => format, :locale => locale, :partial => partial, - :virtual_path => virtual_path - end + handler = Template.handler_class_for_extension(pieces.pop) + format = pieces.last && Mime[pieces.last] && pieces.pop.to_sym + [handler, format] end end class FileSystemResolver < PathResolver - def initialize(path, options = {}) + def initialize(path) raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver) - super(options) + super() @path = Pathname.new(path).expand_path end - end - - # TODO: remove hack - class FileSystemResolverWithFallback < Resolver - def initialize(path, options = {}) - super(options) - @paths = [FileSystemResolver.new(path, options), FileSystemResolver.new("", options), FileSystemResolver.new("/", options)] - end - def find_templates(*args) - @paths.each do |p| - template = p.find_templates(*args) - return template unless template.empty? - end - [] - end - - def to_s - @paths.first.to_s + def eql?(resolver) + self.class.equal?(resolver.class) && to_path == resolver.to_path end + alias :== :eql? end end diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index 2abb352d4e..df394b0fb0 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -1,15 +1,10 @@ module ActionView #:nodoc: class Template class Text < String #:nodoc: - HTML = Mime[:html] - - def initialize(string, content_type = HTML) + def initialize(string, content_type = nil) super(string.to_s) - @content_type = Mime[content_type] || content_type - end - - def details - {:formats => [@content_type.to_sym]} + @content_type = Mime[content_type] || content_type if content_type + @content_type ||= Mime::TEXT end def identifier @@ -29,7 +24,7 @@ module ActionView #:nodoc: end def formats - [mime_type] + [@content_type.to_sym] end def partial? diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index fc29acea6d..1578ac9479 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -1,4 +1,5 @@ require 'action_controller/test_case' +require 'action_view' module ActionView class Base @@ -34,6 +35,8 @@ module ActionView @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new + @request.env.delete('PATH_INFO') + @params = {} end end @@ -60,6 +63,10 @@ module ActionView make_test_case_available_to_view! end + def config + @controller.config + end + def render(options = {}, local_assigns = {}, &block) @rendered << output = _view.render(options, local_assigns, &block) output @@ -152,7 +159,7 @@ module ActionView end def method_missing(selector, *args) - if ActionController::Routing::Routes.named_routes.helpers.include?(selector) + if @controller._router.named_routes.helpers.include?(selector) @controller.__send__(selector, *args) else super diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb index 4ad87d9762..f70d497481 100644 --- a/actionpack/test/abstract/abstract_controller_test.rb +++ b/actionpack/test/abstract/abstract_controller_test.rb @@ -59,11 +59,11 @@ module AbstractController end def rendering_to_body - self.response_body = render_to_body :_template_name => "naked_render.erb" + self.response_body = render_to_body :template => "naked_render.erb" end def rendering_to_string - self.response_body = render_to_string :_template_name => "naked_render.erb" + self.response_body = render_to_string :template => "naked_render.erb" end end diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb index b6d89ea489..65a50807fd 100644 --- a/actionpack/test/abstract/layouts_test.rb +++ b/actionpack/test/abstract/layouts_test.rb @@ -8,41 +8,52 @@ module AbstractControllerTests include AbstractController::Rendering include AbstractController::Layouts + def _prefix + "template" + end + self.view_paths = [ActionView::FixtureResolver.new( - "layouts/hello.erb" => "With String <%= yield %>", - "layouts/hello_override.erb" => "With Override <%= yield %>", - "layouts/abstract_controller_tests/layouts/with_string_implied_child.erb" => - "With Implied <%= yield %>", - "layouts/overwrite.erb" => "Overwrite <%= yield %>", - "layouts/with_false_layout.erb" => "False Layout <%= yield %>" + "abstract_controller_tests/layouts/with_string_implied_child.erb" => + "With Implied <%= yield %>", + "layouts/hello.erb" => "With String <%= yield %>", + "layouts/hello_override.erb" => "With Override <%= yield %>", + "layouts/overwrite.erb" => "Overwrite <%= yield %>", + "layouts/with_false_layout.erb" => "False Layout <%= yield %>" )] end class Blank < Base - self.view_paths = [] - + self.view_paths = ActionView::FixtureResolver.new("template/index.erb" => "Hello blank!") + def index - render :_template => ActionView::Template::Text.new("Hello blank!") + render end end class WithString < Base layout "hello" - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello string!", + "template/overwrite_default.erb" => "Hello string!", + "template/overwrite_false.erb" => "Hello string!", + "template/overwrite_string.erb" => "Hello string!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello string!") + render end def overwrite_default - render :_template => ActionView::Template::Text.new("Hello string!"), :layout => :default + render :layout => :default end def overwrite_false - render :_template => ActionView::Template::Text.new("Hello string!"), :layout => false + render :layout => false end def overwrite_string - render :_template => ActionView::Template::Text.new("Hello string!"), :layout => "overwrite" + render :layout => "overwrite" end def overwrite_skip @@ -70,18 +81,28 @@ module AbstractControllerTests class WithProc < Base layout proc { |c| "overwrite" } + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello proc!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello proc!") + render end end class WithSymbol < Base layout :hello - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello symbol!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello symbol!") + render end - private + + private + def hello "overwrite" end @@ -89,11 +110,17 @@ module AbstractControllerTests class WithSymbolReturningString < Base layout :no_hello - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello missing symbol!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello missing symbol!") + render end - private + + private + def no_hello nil end @@ -101,19 +128,28 @@ module AbstractControllerTests class WithSymbolReturningNil < Base layout :nilz - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello nilz!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello nilz!") + render end - def nilz() end + def nilz + end end class WithSymbolReturningObj < Base layout :objekt - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello object!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello nilz!") + render end def objekt @@ -123,33 +159,49 @@ module AbstractControllerTests class WithSymbolAndNoMethod < Base layout :no_method - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello boom!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello boom!") + render end end class WithMissingLayout < Base layout "missing" - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello missing!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello missing!") + render end end class WithFalseLayout < Base layout false - + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello false!" + ) + def index - render :_template => ActionView::Template::Text.new("Hello false!") + render end end class WithNilLayout < Base layout nil + + append_view_path ActionView::FixtureResolver.new( + "template/index.erb" => "Hello nil!" + ) def index - render :_template => ActionView::Template::Text.new("Hello nil!") + render end end diff --git a/actionpack/test/abstract/localized_cache_test.rb b/actionpack/test/abstract/localized_cache_test.rb deleted file mode 100644 index 8b0b0fff03..0000000000 --- a/actionpack/test/abstract/localized_cache_test.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'abstract_unit' - -module AbstractController - module Testing - - class CachedController < AbstractController::Base - include AbstractController::Rendering - include AbstractController::LocalizedCache - - self.view_paths = [ActionView::FixtureResolver.new( - "default.erb" => "With Default", - "template.erb" => "With Template", - "some/file.erb" => "With File", - "template_name.erb" => "With Template Name" - )] - end - - class TestLocalizedCache < ActiveSupport::TestCase - - def setup - @controller = CachedController.new - CachedController.clear_template_caches! - end - - def test_templates_are_cached - @controller.render :template => "default.erb" - assert_equal "With Default", @controller.response_body - - cached = @controller.class.template_cache - assert_equal 1, cached.size - assert_kind_of ActionView::Template, cached.values.first["default.erb"] - end - - def test_cache_is_used - CachedController.new.render :template => "default.erb" - - @controller.expects(:find_template).never - @controller.render :template => "default.erb" - - assert_equal 1, @controller.class.template_cache.size - end - - def test_cache_changes_with_locale - CachedController.new.render :template => "default.erb" - - I18n.locale = :es - @controller.render :template => "default.erb" - - assert_equal 2, @controller.class.template_cache.size - ensure - I18n.locale = :en - end - - end - - end -end diff --git a/actionpack/test/abstract/render_test.rb b/actionpack/test/abstract/render_test.rb index db924633ca..25dc8bd804 100644 --- a/actionpack/test/abstract/render_test.rb +++ b/actionpack/test/abstract/render_test.rb @@ -15,13 +15,8 @@ module AbstractController "renderer/default.erb" => "With Default", "renderer/string.erb" => "With String", "renderer/symbol.erb" => "With Symbol", - "renderer/template_name.erb" => "With Template Name", "string/with_path.erb" => "With String With Path", - "some/file.erb" => "With File", - "with_format.html.erb" => "With html format", - "with_format.xml.erb" => "With xml format", - "with_locale.en.erb" => "With en locale", - "with_locale.pl.erb" => "With pl locale" + "some/file.erb" => "With File" )] def template @@ -55,30 +50,6 @@ module AbstractController def symbol render :symbol end - - def template_name - render :_template_name => :template_name - end - - def object - render :_template => ActionView::Template::Text.new("With Object") - end - - def with_html_format - render :template => "with_format", :format => :html - end - - def with_xml_format - render :template => "with_format", :format => :xml - end - - def with_en_locale - render :template => "with_locale" - end - - def with_pl_locale - render :template => "with_locale", :locale => :pl - end end class TestRenderer < ActiveSupport::TestCase @@ -126,36 +97,6 @@ module AbstractController @controller.process(:string_with_path) assert_equal "With String With Path", @controller.response_body end - - def test_render_template_name - @controller.process(:template_name) - assert_equal "With Template Name", @controller.response_body - end - - def test_render_object - @controller.process(:object) - assert_equal "With Object", @controller.response_body - end - - def test_render_with_html_format - @controller.process(:with_html_format) - assert_equal "With html format", @controller.response_body - end - - def test_render_with_xml_format - @controller.process(:with_xml_format) - assert_equal "With xml format", @controller.response_body - end - - def test_render_with_en_locale - @controller.process(:with_en_locale) - assert_equal "With en locale", @controller.response_body - end - - def test_render_with_pl_locale - @controller.process(:with_pl_locale) - assert_equal "With pl locale", @controller.response_body - end end end end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 867e50d5b9..67aa412d3d 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -1,5 +1,11 @@ require File.expand_path('../../../load_paths', __FILE__) +lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) + +activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) +$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) + $:.unshift(File.dirname(__FILE__) + '/lib') $:.unshift(File.dirname(__FILE__) + '/fixtures/helpers') $:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers') @@ -10,7 +16,6 @@ require 'test/unit' require 'abstract_controller' require 'action_controller' require 'action_view' -require 'action_view/base' require 'action_dispatch' require 'fixture_template' require 'active_support/dependencies' @@ -64,29 +69,66 @@ module SetupOnce end end -class ActiveSupport::TestCase - include SetupOnce +SharedTestRoutes = ActionDispatch::Routing::RouteSet.new + +module ActiveSupport + class TestCase + include SetupOnce + # Hold off drawing routes until all the possible controller classes + # have been loaded. + setup_once do + SharedTestRoutes.draw do |map| + # FIXME: match ':controller(/:action(/:id))' + map.connect ':controller/:action/:id' + end + + ActionController::IntegrationTest.app.router.draw do |map| + # FIXME: match ':controller(/:action(/:id))' + map.connect ':controller/:action/:id' + end + end + end +end + +class RoutedRackApp + attr_reader :router + alias routes router + + def initialize(router, &blk) + @router = router + @stack = ActionDispatch::MiddlewareStack.new(&blk).build(@router) + end + + def call(env) + @stack.call(env) + end +end - # Hold off drawing routes until all the possible controller classes - # have been loaded. - setup_once do - ActionController::Routing::Routes.draw do |map| - match ':controller(/:action(/:id))' +class BasicController + attr_accessor :request + + def config + @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config| + # VIEW TODO: View tests should not require a controller + public_dir = File.expand_path("../fixtures/public", __FILE__) + config.assets_dir = public_dir + config.javascripts_dir = "#{public_dir}/javascripts" + config.stylesheets_dir = "#{public_dir}/stylesheets" + config end end end class ActionController::IntegrationTest < ActiveSupport::TestCase def self.build_app(routes = nil) - ActionDispatch::Flash - ActionDispatch::MiddlewareStack.new { |middleware| + RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware| middleware.use "ActionDispatch::ShowExceptions" middleware.use "ActionDispatch::Callbacks" middleware.use "ActionDispatch::ParamsParser" middleware.use "ActionDispatch::Cookies" middleware.use "ActionDispatch::Flash" middleware.use "ActionDispatch::Head" - }.build(routes || ActionController::Routing::Routes) + end end self.app = build_app @@ -112,28 +154,22 @@ class ActionController::IntegrationTest < ActiveSupport::TestCase end def with_routing(&block) - real_routes = ActionController::Routing::Routes - ActionController::Routing.module_eval { remove_const :Routes } - - temporary_routes = ActionController::Routing::RouteSet.new - self.class.app = self.class.build_app(temporary_routes) - ActionController::Routing.module_eval { const_set :Routes, temporary_routes } + temporary_routes = ActionDispatch::Routing::RouteSet.new + old_app, self.class.app = self.class.app, self.class.build_app(temporary_routes) + old_routes = SharedTestRoutes + silence_warnings { Object.const_set(:SharedTestRoutes, temporary_routes) } yield temporary_routes ensure - if ActionController::Routing.const_defined? :Routes - ActionController::Routing.module_eval { remove_const :Routes } - end - ActionController::Routing.const_set(:Routes, real_routes) if real_routes - self.class.app = self.class.build_app + self.class.app = old_app + silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) } end end # Temporary base class class Rack::TestCase < ActionController::IntegrationTest setup do - ActionController::Base.session_options[:key] = "abc" - ActionController::Base.session_options[:secret] = ("*" * 30) + ActionController::Base.config.secret = "abc" * 30 end def self.testing(klass = nil) @@ -180,6 +216,16 @@ end class ::ApplicationController < ActionController::Base end +module ActionView + class TestCase + # Must repeat the setup because AV::TestCase is a duplication + # of AC::TestCase + setup do + @router = SharedTestRoutes + end + end +end + module ActionController class Base include ActionController::Testing @@ -190,6 +236,10 @@ module ActionController class TestCase include ActionDispatch::TestProcess + setup do + @router = SharedTestRoutes + end + def assert_template(options = {}, message = nil) validate_request! @@ -232,3 +282,10 @@ module ActionController end end end + +# This stub emulates the Railtie including the URL helpers from a Rails application +module ActionController + class Base + include SharedTestRoutes.url_helpers + end +end
\ No newline at end of file diff --git a/actionpack/test/activerecord/controller_runtime_test.rb b/actionpack/test/activerecord/controller_runtime_test.rb index ed8e324938..331f861d8f 100644 --- a/actionpack/test/activerecord/controller_runtime_test.rb +++ b/actionpack/test/activerecord/controller_runtime_test.rb @@ -1,37 +1,37 @@ require 'active_record_unit' require 'active_record/railties/controller_runtime' require 'fixtures/project' -require 'rails/subscriber/test_helper' -require 'action_controller/railties/subscriber' +require 'rails/log_subscriber/test_helper' +require 'action_controller/railties/log_subscriber' ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime -class ControllerRuntimeSubscriberTest < ActionController::TestCase - class SubscriberController < ActionController::Base +class ControllerRuntimeLogSubscriberTest < ActionController::TestCase + class LogSubscriberController < ActionController::Base def show render :inline => "<%= Project.all %>" end end - - include Rails::Subscriber::TestHelper - tests SubscriberController + + include Rails::LogSubscriber::TestHelper + tests LogSubscriberController def setup - @old_logger = ActionController::Base.logger - Rails::Subscriber.add(:action_controller, ActionController::Railties::Subscriber.new) super + @old_logger = ActionController::Base.logger + Rails::LogSubscriber.add(:action_controller, ActionController::Railties::LogSubscriber.new) end def teardown super - Rails::Subscriber.subscribers.clear + Rails::LogSubscriber.log_subscribers.clear ActionController::Base.logger = @old_logger end def set_logger(logger) ActionController::Base.logger = logger end - + def test_log_with_active_record get :show wait diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index ea82758cf5..5643ad5ad6 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -26,7 +26,7 @@ class Series < ActiveRecord::Base end class PolymorphicRoutesTest < ActionController::TestCase - include ActionController::UrlFor + include SharedTestRoutes.url_helpers self.default_url_options[:host] = 'example.com' def setup @@ -400,7 +400,7 @@ class PolymorphicRoutesTest < ActionController::TestCase map.resources :series end - ActionController::Routing::Routes.install_helpers(self.class) + self.class.send(:include, @router.url_helpers) yield end end @@ -422,7 +422,7 @@ class PolymorphicRoutesTest < ActionController::TestCase end end - ActionController::Routing::Routes.install_helpers(self.class) + self.class.send(:include, @router.url_helpers) yield end end @@ -441,7 +441,7 @@ class PolymorphicRoutesTest < ActionController::TestCase end end - ActionController::Routing::Routes.install_helpers(self.class) + self.class.send(:include, @router.url_helpers) yield end end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index d54be9bdc0..26e0d6d844 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -253,12 +253,14 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase end def test_assert_redirect_to_nested_named_route + @controller = Admin::InnerModuleController.new + with_routing do |set| set.draw do |map| match 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module - match ':controller/:action' + # match ':controller/:action' + map.connect ':controller/:action/:id' end - @controller = Admin::InnerModuleController.new process :redirect_to_index # redirection is <{"action"=>"index", "controller"=>"admin/admin/inner_module"}> assert_redirected_to admin_inner_module_path @@ -266,12 +268,14 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase end def test_assert_redirected_to_top_level_named_route_from_nested_controller + @controller = Admin::InnerModuleController.new + with_routing do |set| set.draw do |map| match '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level - match ':controller/:action' + # match ':controller/:action' + map.connect ':controller/:action/:id' end - @controller = Admin::InnerModuleController.new process :redirect_to_top_level_named_route # assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return assert_redirected_to "/action_pack_assertions/foo" @@ -279,13 +283,15 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase end def test_assert_redirected_to_top_level_named_route_with_same_controller_name_in_both_namespaces + @controller = Admin::InnerModuleController.new + with_routing do |set| set.draw do |map| # this controller exists in the admin namespace as well which is the only difference from previous test match '/user/:id', :to => 'user#index', :as => :top_level - match ':controller/:action' + # match ':controller/:action' + map.connect ':controller/:action/:id' end - @controller = Admin::InnerModuleController.new process :redirect_to_top_level_named_route # assert_redirected_to top_level_url('foo') would pass because of exact match early return assert_redirected_to top_level_path('foo') @@ -463,9 +469,11 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase assert_redirected_to '/some/path' end - def test_redirected_to_url_no_leadling_slash + def test_redirected_to_url_no_leading_slash_fails process :redirect_to_path - assert_redirected_to 'some/path' + assert_raise ActiveSupport::TestCase::Assertion do + assert_redirected_to 'some/path' + end end def test_redirected_to_url_full_url diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index 612827dd41..cb3e848dfa 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -6,18 +6,7 @@ require 'abstract_unit' require 'controller/fake_controllers' - -unless defined?(ActionMailer) - begin - $:.unshift("#{File.dirname(__FILE__)}/../../../actionmailer/lib") - require 'action_mailer' - rescue LoadError => e - raise unless e.message =~ /action_mailer/ - require 'rubygems' - gem 'actionmailer' - end -end - +require 'action_mailer' ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH class AssertSelectTest < ActionController::TestCase diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 4fcfbacf4e..f047e7da30 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -66,6 +66,19 @@ class DefaultUrlOptionsController < ActionController::Base end end +class UrlOptionsController < ActionController::Base + def from_view + render :inline => "<%= #{params[:route]} %>" + end + + def url_options + super.merge(:host => 'www.override.com', :action => 'new', :locale => 'en') + end +end + +class RecordIdentifierController < ActionController::Base +end + class ControllerClassTests < ActiveSupport::TestCase def test_controller_path assert_equal 'empty', EmptyController.controller_path @@ -92,6 +105,11 @@ class ControllerClassTests < ActiveSupport::TestCase assert_equal [:password], parameters end + + def test_record_identifier + assert_respond_to RecordIdentifierController.new, :dom_id + assert_respond_to RecordIdentifierController.new, :dom_class + end end class ControllerInstanceTests < Test::Unit::TestCase @@ -113,6 +131,15 @@ class ControllerInstanceTests < Test::Unit::TestCase assert_equal Set.new(%w(public_action)), c.class.__send__(:action_methods), "#{c.controller_path} should not be empty!" end end + + def test_temporary_anonymous_controllers + name = 'ExamplesController' + klass = Class.new(ActionController::Base) + Object.const_set(name, klass) + + controller = klass.new + assert_equal "examples", controller.controller_path + end end class PerformActionTest < ActionController::TestCase @@ -153,6 +180,31 @@ class PerformActionTest < ActionController::TestCase end end +class UrlOptionsTest < ActionController::TestCase + tests UrlOptionsController + + def setup + super + @request.host = 'www.example.com' + rescue_action_in_public! + end + + def test_url_options_override + with_routing do |set| + set.draw do |map| + match 'from_view', :to => 'url_options#from_view', :as => :from_view + match ':controller/:action' + end + + get :from_view, :route => "from_view_url" + + assert_equal 'http://www.override.com/from_view?locale=en', @response.body + assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url) + assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options') + end + end +end + class DefaultUrlOptionsTest < ActionController::TestCase tests DefaultUrlOptionsController @@ -162,7 +214,7 @@ class DefaultUrlOptionsTest < ActionController::TestCase rescue_action_in_public! end - def test_default_url_options_are_used_if_set + def test_default_url_options_override with_routing do |set| set.draw do |map| match 'from_view', :to => 'default_url_options#from_view', :as => :from_view @@ -219,12 +271,15 @@ class EmptyUrlOptionsTest < ActionController::TestCase end def test_named_routes_with_path_without_doing_a_request_first + @controller = EmptyController.new + @controller.request = @request + with_routing do |set| set.draw do |map| resources :things end - assert_equal '/things', EmptyController.new.send(:things_path) + assert_equal '/things', @controller.send(:things_path) end end end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index de92fc56fd..a3c8fdbb6e 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -6,12 +6,18 @@ 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) ActionController::Base.page_cache_directory = FILE_STORE_PATH -ActionController::Base.cache_store = :file_store, FILE_STORE_PATH -class PageCachingTestController < ActionController::Base +class CachingController < ActionController::Base + abstract! + + self.cache_store = :file_store, FILE_STORE_PATH +end + +class PageCachingTestController < CachingController caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? } caches_page :found, :not_found + def ok head :ok end @@ -51,12 +57,13 @@ class PageCachingTest < ActionController::TestCase @request = ActionController::TestRequest.new @request.host = 'hostname.com' + @request.env.delete('PATH_INFO') @response = ActionController::TestResponse.new @controller = PageCachingTestController.new + @controller.cache_store = :file_store, FILE_STORE_PATH - @params = {:controller => 'posts', :action => 'index', :only_path => true, :skip_relative_url_root => true} - @rewriter = ActionController::UrlRewriter.new(@request, @params) + @params = {:controller => 'posts', :action => 'index', :only_path => true} FileUtils.rm_rf(File.dirname(FILE_STORE_PATH)) FileUtils.mkdir_p(FILE_STORE_PATH) @@ -74,9 +81,9 @@ class PageCachingTest < ActionController::TestCase match '/', :to => 'posts#index', :as => :main end @params[:format] = 'rss' - assert_equal '/posts.rss', @rewriter.rewrite(@params) + assert_equal '/posts.rss', @router.url_for(@params) @params[:format] = nil - assert_equal '/', @rewriter.rewrite(@params) + assert_equal '/', @router.url_for(@params) end end @@ -110,7 +117,7 @@ class PageCachingTest < ActionController::TestCase end def test_should_cache_ok_at_custom_path - @request.request_uri = "/index.html" + @request.env['PATH_INFO'] = '/index.html' get :ok assert_response :ok assert File.exist?("#{FILE_STORE_PATH}/index.html") @@ -147,7 +154,7 @@ class PageCachingTest < ActionController::TestCase end end -class ActionCachingTestController < ActionController::Base +class ActionCachingTestController < CachingController rescue_from(Exception) { head 500 } if defined? ActiveRecord rescue_from(ActiveRecord::RecordNotFound) { head :not_found } @@ -305,12 +312,9 @@ class ActionCacheTest < ActionController::TestCase end def test_action_cache_conditional_options - old_use_accept_header = ActionController::Base.use_accept_header - ActionController::Base.use_accept_header = true @request.env['HTTP_ACCEPT'] = 'application/json' get :index assert !fragment_exist?('hostname.com/action_caching_test') - ActionController::Base.use_accept_header = old_use_accept_header end def test_action_cache_with_store_options @@ -511,6 +515,7 @@ class ActionCacheTest < ActionController::TestCase @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @controller = ActionCachingTestController.new + @controller.singleton_class.send(:include, @router.url_helpers) @request.host = 'hostname.com' end @@ -523,7 +528,7 @@ class ActionCacheTest < ActionController::TestCase end end -class FragmentCachingTestController < ActionController::Base +class FragmentCachingTestController < CachingController def some_action; end; end @@ -532,8 +537,8 @@ class FragmentCachingTest < ActionController::TestCase super ActionController::Base.perform_caching = true @store = ActiveSupport::Cache::MemoryStore.new - ActionController::Base.cache_store = @store @controller = FragmentCachingTestController.new + @controller.cache_store = @store @params = {:controller => 'posts', :action => 'index'} @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @@ -631,7 +636,7 @@ class FragmentCachingTest < ActionController::TestCase end -class FunctionalCachingController < ActionController::Base +class FunctionalCachingController < CachingController def fragment_cached end @@ -665,8 +670,8 @@ class FunctionalFragmentCachingTest < ActionController::TestCase super ActionController::Base.perform_caching = true @store = ActiveSupport::Cache::MemoryStore.new - ActionController::Base.cache_store = @store @controller = FunctionalCachingController.new + @controller.cache_store = @store @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new end diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb index e5ffe20ecc..967107853b 100644 --- a/actionpack/test/controller/content_type_test.rb +++ b/actionpack/test/controller/content_type_test.rb @@ -145,18 +145,6 @@ end class AcceptBasedContentTypeTest < ActionController::TestCase tests OldContentTypeController - def setup - super - @_old_accept_header = ActionController::Base.use_accept_header - ActionController::Base.use_accept_header = true - end - - def teardown - super - ActionController::Base.use_accept_header = @_old_accept_header - end - - def test_render_default_content_types_for_respond_to @request.accept = Mime::HTML.to_s get :render_default_content_types_for_respond_to diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index f5ccef8aaf..908967a110 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -157,7 +157,7 @@ class CookieTest < ActionController::TestCase def assert_cookie_header(expected) header = @response.headers["Set-Cookie"] if header.respond_to?(:to_str) - assert_equal expected, header + assert_equal expected.split("\n").sort, header.split("\n").sort else assert_equal expected.split("\n"), header end diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index 7e9a2625f1..eb2af523a2 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -40,11 +40,13 @@ class HttpDigestAuthenticationTest < ActionController::TestCase setup do # Used as secret in generating nonce to prevent tampering of timestamp - @old_secret, ActionController::Base.session_options[:secret] = ActionController::Base.session_options[:secret], "session_options_secret" + @secret = "session_options_secret" + @controller.config.secret = @secret + # @old_secret, ActionController::Base.config.secret[:secret] = ActionController::Base.session_options[:secret], @secret end teardown do - ActionController::Base.session_options[:secret] = @old_secret + # ActionController::Base.session_options[:secret] = @old_secret end AUTH_HEADERS.each do |header| @@ -138,7 +140,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase test "authentication request with request-uri that doesn't match credentials digest-uri" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') - @request.env['REQUEST_URI'] = "/http_digest_authentication_test/dummy_digest/altered/uri" + @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/altered/uri" get :display assert_response :unauthorized @@ -147,7 +149,8 @@ class HttpDigestAuthenticationTest < ActionController::TestCase test "authentication request with absolute request uri (as in webrick)" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') - @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest" + @request.env["SERVER_NAME"] = "test.host" + @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" get :display @@ -170,7 +173,8 @@ class HttpDigestAuthenticationTest < ActionController::TestCase test "authentication request with absolute uri in both request and credentials (as in Webrick with IE)" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest", :username => 'pretty', :password => 'please') - @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest" + @request.env['SERVER_NAME'] = "test.host" + @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest" get :display @@ -202,7 +206,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase test "validate_digest_response should fail with nil returning password_procedure" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => nil, :password => nil) - assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret"){nil} + assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@secret, @request, "SuperSecret"){nil} end private @@ -225,7 +229,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase credentials = decode_credentials(@response.headers['WWW-Authenticate']) credentials.merge!(options) - credentials.merge!(:uri => @request.env['REQUEST_URI'].to_s) + credentials.merge!(:uri => @request.env['PATH_INFO'].to_s) ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1]) end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 683ab5236c..2180466ca7 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -75,23 +75,6 @@ class SessionTest < Test::Unit::TestCase @session.delete_via_redirect(path, args, headers) end - def test_url_for_with_controller - options = {:action => 'show'} - mock_controller = mock() - mock_controller.expects(:url_for).with(options).returns('/show') - @session.stubs(:controller).returns(mock_controller) - assert_equal '/show', @session.url_for(options) - end - - def test_url_for_without_controller - options = {:action => 'show'} - mock_rewriter = mock() - mock_rewriter.expects(:rewrite).with(options).returns('/show') - @session.stubs(:generic_url_rewriter).returns(mock_rewriter) - @session.stubs(:controller).returns(nil) - assert_equal '/show', @session.url_for(options) - end - def test_get path = "/index"; params = "blah"; headers = {:location => 'blah'} @session.expects(:process).with(:get,path,params,headers) @@ -195,8 +178,8 @@ class IntegrationTestTest < Test::Unit::TestCase session1 = @test.open_session { |sess| } session2 = @test.open_session # implicit session - assert_equal ::ActionController::Integration::Session, session1.class - assert_equal ::ActionController::Integration::Session, session2.class + assert_kind_of ::ActionController::Integration::Session, session1 + assert_kind_of ::ActionController::Integration::Session, session2 assert_not_equal session1, session2 end @@ -301,18 +284,13 @@ class IntegrationProcessTest < ActionController::IntegrationTest end end - def test_cookie_monster + test 'response cookies are added to the cookie jar for the next request' do with_test_route_set do self.cookies['cookie_1'] = "sugar" self.cookies['cookie_2'] = "oatmeal" get '/cookie_monster' - assert_equal 410, status - assert_equal "Gone", status_message - assert_response 410 - assert_response :gone assert_equal "cookie_1=; path=/\ncookie_3=chocolate; path=/", headers["Set-Cookie"] assert_equal({"cookie_1"=>"", "cookie_2"=>"oatmeal", "cookie_3"=>"chocolate"}, cookies.to_hash) - assert_equal "Gone", response.body end end @@ -350,7 +328,7 @@ class IntegrationProcessTest < ActionController::IntegrationTest with_test_route_set do get '/get_with_params?foo=bar' assert_equal '/get_with_params?foo=bar', request.env["REQUEST_URI"] - assert_equal '/get_with_params?foo=bar', request.request_uri + assert_equal '/get_with_params?foo=bar', request.fullpath assert_equal "foo=bar", request.env["QUERY_STRING"] assert_equal 'foo=bar', request.query_string assert_equal 'bar', request.parameters['foo'] @@ -363,8 +341,8 @@ class IntegrationProcessTest < ActionController::IntegrationTest def test_get_with_parameters with_test_route_set do get '/get_with_params', :foo => "bar" - assert_equal '/get_with_params', request.env["REQUEST_URI"] - assert_equal '/get_with_params', request.request_uri + assert_equal '/get_with_params', request.env["PATH_INFO"] + assert_equal '/get_with_params', request.path_info assert_equal 'foo=bar', request.env["QUERY_STRING"] assert_equal 'foo=bar', request.query_string assert_equal 'bar', request.parameters['foo'] @@ -401,16 +379,26 @@ class IntegrationProcessTest < ActionController::IntegrationTest private def with_test_route_set with_routing do |set| + controller = ::IntegrationProcessTest::IntegrationController.clone + controller.class_eval do + include set.url_helpers + end + set.draw do |map| - match ':action', :to => ::IntegrationProcessTest::IntegrationController - get 'get/:action', :to => ::IntegrationProcessTest::IntegrationController + match ':action', :to => controller + get 'get/:action', :to => controller end + + self.singleton_class.send(:include, set.url_helpers) + yield end end end class MetalIntegrationTest < ActionController::IntegrationTest + include SharedTestRoutes.url_helpers + class Poller def self.call(env) if env["PATH_INFO"] =~ /^\/success/ diff --git a/actionpack/test/controller/subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index d7c1166f14..20d3e77b6e 100644 --- a/actionpack/test/controller/subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -1,9 +1,9 @@ require "abstract_unit" -require "rails/subscriber/test_helper" -require "action_controller/railties/subscriber" +require "rails/log_subscriber/test_helper" +require "action_controller/railties/log_subscriber" module Another - class SubscribersController < ActionController::Base + class LogSubscribersController < ActionController::Base def show render :nothing => true end @@ -35,24 +35,24 @@ module Another end end -class ACSubscriberTest < ActionController::TestCase - tests Another::SubscribersController - include Rails::Subscriber::TestHelper +class ACLogSubscriberTest < ActionController::TestCase + tests Another::LogSubscribersController + include Rails::LogSubscriber::TestHelper def setup + super + @old_logger = ActionController::Base.logger @cache_path = File.expand_path('../temp/test_cache', File.dirname(__FILE__)) ActionController::Base.page_cache_directory = @cache_path - ActionController::Base.cache_store = :file_store, @cache_path - - Rails::Subscriber.add(:action_controller, ActionController::Railties::Subscriber.new) - super + @controller.cache_store = :file_store, @cache_path + Rails::LogSubscriber.add(:action_controller, ActionController::Railties::LogSubscriber.new) end def teardown super - Rails::Subscriber.subscribers.clear + Rails::LogSubscriber.log_subscribers.clear FileUtils.rm_rf(@cache_path) ActionController::Base.logger = @old_logger end @@ -65,7 +65,7 @@ class ACSubscriberTest < ActionController::TestCase get :show wait assert_equal 2, logs.size - assert_equal "Processing by Another::SubscribersController#show as HTML", logs.first + assert_equal "Processing by Another::LogSubscribersController#show as HTML", logs.first end def test_process_action @@ -134,11 +134,11 @@ class ACSubscriberTest < ActionController::TestCase end def test_send_xfile - get :xfile_sender + assert_deprecated { get :xfile_sender } wait assert_equal 3, logs.size - assert_match /Sent X\-Sendfile header/, logs[1] + assert_match /Sent file/, logs[1] assert_match /test\/fixtures\/company\.rb/, logs[1] end diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 3bd3369242..5c1eaf453c 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -155,13 +155,11 @@ class RespondToControllerTest < ActionController::TestCase def setup super - ActionController::Base.use_accept_header = true @request.host = "www.example.com" end def teardown super - ActionController::Base.use_accept_header = false end def test_html @@ -513,7 +511,7 @@ class RespondWithController < ActionController::Base protected def resource - Customer.new("david", 13) + Customer.new("david", request.delete? ? nil : 13) end def _render_js(js, options) @@ -544,13 +542,11 @@ class RespondWithControllerTest < ActionController::TestCase def setup super - ActionController::Base.use_accept_header = true @request.host = "www.example.com" end def teardown super - ActionController::Base.use_accept_header = false end def test_using_resource @@ -717,7 +713,7 @@ class RespondWithControllerTest < ActionController::TestCase delete :using_resource assert_equal "text/html", @response.content_type assert_equal 302, @response.status - assert_equal "http://www.example.com/customers/13", @response.location + assert_equal "http://www.example.com/customers", @response.location end end diff --git a/actionpack/test/controller/new_base/render_rjs_test.rb b/actionpack/test/controller/new_base/render_rjs_test.rb index 8c47b38ab6..f4516ade63 100644 --- a/actionpack/test/controller/new_base/render_rjs_test.rb +++ b/actionpack/test/controller/new_base/render_rjs_test.rb @@ -17,7 +17,7 @@ module RenderRjs end def index_locale - old_locale, I18n.locale = I18n.locale, :da + self.locale = :da end end diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb index 6b6d154faa..813dedc80d 100644 --- a/actionpack/test/controller/record_identifier_test.rb +++ b/actionpack/test/controller/record_identifier_test.rb @@ -5,6 +5,7 @@ class Comment include ActiveModel::Conversion attr_reader :id + def to_key; id ? [id] : nil end def save; @id = 1 end def new_record?; @id.nil? end def name diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index 570ff4a41b..441bc47908 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -6,14 +6,14 @@ end class Workshop extend ActiveModel::Naming include ActiveModel::Conversion - attr_accessor :id, :new_record + attr_accessor :id - def initialize(id, new_record) - @id, @new_record = id, new_record + def initialize(id) + @id = id end - def new_record? - @new_record + def persisted? + id.present? end def to_s @@ -88,11 +88,11 @@ class RedirectController < ActionController::Base end def redirect_to_existing_record - redirect_to Workshop.new(5, false) + redirect_to Workshop.new(5) end def redirect_to_new_record - redirect_to Workshop.new(5, true) + redirect_to Workshop.new(nil) end def redirect_to_nil @@ -239,11 +239,11 @@ class RedirectTest < ActionController::TestCase get :redirect_to_existing_record assert_equal "http://test.host/workshops/5", redirect_to_url - assert_redirected_to Workshop.new(5, false) + assert_redirected_to Workshop.new(5) get :redirect_to_new_record assert_equal "http://test.host/workshops", redirect_to_url - assert_redirected_to Workshop.new(5, true) + assert_redirected_to Workshop.new(nil) end end diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb index 3938fc7061..2580ada88b 100644 --- a/actionpack/test/controller/render_json_test.rb +++ b/actionpack/test/controller/render_json_test.rb @@ -18,6 +18,10 @@ class RenderJsonTest < ActionController::TestCase render :json => ActiveSupport::JSON.encode(:hello => 'world') end + def render_json_hello_world_with_status + render :json => ActiveSupport::JSON.encode(:hello => 'world'), :status => 401 + end + def render_json_hello_world_with_callback render :json => ActiveSupport::JSON.encode(:hello => 'world'), :callback => 'alert' end @@ -58,6 +62,12 @@ class RenderJsonTest < ActionController::TestCase assert_equal 'application/json', @response.content_type end + def test_render_json_with_status + get :render_json_hello_world_with_status + assert_equal '{"hello":"world"}', @response.body + assert_equal 401, @response.status + end + def test_render_json_with_callback get :render_json_hello_world_with_callback assert_equal 'alert({"hello":"world"})', @response.body diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 2c3dc2a72d..e3c4869391 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -21,6 +21,10 @@ class TestController < ActionController::Base def hello_world end + def hello_world_file + render :file => File.expand_path("../../fixtures/hello.html", __FILE__) + end + def conditional_hello if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123]) render :action => 'hello_world' @@ -214,6 +218,10 @@ class TestController < ActionController::Base render :text => false end + def render_text_with_resource + render :text => Customer.new("David") + end + # :ported: def render_nothing_with_appendix render :text => "appended" @@ -347,7 +355,7 @@ class TestController < ActionController::Base @before = "i'm before the render" render_to_string :text => "foo" @after = "i'm after the render" - render :action => "test/hello_world" + render :template => "test/hello_world" end def render_to_string_with_exception @@ -361,7 +369,7 @@ class TestController < ActionController::Base rescue end @after = "i'm after the render" - render :action => "test/hello_world" + render :template => "test/hello_world" end def accessing_params_in_template_with_layout @@ -506,7 +514,7 @@ class TestController < ActionController::Base def render_to_string_with_partial @partial_only = render_to_string :partial => "partial_only" @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") } - render :action => "test/hello_world" + render :template => "test/hello_world" end def partial_with_counter @@ -747,6 +755,11 @@ class RenderTest < ActionController::TestCase assert_equal "The secret is in the sauce\n", @response.body end + def test_render_file + get :hello_world_file + assert_equal "Hello world!", @response.body + end + # :ported: def test_render_file_as_string_with_instance_variables get :render_file_as_string_with_instance_variables @@ -817,6 +830,11 @@ class RenderTest < ActionController::TestCase assert_equal 'appended', @response.body end + def test_render_text_with_resource + get :render_text_with_resource + assert_equal 'name: "David"', @response.body + end + # :ported: def test_attempt_to_access_object_method assert_raise(ActionController::UnknownAction, "No action responded to [clone]") { get :clone } diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb index b5b0d0b9d5..4da6c954cf 100644 --- a/actionpack/test/controller/render_xml_test.rb +++ b/actionpack/test/controller/render_xml_test.rb @@ -62,7 +62,8 @@ class RenderXmlTest < ActionController::TestCase with_routing do |set| set.draw do |map| resources :customers - match ':controller/:action' + # match ':controller/:action' + map.connect ':controller/:action/:id' end get :render_with_object_location diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 37367eaafc..dd991898a8 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -326,7 +326,7 @@ class RescueTest < ActionController::IntegrationTest end test 'rescue routing exceptions' do - @app = ActionDispatch::Rescue.new(ActionController::Routing::Routes) do + @app = ActionDispatch::Rescue.new(SharedTestRoutes) do rescue_from ActionController::RoutingError, lambda { |env| [200, {"Content-Type" => "text/html"}, ["Gotcha!"]] } end @@ -335,7 +335,7 @@ class RescueTest < ActionController::IntegrationTest end test 'unrescued exception' do - @app = ActionDispatch::Rescue.new(ActionController::Routing::Routes) + @app = ActionDispatch::Rescue.new(SharedTestRoutes) assert_raise(ActionController::RoutingError) { get '/b00m' } end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 01ed491732..f60045bba6 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -31,10 +31,10 @@ end class ResourcesTest < ActionController::TestCase def test_should_arrange_actions - resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, { :collection => { :rss => :get, :reorder => :post, :csv => :post }, :member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post }, - :new => { :preview => :get, :draft => :get }) + :new => { :preview => :get, :draft => :get }}, {}) assert_resource_methods [:rss], resource, :collection, :get assert_resource_methods [:csv, :reorder], resource, :collection, :post @@ -44,18 +44,18 @@ class ResourcesTest < ActionController::TestCase end def test_should_resource_controller_name_equal_resource_name_by_default - resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, {}) + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, {}, {}) assert_equal 'messages', resource.controller end def test_should_resource_controller_name_equal_controller_option - resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, :controller => 'posts') + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, {:controller => 'posts'}, {}) assert_equal 'posts', resource.controller end def test_should_all_singleton_paths_be_the_same [ :path, :nesting_path_prefix, :member_path ].each do |method| - resource = ActionDispatch::Routing::DeprecatedMapper::SingletonResource.new(:messages, :path_prefix => 'admin') + resource = ActionDispatch::Routing::DeprecatedMapper::SingletonResource.new(:messages, {:path_prefix => 'admin'}, {}) assert_equal 'admin/messages', resource.send(method) end end @@ -111,8 +111,8 @@ class ResourcesTest < ActionController::TestCase end def test_override_paths_for_default_restful_actions - resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, - :path_names => {:new => 'nuevo', :edit => 'editar'}) + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, { + :path_names => {:new => 'nuevo', :edit => 'editar'}}, {}) assert_equal resource.new_path, "#{resource.path}/nuevo" end @@ -125,7 +125,7 @@ class ResourcesTest < ActionController::TestCase def test_with_custom_conditions with_restful_routing :messages, :conditions => { :subdomain => 'app' } do - assert ActionDispatch::Routing::Routes.recognize_path("/messages", :method => :get, :subdomain => 'app') + assert @router.recognize_path("/messages", :method => :get, :subdomain => 'app') end end @@ -394,7 +394,7 @@ class ResourcesTest < ActionController::TestCase assert_restful_routes_for :messages do |options| assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :get) assert_raise(ActionController::RoutingError) do - ActionController::Routing::Routes.recognize_path("/messages/new", :method => :post) + @router.recognize_path("/messages/new", :method => :post) end end end @@ -504,7 +504,7 @@ class ResourcesTest < ActionController::TestCase def test_restful_routes_dont_generate_duplicates with_restful_routing :messages do - routes = ActionController::Routing::Routes.routes + routes = @router.routes routes.each do |route| routes.each do |r| next if route === r # skip the comparison instance @@ -1162,8 +1162,9 @@ class ResourcesTest < ActionController::TestCase options[:shallow_options] = options[:options] end - new_action = ActionController::Base.resources_path_names[:new] || "new" - edit_action = ActionController::Base.resources_path_names[:edit] || "edit" + new_action = @router.resources_path_names[:new] || "new" + edit_action = @router.resources_path_names[:edit] || "edit" + if options[:path_names] new_action = options[:path_names][:new] if options[:path_names][:new] edit_action = options[:path_names][:edit] if options[:path_names][:edit] @@ -1229,6 +1230,7 @@ class ResourcesTest < ActionController::TestCase end @controller = "#{options[:options][:controller].camelize}Controller".constantize.new + @controller.singleton_class.send(:include, @router.url_helpers) @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new get :index, options[:options] @@ -1298,6 +1300,7 @@ class ResourcesTest < ActionController::TestCase def assert_singleton_named_routes_for(singleton_name, options = {}) (options[:options] ||= {})[:controller] ||= singleton_name.to_s.pluralize @controller = "#{options[:options][:controller].camelize}Controller".constantize.new + @controller.singleton_class.send(:include, @router.url_helpers) @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new get :show, options[:options] diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index f390bbdc89..fc85b01394 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -52,29 +52,17 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase end class MockController - attr_accessor :routes + def self.build(helpers) + Class.new do + def url_for(options) + options[:protocol] ||= "http" + options[:host] ||= "test.host" - def initialize(routes) - self.routes = routes - end - - def url_for(options) - only_path = options.delete(:only_path) - - port = options.delete(:port) || 80 - port_string = port == 80 ? '' : ":#{port}" - - protocol = options.delete(:protocol) || "http" - host = options.delete(:host) || "test.host" - anchor = "##{options.delete(:anchor)}" if options.key?(:anchor) - - path = routes.generate(options) - - only_path ? "#{path}#{anchor}" : "#{protocol}://#{host}#{port_string}#{path}#{anchor}" - end + super(options) + end - def request - @request ||= ActionController::TestRequest.new + include helpers + end end end @@ -268,9 +256,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase end def setup_for_named_route - klass = Class.new(MockController) - rs.install_helpers(klass) - klass.new(rs) + MockController.build(rs.url_helpers).new end def test_named_route_without_hash @@ -759,9 +745,7 @@ class RouteSetTest < ActiveSupport::TestCase map.users '/admin/users', :controller => 'admin/users', :action => 'index' end - klass = Class.new(MockController) - set.install_helpers(klass) - klass.new(set) + MockController.build(set.url_helpers).new end def test_named_route_hash_access_method diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index 0afebac68c..30c9a65b7c 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -46,44 +46,32 @@ class SendFileTest < ActionController::TestCase response = nil assert_nothing_raised { response = process('file') } assert_not_nil response - assert_kind_of String, response.body - assert_equal file_data, response.body + body = response.body + assert_kind_of String, body + assert_equal file_data, body end def test_file_stream - pending do - response = nil - assert_nothing_raised { response = process('file') } - assert_not_nil response - assert_kind_of Array, response.body_parts - - require 'stringio' - output = StringIO.new - output.binmode - output.string.force_encoding(file_data.encoding) if output.string.respond_to?(:force_encoding) - assert_nothing_raised { response.body_parts.each { |part| output << part.to_s } } - assert_equal file_data, output.string - end - end - - def test_file_url_based_filename - @controller.options = { :url_based_filename => true } response = nil assert_nothing_raised { response = process('file') } assert_not_nil response - assert_equal "attachment", response.headers["Content-Disposition"] - end + assert response.body_parts.respond_to?(:each) + assert response.body_parts.respond_to?(:to_path) - def test_x_sendfile_header - @controller.options = { :x_sendfile => true } + require 'stringio' + output = StringIO.new + output.binmode + output.string.force_encoding(file_data.encoding) if output.string.respond_to?(:force_encoding) + assert_nothing_raised { response.body_parts.each { |part| output << part.to_s } } + assert_equal file_data, output.string + end + def test_file_url_based_filename + @controller.options = { :url_based_filename => true } response = nil assert_nothing_raised { response = process('file') } assert_not_nil response - - assert_equal @controller.file_path, response.headers['X-Sendfile'] - assert response.body.blank? - assert !response.etag? + assert_equal "attachment", response.headers["Content-Disposition"] end def test_data @@ -104,9 +92,8 @@ class SendFileTest < ActionController::TestCase end # Test that send_file_headers! is setting the correct HTTP headers. - def test_send_file_headers! + def test_send_file_headers_bang options = { - :length => 1, :type => Mime::PNG, :disposition => 'disposition', :filename => 'filename' @@ -121,13 +108,11 @@ class SendFileTest < ActionController::TestCase @controller.send(:send_file_headers!, options) h = @controller.headers - assert_equal '1', h['Content-Length'] assert_equal 'image/png', @controller.content_type assert_equal 'disposition; filename="filename"', h['Content-Disposition'] assert_equal 'binary', h['Content-Transfer-Encoding'] # test overriding Cache-Control: no-cache header to fix IE open/save dialog - @controller.headers = { 'Cache-Control' => 'no-cache' } @controller.send(:send_file_headers!, options) @controller.response.prepare! assert_equal 'private', h['Cache-Control'] @@ -135,7 +120,6 @@ class SendFileTest < ActionController::TestCase def test_send_file_headers_with_mime_lookup_with_symbol options = { - :length => 1, :type => :png } @@ -148,7 +132,6 @@ class SendFileTest < ActionController::TestCase def test_send_file_headers_with_bad_symbol options = { - :length => 1, :type => :this_type_is_not_registered } @@ -163,17 +146,16 @@ class SendFileTest < ActionController::TestCase assert_equal 500, @response.status end + define_method "test_send_#{method}_content_type" do + @controller.options = { :stream => false, :content_type => "application/x-ruby" } + assert_nothing_raised { assert_not_nil process(method) } + assert_equal "application/x-ruby", @response.content_type + end + define_method "test_default_send_#{method}_status" do @controller.options = { :stream => false } assert_nothing_raised { assert_not_nil process(method) } assert_equal 200, @response.status end end - - def test_send_data_content_length_header - @controller.headers = {} - @controller.options = { :type => :text, :filename => 'file_with_utf8_text' } - process('multibyte_text_data') - assert_equal '29', @controller.headers['Content-Length'] - end end diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 0f074b32e6..f6ba275849 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -42,7 +42,7 @@ class TestTest < ActionController::TestCase end def test_uri - render :text => request.request_uri + render :text => request.fullpath end def test_query_string @@ -128,6 +128,7 @@ XML @controller = TestController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new + @request.env['PATH_INFO'] = nil end def test_raw_post_handling @@ -199,7 +200,7 @@ XML end def test_process_with_request_uri_with_params_with_explicit_uri - @request.request_uri = "/explicit/uri" + @request.env['PATH_INFO'] = "/explicit/uri" process :test_uri, :id => 7 assert_equal "/explicit/uri", @response.body end @@ -210,7 +211,8 @@ XML end def test_process_with_query_string_with_explicit_uri - @request.request_uri = "/explicit/uri?q=test?extra=question" + @request.env['PATH_INFO'] = '/explicit/uri' + @request.env['QUERY_STRING'] = 'q=test?extra=question' process :test_query_string assert_equal "q=test?extra=question", @response.body end @@ -476,8 +478,8 @@ XML end def test_with_routing_places_routes_back - assert ActionController::Routing::Routes - routes_id = ActionController::Routing::Routes.object_id + assert @router + routes_id = @router.object_id begin with_routing { raise 'fail' } @@ -485,8 +487,8 @@ XML rescue RuntimeError end - assert ActionController::Routing::Routes - assert_equal routes_id, ActionController::Routing::Routes.object_id + assert @router + assert_equal routes_id, @router.object_id end def test_remote_addr diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 749fa5861f..fc7773dffe 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -5,7 +5,7 @@ module AbstractController class UrlForTests < ActionController::TestCase class W - include ActionController::UrlFor + include SharedTestRoutes.url_helpers end def teardown @@ -113,15 +113,13 @@ module AbstractController end def test_relative_url_root_is_respected - orig_relative_url_root = ActionController::Base.relative_url_root - ActionController::Base.relative_url_root = '/subdir' + # ROUTES TODO: Tests should not have to pass :relative_url_root directly. This + # should probably come from the router. add_host! assert_equal('https://www.basecamphq.com/subdir/c/a/i', - W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https') + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https', :script_name => '/subdir') ) - ensure - ActionController::Base.relative_url_root = orig_relative_url_root end def test_named_routes @@ -132,7 +130,8 @@ module AbstractController end # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlFor } + kls = Class.new { include set.url_helpers } + controller = kls.new assert controller.respond_to?(:home_url) assert_equal 'http://www.basecamphq.com/home/sweet/home/again', @@ -145,22 +144,17 @@ module AbstractController end def test_relative_url_root_is_respected_for_named_routes - orig_relative_url_root = ActionController::Base.relative_url_root - ActionController::Base.relative_url_root = '/subdir' - with_routing do |set| set.draw do |map| match '/home/sweet/home/:user', :to => 'home#index', :as => :home end - kls = Class.new { include ActionController::UrlFor } + kls = Class.new { include set.url_helpers } controller = kls.new assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again', - controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again') + controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again', :script_name => "/subdir") end - ensure - ActionController::Base.relative_url_root = orig_relative_url_root end def test_only_path @@ -171,7 +165,7 @@ module AbstractController end # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlFor } + kls = Class.new { include set.url_helpers } controller = kls.new assert controller.respond_to?(:home_url) assert_equal '/brave/new/world', @@ -239,7 +233,7 @@ module AbstractController end # We need to create a new class in order to install the new named route. - kls = Class.new { include ActionController::UrlFor } + kls = Class.new { include set.url_helpers } kls.default_url_options[:host] = 'www.basecamphq.com' controller = kls.new diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index c2b8cd85d8..7b46a48a1d 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -1,102 +1,85 @@ require 'abstract_unit' require 'controller/fake_controllers' -ActionController::UrlRewriter - class UrlRewriterTests < ActionController::TestCase + class Rewriter + def initialize(request) + @options = { + :host => request.host_with_port, + :protocol => request.protocol + } + end + + def rewrite(router, options) + router.url_for(@options.merge(options)) + end + end + def setup @request = ActionController::TestRequest.new @params = {} - @rewriter = ActionController::UrlRewriter.new(@request, @params) + @rewriter = Rewriter.new(@request) #.new(@request, @params) end def test_port assert_equal('http://test.host:1271/c/a/i', - @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :port => 1271) + @rewriter.rewrite(@router, :controller => 'c', :action => 'a', :id => 'i', :port => 1271) ) end def test_protocol_with_and_without_separator assert_equal('https://test.host/c/a/i', - @rewriter.rewrite(:protocol => 'https', :controller => 'c', :action => 'a', :id => 'i') + @rewriter.rewrite(@router, :protocol => 'https', :controller => 'c', :action => 'a', :id => 'i') ) assert_equal('https://test.host/c/a/i', - @rewriter.rewrite(:protocol => 'https://', :controller => 'c', :action => 'a', :id => 'i') + @rewriter.rewrite(@router, :protocol => 'https://', :controller => 'c', :action => 'a', :id => 'i') ) end def test_user_name_and_password assert_equal( 'http://david:secret@test.host/c/a/i', - @rewriter.rewrite(:user => "david", :password => "secret", :controller => 'c', :action => 'a', :id => 'i') + @rewriter.rewrite(@router, :user => "david", :password => "secret", :controller => 'c', :action => 'a', :id => 'i') ) end def test_user_name_and_password_with_escape_codes assert_equal( 'http://openid.aol.com%2Fnextangler:one+two%3F@test.host/c/a/i', - @rewriter.rewrite(:user => "openid.aol.com/nextangler", :password => "one two?", :controller => 'c', :action => 'a', :id => 'i') + @rewriter.rewrite(@router, :user => "openid.aol.com/nextangler", :password => "one two?", :controller => 'c', :action => 'a', :id => 'i') ) end def test_anchor assert_equal( 'http://test.host/c/a/i#anchor', - @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor') + @rewriter.rewrite(@router, :controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor') ) end def test_anchor_should_call_to_param assert_equal( 'http://test.host/c/a/i#anchor', - @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anchor')) + @rewriter.rewrite(@router, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anchor')) ) end def test_anchor_should_be_cgi_escaped assert_equal( 'http://test.host/c/a/i#anc%2Fhor', - @rewriter.rewrite(:controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor')) + @rewriter.rewrite(@router, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor')) ) end - def test_overwrite_params - @params[:controller] = 'hi' - @params[:action] = 'bye' - @params[:id] = '2' - - assert_equal '/hi/hi/2', @rewriter.rewrite(:only_path => true, :overwrite_params => {:action => 'hi'}) - u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:action => 'hi'}) - assert_match %r(/hi/hi/2$), u - end - - def test_overwrite_removes_original - @params[:controller] = 'search' - @params[:action] = 'list' - @params[:list_page] = 1 - - assert_equal '/search/list?list_page=2', @rewriter.rewrite(:only_path => true, :overwrite_params => {"list_page" => 2}) - u = @rewriter.rewrite(:only_path => false, :overwrite_params => {:list_page => 2}) - assert_equal 'http://test.host/search/list?list_page=2', u - end - - def test_to_str - @params[:controller] = 'hi' - @params[:action] = 'bye' - @request.parameters[:id] = '2' - - assert_equal 'http://, test.host, /, hi, bye, {"id"=>"2"}', @rewriter.to_str - end - def test_trailing_slash options = {:controller => 'foo', :action => 'bar', :id => '3', :only_path => true} - assert_equal '/foo/bar/3', @rewriter.rewrite(options) - assert_equal '/foo/bar/3?query=string', @rewriter.rewrite(options.merge({:query => 'string'})) + assert_equal '/foo/bar/3', @rewriter.rewrite(@router, options) + assert_equal '/foo/bar/3?query=string', @rewriter.rewrite(@router, options.merge({:query => 'string'})) options.update({:trailing_slash => true}) - assert_equal '/foo/bar/3/', @rewriter.rewrite(options) + assert_equal '/foo/bar/3/', @rewriter.rewrite(@router, options) options.update({:query => 'string'}) - assert_equal '/foo/bar/3/?query=string', @rewriter.rewrite(options) + assert_equal '/foo/bar/3/?query=string', @rewriter.rewrite(@router, options) end end diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb index 56821332c5..b8972b04b6 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionpack/test/controller/view_paths_test.rb @@ -36,9 +36,12 @@ class ViewLoadPathsTest < ActionController::TestCase @old_behavior = ActiveSupport::Deprecation.behavior @last_message = nil ActiveSupport::Deprecation.behavior = Proc.new { |message, callback| @last_message = message } + + @paths = TestController.view_paths end def teardown + TestController.view_paths = @paths ActiveSupport::Deprecation.behavior = @old_behavior end diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 5882a8cfa3..05545395fb 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -245,7 +245,7 @@ class WebServiceTest < ActionController::IntegrationTest private def with_params_parsers(parsers = {}) old_session = @integration_session - @app = ActionDispatch::ParamsParser.new(ActionController::Routing::Routes, parsers) + @app = ActionDispatch::ParamsParser.new(app.router, parsers) reset! yield ensure diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb new file mode 100644 index 0000000000..00ca5ec9dc --- /dev/null +++ b/actionpack/test/dispatch/mount_test.rb @@ -0,0 +1,36 @@ +require 'abstract_unit' + +class TestRoutingMount < ActionDispatch::IntegrationTest + Router = ActionDispatch::Routing::RouteSet.new + Router.draw do + SprocketsApp = lambda { |env| + [200, {"Content-Type" => "text/html"}, ["#{env["SCRIPT_NAME"]} -- #{env["PATH_INFO"]}"]] + } + + mount SprocketsApp, :at => "/sprockets" + mount SprocketsApp => "/shorthand" + + scope "/its_a" do + mount SprocketsApp, :at => "/sprocket" + end + end + + def app + Router + end + + def test_mounting_sets_script_name + get "/sprockets/omg" + assert_equal "/sprockets -- /omg", response.body + end + + def test_mounting_works_with_scope + get "/its_a/sprocket/omg" + assert_equal "/its_a/sprocket -- /omg", response.body + end + + def test_mounting_with_shorthand + get "/shorthand/omg" + assert_equal "/shorthand -- /omg", response.body + end +end
\ No newline at end of file diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 2b5c19361a..badef4e92e 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -1,14 +1,6 @@ require 'abstract_unit' class RequestTest < ActiveSupport::TestCase - def setup - ActionController::Base.relative_url_root = nil - end - - def teardown - ActionController::Base.relative_url_root = nil - end - test "remote ip" do request = stub_request 'REMOTE_ADDR' => '1.2.3.4' assert_equal '1.2.3.4', request.remote_ip @@ -50,7 +42,7 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', 'HTTP_CLIENT_IP' => '2.2.2.2' - e = assert_raise(ActionController::ActionControllerError) { + e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) { request.remote_ip } assert_match /IP spoofing attack/, e.message @@ -62,18 +54,17 @@ class RequestTest < ActiveSupport::TestCase # example is WAP. Since the cellular network is not IP based, it's a # leap of faith to assume that their proxies are ever going to set the # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly. - ActionController::Base.ip_spoofing_check = false request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', - 'HTTP_CLIENT_IP' => '2.2.2.2' + 'HTTP_CLIENT_IP' => '2.2.2.2', + :ip_spoofing_check => false assert_equal '2.2.2.2', request.remote_ip - ActionController::Base.ip_spoofing_check = true request = stub_request 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9' assert_equal '9.9.9.9', request.remote_ip end test "remote ip with user specified trusted proxies" do - ActionController::Base.trusted_proxies = /^67\.205\.106\.73$/i + @trusted_proxies = /^67\.205\.106\.73$/i request = stub_request 'REMOTE_ADDR' => '67.205.106.73', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' @@ -96,8 +87,6 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73' assert_equal '3.4.5.6', request.remote_ip - - ActionController::Base.trusted_proxies = nil end test "domains" do @@ -151,104 +140,34 @@ class RequestTest < ActiveSupport::TestCase assert_equal ":8080", request.port_string end - test "request uri" do - request = stub_request 'REQUEST_URI' => "http://www.rubyonrails.org/path/of/some/uri?mapped=1" - assert_equal "/path/of/some/uri?mapped=1", request.request_uri - assert_equal "/path/of/some/uri", request.path - - request = stub_request 'REQUEST_URI' => "http://www.rubyonrails.org/path/of/some/uri" - assert_equal "/path/of/some/uri", request.request_uri - assert_equal "/path/of/some/uri", request.path - - request = stub_request 'REQUEST_URI' => "/path/of/some/uri" - assert_equal "/path/of/some/uri", request.request_uri - assert_equal "/path/of/some/uri", request.path - - request = stub_request 'REQUEST_URI' => "/" - assert_equal "/", request.request_uri - assert_equal "/", request.path - - request = stub_request 'REQUEST_URI' => "/?m=b" - assert_equal "/?m=b", request.request_uri - assert_equal "/", request.path - - request = stub_request 'REQUEST_URI' => "/", 'SCRIPT_NAME' => '/dispatch.cgi' - assert_equal "/", request.request_uri - assert_equal "/", request.path - - ActionController::Base.relative_url_root = "/hieraki" - request = stub_request 'REQUEST_URI' => "/hieraki/", 'SCRIPT_NAME' => "/hieraki/dispatch.cgi" - assert_equal "/hieraki/", request.request_uri - assert_equal "/", request.path - ActionController::Base.relative_url_root = nil - - ActionController::Base.relative_url_root = "/collaboration/hieraki" - request = stub_request 'REQUEST_URI' => "/collaboration/hieraki/books/edit/2", - 'SCRIPT_NAME' => "/collaboration/hieraki/dispatch.cgi" - assert_equal "/collaboration/hieraki/books/edit/2", request.request_uri - assert_equal "/books/edit/2", request.path - ActionController::Base.relative_url_root = nil - - # The following tests are for when REQUEST_URI is not supplied (as in IIS) - request = stub_request 'PATH_INFO' => "/path/of/some/uri?mapped=1", - 'SCRIPT_NAME' => nil, - 'REQUEST_URI' => nil - assert_equal "/path/of/some/uri?mapped=1", request.request_uri - assert_equal "/path/of/some/uri", request.path - - ActionController::Base.relative_url_root = '/path' - request = stub_request 'PATH_INFO' => "/path/of/some/uri?mapped=1", - 'SCRIPT_NAME' => "/path/dispatch.rb", - 'REQUEST_URI' => nil - assert_equal "/path/of/some/uri?mapped=1", request.request_uri - assert_equal "/of/some/uri", request.path - ActionController::Base.relative_url_root = nil - - request = stub_request 'PATH_INFO' => "/path/of/some/uri", - 'SCRIPT_NAME' => nil, - 'REQUEST_URI' => nil - assert_equal "/path/of/some/uri", request.request_uri - assert_equal "/path/of/some/uri", request.path - - request = stub_request 'PATH_INFO' => '/', 'REQUEST_URI' => nil - assert_equal "/", request.request_uri - assert_equal "/", request.path - - request = stub_request 'PATH_INFO' => '/?m=b', 'REQUEST_URI' => nil - assert_equal "/?m=b", request.request_uri - assert_equal "/", request.path - - request = stub_request 'PATH_INFO' => "/", - 'SCRIPT_NAME' => "/dispatch.cgi", - 'REQUEST_URI' => nil - assert_equal "/", request.request_uri - assert_equal "/", request.path - - ActionController::Base.relative_url_root = '/hieraki' - request = stub_request 'PATH_INFO' => "/hieraki/", - 'SCRIPT_NAME' => "/hieraki/dispatch.cgi", - 'REQUEST_URI' => nil - assert_equal "/hieraki/", request.request_uri - assert_equal "/", request.path - ActionController::Base.relative_url_root = nil - - request = stub_request 'REQUEST_URI' => '/hieraki/dispatch.cgi' - ActionController::Base.relative_url_root = '/hieraki' - assert_equal "/dispatch.cgi", request.path - ActionController::Base.relative_url_root = nil - - request = stub_request 'REQUEST_URI' => '/hieraki/dispatch.cgi' - ActionController::Base.relative_url_root = '/foo' - assert_equal "/hieraki/dispatch.cgi", request.path - ActionController::Base.relative_url_root = nil - - # This test ensures that Rails uses REQUEST_URI over PATH_INFO - ActionController::Base.relative_url_root = nil - request = stub_request 'REQUEST_URI' => "/some/path", - 'PATH_INFO' => "/another/path", - 'SCRIPT_NAME' => "/dispatch.cgi" - assert_equal "/some/path", request.request_uri - assert_equal "/some/path", request.path + test "full path" do + request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/path/of/some/uri', 'QUERY_STRING' => 'mapped=1' + assert_equal "/path/of/some/uri?mapped=1", request.fullpath + assert_equal "/path/of/some/uri", request.path_info + + request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/path/of/some/uri' + assert_equal "/path/of/some/uri", request.fullpath + assert_equal "/path/of/some/uri", request.path_info + + request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/' + assert_equal "/", request.fullpath + assert_equal "/", request.path_info + + request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/', 'QUERY_STRING' => 'm=b' + assert_equal "/?m=b", request.fullpath + assert_equal "/", request.path_info + + request = stub_request 'SCRIPT_NAME' => '/hieraki', 'PATH_INFO' => '/' + assert_equal "/hieraki/", request.fullpath + assert_equal "/", request.path_info + + request = stub_request 'SCRIPT_NAME' => '/collaboration/hieraki', 'PATH_INFO' => '/books/edit/2' + assert_equal "/collaboration/hieraki/books/edit/2", request.fullpath + assert_equal "/books/edit/2", request.path_info + + request = stub_request 'SCRIPT_NAME' => '/path', 'PATH_INFO' => '/of/some/uri', 'QUERY_STRING' => 'mapped=1' + assert_equal "/path/of/some/uri?mapped=1", request.fullpath + assert_equal "/of/some/uri", request.path_info end @@ -462,7 +381,7 @@ class RequestTest < ActiveSupport::TestCase [{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'], [{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'], [{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana'], - [{'baz'=>[{'foo'=>'baz'}]}, {'baz'=>[{'foo'=>'[FILTERED]'}]}, [/foo/]]] + [{'baz'=>[{'foo'=>'baz'}, "1"]}, {'baz'=>[{'foo'=>'[FILTERED]'}, "1"]}, [/foo/]]] test_hashes.each do |before_filter, after_filter, filter_words| request = stub_request('action_dispatch.parameter_filter' => filter_words) @@ -506,18 +425,14 @@ class RequestTest < ActiveSupport::TestCase protected - def stub_request(env={}) + def stub_request(env = {}) + ip_spoofing_check = env.key?(:ip_spoofing_check) ? env.delete(:ip_spoofing_check) : true + ip_app = ActionDispatch::RemoteIp.new(Proc.new { }, ip_spoofing_check, @trusted_proxies) + ip_app.call(env) ActionDispatch::Request.new(env) end def with_set(*args) args end - - def with_accept_header(value) - ActionController::Base.use_accept_header, old = value, ActionController::Base.use_accept_header - yield - ensure - ActionController::Base.use_accept_header = old - end end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 4697fa3e2b..c7f7f3102d 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -165,10 +165,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest assert_equal('public', @response.headers['Cache-Control']) assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag']) - pending do - assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag) - assert_equal({:public => true}, @response.cache_control) - end + assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag) + assert_equal({:public => true}, @response.cache_control) end test "response cache control from rackish app" do @@ -184,10 +182,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest assert_equal('public', @response.headers['Cache-Control']) assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag']) - pending do - assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag) - assert_equal({:public => true}, @response.cache_control) - end + assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag) + assert_equal({:public => true}, @response.cache_control) end test "response charset and content type from railsish app" do @@ -202,10 +198,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest get '/' assert_response :success - pending do - assert_equal('utf-16', @response.charset) - assert_equal(Mime::XML, @response.content_type) - end + assert_equal('utf-16', @response.charset) + assert_equal(Mime::XML, @response.content_type) assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type']) end @@ -220,10 +214,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest get '/' assert_response :success - pending do - assert_equal('utf-16', @response.charset) - assert_equal(Mime::XML, @response.content_type) - end + assert_equal('utf-16', @response.charset) + assert_equal(Mime::XML, @response.content_type) assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type']) end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index bcb97e4ae0..f5fcf9b0df 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -15,6 +15,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest stub_controllers do |routes| Routes = routes Routes.draw do + default_url_options :host => "rubyonrails.org" + controller :sessions do get 'login' => :new, :as => :login post 'login' => :create @@ -24,6 +26,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest resource :session do get :create + + resource :info end match 'account/logout' => redirect("/logout"), :as => :logout_redirect @@ -104,7 +108,9 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - resources :posts, :only => [:index, :show] + resources :posts, :only => [:index, :show] do + resources :comments, :except => :destroy + end match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp @@ -162,6 +168,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest Routes end + include Routes.url_helpers + def test_logout with_test_routes do delete '/logout' @@ -183,6 +191,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true) assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true) + + assert_equal 'http://rubyonrails.org/login', Routes.url_for(:controller => 'sessions', :action => 'create') end end @@ -230,6 +240,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end + def test_session_info_nested_singleton_resource + with_test_routes do + get '/session/info' + assert_equal 'infos#show', @response.body + assert_equal '/session/info', session_info_path + end + end + def test_redirect_modulo with_test_routes do get '/account/modulo/name' @@ -471,7 +489,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - def test_posts + def test_resource_routes_with_only_and_except with_test_routes do get '/posts' assert_equal 'posts#index', @response.body @@ -481,9 +499,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'posts#show', @response.body assert_equal '/posts/1', post_path(:id => 1) + get '/posts/1/comments' + assert_equal 'comments#index', @response.body + assert_equal '/posts/1/comments', post_comments_path(:post_id => 1) + assert_raise(ActionController::RoutingError) { post '/posts' } assert_raise(ActionController::RoutingError) { put '/posts/1' } assert_raise(ActionController::RoutingError) { delete '/posts/1' } + assert_raise(ActionController::RoutingError) { delete '/posts/1/comments' } end end @@ -721,14 +744,6 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest private def with_test_routes - real_routes, temp_routes = ActionController::Routing::Routes, Routes - - ActionController::Routing.module_eval { remove_const :Routes } - ActionController::Routing.module_eval { const_set :Routes, temp_routes } - yield - ensure - ActionController::Routing.module_eval { remove_const :Routes } - ActionController::Routing.const_set(:Routes, real_routes) end end diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb new file mode 100644 index 0000000000..18b5b7ee00 --- /dev/null +++ b/actionpack/test/dispatch/url_generation_test.rb @@ -0,0 +1,38 @@ +require 'abstract_unit' + +module TestUrlGeneration + class WithMountPoint < ActionDispatch::IntegrationTest + Router = ActionDispatch::Routing::RouteSet.new + Router.draw { match "/foo", :to => "my_route_generating#index", :as => :foo } + + class ::MyRouteGeneratingController < ActionController::Base + include Router.url_helpers + def index + render :text => foo_path + end + end + + include Router.url_helpers + + def _router + Router + end + + def app + Router + end + + test "generating URLS normally" do + assert_equal "/foo", foo_path + end + + test "accepting a :script_name option" do + assert_equal "/bar/foo", foo_path(:script_name => "/bar") + end + + test "the request's SCRIPT_NAME takes precedence over the router's" do + get "/foo", {}, 'SCRIPT_NAME' => "/new" + assert_equal "/new/foo", response.body + end + end +end
\ No newline at end of file diff --git a/actionpack/test/fixtures/hello.html b/actionpack/test/fixtures/hello.html new file mode 100644 index 0000000000..6769dd60bd --- /dev/null +++ b/actionpack/test/fixtures/hello.html @@ -0,0 +1 @@ +Hello world!
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/layout_render_file.erb b/actionpack/test/fixtures/test/layout_render_file.erb new file mode 100644 index 0000000000..2f8e921c5f --- /dev/null +++ b/actionpack/test/fixtures/test/layout_render_file.erb @@ -0,0 +1,2 @@ +<% content_for :title do %>title<% end -%> +<%= render :file => 'layouts/yield' -%>
\ No newline at end of file diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index 7346cb22bc..bf3e175f1f 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -6,10 +6,6 @@ class Customer < Struct.new(:name, :id) undef_method :to_json - def to_param - id.to_s - end - def to_xml(options={}) if options[:builder] options[:builder].name name @@ -21,13 +17,14 @@ class Customer < Struct.new(:name, :id) def to_js(options={}) "name: #{name.inspect}" end + alias :to_text :to_js def errors [] end - def destroyed? - false + def persisted? + id.present? end end @@ -42,8 +39,8 @@ module Quiz extend ActiveModel::Naming include ActiveModel::Conversion - def to_param - id.to_s + def persisted? + id.present? end end @@ -58,12 +55,12 @@ class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost alias_method :secret?, :secret - def new_record=(boolean) - @new_record = boolean + def persisted=(boolean) + @persisted = boolean end - def new_record? - @new_record + def persisted? + @persisted end attr_accessor :author @@ -83,8 +80,9 @@ class Comment attr_reader :id attr_reader :post_id def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end + def to_key; id ? [id] : nil end def save; @id = 1; @post_id = 1 end - def new_record?; @id.nil? end + def persisted?; @id.present? end def to_param; @id; end def name @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" @@ -102,8 +100,9 @@ class Tag attr_reader :id attr_reader :post_id def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end + def to_key; id ? [id] : nil end def save; @id = 1; @post_id = 1 end - def new_record?; @id.nil? end + def persisted?; @id.present? end def to_param; @id; end def value @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" @@ -121,8 +120,9 @@ class CommentRelevance attr_reader :id attr_reader :comment_id def initialize(id = nil, comment_id = nil); @id, @comment_id = id, comment_id end + def to_key; id ? [id] : nil end def save; @id = 1; @comment_id = 1 end - def new_record?; @id.nil? end + def persisted?; @id.present? end def to_param; @id; end def value @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" @@ -136,8 +136,9 @@ class TagRelevance attr_reader :id attr_reader :tag_id def initialize(id = nil, tag_id = nil); @id, @tag_id = id, tag_id end + def to_key; id ? [id] : nil end def save; @id = 1; @tag_id = 1 end - def new_record?; @id.nil? end + def persisted?; @id.present? end def to_param; @id; end def value @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}" diff --git a/actionpack/test/lib/fixture_template.rb b/actionpack/test/lib/fixture_template.rb index 6b9e7c5270..02248d84ad 100644 --- a/actionpack/test/lib/fixture_template.rb +++ b/actionpack/test/lib/fixture_template.rb @@ -1,23 +1,28 @@ module ActionView #:nodoc: class FixtureResolver < PathResolver - def initialize(hash = {}, options = {}) - super(options) + attr_reader :hash + + def initialize(hash = {}) + super() @hash = hash end private - def query(path, exts) + def query(partial, path, exts) query = Regexp.escape(path) exts.each do |ext| - query << '(?:' << ext.map {|e| e && Regexp.escape(".#{e}") }.join('|') << ')' + query << '(' << ext.map {|e| e && Regexp.escape(".#{e}") }.join('|') << ')' end templates = [] @hash.select { |k,v| k =~ /^#{query}$/ }.each do |path, source| - templates << Template.new(source, path, *path_to_details(path)) + handler, format = extract_handler_and_format(path) + templates << Template.new(source, path, handler, + :partial => partial, :virtual_path => path, :format => format) end - templates.sort_by {|t| -t.details.values.compact.size } + + templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } end end diff --git a/actionpack/test/template/active_model_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb index 3e01ae78c3..371aa53c68 100644 --- a/actionpack/test/template/active_model_helper_test.rb +++ b/actionpack/test/template/active_model_helper_test.rb @@ -64,7 +64,7 @@ class ActiveModelHelperTest < ActionView::TestCase }.new end - def @post.new_record?() true end + def @post.persisted?() false end def @post.to_param() nil end def @post.column_for_attribute(attr_name) @@ -155,7 +155,7 @@ class ActiveModelHelperTest < ActionView::TestCase silence_warnings do class << @post - def new_record?() false end + def persisted?() true end def to_param() id end def id() 1 end end diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 586de66714..d9a89959da 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -1,14 +1,16 @@ require 'abstract_unit' +require 'active_support/ordered_options' -class AssetTagHelperTest < ActionView::TestCase - tests ActionView::Helpers::AssetTagHelper +class FakeController + attr_accessor :request - DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG.merge( - :assets_dir => File.dirname(__FILE__) + "/../fixtures/public", - :javascripts_dir => File.dirname(__FILE__) + "/../fixtures/public/javascripts", - :stylesheets_dir => File.dirname(__FILE__) + "/../fixtures/public/stylesheets") + def config + @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config) + end +end - include ActiveSupport::Configurable +class AssetTagHelperTest < ActionView::TestCase + tests ActionView::Helpers::AssetTagHelper def setup super @@ -32,8 +34,7 @@ class AssetTagHelperTest < ActionView::TestCase ) end - @controller = Class.new do - attr_accessor :request + @controller = Class.new(BasicController) do def url_for(*args) "http://www.example.com" end end.new @@ -50,7 +51,6 @@ class AssetTagHelperTest < ActionView::TestCase def teardown ActionController::Base.perform_caching = false - ActionController::Base.asset_host = nil ENV.delete('RAILS_ASSET_ID') end @@ -141,13 +141,16 @@ class AssetTagHelperTest < ActionView::TestCase ImageLinkToTag = { %(image_tag("xml.png")) => %(<img alt="Xml" src="/images/xml.png" />), - %(image_tag("..jpg")) => %(<img alt="" src="/images/..jpg" />), + %(image_tag("..jpg")) => %(<img alt="..jpg" src="/images/..jpg" />), %(image_tag("rss.gif", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.gif" />), %(image_tag("gold.png", :size => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />), %(image_tag("gold.png", "size" => "45x70")) => %(<img alt="Gold" height="70" src="/images/gold.png" width="45" />), %(image_tag("error.png", "size" => "45")) => %(<img alt="Error" src="/images/error.png" />), %(image_tag("error.png", "size" => "45 x 70")) => %(<img alt="Error" src="/images/error.png" />), %(image_tag("error.png", "size" => "x")) => %(<img alt="Error" src="/images/error.png" />), + %(image_tag("google.com.png")) => %(<img alt="Google.com" src="/images/google.com.png" />), + %(image_tag("slash..png")) => %(<img alt="Slash." src="/images/slash..png" />), + %(image_tag(".pdf.png")) => %(<img alt=".pdf" src="/images/.pdf.png" />), %(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="http://www.rubyonrails.com/images/rails.png" />), %(image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />), %(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />) @@ -372,11 +375,9 @@ class AssetTagHelperTest < ActionView::TestCase end def test_timebased_asset_id_with_relative_url_root - ActionController::Base.relative_url_root = "/collaboration/hieraki" - expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s - assert_equal %(<img alt="Rails" src="#{ActionController::Base.relative_url_root}/images/rails.png?#{expected_time}" />), image_tag("rails.png") - ensure - ActionController::Base.relative_url_root = "" + @controller.config.relative_url_root = "/collaboration/hieraki" + expected_time = File.stat(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).mtime.to_i.to_s + assert_equal %(<img alt="Rails" src="#{@controller.config.relative_url_root}/images/rails.png?#{expected_time}" />), image_tag("rails.png") end def test_should_skip_asset_id_on_complete_url @@ -402,7 +403,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_image_path_with_caching_and_proc_asset_host_using_request ENV['RAILS_ASSET_ID'] = '' - ActionController::Base.asset_host = Proc.new do |source, request| + @controller.config.asset_host = Proc.new do |source, request| if request.ssl? "#{request.protocol}#{request.host_with_port}" else @@ -410,8 +411,6 @@ class AssetTagHelperTest < ActionView::TestCase end end - ActionController::Base.perform_caching = true - @controller.request.stubs(:ssl?).returns(false) assert_equal "http://assets15.example.com/images/xml.png", image_path("xml.png") @@ -422,7 +421,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_when_caching_on ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.asset_host = 'http://a0.example.com' + @controller.config.asset_host = 'http://a0.example.com' ActionController::Base.perform_caching = true assert_dom_equal( @@ -454,7 +453,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_when_caching_on_with_proc_asset_host ENV['RAILS_ASSET_ID'] = '' - ActionController::Base.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } + @controller.config.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } ActionController::Base.perform_caching = true assert_equal '/javascripts/scripts.js'.length, 23 @@ -471,7 +470,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_when_caching_on_with_2_argument_proc_asset_host ENV['RAILS_ASSET_ID'] = '' - ActionController::Base.asset_host = Proc.new { |source, request| + @controller.config.asset_host = Proc.new { |source, request| if request.ssl? "#{request.protocol}#{request.host_with_port}" else @@ -508,7 +507,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_when_caching_on_with_2_argument_object_asset_host ENV['RAILS_ASSET_ID'] = '' - ActionController::Base.asset_host = Class.new do + @controller.config.asset_host = Class.new do def call(source, request) if request.ssl? "#{request.protocol}#{request.host_with_port}" @@ -548,7 +547,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_when_caching_on_and_using_subdirectory ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.asset_host = 'http://a%d.example.com' + @controller.config.asset_host = 'http://a%d.example.com' ActionController::Base.perform_caching = true hash = '/javascripts/cache/money.js'.hash % 4 @@ -564,7 +563,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_with_all_and_recursive_puts_defaults_at_the_start_of_the_file ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.asset_host = 'http://a0.example.com' + @controller.config.asset_host = 'http://a0.example.com' ActionController::Base.perform_caching = true assert_dom_equal( @@ -585,7 +584,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_with_all_puts_defaults_at_the_start_of_the_file ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.asset_host = 'http://a0.example.com' + @controller.config.asset_host = 'http://a0.example.com' ActionController::Base.perform_caching = true assert_dom_equal( @@ -606,7 +605,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_javascript_include_tag_with_relative_url_root ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.relative_url_root = "/collaboration/hieraki" + @controller.config.relative_url_root = "/collaboration/hieraki" ActionController::Base.perform_caching = true assert_dom_equal( @@ -624,7 +623,6 @@ class AssetTagHelperTest < ActionView::TestCase assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) ensure - ActionController::Base.relative_url_root = nil 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 @@ -694,7 +692,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_stylesheet_link_tag_when_caching_on ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.asset_host = 'http://a0.example.com' + @controller.config.asset_host = 'http://a0.example.com' ActionController::Base.perform_caching = true assert_dom_equal( @@ -804,7 +802,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_stylesheet_link_tag_when_caching_on_with_proc_asset_host ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } + @controller.config.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } ActionController::Base.perform_caching = true assert_equal '/stylesheets/styles.css'.length, 23 @@ -821,7 +819,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_stylesheet_link_tag_with_relative_url_root ENV["RAILS_ASSET_ID"] = "" - ActionController::Base.relative_url_root = "/collaboration/hieraki" + @controller.config.relative_url_root = "/collaboration/hieraki" ActionController::Base.perform_caching = true assert_dom_equal( @@ -841,7 +839,6 @@ class AssetTagHelperTest < ActionView::TestCase assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) ensure - ActionController::Base.relative_url_root = nil 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 @@ -879,21 +876,16 @@ end class AssetTagHelperNonVhostTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper - DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG - include ActiveSupport::Configurable - def setup super - ActionController::Base.relative_url_root = "/collaboration/hieraki" - - @controller = Class.new do - attr_accessor :request - + @controller = Class.new(BasicController) do def url_for(options) "http://www.example.com/collaboration/hieraki" end end.new + @controller.config.relative_url_root = "/collaboration/hieraki" + @request = Class.new do def protocol 'gopher://' @@ -905,10 +897,6 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase ActionView::Helpers::AssetTagHelper::reset_javascript_include_default end - def teardown - ActionController::Base.relative_url_root = nil - end - def test_should_compute_proper_path assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag) assert_dom_equal(%(/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) @@ -923,43 +911,33 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase end def test_should_compute_proper_path_with_asset_host - ActionController::Base.asset_host = "http://assets.example.com" + @controller.config.asset_host = "http://assets.example.com" assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag) assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='http://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='http://assets.example.com/collaboration/hieraki/images/mouse.png'" src="http://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='http://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='http://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="http://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) - ensure - ActionController::Base.asset_host = "" end def test_should_ignore_asset_host_on_complete_url - ActionController::Base.asset_host = "http://assets.example.com" + @controller.config.asset_host = "http://assets.example.com" assert_dom_equal(%(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag("http://bar.example.com/stylesheets/style.css")) - ensure - ActionController::Base.asset_host = "" end def test_should_wildcard_asset_host_between_zero_and_four - ActionController::Base.asset_host = 'http://a%d.example.com' + @controller.config.asset_host = 'http://a%d.example.com' assert_match %r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png') - ensure - ActionController::Base.asset_host = nil end def test_asset_host_without_protocol_should_use_request_protocol - ActionController::Base.asset_host = 'a.example.com' + @controller.config.asset_host = 'a.example.com' assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_path('xml.png') - ensure - ActionController::Base.asset_host = nil end def test_asset_host_without_protocol_should_use_request_protocol_even_if_path_present - ActionController::Base.asset_host = 'a.example.com/files/go/here' + @controller.config.asset_host = 'a.example.com/files/go/here' assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_path('xml.png') - ensure - ActionController::Base.asset_host = nil end def test_assert_css_and_js_of_the_same_name_return_correct_extension diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb index 347cb98303..869ea22469 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionpack/test/template/atom_feed_helper_test.rb @@ -4,8 +4,8 @@ class Scroll < Struct.new(:id, :to_param, :title, :body, :updated_at, :created_a extend ActiveModel::Naming include ActiveModel::Conversion - def new_record? - true + def persisted? + false end end @@ -34,7 +34,7 @@ class ScrollsController < ActionController::Base feed.updated((@scrolls.first.created_at)) for scroll in @scrolls - feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param, :updated => Time.utc(2007, 1, scroll.id)) do |entry| + feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param.to_s, :updated => Time.utc(2007, 1, scroll.id)) do |entry| entry.title(scroll.title) entry.content(scroll.body, :type => 'html') @@ -55,7 +55,7 @@ class ScrollsController < ActionController::Base end for scroll in @scrolls - feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param, :updated => Time.utc(2007, 1, scroll.id)) do |entry| + feed.entry(scroll, :url => "/otherstuff/" + scroll.to_param.to_s, :updated => Time.utc(2007, 1, scroll.id)) do |entry| entry.title(scroll.title) entry.content(scroll.body, :type => 'html') end diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb index 632988bb2e..2c719757e4 100644 --- a/actionpack/test/template/compiled_templates_test.rb +++ b/actionpack/test/template/compiled_templates_test.rb @@ -14,9 +14,6 @@ class CompiledTemplatesTest < Test::Unit::TestCase assert_equal "two", render(:file => "test/render_file_with_locals_and_default.erb", :locals => { :secret => "two" }) end - # This is broken in 1.8.6 (not supported in Rails 3.0) because the cache uses a Hash - # key. Since Ruby 1.8.6 implements Hash#hash using the hash's object_id, it will never - # successfully get a cache hit here. def test_template_changes_are_not_reflected_with_cached_templates assert_equal "Hello world!", render(:file => "test/hello_world.erb") modify_template "test/hello_world.erb", "Goodbye world!" do @@ -44,7 +41,7 @@ class CompiledTemplatesTest < Test::Unit::TestCase end def render_without_cache(*args) - path = ActionView::FileSystemResolverWithFallback.new(FIXTURE_LOAD_PATH) + path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH) view_paths = ActionView::Base.process_view_paths(path) ActionView::Base.new(view_paths, {}).render(*args) end diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index fb51b67185..da2477b6f8 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -1229,11 +1229,28 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, date_select("post", "written_on", :order => [ :month, :year ]) end + def test_date_select_without_day_and_with_disabled_html_option + @post = Post.new + @post.written_on = Date.new(2004, 6, 15) + + expected = "<input type=\"hidden\" id=\"post_written_on_3i\" disabled=\"disabled\" name=\"post[written_on(3i)]\" value=\"1\" />\n" + + expected << %{<select id="post_written_on_2i" disabled="disabled" name="post[written_on(2i)]">\n} + expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} + expected << "</select>\n" + + expected << %{<select id="post_written_on_1i" disabled="disabled" name="post[written_on(1i)]">\n} + expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} + expected << "</select>\n" + + assert_dom_equal expected, date_select("post", "written_on", { :order => [ :month, :year ] }, :disabled => true) + end + def test_date_select_within_fields_for @post = Post.new @post.written_on = Date.new(2004, 6, 15) - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.date_select(:written_on) end @@ -1249,7 +1266,7 @@ class DateHelperTest < ActionView::TestCase @post.written_on = Date.new(2004, 6, 15) id = 27 - fields_for :post, @post, :index => id do |f| + output_buffer = fields_for :post, @post, :index => id do |f| concat f.date_select(:written_on) end @@ -1265,7 +1282,7 @@ class DateHelperTest < ActionView::TestCase @post.written_on = Date.new(2004, 6, 15) id = nil - fields_for :post, @post, :index => id do |f| + output_buffer = fields_for :post, @post, :index => id do |f| concat f.date_select(:written_on) end @@ -1461,7 +1478,7 @@ class DateHelperTest < ActionView::TestCase @post = Post.new @post.written_on = Date.new(2004, 6, 15) - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.date_select(:written_on, {}, :class => 'selector') end @@ -1625,7 +1642,7 @@ class DateHelperTest < ActionView::TestCase @post = Post.new @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.time_select(:written_on, {}, :class => 'selector') end @@ -1713,6 +1730,25 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, time_select("post", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute'}) end + def test_time_select_with_disabled_html_option + @post = Post.new + @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<input type="hidden" id="post_written_on_1i" disabled="disabled" name="post[written_on(1i)]" value="2004" />\n} + expected << %{<input type="hidden" id="post_written_on_2i" disabled="disabled" name="post[written_on(2i)]" value="6" />\n} + expected << %{<input type="hidden" id="post_written_on_3i" disabled="disabled" name="post[written_on(3i)]" value="15" />\n} + + expected << %(<select id="post_written_on_4i" disabled="disabled" name="post[written_on(4i)]">\n) + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + expected << " : " + expected << %(<select id="post_written_on_5i" disabled="disabled" name="post[written_on(5i)]">\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, time_select("post", "written_on", {}, :disabled => true) + end + def test_datetime_select @post = Post.new @post.updated_at = Time.local(2004, 6, 15, 16, 35) @@ -1780,7 +1816,7 @@ class DateHelperTest < ActionView::TestCase @post = Post.new @post.updated_at = Time.local(2004, 6, 15, 16, 35) - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.datetime_select(:updated_at, {}, :class => 'selector') end @@ -2016,7 +2052,7 @@ class DateHelperTest < ActionView::TestCase @post.updated_at = Time.local(2004, 6, 15, 16, 35) id = 456 - fields_for :post, @post, :index => id do |f| + output_buffer = fields_for :post, @post, :index => id do |f| concat f.datetime_select(:updated_at) end @@ -2173,6 +2209,66 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, datetime_select("post", "updated_at", :discard_year => true, :discard_month => true) end + def test_datetime_select_discard_year_and_month_with_disabled_html_option + @post = Post.new + @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<input type="hidden" id="post_updated_at_1i" disabled="disabled" name="post[updated_at(1i)]" value="2004" />\n} + expected << %{<input type="hidden" id="post_updated_at_2i" disabled="disabled" name="post[updated_at(2i)]" value="6" />\n} + expected << %{<input type="hidden" id="post_updated_at_3i" disabled="disabled" name="post[updated_at(3i)]" value="15" />\n} + + expected << %{<select id="post_updated_at_4i" disabled="disabled" name="post[updated_at(4i)]">\n} + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + expected << " : " + expected << %{<select id="post_updated_at_5i" disabled="disabled" name="post[updated_at(5i)]">\n} + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, datetime_select("post", "updated_at", { :discard_year => true, :discard_month => true }, :disabled => true) + end + + def test_datetime_select_discard_hour + @post = Post.new + @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} + 1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } + expected << "</select>\n" + expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} + 1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) } + expected << "</select>\n" + expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} + 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, datetime_select("post", "updated_at", :discard_hour => true) + end + + def test_datetime_select_discard_minute + @post = Post.new + @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} + 1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } + expected << "</select>\n" + expected << %{<select id="post_updated_at_2i" name="post[updated_at(2i)]">\n} + 1.upto(12) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 6}>#{Date::MONTHNAMES[i]}</option>\n) } + expected << "</select>\n" + expected << %{<select id="post_updated_at_3i" name="post[updated_at(3i)]">\n} + 1.upto(31) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 15}>#{i}</option>\n) } + expected << "</select>\n" + + expected << " — " + + expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + expected << %{<input type="hidden" id="post_updated_at_5i" name="post[updated_at(5i)]" value="16" />\n} + + assert_dom_equal expected, datetime_select("post", "updated_at", :discard_minute => true) + end + def test_datetime_select_invalid_order @post = Post.new @post.updated_at = Time.local(2004, 6, 15, 15, 16, 35) diff --git a/actionpack/test/template/erb/tag_helper_test.rb b/actionpack/test/template/erb/tag_helper_test.rb new file mode 100644 index 0000000000..cc96a43901 --- /dev/null +++ b/actionpack/test/template/erb/tag_helper_test.rb @@ -0,0 +1,82 @@ +require "abstract_unit" + +module ERBTest + class ViewContext + mock_controller = Class.new do + include SharedTestRoutes.url_helpers + end + + include ActionView::Helpers::TagHelper + include ActionView::Helpers::JavaScriptHelper + include ActionView::Helpers::FormHelper + + attr_accessor :output_buffer + + def protect_against_forgery?() false end + + define_method(:controller) do + mock_controller.new + end + end + + class DeprecatedViewContext < ViewContext + include ActionView::Helpers::DeprecatedBlockHelpers + end + + module SharedTagHelpers + extend ActiveSupport::Testing::Declarative + + def render_content(start, inside) + template = block_helper(start, inside) + ActionView::Template::Handlers::Erubis.new(template).evaluate(context.new) + end + + test "percent equals works for content_tag and does not require parenthesis on method call" do + assert_equal "<div>Hello world</div>", render_content("content_tag :div", "Hello world") + end + + test "percent equals works for javascript_tag" do + expected_output = "<script type=\"text/javascript\">\n//<![CDATA[\nalert('Hello')\n//]]>\n</script>" + assert_equal expected_output, render_content("javascript_tag", "alert('Hello')") + end + + test "percent equals works for javascript_tag with options" do + expected_output = "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('Hello')\n//]]>\n</script>" + assert_equal expected_output, render_content("javascript_tag(:id => 'the_js_tag')", "alert('Hello')") + end + + test "percent equals works with form tags" do + expected_output = "<form action=\"foo\" method=\"post\">hello</form>" + assert_equal expected_output, render_content("form_tag('foo')", "<%= 'hello' %>") + end + + test "percent equals works with fieldset tags" do + expected_output = "<fieldset><legend>foo</legend>hello</fieldset>" + assert_equal expected_output, render_content("field_set_tag('foo')", "<%= 'hello' %>") + end + end + + class TagHelperTest < ActiveSupport::TestCase + def context + ViewContext + end + + def block_helper(str, rest) + "<%= #{str} do %>#{rest}<% end %>" + end + + include SharedTagHelpers + end + + class DeprecatedTagHelperTest < ActiveSupport::TestCase + def context + DeprecatedViewContext + end + + def block_helper(str, rest) + "<% __in_erb_template=true %><% #{str} do %>#{rest}<% end %>" + end + + include SharedTagHelpers + end +end
\ No newline at end of file diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 7b909fff82..7c5ccfd6ed 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -4,6 +4,10 @@ require 'controller/fake_models' class FormHelperTest < ActionView::TestCase tests ActionView::Helpers::FormHelper + def form_for(*) + @output_buffer = super + end + def setup super @@ -53,6 +57,7 @@ class FormHelperTest < ActionView::TestCase def @post.id_before_type_cast; 123; end def @post.to_param; '123'; end + @post.persisted = true @post.title = "Hello World" @post.author_name = "" @post.body = "Back to the hill and over it again!" @@ -529,7 +534,7 @@ class FormHelperTest < ActionView::TestCase def test_submit_with_object_as_new_record_and_locale_strings old_locale, I18n.locale = I18n.locale, :submit - def @post.new_record?() true; end + @post.persisted = false form_for(:post, @post) do |f| concat f.submit end @@ -589,9 +594,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for form_for(:post, @post) do |f| - f.fields_for(:comment, @post) do |c| + concat f.fields_for(:comment, @post) { |c| concat c.text_field(:title) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -604,9 +609,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_nested_collections form_for('post[]', @post) do |f| concat f.text_field(:title) - f.fields_for('comment[]', @comment) do |c| + concat f.fields_for('comment[]', @comment) { |c| concat c.text_field(:name) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -620,9 +625,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_index_and_parent_fields form_for('post', @post, :index => 1) do |c| concat c.text_field(:title) - c.fields_for('comment', @comment, :index => 1) do |r| + concat c.fields_for('comment', @comment, :index => 1) { |r| concat r.text_field(:name) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -634,10 +639,10 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_index_and_nested_fields_for - form_for(:post, @post, :index => 1) do |f| - f.fields_for(:comment, @post) do |c| + output_buffer = form_for(:post, @post, :index => 1) do |f| + concat f.fields_for(:comment, @post) { |c| concat c.text_field(:title) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -649,9 +654,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_index_on_both form_for(:post, @post, :index => 1) do |f| - f.fields_for(:comment, @post, :index => 5) do |c| + concat f.fields_for(:comment, @post, :index => 5) { |c| concat c.text_field(:title) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -663,9 +668,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_auto_index form_for("post[]", @post) do |f| - f.fields_for(:comment, @post) do |c| + concat f.fields_for(:comment, @post) { |c| concat c.text_field(:title) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -677,9 +682,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_index_radio_button form_for(:post, @post) do |f| - f.fields_for(:comment, @post, :index => 5) do |c| + concat f.fields_for(:comment, @post, :index => 5) { |c| concat c.radio_button(:title, "hello") - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -691,9 +696,9 @@ class FormHelperTest < ActionView::TestCase def test_nested_fields_for_with_auto_index_on_both form_for("post[]", @post) do |f| - f.fields_for("comment[]", @post) do |c| + concat f.fields_for("comment[]", @post) { |c| concat c.text_field(:title) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -704,16 +709,16 @@ class FormHelperTest < ActionView::TestCase end def test_nested_fields_for_with_index_and_auto_index - form_for("post[]", @post) do |f| - f.fields_for(:comment, @post, :index => 5) do |c| + output_buffer = form_for("post[]", @post) do |f| + concat f.fields_for(:comment, @post, :index => 5) { |c| concat c.text_field(:title) - end + } end - form_for(:post, @post, :index => 1) do |f| - f.fields_for("comment[]", @post) do |c| + output_buffer << form_for(:post, @post, :index => 1) do |f| + concat f.fields_for("comment[]", @post) { |c| concat c.text_field(:title) - end + } end expected = "<form action='http://www.example.com' method='post'>" + @@ -731,9 +736,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) - f.fields_for(:author) do |af| + concat f.fields_for(:author) { |af| concat af.text_field(:name) - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -758,9 +763,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) - f.fields_for(:author) do |af| + concat f.fields_for(:author) { |af| concat af.text_field(:name) - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -777,10 +782,10 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) - f.fields_for(:author) do |af| + concat f.fields_for(:author) { |af| concat af.hidden_field(:id) concat af.text_field(:name) - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -798,9 +803,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) @post.comments.each do |comment| - f.fields_for(:comments, comment) do |cf| + concat f.fields_for(:comments, comment) { |cf| concat cf.text_field(:name) - end + } end end @@ -821,10 +826,10 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) @post.comments.each do |comment| - f.fields_for(:comments, comment) do |cf| + concat f.fields_for(:comments, comment) { |cf| concat cf.hidden_field(:id) concat cf.text_field(:name) - end + } end end @@ -845,9 +850,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) @post.comments.each do |comment| - f.fields_for(:comments, comment) do |cf| + concat f.fields_for(:comments, comment) { |cf| concat cf.text_field(:name) - end + } end end @@ -866,9 +871,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) @post.comments.each do |comment| - f.fields_for(:comments, comment) do |cf| + concat f.fields_for(:comments, comment) { |cf| concat cf.text_field(:name) - end + } end end @@ -902,9 +907,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) - f.fields_for(:comments, @post.comments) do |cf| + concat f.fields_for(:comments, @post.comments) { |cf| concat cf.text_field(:name) - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -924,9 +929,9 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) - f.fields_for(:comments, comments) do |cf| + concat f.fields_for(:comments, comments) { |cf| concat cf.text_field(:name) - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -946,10 +951,10 @@ class FormHelperTest < ActionView::TestCase form_for(:post, @post) do |f| concat f.text_field(:title) - f.fields_for(:comments) do |cf| + concat f.fields_for(:comments) { |cf| concat cf.text_field(:name) yielded_comments << cf.object - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -967,9 +972,9 @@ class FormHelperTest < ActionView::TestCase @post.comments = [] form_for(:post, @post) do |f| - f.fields_for(:comments, Comment.new(321), :child_index => 'abc') do |cf| + concat f.fields_for(:comments, Comment.new(321), :child_index => 'abc') { |cf| concat cf.text_field(:name) - end + } end expected = '<form action="http://www.example.com" method="post">' + @@ -987,24 +992,24 @@ class FormHelperTest < ActionView::TestCase @post.tags[0].relevances = [] @post.tags[1].relevances = [] form_for(:post, @post) do |f| - f.fields_for(:comments, @post.comments[0]) do |cf| + concat f.fields_for(:comments, @post.comments[0]) { |cf| concat cf.text_field(:name) - cf.fields_for(:relevances, CommentRelevance.new(314)) do |crf| + concat cf.fields_for(:relevances, CommentRelevance.new(314)) { |crf| concat crf.text_field(:value) - end - end - f.fields_for(:tags, @post.tags[0]) do |tf| + } + } + concat f.fields_for(:tags, @post.tags[0]) { |tf| concat tf.text_field(:value) - tf.fields_for(:relevances, TagRelevance.new(3141)) do |trf| + concat tf.fields_for(:relevances, TagRelevance.new(3141)) { |trf| concat trf.text_field(:value) - end - end - f.fields_for('tags', @post.tags[1]) do |tf| + } + } + concat f.fields_for('tags', @post.tags[1]) { |tf| concat tf.text_field(:value) - tf.fields_for(:relevances, TagRelevance.new(31415)) do |trf| + concat tf.fields_for(:relevances, TagRelevance.new(31415)) { |trf| concat trf.text_field(:value) - end - end + } + } end expected = '<form action="http://www.example.com" method="post">' + @@ -1026,7 +1031,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for - fields_for(:post, @post) do |f| + output_buffer = fields_for(:post, @post) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1042,7 +1047,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_index - fields_for("post[]", @post) do |f| + output_buffer = fields_for("post[]", @post) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1058,7 +1063,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_nil_index_option_override - fields_for("post[]", @post, :index => nil) do |f| + output_buffer = fields_for("post[]", @post, :index => nil) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1074,7 +1079,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_index_option_override - fields_for("post[]", @post, :index => "abc") do |f| + output_buffer = fields_for("post[]", @post, :index => "abc") do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1090,7 +1095,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_without_object - fields_for(:post) do |f| + output_buffer = fields_for(:post) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1106,7 +1111,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_only_object - fields_for(@post) do |f| + output_buffer = fields_for(@post) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1122,7 +1127,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_object_with_bracketed_name - fields_for("author[post]", @post) do |f| + output_buffer = fields_for("author[post]", @post) do |f| concat f.label(:title) concat f.text_field(:title) end @@ -1133,7 +1138,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_object_with_bracketed_name_and_index - fields_for("author[post]", @post, :index => 1) do |f| + output_buffer = fields_for("author[post]", @post, :index => 1) do |f| concat f.label(:title) concat f.text_field(:title) end @@ -1152,9 +1157,9 @@ class FormHelperTest < ActionView::TestCase concat post_form.text_field(:title) concat post_form.text_area(:body) - fields_for(:parent_post, @post) do |parent_fields| + concat fields_for(:parent_post, @post) { |parent_fields| concat parent_fields.check_box(:secret) - end + } end expected = @@ -1173,9 +1178,9 @@ class FormHelperTest < ActionView::TestCase concat post_form.text_field(:title) concat post_form.text_area(:body) - post_form.fields_for(@comment) do |comment_fields| + concat post_form.fields_for(@comment) { |comment_fields| concat comment_fields.text_field(:name) - end + } end expected = @@ -1272,7 +1277,7 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_labelled_builder - fields_for(:post, @post, :builder => LabelledFormBuilder) do |f| + output_buffer = fields_for(:post, @post, :builder => LabelledFormBuilder) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -1363,7 +1368,7 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_new_object post = Post.new - post.new_record = true + post.persisted = false def post.id() nil end form_for(post) do |f| end @@ -1373,9 +1378,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_existing_object_in_list - @post.new_record = false @comment.save - form_for([@post, @comment]) {} expected = %(<form action="#{comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="_method" type="hidden" value="put" /></div></form>) @@ -1383,8 +1386,6 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_new_object_in_list - @post.new_record = false - form_for([@post, @comment]) {} expected = %(<form action="#{comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>) @@ -1392,9 +1393,7 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_existing_object_and_namespace_in_list - @post.new_record = false @comment.save - form_for([:admin, @post, @comment]) {} expected = %(<form action="#{admin_comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="_method" type="hidden" value="put" /></div></form>) @@ -1402,8 +1401,6 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_new_object_and_namespace_in_list - @post.new_record = false - form_for([:admin, @post, @comment]) {} expected = %(<form action="#{admin_comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>) diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index aa40e46aa8..5799e3db53 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -293,7 +293,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.category = "<mus>" - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.select(:category, %w( abe <mus> hest)) end @@ -307,7 +307,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.category = "<mus>" - fields_for :post, @post, :index => 108 do |f| + output_buffer = fields_for :post, @post, :index => 108 do |f| concat f.select(:category, %w( abe <mus> hest)) end @@ -322,7 +322,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.category = "<mus>" def @post.to_param; 108; end - fields_for "post[]", @post do |f| + output_buffer = fields_for "post[]", @post do |f| concat f.select(:category, %w( abe <mus> hest)) end @@ -336,7 +336,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new options = "<option value=\"abe\">abe</option><option value=\"mus\">mus</option><option value=\"hest\">hest</option>" - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.select(:category, options, :prompt => 'The prompt') end @@ -462,7 +462,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.author_name = "Babe" - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.collection_select(:author_name, dummy_posts, :author_name, :author_name) end @@ -476,7 +476,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.author_name = "Babe" - fields_for :post, @post, :index => 815 do |f| + output_buffer = fields_for :post, @post, :index => 815 do |f| concat f.collection_select(:author_name, dummy_posts, :author_name, :author_name) end @@ -491,7 +491,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post.author_name = "Babe" def @post.to_param; 815; end - fields_for "post[]", @post do |f| + output_buffer = fields_for "post[]", @post do |f| concat f.collection_select(:author_name, dummy_posts, :author_name, :author_name) end @@ -570,7 +570,7 @@ class FormOptionsHelperTest < ActionView::TestCase def test_time_zone_select_under_fields_for @firm = Firm.new("D") - fields_for :firm, @firm do |f| + output_buffer = fields_for :firm, @firm do |f| concat f.time_zone_select(:time_zone) end @@ -589,7 +589,7 @@ class FormOptionsHelperTest < ActionView::TestCase def test_time_zone_select_under_fields_for_with_index @firm = Firm.new("D") - fields_for :firm, @firm, :index => 305 do |f| + output_buffer = fields_for :firm, @firm, :index => 305 do |f| concat f.time_zone_select(:time_zone) end @@ -609,7 +609,7 @@ class FormOptionsHelperTest < ActionView::TestCase @firm = Firm.new("D") def @firm.to_param; 305; end - fields_for "firm[]", @firm do |f| + output_buffer = fields_for "firm[]", @firm do |f| concat f.time_zone_select(:time_zone) end @@ -787,7 +787,7 @@ class FormOptionsHelperTest < ActionView::TestCase @post = Post.new @post.origin = 'dk' - fields_for :post, @post do |f| + output_buffer = fields_for :post, @post do |f| concat f.grouped_collection_select("origin", @continents, :countries, :continent_name, :country_id, :country_name) end diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 6ac5df1ea9..868a35c476 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -3,17 +3,16 @@ require 'abstract_unit' class FormTagHelperTest < ActionView::TestCase tests ActionView::Helpers::FormTagHelper - include ActiveSupport::Configurable - DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG + # include ActiveSupport::Configurable + # DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG def setup super - @controller = Class.new do + @controller = Class.new(BasicController) do def url_for(options) "http://www.example.com" end - end - @controller = @controller.new + end.new end VALID_HTML_ID = /^[A-Za-z][-_:.A-Za-z0-9]*$/ # see http://www.w3.org/TR/html4/types.html#type-name @@ -60,16 +59,14 @@ class FormTagHelperTest < ActionView::TestCase end def test_form_tag_with_block_in_erb - __in_erb_template = '' - form_tag("http://example.com") { concat "Hello world!" } + output_buffer = form_tag("http://example.com") { concat "Hello world!" } expected = %(<form action="http://example.com" method="post">Hello world!</form>) assert_dom_equal expected, output_buffer end def test_form_tag_with_block_and_method_in_erb - __in_erb_template = '' - form_tag("http://example.com", :method => :put) { concat "Hello world!" } + output_buffer = form_tag("http://example.com", :method => :put) { concat "Hello world!" } expected = %(<form action="http://example.com" method="post"><div style='margin:0;padding:0;display:inline'><input type="hidden" name="_method" value="put" /></div>Hello world!</form>) assert_dom_equal expected, output_buffer @@ -127,19 +124,19 @@ class FormTagHelperTest < ActionView::TestCase end def test_select_tag - actual = select_tag "people", "<option>david</option>" + actual = select_tag "people", "<option>david</option>".html_safe expected = %(<select id="people" name="people"><option>david</option></select>) assert_dom_equal expected, actual end def test_select_tag_with_multiple - actual = select_tag "colors", "<option>Red</option><option>Blue</option><option>Green</option>", :multiple => :true + actual = select_tag "colors", "<option>Red</option><option>Blue</option><option>Green</option>".html_safe, :multiple => :true expected = %(<select id="colors" multiple="multiple" name="colors"><option>Red</option><option>Blue</option><option>Green</option></select>) assert_dom_equal expected, actual end def test_select_tag_disabled - actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>", :disabled => :true + actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :disabled => :true expected = %(<select id="places" disabled="disabled" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>) assert_dom_equal expected, actual end @@ -150,13 +147,13 @@ class FormTagHelperTest < ActionView::TestCase end def test_select_tag_with_include_blank - actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>", :include_blank => true + actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :include_blank => true expected = %(<select id="places" name="places"><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>) assert_dom_equal expected, actual end def test_select_tag_with_include_blank_with_string - actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>", :include_blank => "string" + actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :include_blank => "string" expected = %(<select id="places" name="places"><option value="">string</option><option>Home</option><option>Work</option><option>Pub</option></select>) assert_dom_equal expected, actual end @@ -282,9 +279,9 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes") assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil) assert_dom_equal %(<input type="checkbox" />), tag(:input, :type => "checkbox", :checked => false) - assert_dom_equal %(<select id="people" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => true) - assert_dom_equal %(<select id="people_" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people[]", "<option>david</option>", :multiple => true) - assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>", :multiple => nil) + assert_dom_equal %(<select id="people" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people", "<option>david</option>".html_safe, :multiple => true) + assert_dom_equal %(<select id="people_" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people[]", "<option>david</option>".html_safe, :multiple => true) + assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>".html_safe, :multiple => nil) end def test_stringify_symbol_keys @@ -333,26 +330,22 @@ class FormTagHelperTest < ActionView::TestCase end def test_field_set_tag_in_erb - __in_erb_template = '' - field_set_tag("Your details") { concat "Hello world!" } + output_buffer = field_set_tag("Your details") { concat "Hello world!" } expected = %(<fieldset><legend>Your details</legend>Hello world!</fieldset>) assert_dom_equal expected, output_buffer - self.output_buffer = ''.html_safe - field_set_tag { concat "Hello world!" } + output_buffer = field_set_tag { concat "Hello world!" } expected = %(<fieldset>Hello world!</fieldset>) assert_dom_equal expected, output_buffer - self.output_buffer = ''.html_safe - field_set_tag('') { concat "Hello world!" } + output_buffer = field_set_tag('') { concat "Hello world!" } expected = %(<fieldset>Hello world!</fieldset>) assert_dom_equal expected, output_buffer - self.output_buffer = ''.html_safe - field_set_tag('', :class => 'format') { concat "Hello world!" } + output_buffer = field_set_tag('', :class => 'format') { concat "Hello world!" } expected = %(<fieldset class="format">Hello world!</fieldset>) assert_dom_equal expected, output_buffer diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index b3e7abc387..f49b763881 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -7,8 +7,9 @@ class JavaScriptHelperTest < ActionView::TestCase attr_accessor :formats, :output_buffer - def reset_formats(format) - @format = format + def update_details(details) + @details = details + yield if block_given? end def setup @@ -73,18 +74,6 @@ class JavaScriptHelperTest < ActionView::TestCase javascript_tag("alert('hello')", :id => "the_js_tag") end - def test_javascript_tag_with_block_in_erb - __in_erb_template = '' - javascript_tag { concat "alert('hello')" } - assert_dom_equal "<script type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", output_buffer - end - - def test_javascript_tag_with_block_and_options_in_erb - __in_erb_template = '' - javascript_tag(:id => "the_js_tag") { concat "alert('hello')" } - assert_dom_equal "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", output_buffer - end - def test_javascript_cdata_section assert_dom_equal "\n//<![CDATA[\nalert('hello')\n//]]>\n", javascript_cdata_section("alert('hello')") end diff --git a/actionpack/test/template/subscriber_test.rb b/actionpack/test/template/log_subscriber_test.rb index 8bacab7088..5076dfa70f 100644 --- a/actionpack/test/template/subscriber_test.rb +++ b/actionpack/test/template/log_subscriber_test.rb @@ -1,22 +1,22 @@ require "abstract_unit" -require "rails/subscriber/test_helper" -require "action_view/railties/subscriber" +require "rails/log_subscriber/test_helper" +require "action_view/railties/log_subscriber" require "controller/fake_models" -class AVSubscriberTest < ActiveSupport::TestCase - include Rails::Subscriber::TestHelper +class AVLogSubscriberTest < ActiveSupport::TestCase + include Rails::LogSubscriber::TestHelper def setup + super @old_logger = ActionController::Base.logger @view = ActionView::Base.new(ActionController::Base.view_paths, {}) Rails.stubs(:root).returns(File.expand_path(FIXTURE_LOAD_PATH)) - Rails::Subscriber.add(:action_view, ActionView::Railties::Subscriber.new) - super + Rails::LogSubscriber.add(:action_view, ActionView::Railties::LogSubscriber.new) end def teardown super - Rails::Subscriber.subscribers.clear + Rails::LogSubscriber.log_subscribers.clear ActionController::Base.logger = @old_logger end diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb new file mode 100644 index 0000000000..697ebc694a --- /dev/null +++ b/actionpack/test/template/lookup_context_test.rb @@ -0,0 +1,165 @@ +require "abstract_unit" +require "abstract_controller/rendering" + +ActionView::LookupContext::DetailsKey.class_eval do + def self.details_keys + @details_keys + end +end + +class LookupContextTest < ActiveSupport::TestCase + def setup + @lookup_context = ActionView::LookupContext.new(FIXTURE_LOAD_PATH, {}) + end + + def teardown + I18n.locale = :en + ActionView::LookupContext::DetailsKey.details_keys.clear + end + + test "process view paths on initialization" do + assert_kind_of ActionView::PathSet, @lookup_context.view_paths + end + + test "normalizes details on initialization" do + formats = Mime::SET + [nil] + locale = [I18n.locale, nil] + assert_equal Hash[:formats => formats, :locale => locale], @lookup_context.details + end + + test "allows me to set details" do + @lookup_context.details = { :formats => [:html], :locale => :pt } + assert_equal Hash[:formats => [:html, nil], :locale => [:pt, nil]], @lookup_context.details + end + + test "does not allow details to be modified in place" do + assert @lookup_context.details.frozen? + end + + test "allows me to update an specific detail" do + @lookup_context.update_details(:locale => :pt) + assert_equal :pt, I18n.locale + formats = Mime::SET + [nil] + locale = [I18n.locale, nil] + assert_equal Hash[:formats => formats, :locale => locale], @lookup_context.details + end + + test "allows me to change some details to execute an specific block of code" do + formats = Mime::SET + [nil] + @lookup_context.update_details(:locale => :pt) do + assert_equal Hash[:formats => formats, :locale => [:pt, nil]], @lookup_context.details + end + assert_equal Hash[:formats => formats, :locale => [:en, nil]], @lookup_context.details + end + + test "provides getters and setters for formats" do + @lookup_context.formats = :html + assert_equal [:html], @lookup_context.formats + end + + test "handles */* formats" do + @lookup_context.formats = [:"*/*"] + assert_equal Mime::SET, @lookup_context.formats + end + + test "provides getters and setters for locale" do + @lookup_context.locale = :pt + assert_equal :pt, @lookup_context.locale + end + + test "changing lookup_context locale, changes I18n.locale" do + @lookup_context.locale = :pt + assert_equal :pt, I18n.locale + end + + test "delegates changing the locale to the I18n configuration object if it contains a lookup_context object" do + begin + I18n.config = AbstractController::I18nProxy.new(I18n.config, @lookup_context) + @lookup_context.locale = :pt + assert_equal :pt, I18n.locale + assert_equal :pt, @lookup_context.locale + ensure + I18n.config = I18n.config.i18n_config + end + + assert_equal :pt, I18n.locale + end + + test "find templates using the given view paths and configured details" do + template = @lookup_context.find("hello_world", "test") + assert_equal "Hello world!", template.source + + @lookup_context.locale = :da + template = @lookup_context.find("hello_world", "test") + assert_equal "Hey verden", template.source + end + + test "adds fallbacks to view paths when required" do + assert_equal 1, @lookup_context.view_paths.size + + @lookup_context.with_fallbacks do + assert_equal 3, @lookup_context.view_paths.size + assert @lookup_context.view_paths.include?(ActionView::FileSystemResolver.new("")) + assert @lookup_context.view_paths.include?(ActionView::FileSystemResolver.new("/")) + end + end + + test "add fallbacks just once in nested fallbacks calls" do + @lookup_context.with_fallbacks do + @lookup_context.with_fallbacks do + assert_equal 3, @lookup_context.view_paths.size + end + end + end + + test "generates a new details key for each details hash" do + keys = [] + keys << @lookup_context.details_key + assert_equal 1, keys.uniq.size + + @lookup_context.locale = :da + keys << @lookup_context.details_key + assert_equal 2, keys.uniq.size + + @lookup_context.locale = :en + keys << @lookup_context.details_key + assert_equal 2, keys.uniq.size + + @lookup_context.formats = :html + keys << @lookup_context.details_key + assert_equal 3, keys.uniq.size + + @lookup_context.formats = nil + keys << @lookup_context.details_key + assert_equal 3, keys.uniq.size + end + + test "gives the key forward to the resolver, so it can be used as cache key" do + @lookup_context.view_paths = ActionView::FixtureResolver.new("test/_foo.erb" => "Foo") + template = @lookup_context.find("foo", "test", true) + assert_equal "Foo", template.source + + # Now we are going to change the template, but it won't change the returned template + # since we will hit the cache. + @lookup_context.view_paths.first.hash["test/_foo.erb"] = "Bar" + template = @lookup_context.find("foo", "test", true) + assert_equal "Foo", template.source + + # This time we will change the locale. The updated template should be picked since + # lookup_context generated a new key after we changed the locale. + @lookup_context.locale = :da + template = @lookup_context.find("foo", "test", true) + assert_equal "Bar", template.source + + # Now we will change back the locale and it will still pick the old template. + # This is expected because lookup_context will reuse the previous key for :en locale. + @lookup_context.locale = :en + template = @lookup_context.find("foo", "test", true) + assert_equal "Foo", template.source + + # Finally, we can expire the cache. And the expected template will be used. + @lookup_context.view_paths.first.clear_cache + template = @lookup_context.find("foo", "test", true) + assert_equal "Bar", template.source + end +end
\ No newline at end of file diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb index d95bdc2b90..619293dc43 100644 --- a/actionpack/test/template/prototype_helper_test.rb +++ b/actionpack/test/template/prototype_helper_test.rb @@ -4,6 +4,7 @@ require 'active_model' class Bunny < Struct.new(:Bunny, :id) extend ActiveModel::Naming include ActiveModel::Conversion + def to_key() id ? [id] : nil end end class Author @@ -11,6 +12,7 @@ class Author include ActiveModel::Conversion attr_reader :id + def to_key() id ? [id] : nil end def save; @id = 1 end def new_record?; @id.nil? end def name @@ -23,6 +25,7 @@ class Article include ActiveModel::Conversion attr_reader :id attr_reader :author_id + def to_key() id ? [id] : nil end def save; @id = 1; @author_id = 1 end def new_record?; @id.nil? end def name @@ -36,8 +39,9 @@ class Author::Nested < Author; end class PrototypeHelperBaseTest < ActionView::TestCase attr_accessor :formats, :output_buffer - def reset_formats(format) - @format = format + def update_details(details) + @details = details + yield if block_given? end def setup diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb index 1cd18c0692..74d7bba4fe 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionpack/test/template/record_tag_helper_test.rb @@ -18,6 +18,7 @@ class RecordTagHelperTest < ActionView::TestCase def setup super @post = Post.new + @post.persisted = true end def test_content_tag_for diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index fdf3db1cdb..cea8ab1bce 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -33,17 +33,17 @@ module RenderTestCases end def test_render_file_with_localization - old_locale, I18n.locale = I18n.locale, :da + old_locale, @view.locale = @view.locale, :da assert_equal "Hey verden", @view.render(:file => "test/hello_world") ensure - I18n.locale = old_locale + @view.locale = old_locale end def test_render_file_with_dashed_locale - old_locale, I18n.locale = I18n.locale, :"pt-BR" + old_locale, @view.locale = @view.locale, :"pt-BR" assert_equal "Ola mundo", @view.render(:file => "test/hello_world") ensure - I18n.locale = old_locale + @view.locale = old_locale end def test_render_file_at_top_level @@ -108,7 +108,7 @@ module RenderTestCases @view.render(:partial => "test/raise") flunk "Render did not raise Template::Error" rescue ActionView::Template::Error => e - assert_match "undefined local variable or method `doesnt_exist'", e.message + assert_match %r!method.*doesnt_exist!, e.message assert_equal "", e.sub_template_message assert_equal "1", e.line_number assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name @@ -118,7 +118,7 @@ module RenderTestCases @view.render(:file => "test/sub_template_raise") flunk "Render did not raise Template::Error" rescue ActionView::Template::Error => e - assert_match "undefined local variable or method `doesnt_exist'", e.message + assert_match %r!method.*doesnt_exist!, e.message assert_equal "Trace of template inclusion: #{File.expand_path("#{FIXTURE_LOAD_PATH}/test/sub_template_raise.html.erb")}", e.sub_template_message assert_equal "1", e.line_number assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name @@ -233,6 +233,11 @@ module RenderTestCases @view.render(:file => "test/nested_layout.erb", :layout => "layouts/yield") end + def test_render_with_file_in_layout + assert_equal %(\n<title>title</title>\n\n), + @view.render(:file => "test/layout_render_file.erb") + end + if '1.9'.respond_to?(:force_encoding) def test_render_utf8_template_with_magic_comment with_external_encoding Encoding::ASCII_8BIT do @@ -265,7 +270,7 @@ class CachedViewRenderTest < ActiveSupport::TestCase # Ensure view path cache is primed def setup view_paths = ActionController::Base.view_paths - assert_equal ActionView::FileSystemResolverWithFallback, view_paths.first.class + assert_equal ActionView::FileSystemResolver, view_paths.first.class setup_view(view_paths) end end @@ -276,9 +281,9 @@ class LazyViewRenderTest < ActiveSupport::TestCase # Test the same thing as above, but make sure the view path # is not eager loaded def setup - path = ActionView::FileSystemResolverWithFallback.new(FIXTURE_LOAD_PATH) + path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH) view_paths = ActionView::Base.process_view_paths(path) - assert_equal ActionView::FileSystemResolverWithFallback, view_paths.first.class + assert_equal ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH), view_paths.first setup_view(view_paths) end end diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb index 433f6514cf..256d9bdcde 100644 --- a/actionpack/test/template/tag_helper_test.rb +++ b/actionpack/test/template/tag_helper_test.rb @@ -37,18 +37,18 @@ class TagHelperTest < ActionView::TestCase assert content_tag("a", "Create", "href" => "create").html_safe? assert_equal content_tag("a", "Create", "href" => "create"), content_tag("a", "Create", :href => "create") + assert_equal "<p><script>evil_js</script></p>", + content_tag(:p, '<script>evil_js</script>') end def test_content_tag_with_block_in_erb - __in_erb_template = '' - content_tag(:div) { concat "Hello world!" } - assert_dom_equal "<div>Hello world!</div>", output_buffer + buffer = content_tag(:div) { concat "Hello world!" } + assert_dom_equal "<div>Hello world!</div>", buffer end def test_content_tag_with_block_and_options_in_erb - __in_erb_template = '' - content_tag(:div, :class => "green") { concat "Hello world!" } - assert_dom_equal %(<div class="green">Hello world!</div>), output_buffer + buffer = content_tag(:div, :class => "green") { concat "Hello world!" } + assert_dom_equal %(<div class="green">Hello world!</div>), buffer end def test_content_tag_with_block_and_options_out_of_erb @@ -66,10 +66,10 @@ class TagHelperTest < ActionView::TestCase output_buffer end + # TAG TODO: Move this into a real template def test_content_tag_nested_in_content_tag_in_erb - __in_erb_template = true - content_tag("p") { concat content_tag("b", "Hello") } - assert_equal '<p><b>Hello</b></p>', output_buffer + buffer = content_tag("p") { concat content_tag("b", "Hello") } + assert_equal '<p><b>Hello</b></p>', buffer end def test_content_tag_with_escaped_array_class diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index be2c6b3108..195a6ea3ae 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -89,16 +89,23 @@ module ActionView end test "helper class that is being tested is always included in view instance" do - self.class.helper_class.module_eval do - def render_from_helper - render :partial => 'customer', :collection => @customers + # This ensure is a hidious hack to deal with these tests bleeding + # methods between eachother + begin + self.class.helper_class.module_eval do + def render_from_helper + render :partial => 'customer', :collection => @customers + end end - end - TestController.stubs(:controller_path).returns('test') + TestController.stubs(:controller_path).returns('test') - @customers = [stub(:name => 'Eloy'), stub(:name => 'Manfred')] - assert_match /Hello: EloyHello: Manfred/, render(:partial => 'test/from_helper') + @customers = [stub(:name => 'Eloy'), stub(:name => 'Manfred')] + assert_match /Hello: EloyHello: Manfred/, render(:partial => 'test/from_helper') + + ensure + self.class.helper_class.send(:remove_method, :render_from_helper) + end end test "no additional helpers should shared across test cases" do @@ -107,7 +114,7 @@ module ActionView end test "is able to use routes" do - controller.request.assign_parameters('foo', 'index') + controller.request.assign_parameters(@router, 'foo', 'index') assert_equal '/foo', url_for assert_equal '/bar', url_for(:controller => 'bar') end @@ -147,10 +154,16 @@ module ActionView end test "is able to make methods available to the view" do - _helpers.module_eval do - def render_from_helper; from_test_case end + # This ensure is a hidious hack to deal with these tests bleeding + # methods between eachother + begin + _helpers.module_eval do + def render_from_helper; from_test_case end + end + assert_equal 'Word!', render(:partial => 'test/from_helper') + ensure + _helpers.send(:remove_method, :render_from_helper) end - assert_equal 'Word!', render(:partial => 'test/from_helper') end def from_test_case; 'Word!'; end diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb index 4b73c44f7e..699fb2f5bc 100644 --- a/actionpack/test/template/translation_helper_test.rb +++ b/actionpack/test/template/translation_helper_test.rb @@ -18,6 +18,11 @@ class TranslationHelperTest < ActiveSupport::TestCase assert_equal expected, translate(:foo) end + def test_translation_of_an_array + I18n.expects(:translate).with(["foo", "bar"], :raise => true).returns(["foo", "bar"]) + assert_equal ["foo", "bar"], translate(["foo", "bar"]) + end + def test_delegates_localize_to_i18n @time = Time.utc(2008, 7, 8, 12, 18, 38) I18n.expects(:localize).with(@time) diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index e904e88f49..165cb655da 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -1,22 +1,21 @@ # encoding: utf-8 require 'abstract_unit' +require 'active_support/ordered_options' require 'controller/fake_controllers' -RequestMock = Struct.new("Request", :request_uri, :protocol, :host_with_port, :env) - class UrlHelperTest < ActionView::TestCase - include ActiveSupport::Configurable - DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG def setup super - @controller = Class.new do - attr_accessor :url, :request + @controller = Class.new(BasicController) do + attr_accessor :url def url_for(options) url end end + @controller = @controller.new + @request = @controller.request = ActionDispatch::TestRequest.new @controller.url = "http://www.example.com" end @@ -38,12 +37,13 @@ class UrlHelperTest < ActionView::TestCase end def test_url_for_with_back - @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'}) + @request.env['HTTP_REFERER'] = 'http://www.example.com/referer' assert_equal 'http://www.example.com/referer', url_for(:back) end def test_url_for_with_back_and_no_referer - @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {}) + @request.env['HOST_NAME'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' assert_equal 'javascript:history.back()', url_for(:back) end @@ -122,7 +122,6 @@ class UrlHelperTest < ActionView::TestCase url = {:controller => 'weblog', :action => 'show'} @controller = ActionController::Base.new @controller.request = ActionController::TestRequest.new - @controller.url = ActionController::UrlRewriter.new(@controller.request, url) assert_dom_equal(%q{<a href="/weblog/show">Test Link</a>}, link_to('Test Link', url)) end @@ -131,7 +130,6 @@ class UrlHelperTest < ActionView::TestCase url = {:controller => 'weblog', :action => 'show', :host => 'www.example.com'} @controller = ActionController::Base.new @controller.request = ActionController::TestRequest.new - @controller.url = ActionController::UrlRewriter.new(@controller.request, url) assert_dom_equal(%q{<a href="http://www.example.com/weblog/show">Test Link</a>}, link_to('Test Link', url)) end @@ -144,22 +142,28 @@ class UrlHelperTest < ActionView::TestCase end def test_link_tag_with_back - @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'}) + @request.env['HOST_NAME'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' + @request.env['HTTP_REFERER'] = 'http://www.example.com/referer' assert_dom_equal "<a href=\"http://www.example.com/referer\">go back</a>", link_to('go back', :back) end def test_link_tag_with_back_and_no_referer - @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {}) + @request.env['HOST_NAME'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' assert_dom_equal "<a href=\"javascript:history.back()\">go back</a>", link_to('go back', :back) end def test_link_tag_with_back - @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'}) + @request.env['HOST_NAME'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' + @request.env['HTTP_REFERER'] = 'http://www.example.com/referer' assert_dom_equal "<a href=\"http://www.example.com/referer\">go back</a>", link_to('go back', :back) end def test_link_tag_with_back_and_no_referer - @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {}) + @request.env['HOST_NAME'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' assert_dom_equal "<a href=\"javascript:history.back()\">go back</a>", link_to('go back', :back) end @@ -263,55 +267,60 @@ class UrlHelperTest < ActionView::TestCase end def test_current_page_with_simple_url - @controller.request = RequestMock.new("http://www.example.com/weblog/show") + @request.env['HTTP_HOST'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' @controller.url = "http://www.example.com/weblog/show" assert current_page?({ :action => "show", :controller => "weblog" }) assert current_page?("http://www.example.com/weblog/show") end def test_current_page_ignoring_params - @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") + @request.env['HTTP_HOST'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' + @request.env['QUERY_STRING'] = 'order=desc&page=1' @controller.url = "http://www.example.com/weblog/show?order=desc&page=1" assert current_page?({ :action => "show", :controller => "weblog" }) assert current_page?("http://www.example.com/weblog/show") end def test_current_page_with_params_that_match - @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") + @request.env['HTTP_HOST'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' + @request.env['QUERY_STRING'] = 'order=desc&page=1' @controller.url = "http://www.example.com/weblog/show?order=desc&page=1" assert current_page?({ :action => "show", :controller => "weblog", :order => "desc", :page => "1" }) assert current_page?("http://www.example.com/weblog/show?order=desc&page=1") end def test_link_unless_current - @controller.request = RequestMock.new("http://www.example.com/weblog/show") + @request.env['HTTP_HOST'] = 'www.example.com' + @request.env['PATH_INFO'] = '/weblog/show' @controller.url = "http://www.example.com/weblog/show" assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show") - @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc") + @request.env['QUERY_STRING'] = 'order=desc' @controller.url = "http://www.example.com/weblog/show" assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show") - @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") + @request.env['QUERY_STRING'] = 'order=desc&page=1' @controller.url = "http://www.example.com/weblog/show?order=desc&page=1" assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog", :order=>'desc', :page=>'1' }) assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=1") assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=1") - @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc") + @request.env['QUERY_STRING'] = 'order=desc' @controller.url = "http://www.example.com/weblog/show?order=asc" assert_equal "<a href=\"http://www.example.com/weblog/show?order=asc\">Showing</a>", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) assert_equal "<a href=\"http://www.example.com/weblog/show?order=asc\">Showing</a>", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=asc") - @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") + @request.env['QUERY_STRING'] = 'order=desc&page=1' @controller.url = "http://www.example.com/weblog/show?order=desc&page=2" assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&page=2\">Showing</a>", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&page=2\">Showing</a>", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=2") - - @controller.request = RequestMock.new("http://www.example.com/weblog/show") + @request.env['QUERY_STRING'] = '' @controller.url = "http://www.example.com/weblog/list" assert_equal "<a href=\"http://www.example.com/weblog/list\">Listing</a>", link_to_unless_current("Listing", :action => "list", :controller => "weblog") @@ -346,7 +355,7 @@ class UrlHelperTest < ActionView::TestCase end def test_mail_to_with_img - assert_dom_equal %(<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>), mail_to('feedback@example.com', '<img src="/feedback.png" />') + 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 @@ -464,8 +473,6 @@ end class LinkToUnlessCurrentWithControllerTest < ActionController::TestCase def setup super - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new @controller = TasksController.new end @@ -499,14 +506,14 @@ end class Workshop extend ActiveModel::Naming include ActiveModel::Conversion - attr_accessor :id, :new_record + attr_accessor :id - def initialize(id, new_record) - @id, @new_record = id, new_record + def initialize(id) + @id = id end - def new_record? - @new_record + def persisted? + id.present? end def to_s @@ -517,14 +524,14 @@ end class Session extend ActiveModel::Naming include ActiveModel::Conversion - attr_accessor :id, :workshop_id, :new_record + attr_accessor :id, :workshop_id - def initialize(id, new_record) - @id, @new_record = id, new_record + def initialize(id) + @id = id end - def new_record? - @new_record + def persisted? + id.present? end def to_s @@ -534,12 +541,12 @@ end class WorkshopsController < ActionController::Base def index - @workshop = Workshop.new(1, true) + @workshop = Workshop.new(nil) render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" end def show - @workshop = Workshop.new(params[:id], false) + @workshop = Workshop.new(params[:id]) render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>" end @@ -548,14 +555,14 @@ end class SessionsController < ActionController::Base def index - @workshop = Workshop.new(params[:workshop_id], false) - @session = Session.new(1, true) + @workshop = Workshop.new(params[:workshop_id]) + @session = Session.new(nil) render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>" end def show - @workshop = Workshop.new(params[:workshop_id], false) - @session = Session.new(params[:id], false) + @workshop = Workshop.new(params[:workshop_id]) + @session = Session.new(params[:id]) render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>" end @@ -565,8 +572,7 @@ end class PolymorphicControllerTest < ActionController::TestCase def setup super - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new + @response = ActionController::TestResponse.new end def test_new_resource diff --git a/activemodel/Rakefile b/activemodel/Rakefile index 14c02f183f..d4a00c5073 100755 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -1,19 +1,11 @@ dir = File.dirname(__FILE__) -require "#{dir}/lib/active_model/version" - -PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' -PKG_NAME = 'activemodel' -PKG_VERSION = ActiveModel::VERSION::STRING + PKG_BUILD -PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" -RELEASE_NAME = "REL #{PKG_VERSION}" - require 'rake/testtask' task :default => :test Rake::TestTask.new do |t| - t.libs << "#{dir}/test" + t.libs << "test" t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort t.warning = true end diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index b4fbe26d97..eb9ef7b7c0 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -1,9 +1,11 @@ +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip + Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'activemodel' - s.version = '3.0.0.beta1' - s.summary = "A toolkit for building other modeling frameworks like ActiveRecord" - s.description = %q{Extracts common modeling concerns from ActiveRecord to share between similar frameworks like ActiveResource.} + s.version = version + s.summary = 'A toolkit for building modeling frameworks (part of Rails).' + s.description = 'A toolkit for building modeling frameworks like Active Record and Active Resource. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.' s.required_ruby_version = '>= 1.8.7' s.author = "David Heinemeier Hansson" @@ -13,7 +15,7 @@ Gem::Specification.new do |s| s.has_rdoc = true - s.add_dependency('activesupport', '= 3.0.0.beta1') + s.add_dependency('activesupport', version) s.require_path = 'lib' s.files = Dir["CHANGELOG", "MIT-LICENSE", "README", "lib/**/*"] diff --git a/activemodel/examples/validations.rb b/activemodel/examples/validations.rb index b039897ea5..0b2706076f 100644 --- a/activemodel/examples/validations.rb +++ b/activemodel/examples/validations.rb @@ -16,7 +16,7 @@ class Person @persisted = true end - def new_record? + def persisted? @persisted end end diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index f7fb66bdfc..c1334069fa 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -21,7 +21,6 @@ module ActiveModel # A minimal implementation could be: # # class Person - # # include ActiveModel::AttributeMethods # # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!' @@ -44,9 +43,13 @@ module ActiveModel # def reset_attribute_to_default!(attr) # send("#{attr}=", "Default Name") # end - # # end - # + # + # Please notice that whenever you include ActiveModel::AtributeMethods in your class, + # it requires you to implement a <tt>attributes</tt> methods which returns a hash with + # each attribute name in your model as hash key and the attribute value as hash value. + # Hash keys must be a string. + # module AttributeMethods extend ActiveSupport::Concern @@ -85,7 +88,7 @@ module ActiveModel # AttributePerson.inheritance_column # # => 'address_id' def define_attr_method(name, value=nil, &block) - sing = metaclass + sing = singleton_class sing.send :alias_method, "original_#{name}", name if block_given? sing.send :define_method, name, &block diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index c14a07c7dc..585c20dcdf 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -1,19 +1,44 @@ module ActiveModel - # If your object is already designed to implement all of the Active Model featurs - # include this module in your Class. - # - # class MyClass + # Handle default conversions: to_model, to_key and to_param. + # + # == Example + # + # Let's take for example this non persisted object. + # + # class ContactMessage # include ActiveModel::Conversion + # + # # ContactMessage are never persisted in the DB + # def persisted? + # false + # end # end - # - # Returns self to the <tt>:to_model</tt> method. - # - # If your model does not act like an Active Model object, then you should define - # <tt>:to_model</tt> yourself returning a proxy object that wraps your object - # with Active Model compliant methods. + # + # cm = ContactMessage.new + # cm.to_model == self #=> true + # cm.to_key #=> nil + # cm.to_param #=> nil + # module Conversion + # If your object is already designed to implement all of the Active Model you can use + # the default to_model implementation, which simply returns self. + # + # If your model does not act like an Active Model object, then you should define + # <tt>:to_model</tt> yourself returning a proxy object that wraps your object + # with Active Model compliant methods. def to_model self end + + # Returns an Enumerable of all (primary) key attributes or nil if persisted? is fakse + def to_key + persisted? ? [id] : nil + end + + # Returns a string representing the object's key suitable for use in URLs, + # or nil if persisted? is false + def to_param + to_key ? to_key.join('-') : nil + end end end diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 7bf0ad712d..13ddb622d1 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -13,6 +13,33 @@ module ActiveModel module Lint module Tests + + # == Responds to <tt>to_key</tt> + # + # Returns an Enumerable of all (primary) key attributes + # or nil if model.persisted? is false + def test_to_key + assert model.respond_to?(:to_key), "The model should respond to to_key" + def model.persisted?() false end + assert model.to_key.nil? + end + + # == Responds to <tt>to_param</tt> + # + # Returns a string representing the object's key suitable for use in URLs + # or nil if model.persisted? is false. + # + # Implementers can decide to either raise an exception or provide a default + # in case the record uses a composite primary key. There are no tests for this + # behavior in lint because it doesn't make sense to force any of the possible + # implementation strategies on the implementer. However, if the resource is + # not persisted?, then to_param should always return nil. + def test_to_param + assert model.respond_to?(:to_param), "The model should respond to to_param" + def model.persisted?() false end + assert model.to_param.nil? + end + # == Responds to <tt>valid?</tt> # # Returns a boolean that specifies whether the object is in a valid or invalid @@ -22,21 +49,16 @@ module ActiveModel assert_boolean model.valid?, "valid?" end - # == Responds to <tt>new_record?</tt> + # == Responds to <tt>persisted?</tt> # # Returns a boolean that specifies whether the object has been persisted yet. # This is used when calculating the URL for an object. If the object is # not persisted, a form for that object, for instance, will be POSTed to the # collection. If it is persisted, a form for the object will put PUTed to the # URL for the object. - def test_new_record? - assert model.respond_to?(:new_record?), "The model should respond to new_record?" - assert_boolean model.new_record?, "new_record?" - end - - def test_destroyed? - assert model.respond_to?(:destroyed?), "The model should respond to destroyed?" - assert_boolean model.destroyed?, "destroyed?" + def test_persisted? + assert model.respond_to?(:persisted?), "The model should respond to persisted?" + assert_boolean model.persisted?, "persisted?" end # == Naming diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index b9fb5fe0c8..8cdd3d2fe8 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -42,7 +42,7 @@ module ActiveModel # To implement, just extend ActiveModel::Naming in your object: # # class BookCover - # exten ActiveModel::Naming + # extend ActiveModel::Naming # end # # BookCover.model_name #=> "BookCover" diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 03733a9c89..ba8648f8c9 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/array/extract_options' +require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/hash/keys' require 'active_model/errors' @@ -45,6 +46,9 @@ module ActiveModel included do extend ActiveModel::Translation define_callbacks :validate, :scope => :name + + class_attribute :_validators + self._validators = Hash.new { |h,k| h[k] = [] } end module ClassMethods @@ -117,12 +121,23 @@ module ActiveModel end set_callback(:validate, *args, &block) end - - private - + + # List all validators that being used to validate the model using +validates_with+ + # method. + def validators + _validators.values.flatten.uniq + end + + # List all validators that being used to validate a specific attribute. + def validators_on(attribute) + _validators[attribute.to_sym] + end + + private + def _merge_attributes(attr_names) options = attr_names.extract_options! - options.merge(:attributes => attr_names) + options.merge(:attributes => attr_names.flatten) end end diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index db563876af..83d3ea80d6 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -62,6 +62,15 @@ module ActiveModel args.each do |klass| validator = klass.new(options, &block) validator.setup(self) if validator.respond_to?(:setup) + + if validator.respond_to?(:attributes) && !validator.attributes.empty? + validator.attributes.each do |attribute| + _validators[attribute.to_sym] << validator + end + else + _validators[nil] << validator + end + validate(validator, options) end end diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index ad9729de00..b61f0cb266 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -1,3 +1,5 @@ +require "active_support/core_ext/module/anonymous" + module ActiveModel #:nodoc: # A simple base class that can be used along with # +ActiveModel::Validations::ClassMethods.validates_with+ @@ -88,11 +90,27 @@ module ActiveModel #:nodoc: class Validator attr_reader :options + # Returns the kind of the validator. + # + # == Examples + # + # PresenceValidator.kind #=> :presence + # UniquenessValidator.kind #=> :uniqueness + # + def self.kind + @kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous? + end + # Accepts options that will be made availible through the +options+ reader. def initialize(options) @options = options end + # Return the kind for this validator. + def kind + self.class.kind + end + # Override this method in subclasses with validation logic, adding errors # to the records +errors+ array where necessary. def validate(record) diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index d51423ae1b..85d0eed180 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -2,8 +2,9 @@ module ActiveModel module VERSION #:nodoc: MAJOR = 3 MINOR = 0 - TINY = "0.beta1" + TINY = 0 + BUILD = "beta1" - STRING = [MAJOR, MINOR, TINY].join('.') + STRING = [MAJOR, MINOR, TINY, BUILD].join('.') end end diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb new file mode 100644 index 0000000000..7669bf5f65 --- /dev/null +++ b/activemodel/test/cases/conversion_test.rb @@ -0,0 +1,25 @@ +require 'cases/helper' +require 'models/contact' + +class ConversionTest < ActiveModel::TestCase + test "to_model default implementation returns self" do + contact = Contact.new + assert_equal contact, contact.to_model + end + + test "to_key default implementation returns nil for new records" do + assert_nil Contact.new.to_key + end + + test "to_key default implementation returns the id in an array for persisted records" do + assert_equal [1], Contact.new(:id => 1).to_key + end + + test "to_param default implementation returns nil for new records" do + assert_nil Contact.new.to_param + end + + test "to_param default implementation returns a string of ids for persisted records" do + assert_equal "1", Contact.new(:id => 1).to_param + end +end
\ No newline at end of file diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index 8bcbe54651..8578ab7dbd 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -1,5 +1,8 @@ require File.expand_path('../../../../load_paths', __FILE__) +lib = File.expand_path("#{File.dirname(__FILE__)}/../../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) + require 'config' require 'active_model' diff --git a/activemodel/test/cases/lint_test.rb b/activemodel/test/cases/lint_test.rb index 63804004ee..68372160cd 100644 --- a/activemodel/test/cases/lint_test.rb +++ b/activemodel/test/cases/lint_test.rb @@ -1,18 +1,14 @@ -require "cases/helper" +require 'cases/helper' class LintTest < ActiveModel::TestCase include ActiveModel::Lint::Tests class CompliantModel extend ActiveModel::Naming - - def to_model - self - end + include ActiveModel::Conversion def valid?() true end - def new_record?() true end - def destroyed?() true end + def persisted?() false end def errors obj = Object.new diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb index 8b9795a90c..c4d787dadb 100644 --- a/activemodel/test/cases/validations/presence_validation_test.rb +++ b/activemodel/test/cases/validations/presence_validation_test.rb @@ -10,6 +10,12 @@ require 'models/custom_reader' class PresenceValidationTest < ActiveModel::TestCase include ActiveModel::TestsDatabase + teardown do + Topic.reset_callbacks(:validate) + Person.reset_callbacks(:validate) + CustomReader.reset_callbacks(:validate) + end + def test_validate_presences Topic.validates_presence_of(:title, :content) @@ -27,17 +33,21 @@ class PresenceValidationTest < ActiveModel::TestCase t.content = "like stuff" assert t.save - ensure - Topic.reset_callbacks(:validate) + end + + test 'accepts array arguments' do + Topic.validates_presence_of %w(title content) + t = Topic.new + assert !t.valid? + assert_equal ["can't be blank"], t.errors[:title] + assert_equal ["can't be blank"], t.errors[:content] end def test_validates_acceptance_of_with_custom_error_using_quotes - Person.validates_presence_of :karma, :message=> "This string contains 'single' and \"double\" quotes" + Person.validates_presence_of :karma, :message => "This string contains 'single' and \"double\" quotes" p = Person.new assert !p.valid? assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last - ensure - Person.reset_callbacks(:validate) end def test_validates_presence_of_for_ruby_class @@ -50,10 +60,8 @@ class PresenceValidationTest < ActiveModel::TestCase p.karma = "Cold" assert p.valid? - ensure - Person.reset_callbacks(:validate) end - + def test_validates_presence_of_for_ruby_class_with_custom_reader CustomReader.validates_presence_of :karma @@ -64,7 +72,5 @@ class PresenceValidationTest < ActiveModel::TestCase p[:karma] = "Cold" assert p.valid? - ensure - CustomReader.reset_callbacks(:validate) end end diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index 66b072ea38..92df4dd6cd 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -9,6 +9,7 @@ class ValidatesWithTest < ActiveRecord::TestCase def teardown Topic.reset_callbacks(:validate) + Topic._validators.clear end ERROR_MESSAGE = "Validation error from validator" diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index eb100d1c35..9fedd84c73 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -10,6 +10,10 @@ require 'models/custom_reader' class ValidationsTest < ActiveModel::TestCase include ActiveModel::TestsDatabase + def setup + Topic._validators.clear + end + # Most of the tests mess with the validations of Topic, so lets repair it all the time. # Other classes we mess with will be dealt with in the specific tests def teardown @@ -220,4 +224,27 @@ class ValidationsTest < ActiveModel::TestCase assert !t.valid? assert ["NO BLANKS HERE"], t.errors[:title] end + + def test_list_of_validators_for_model + Topic.validates_presence_of :title + Topic.validates_length_of :title, :minimum => 2 + + assert_equal 2, Topic.validators.count + assert_equal [:presence, :length], Topic.validators.map(&:kind) + end + + def test_list_of_validators_on_an_attribute + Topic.validates_presence_of :title, :content + Topic.validates_length_of :title, :minimum => 2 + + assert_equal 2, Topic.validators_on(:title).count + assert_equal [:presence, :length], Topic.validators_on(:title).map(&:kind) + assert_equal 1, Topic.validators_on(:content).count + assert_equal [:presence], Topic.validators_on(:content).map(&:kind) + end + + def test_accessing_instance_of_validator_on_an_attribute + Topic.validates_length_of :title, :minimum => 10 + assert_equal 10, Topic.validators_on(:title).first.options[:minimum] + end end diff --git a/activemodel/test/models/contact.rb b/activemodel/test/models/contact.rb index f9fb0af027..a9009fbdef 100644 --- a/activemodel/test/models/contact.rb +++ b/activemodel/test/models/contact.rb @@ -1,7 +1,13 @@ class Contact - attr_accessor :name, :age, :created_at, :awesome, :preferences + include ActiveModel::Conversion + + attr_accessor :id, :name, :age, :created_at, :awesome, :preferences def initialize(options = {}) options.each { |name, value| send("#{name}=", value) } end + + def persisted? + id + end end diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 8a414a751b..e638da0f3e 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -5,25 +5,10 @@ require 'rake/rdoctask' require 'rake/packagetask' require 'rake/gempackagetask' -require File.join(File.dirname(__FILE__), 'lib', 'active_record', 'version') require File.expand_path(File.dirname(__FILE__)) + "/test/config" -PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' -PKG_NAME = 'activerecord' -PKG_VERSION = ActiveRecord::VERSION::STRING + PKG_BUILD -PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" - -RELEASE_NAME = "REL #{PKG_VERSION}" - -RUBY_FORGE_PROJECT = "activerecord" -RUBY_FORGE_USER = "webster132" - MYSQL_DB_USER = 'rails' -PKG_FILES = FileList[ - "lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile" -].exclude(/\bCVS\b|~$/) - def run_without_aborting(*tasks) errors = [] diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index aeaafbb694..8b1ea8596a 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -1,9 +1,11 @@ +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip + Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'activerecord' - s.version = '3.0.0.beta1' + s.version = version s.summary = 'Object-relational mapper framework (part of Rails).' - s.description = 'Object-relational mapper framework (part of Rails).' + s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.' s.required_ruby_version = '>= 1.8.7' s.author = 'David Heinemeier Hansson' @@ -18,7 +20,7 @@ Gem::Specification.new do |s| s.extra_rdoc_files = %w( README ) s.rdoc_options.concat ['--main', 'README'] - s.add_dependency('activesupport', '= 3.0.0.beta1') - s.add_dependency('activemodel', '= 3.0.0.beta1') - s.add_dependency('arel', '~> 0.2.1') + s.add_dependency('activesupport', version) + s.add_dependency('activemodel', version) + s.add_dependency('arel', '~> 0.3.1') end diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index b79da4565d..5942640c85 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -30,7 +30,6 @@ $:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include require 'active_support' require 'active_model' -require 'arel' module ActiveRecord extend ActiveSupport::Autoload @@ -38,8 +37,8 @@ module ActiveRecord eager_autoload do autoload :VERSION - autoload :ActiveRecordError, 'active_record/base' - autoload :ConnectionNotEstablished, 'active_record/base' + autoload :ActiveRecordError, 'active_record/errors' + autoload :ConnectionNotEstablished, 'active_record/errors' autoload :Aggregations autoload :AssociationPreload @@ -106,12 +105,16 @@ module ActiveRecord eager_autoload do autoload :AbstractAdapter + autoload :ConnectionManagement, "active_record/connection_adapters/abstract/connection_pool" end end autoload :TestCase autoload :TestFixtures, 'active_record/fixtures' + + base_hook do + Arel::Table.engine = Arel::Sql::Engine.new(self) + end end -Arel::Table.engine = Arel::Sql::Engine.new(ActiveRecord::Base) -I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' +I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
\ No newline at end of file diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index a5b06460fe..6725d4e88b 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -126,7 +126,7 @@ module ActiveRecord parent_records.each do |parent_record| association_proxy = parent_record.send(reflection_name) association_proxy.loaded - association_proxy.target.push *Array.wrap(associated_record) + association_proxy.target.push(*Array.wrap(associated_record)) association_proxy.__send__(:set_inverse_instance, associated_record, parent_record) end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 57785b4c93..b69577f8dd 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1537,58 +1537,42 @@ module ActiveRecord # has_one associated objects, according to the defined :dependent rule. def configure_dependency_for_has_one(reflection) if reflection.options.include?(:dependent) - case reflection.options[:dependent] - when :destroy - method_name = "has_one_dependent_destroy_for_#{reflection.name}".to_sym - define_method(method_name) do - association = send(reflection.name) - association.destroy unless association.nil? - end - before_destroy method_name - when :delete - method_name = "has_one_dependent_delete_for_#{reflection.name}".to_sym + name = reflection.options[:dependent] + method_name = :"has_one_dependent_#{name}_for_#{reflection.name}" + + case name + when :destroy, :delete define_method(method_name) do - # Retrieve the associated object and delete it. The retrieval - # is necessary because there may be multiple associated objects - # with foreign keys pointing to this object, and we only want - # to delete the correct one, not all of them. association = send(reflection.name) - association.delete unless association.nil? + association.send(name) if association end - before_destroy method_name when :nullify - method_name = "has_one_dependent_nullify_for_#{reflection.name}".to_sym define_method(method_name) do association = send(reflection.name) - association.update_attribute(reflection.primary_key_name, nil) unless association.nil? + association.update_attribute(reflection.primary_key_name, nil) if association end - before_destroy method_name else raise ArgumentError, "The :dependent option expects either :destroy, :delete or :nullify (#{reflection.options[:dependent].inspect})" end + + before_destroy method_name end end def configure_dependency_for_belongs_to(reflection) if reflection.options.include?(:dependent) - case reflection.options[:dependent] - when :destroy - method_name = "belongs_to_dependent_destroy_for_#{reflection.name}".to_sym - define_method(method_name) do - association = send(reflection.name) - association.destroy unless association.nil? - end - after_destroy method_name - when :delete - method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym - define_method(method_name) do - association = send(reflection.name) - association.delete unless association.nil? - end - after_destroy method_name - else - raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})" + name = reflection.options[:dependent] + + unless [:destroy, :delete].include?(name) + raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})" + end + + method_name = :"belongs_to_dependent_#{name}_for_#{reflection.name}" + define_method(method_name) do + association = send(reflection.name) + association.send(name) if association end + after_destroy method_name end end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 365fdeb55a..095814b635 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -39,6 +39,22 @@ module ActiveRecord end alias :primary_key= :set_primary_key end + + module InstanceMethods + + # Returns this record's primary key value wrapped in an Array + # or nil if the record is a new_record? + # This is done to comply with the AMo interface that expects + # every AMo compliant object to respond_to?(:to_key) and return + # an Enumerable object from that call, or nil if new_record? + # This method also takes custom primary keys specified via + # the +set_primary_key+ into account. + def to_key + new_record? ? nil : [ self.primary_key ] + end + + end + end end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 318bfbe826..52587ef251 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -11,174 +11,12 @@ require 'active_support/core_ext/hash/deep_merge' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/behavior' -require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/object/singleton_class' require 'active_support/core_ext/module/delegation' +require 'arel' +require 'active_record/errors' module ActiveRecord #:nodoc: - # Generic Active Record exception class. - class ActiveRecordError < StandardError - end - - # Raised when the single-table inheritance mechanism fails to locate the subclass - # (for example due to improper usage of column that +inheritance_column+ points to). - class SubclassNotFound < ActiveRecordError #:nodoc: - end - - # Raised when an object assigned to an association has an incorrect type. - # - # class Ticket < ActiveRecord::Base - # has_many :patches - # end - # - # class Patch < ActiveRecord::Base - # belongs_to :ticket - # end - # - # # Comments are not patches, this assignment raises AssociationTypeMismatch. - # @ticket.patches << Comment.new(:content => "Please attach tests to your patch.") - class AssociationTypeMismatch < ActiveRecordError - end - - # Raised when unserialized object's type mismatches one specified for serializable field. - class SerializationTypeMismatch < ActiveRecordError - end - - # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt> misses adapter field). - class AdapterNotSpecified < ActiveRecordError - end - - # Raised when Active Record cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically. - class AdapterNotFound < ActiveRecordError - end - - # Raised when connection to the database could not been established (for example when <tt>connection=</tt> is given a nil object). - class ConnectionNotEstablished < ActiveRecordError - end - - # Raised when Active Record cannot find record by given id or set of ids. - class RecordNotFound < ActiveRecordError - end - - # Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be - # saved because record is invalid. - class RecordNotSaved < ActiveRecordError - end - - # Raised when SQL statement cannot be executed by the database (for example, it's often the case for MySQL when Ruby driver used is too old). - class StatementInvalid < ActiveRecordError - end - - # Raised when SQL statement is invalid and the application gets a blank result. - class ThrowResult < ActiveRecordError - end - - # Parent class for all specific exceptions which wrap database driver exceptions - # provides access to the original exception also. - class WrappedDatabaseException < StatementInvalid - attr_reader :original_exception - - def initialize(message, original_exception) - super(message) - @original_exception = original_exception - end - end - - # Raised when a record cannot be inserted because it would violate a uniqueness constraint. - class RecordNotUnique < WrappedDatabaseException - end - - # Raised when a record cannot be inserted or updated because it references a non-existent record. - class InvalidForeignKey < WrappedDatabaseException - end - - # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example, when using +find+ method) - # does not match number of expected variables. - # - # For example, in - # - # Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362] - # - # two placeholders are given but only one variable to fill them. - class PreparedStatementInvalid < ActiveRecordError - end - - # Raised on attempt to save stale record. Record is stale when it's being saved in another query after - # instantiation, for example, when two users edit the same wiki page and one starts editing and saves - # the page before the other. - # - # Read more about optimistic locking in ActiveRecord::Locking module RDoc. - class StaleObjectError < ActiveRecordError - end - - # Raised when association is being configured improperly or - # user tries to use offset and limit together with has_many or has_and_belongs_to_many associations. - class ConfigurationError < ActiveRecordError - end - - # Raised on attempt to update record that is instantiated as read only. - class ReadOnlyRecord < ActiveRecordError - end - - # <tt>ActiveRecord::Transactions::ClassMethods.transaction</tt> uses this exception - # to distinguish a deliberate rollback from other exceptional situations. - # Normally, raising an exception will cause the +transaction+ method to rollback - # the database transaction *and* pass on the exception. But if you raise an - # <tt>ActiveRecord::Rollback</tt> exception, then the database transaction will be rolled back, - # without passing on the exception. - # - # For example, you could do this in your controller to rollback a transaction: - # - # class BooksController < ActionController::Base - # def create - # Book.transaction do - # book = Book.new(params[:book]) - # book.save! - # if today_is_friday? - # # The system must fail on Friday so that our support department - # # won't be out of job. We silently rollback this transaction - # # without telling the user. - # raise ActiveRecord::Rollback, "Call tech support!" - # end - # end - # # ActiveRecord::Rollback is the only exception that won't be passed on - # # by ActiveRecord::Base.transaction, so this line will still be reached - # # even on Friday. - # redirect_to root_url - # end - # end - class Rollback < ActiveRecordError - end - - # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods). - class DangerousAttributeError < ActiveRecordError - end - - # Raised when unknown attributes are supplied via mass assignment. - class UnknownAttributeError < NoMethodError - end - - # Raised when an error occurred while doing a mass assignment to an attribute through the - # <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the - # offending attribute. - class AttributeAssignmentError < ActiveRecordError - attr_reader :exception, :attribute - def initialize(message, exception, attribute) - @exception = exception - @attribute = attribute - @message = message - end - end - - # Raised when there are multiple errors while doing a mass assignment through the +attributes+ - # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError - # objects, each corresponding to the error while assigning to an attribute. - class MultiparameterAssignmentErrors < ActiveRecordError - attr_reader :errors - def initialize(errors) - @errors = errors - end - end - # Active Record objects don't specify their attributes directly, but rather infer them from the table definition with # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain @@ -551,8 +389,8 @@ module ActiveRecord #:nodoc: class << self # Class methods def colorize_logging(*args) ActiveSupport::Deprecation.warn "ActiveRecord::Base.colorize_logging and " << - "config.active_record.colorize_logging are deprecated. Please use " << - "Rails::Subscriber.colorize_logging or config.colorize_logging instead", caller + "config.active_record.colorize_logging are deprecated. Please use " << + "Rails::LogSubscriber.colorize_logging or config.colorize_logging instead", caller end alias :colorize_logging= :colorize_logging @@ -1669,12 +1507,12 @@ module ActiveRecord #:nodoc: @attributes_cache = {} @new_record = true ensure_proper_type - self.attributes = attributes unless attributes.nil? if scope = self.class.send(:current_scoped_methods) create_with = scope.scope_for_create create_with.each { |att,value| self.send("#{att}=", value) } if create_with end + self.attributes = attributes unless attributes.nil? result = yield self if block_given? _run_initialize_callbacks @@ -1767,6 +1605,11 @@ module ActiveRecord #:nodoc: @destroyed || false end + # Returns if the record is persisted, i.e. it's not a new record and it was not destroyed. + def persisted? + !(new_record? || destroyed?) + end + # :call-seq: # save(options) # @@ -1816,7 +1659,7 @@ module ActiveRecord #:nodoc: # callbacks, Observer methods, or any <tt>:dependent</tt> association # options, use <tt>#destroy</tt>. def delete - self.class.delete(id) unless new_record? + self.class.delete(id) if persisted? @destroyed = true freeze end @@ -1824,7 +1667,7 @@ module ActiveRecord #:nodoc: # Deletes the record in the database and freezes this instance to reflect that no changes should # be made (since they can't be persisted). def destroy - unless new_record? + if persisted? self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all end @@ -1844,6 +1687,7 @@ module ActiveRecord #:nodoc: became.instance_variable_set("@attributes", @attributes) became.instance_variable_set("@attributes_cache", @attributes_cache) became.instance_variable_set("@new_record", new_record?) + became.instance_variable_set("@destroyed", destroyed?) became end @@ -1926,7 +1770,7 @@ module ActiveRecord #:nodoc: def reload(options = nil) clear_aggregation_cache clear_association_cache - @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes')) + @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes')) @attributes_cache = {} self end @@ -1995,10 +1839,9 @@ module ActiveRecord #:nodoc: # Returns a hash of all the attributes with their names as keys and the values of the attributes as values. def attributes - self.attribute_names.inject({}) do |attrs, name| - attrs[name] = read_attribute(name) - attrs - end + attrs = {} + attribute_names.each { |name| attrs[name] = read_attribute(name) } + attrs end # Returns an <tt>#inspect</tt>-like string for the value of the @@ -2042,8 +1885,7 @@ module ActiveRecord #:nodoc: def ==(comparison_object) comparison_object.equal?(self) || (comparison_object.instance_of?(self.class) && - comparison_object.id == id && - !comparison_object.new_record?) + comparison_object.id == id && !comparison_object.new_record?) end # Delegates to == @@ -2343,7 +2185,7 @@ module ActiveRecord #:nodoc: # Returns a comma-separated pair list, like "key1 = val1, key2 = val2". def comma_pair_list(hash) - hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ") + hash.map { |k,v| "#{k} = #{v}" }.join(", ") end def quote_columns(quoter, hash) @@ -2397,8 +2239,10 @@ module ActiveRecord #:nodoc: include Aggregations, Transactions, Reflection, Serialization + NilClass.add_whiner(self) if NilClass.respond_to?(:add_whiner) end end # TODO: Remove this and make it work with LAZY flag require 'active_record/connection_adapters/abstract_adapter' +ActiveRecord.run_base_hooks(ActiveRecord::Base)
\ No newline at end of file diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 027d736484..abb695264e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -113,7 +113,7 @@ module ActiveRecord def transaction(options = {}) options.assert_valid_keys :requires_new, :joinable - last_transaction_joinable = @transaction_joinable + last_transaction_joinable = defined?(@transaction_joinable) ? @transaction_joinable : nil if options.has_key?(:joinable) @transaction_joinable = options[:joinable] else diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 520f3c8c0c..64faaef4a0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -319,16 +319,19 @@ module ActiveRecord def method_missing(symbol, *args) if symbol.to_s == 'xml' xml_column_fallback(args) + else + super end end def xml_column_fallback(*args) case @base.adapter_name.downcase - when 'sqlite', 'mysql' - options = args.extract_options! - column(args[0], :text, options) - end + when 'sqlite', 'mysql' + options = args.extract_options! + column(args[0], :text, options) end + end + # Appends a primary key definition to the table definition. # Can be called multiple times, but this is probably not a good idea. def primary_key(name) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 7e80347f75..6ffffc8654 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -37,6 +37,7 @@ module ActiveRecord @@row_even = true def initialize(connection, logger = nil) #:nodoc: + @active = nil @connection, @logger = connection, logger @runtime = 0 @query_cache_enabled = false diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb new file mode 100644 index 0000000000..cf5ddca2ba --- /dev/null +++ b/activerecord/lib/active_record/errors.rb @@ -0,0 +1,165 @@ +module ActiveRecord + # Generic Active Record exception class. + class ActiveRecordError < StandardError + end + + # Raised when the single-table inheritance mechanism fails to locate the subclass + # (for example due to improper usage of column that +inheritance_column+ points to). + class SubclassNotFound < ActiveRecordError #:nodoc: + end + + # Raised when an object assigned to an association has an incorrect type. + # + # class Ticket < ActiveRecord::Base + # has_many :patches + # end + # + # class Patch < ActiveRecord::Base + # belongs_to :ticket + # end + # + # # Comments are not patches, this assignment raises AssociationTypeMismatch. + # @ticket.patches << Comment.new(:content => "Please attach tests to your patch.") + class AssociationTypeMismatch < ActiveRecordError + end + + # Raised when unserialized object's type mismatches one specified for serializable field. + class SerializationTypeMismatch < ActiveRecordError + end + + # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt> misses adapter field). + class AdapterNotSpecified < ActiveRecordError + end + + # Raised when Active Record cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically. + class AdapterNotFound < ActiveRecordError + end + + # Raised when connection to the database could not been established (for example when <tt>connection=</tt> is given a nil object). + class ConnectionNotEstablished < ActiveRecordError + end + + # Raised when Active Record cannot find record by given id or set of ids. + class RecordNotFound < ActiveRecordError + end + + # Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be + # saved because record is invalid. + class RecordNotSaved < ActiveRecordError + end + + # Raised when SQL statement cannot be executed by the database (for example, it's often the case for MySQL when Ruby driver used is too old). + class StatementInvalid < ActiveRecordError + end + + # Raised when SQL statement is invalid and the application gets a blank result. + class ThrowResult < ActiveRecordError + end + + # Parent class for all specific exceptions which wrap database driver exceptions + # provides access to the original exception also. + class WrappedDatabaseException < StatementInvalid + attr_reader :original_exception + + def initialize(message, original_exception) + super(message) + @original_exception = original_exception + end + end + + # Raised when a record cannot be inserted because it would violate a uniqueness constraint. + class RecordNotUnique < WrappedDatabaseException + end + + # Raised when a record cannot be inserted or updated because it references a non-existent record. + class InvalidForeignKey < WrappedDatabaseException + end + + # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example, when using +find+ method) + # does not match number of expected variables. + # + # For example, in + # + # Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362] + # + # two placeholders are given but only one variable to fill them. + class PreparedStatementInvalid < ActiveRecordError + end + + # Raised on attempt to save stale record. Record is stale when it's being saved in another query after + # instantiation, for example, when two users edit the same wiki page and one starts editing and saves + # the page before the other. + # + # Read more about optimistic locking in ActiveRecord::Locking module RDoc. + class StaleObjectError < ActiveRecordError + end + + # Raised when association is being configured improperly or + # user tries to use offset and limit together with has_many or has_and_belongs_to_many associations. + class ConfigurationError < ActiveRecordError + end + + # Raised on attempt to update record that is instantiated as read only. + class ReadOnlyRecord < ActiveRecordError + end + + # ActiveRecord::Transactions::ClassMethods.transaction uses this exception + # to distinguish a deliberate rollback from other exceptional situations. + # Normally, raising an exception will cause the +transaction+ method to rollback + # the database transaction *and* pass on the exception. But if you raise an + # ActiveRecord::Rollback exception, then the database transaction will be rolled back, + # without passing on the exception. + # + # For example, you could do this in your controller to rollback a transaction: + # + # class BooksController < ActionController::Base + # def create + # Book.transaction do + # book = Book.new(params[:book]) + # book.save! + # if today_is_friday? + # # The system must fail on Friday so that our support department + # # won't be out of job. We silently rollback this transaction + # # without telling the user. + # raise ActiveRecord::Rollback, "Call tech support!" + # end + # end + # # ActiveRecord::Rollback is the only exception that won't be passed on + # # by ActiveRecord::Base.transaction, so this line will still be reached + # # even on Friday. + # redirect_to root_url + # end + # end + class Rollback < ActiveRecordError + end + + # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods). + class DangerousAttributeError < ActiveRecordError + end + + # Raised when unknown attributes are supplied via mass assignment. + class UnknownAttributeError < NoMethodError + end + + # Raised when an error occurred while doing a mass assignment to an attribute through the + # <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the + # offending attribute. + class AttributeAssignmentError < ActiveRecordError + attr_reader :exception, :attribute + def initialize(message, exception, attribute) + @exception = exception + @attribute = attribute + @message = message + end + end + + # Raised when there are multiple errors while doing a mass assignment through the +attributes+ + # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError + # objects, each corresponding to the error while assigning to an attribute. + class MultiparameterAssignmentErrors < ActiveRecordError + attr_reader :errors + def initialize(errors) + @errors = errors + end + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 068d2a25b2..fd5ffc6d77 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/object/singleton_class' module ActiveRecord # Exception that can be raised to stop migrations from going backwards. @@ -303,7 +303,7 @@ module ActiveRecord case sym when :up, :down - metaclass.send(:alias_method_chain, sym, "benchmarks") + singleton_class.send(:alias_method_chain, sym, "benchmarks") end ensure @ignore_new_methods = false diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index ff6c041ef4..394e1587e1 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -1,6 +1,6 @@ require 'active_support/core_ext/array' require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/object/singleton_class' module ActiveRecord module NamedScope @@ -26,7 +26,7 @@ module ActiveRecord if options.present? Scope.init(self, options, &block) else - current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.spawn + current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone end end @@ -112,7 +112,7 @@ module ActiveRecord options.call(*args) end, &block) end - metaclass.instance_eval do + singleton_class.instance_eval do define_method name do |*args| scopes[name].call(self, *args) end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index e70b0d1bfb..60addd46c6 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -20,35 +20,43 @@ module ActiveRecord end # TODO If we require the wrong file, the error never comes up. - require "active_record/railties/subscriber" - subscriber ActiveRecord::Railties::Subscriber.new + require "active_record/railties/log_subscriber" + log_subscriber ActiveRecord::Railties::LogSubscriber.new initializer "active_record.initialize_timezone" do - ActiveRecord::Base.time_zone_aware_attributes = true - ActiveRecord::Base.default_timezone = :utc + ActiveRecord.base_hook do + self.time_zone_aware_attributes = true + self.default_timezone = :utc + end end initializer "active_record.logger" do - ActiveRecord::Base.logger ||= ::Rails.logger + ActiveRecord.base_hook { self.logger ||= ::Rails.logger } end initializer "active_record.set_configs" do |app| - app.config.active_record.each do |k,v| - ActiveRecord::Base.send "#{k}=", v + ActiveRecord.base_hook do + app.config.active_record.each do |k,v| + send "#{k}=", v + end end end # This sets the database configuration from Configuration#database_configuration # and then establishes the connection. initializer "active_record.initialize_database" do |app| - ActiveRecord::Base.configurations = app.config.database_configuration - ActiveRecord::Base.establish_connection + ActiveRecord.base_hook do + self.configurations = app.config.database_configuration + establish_connection + end end # Expose database runtime to controller for logging. initializer "active_record.log_runtime" do |app| require "active_record/railties/controller_runtime" - ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime + ActionController.base_hook do + include ActiveRecord::Railties::ControllerRuntime + end end # Setup database middleware after initializers have run @@ -64,18 +72,22 @@ module ActiveRecord end initializer "active_record.load_observers" do - ActiveRecord::Base.instantiate_observers + ActiveRecord.base_hook { instantiate_observers } - ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do - ActiveRecord::Base.instantiate_observers + ActiveRecord.base_hook do + ActionDispatch::Callbacks.to_prepare(:activerecord_instantiate_observers) do + ActiveRecord::Base.instantiate_observers + end end end initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app| - unless app.config.cache_classes - ActionDispatch::Callbacks.after do - ActiveRecord::Base.reset_subclasses - ActiveRecord::Base.clear_reloadable_connections! + ActiveRecord.base_hook do + unless app.config.cache_classes + ActionDispatch::Callbacks.after do + ActiveRecord::Base.reset_subclasses + ActiveRecord::Base.clear_reloadable_connections! + end end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index ed7d2a045e..fa6caa4910 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -109,7 +109,7 @@ namespace :db do # Only connect to local databases local_database?(config) { drop_database(config) } rescue Exception => e - puts "Couldn't drop #{config['database']} : #{e.inspect}" + $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}" end end end @@ -121,7 +121,7 @@ namespace :db do begin drop_database(config) rescue Exception => e - puts "Couldn't drop #{config['database']} : #{e.inspect}" + $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}" end end @@ -129,7 +129,7 @@ namespace :db do if %w( 127.0.0.1 localhost ).include?(config['host']) || config['host'].blank? yield else - puts "This task only modifies local databases. #{config['database']} is on a remote host." + $stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host." end end @@ -204,7 +204,7 @@ namespace :db do ActiveRecord::Base.establish_connection(config) puts ActiveRecord::Base.connection.encoding else - puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' + $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' end end @@ -216,7 +216,7 @@ namespace :db do ActiveRecord::Base.establish_connection(config) puts ActiveRecord::Base.connection.collation else - puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' + $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' end end diff --git a/activerecord/lib/active_record/railties/subscriber.rb b/activerecord/lib/active_record/railties/log_subscriber.rb index fd873dbff8..48b25032ce 100644 --- a/activerecord/lib/active_record/railties/subscriber.rb +++ b/activerecord/lib/active_record/railties/log_subscriber.rb @@ -1,6 +1,6 @@ module ActiveRecord module Railties - class Subscriber < Rails::Subscriber + class LogSubscriber < Rails::LogSubscriber def sql(event) name = '%s (%.1fms)' % [event.payload[:name], event.duration] sql = event.payload[:sql].squeeze(' ') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 7bc3d3bf33..aca4629dd8 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -21,6 +21,10 @@ module ActiveRecord with_create_scope { @klass.new(*args, &block) } end + def initialize_copy(other) + reset + end + alias build new def create(*args, &block) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 65e5c0495c..7e83eccbb5 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -20,7 +20,7 @@ module ActiveRecord table = Arel::Table.new(table_name, :engine => @engine) end - attribute = table[column] || Arel::Attribute.new(table, column.to_sym) + attribute = table[column] case value when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 0266700f66..e00d9cdf27 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -8,7 +8,7 @@ module ActiveRecord class_eval <<-CEVAL def #{query_method}(*args) - new_relation = spawn + new_relation = clone value = Array.wrap(args.flatten).reject {|x| x.blank? } new_relation.#{query_method}_values += value if value.present? new_relation @@ -19,7 +19,7 @@ module ActiveRecord [:where, :having].each do |query_method| class_eval <<-CEVAL def #{query_method}(*args) - new_relation = spawn + new_relation = clone value = build_where(*args) new_relation.#{query_method}_values += [*value] if value.present? new_relation @@ -32,7 +32,7 @@ module ActiveRecord class_eval <<-CEVAL def #{query_method}(value = true) - new_relation = spawn + new_relation = clone new_relation.#{query_method}_value = value new_relation end @@ -41,12 +41,12 @@ module ActiveRecord end def lock(locks = true) - relation = spawn + relation = clone case locks when String, TrueClass, NilClass - spawn.tap {|new_relation| new_relation.lock_value = locks || true } + clone.tap {|new_relation| new_relation.lock_value = locks || true } else - spawn.tap {|new_relation| new_relation.lock_value = false } + clone.tap {|new_relation| new_relation.lock_value = false } end end @@ -174,7 +174,7 @@ module ActiveRecord arel = arel.lock when String arel = arel.lock(@lock_value) - end + end if defined?(@lock_value) arel end @@ -184,16 +184,16 @@ module ActiveRecord builder = PredicateBuilder.new(table.engine) - conditions = if [String, Array].include?(args.first.class) - @klass.send(:sanitize_sql, args.size > 1 ? args : args.first) - elsif args.first.is_a?(Hash) - attributes = @klass.send(:expand_hash_conditions_for_aggregates, args.first) + opts = args.first + case opts + when String, Array + @klass.send(:sanitize_sql, args.size > 1 ? args : opts) + when Hash + attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts) builder.build_from_hash(attributes, table) else - args.first + opts end - - conditions end private diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index cccf413e67..a18380f01c 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -1,11 +1,7 @@ module ActiveRecord module SpawnMethods - def spawn - clone.reset - end - def merge(r) - merged_relation = spawn + merged_relation = clone return merged_relation unless r merged_relation = merged_relation.eager_load(r.eager_load_values).preload(r.preload_values).includes(r.includes_values) @@ -83,7 +79,7 @@ module ActiveRecord :order, :select, :readonly, :group, :having, :from, :lock ] def apply_finder_options(options) - relation = spawn + relation = clone return relation unless options options.assert_valid_keys(VALID_FIND_OPTIONS) diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index 286ecd0289..eaf5dc6545 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -2,8 +2,9 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 3 MINOR = 0 - TINY = "0.beta1" + TINY = 0 + BUILD = "beta1" - STRING = [MAJOR, MINOR, TINY].join('.') + STRING = [MAJOR, MINOR, TINY, BUILD].join('.') end end diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 4c1589d965..665c387d5d 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -27,6 +27,16 @@ if ActiveRecord::Base.connection.supports_migrations? assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" } assert_equal 7, ActiveRecord::Migrator::current_version end + + def test_schema_raises_an_error_for_invalid_column_ntype + assert_raise NoMethodError do + ActiveRecord::Schema.define(:version => 8) do + create_table :vegetables do |t| + t.unknown :color + end + end + end + end end end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 2a77eed1b5..41a23d7f61 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -18,7 +18,8 @@ require 'models/essay' class BelongsToAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :topics, - :developers_projects, :computers, :authors, :posts, :tags, :taggings, :comments + :developers_projects, :computers, :authors, :author_addresses, + :posts, :tags, :taggings, :comments def test_belongs_to Client.find(3).firm.name @@ -346,14 +347,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_client).readonly_firm.save! } assert companies(:first_client).readonly_firm.readonly? end - + def test_polymorphic_assignment_foreign_type_field_updating # should update when assigning a saved record sponsor = Sponsor.new member = Member.create sponsor.sponsorable = member assert_equal "Member", sponsor.sponsorable_type - + # should update when assigning a new record sponsor = Sponsor.new member = Member.new @@ -374,15 +375,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase essay.writer = writer assert_equal "Author", essay.writer_type end - + def test_polymorphic_assignment_updates_foreign_id_field_for_new_and_saved_records sponsor = Sponsor.new saved_member = Member.create new_member = Member.new - + sponsor.sponsorable = saved_member assert_equal saved_member.id, sponsor.sponsorable_id - + sponsor.sponsorable = new_member assert_equal nil, sponsor.sponsorable_id end @@ -424,4 +425,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase Account.find(@account.id, :include => :firm).save! end end + + def test_dependent_delete_and_destroy_with_belongs_to + author_address = author_addresses(:david_address) + author_address_extra = author_addresses(:david_address_extra) + assert_equal [], AuthorAddress.destroyed_author_address_ids + + assert_difference "AuthorAddress.count", -2 do + authors(:david).destroy + end + + assert_equal [], AuthorAddress.find_all_by_id([author_address.id, author_address_extra.id]) + assert_equal [author_address.id], AuthorAddress.destroyed_author_address_ids + end + + def test_invalid_belongs_to_dependent_option_raises_exception + assert_raise ArgumentError do + Author.belongs_to :special_author_address, :dependent => :nullify + end + end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index ce7eedbb54..54624e79ce 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -14,7 +14,7 @@ require 'models/tagging' class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, - :developers_projects, :topics, :authors, :comments, :author_addresses, + :developers_projects, :topics, :authors, :comments, :people, :posts, :readers, :taggings def setup @@ -684,24 +684,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 'Microsoft', another_ms_client.name end - def test_dependent_delete_and_destroy_with_belongs_to - author_address = author_addresses(:david_address) - assert_equal [], AuthorAddress.destroyed_author_address_ids[authors(:david).id] - - assert_difference "AuthorAddress.count", -2 do - authors(:david).destroy - end - - assert_equal nil, AuthorAddress.find_by_id(authors(:david).author_address_id) - assert_equal nil, AuthorAddress.find_by_id(authors(:david).author_address_extra_id) - end - - def test_invalid_belongs_to_dependent_option_raises_exception - assert_raise ArgumentError do - Author.belongs_to :special_author_address, :dependent => :nullify - end - end - def test_clearing_without_initial_access firm = companies(:first_firm) diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index d359ad48c5..d5dbb88886 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -14,7 +14,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal companies(:first_firm).account, Account.find(1) assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit end - + def test_has_one_cache_nils firm = companies(:another_firm) assert_queries(1) { assert_nil firm.account } @@ -96,7 +96,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_nil Account.find(old_account_id).firm_id end - def test_association_changecalls_delete + def test_association_change_calls_delete companies(:first_firm).deletable_account = Account.new assert_equal [], Account.destroyed_account_ids[companies(:first_firm).id] end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 1441b4278d..e3047fe873 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1330,11 +1330,6 @@ class BasicsTest < ActiveRecord::TestCase end def test_destroyed_returns_boolean - developer = Developer.new - assert_equal developer.destroyed?, false - developer.destroy - assert_equal developer.destroyed?, true - developer = Developer.first assert_equal developer.destroyed?, false developer.destroy @@ -1346,6 +1341,23 @@ class BasicsTest < ActiveRecord::TestCase assert_equal developer.destroyed?, true end + def test_persisted_returns_boolean + developer = Developer.new(:name => "Jose") + assert_equal developer.persisted?, false + developer.save! + assert_equal developer.persisted?, true + + developer = Developer.first + assert_equal developer.persisted?, true + developer.destroy + assert_equal developer.persisted?, false + + developer = Developer.last + assert_equal developer.persisted?, true + developer.delete + assert_equal developer.persisted?, false + end + def test_clone topic = Topic.find(1) cloned_topic = nil @@ -1711,6 +1723,12 @@ class BasicsTest < ActiveRecord::TestCase assert_equal t1.title, t2.title end + def test_reload_with_exclusive_scope + dev = DeveloperCalledDavid.first + dev.update_attributes!( :name => "NotDavid" ) + assert_equal dev, dev.reload + end + def test_define_attr_method_with_value k = Class.new( ActiveRecord::Base ) k.send(:define_attr_method, :table_name, "foo") diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index e6d56a7193..28a1ae5feb 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -38,12 +38,12 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_get_maximum_of_field_with_include - assert_equal 50, Account.maximum(:credit_limit, :include => :firm, :conditions => "companies.name != 'Summit'") + assert_equal 55, Account.maximum(:credit_limit, :include => :firm, :conditions => "companies.name != 'Summit'") end def test_should_get_maximum_of_field_with_scoped_include Account.send :with_scope, :find => { :include => :firm, :conditions => "companies.name != 'Summit'" } do - assert_equal 50, Account.maximum(:credit_limit) + assert_equal 55, Account.maximum(:credit_limit) end end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 9e8bfbbee8..e831ebf36c 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -1,11 +1,15 @@ require File.expand_path('../../../../load_paths', __FILE__) +lib = File.expand_path("#{File.dirname(__FILE__)}/../../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) + require 'config' require 'test/unit' require 'stringio' require 'active_record' +require 'active_support/dependencies' require 'connection' begin diff --git a/activerecord/test/cases/subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 5328d4468b..f3b94eb829 100644 --- a/activerecord/test/cases/subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -1,15 +1,16 @@ require "cases/helper" require "models/developer" -require "rails/subscriber/test_helper" -require "active_record/railties/subscriber" +require "rails/log_subscriber/test_helper" +require "active_record/railties/log_subscriber" -class SubscriberTest < ActiveSupport::TestCase - include Rails::Subscriber::TestHelper - Rails::Subscriber.add(:active_record, ActiveRecord::Railties::Subscriber.new) +class LogSubscriberTest < ActiveSupport::TestCase + include Rails::LogSubscriber::TestHelper def setup @old_logger = ActiveRecord::Base.logger super + + Rails::LogSubscriber.add(:active_record, ActiveRecord::Railties::LogSubscriber.new) end def teardown diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 1081aa40a9..3151457440 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -663,6 +663,16 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal 2, posts.count assert_equal posts(:thinking), posts.first end + + def test_create_attribute_overwrites_default_scoping + assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name + assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary + end + + def test_create_attribute_overwrites_default_values + assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary + assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary + end end =begin diff --git a/activerecord/test/cases/pk_test.rb b/activerecord/test/cases/pk_test.rb index c121e0aa0f..5bba1d5625 100644 --- a/activerecord/test/cases/pk_test.rb +++ b/activerecord/test/cases/pk_test.rb @@ -9,6 +9,20 @@ require 'models/mixed_case_monkey' class PrimaryKeysTest < ActiveRecord::TestCase fixtures :topics, :subscribers, :movies, :mixed_case_monkeys + def test_to_key_with_default_primary_key + topic = Topic.new + assert topic.to_key.nil? + topic = Topic.find(1) + assert_equal topic.to_key, [1] + end + + def test_to_key_with_customized_primary_key + keyboard = Keyboard.new + assert keyboard.to_key.nil? + keyboard.save + assert_equal keyboard.to_key, [keyboard.id] + end + def test_integer_key topic = Topic.find(1) assert_equal(topics(:first).author_name, topic.author_name) diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 7cbc6e803f..025f6207f8 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -35,7 +35,7 @@ class Author < ActiveRecord::Base has_many :ordered_uniq_comments, :through => :posts, :source => :comments, :uniq => true, :order => 'comments.id' has_many :ordered_uniq_comments_desc, :through => :posts, :source => :comments, :uniq => true, :order => 'comments.id DESC' has_many :readonly_comments, :through => :posts, :source => :comments, :readonly => true - + has_many :special_posts has_many :special_post_comments, :through => :special_posts, :source => :comments @@ -130,14 +130,11 @@ class AuthorAddress < ActiveRecord::Base has_one :author def self.destroyed_author_address_ids - @destroyed_author_address_ids ||= Hash.new { |h,k| h[k] = [] } + @destroyed_author_address_ids ||= [] end before_destroy do |author_address| - if author_address.author - AuthorAddress.destroyed_author_address_ids[author_address.author.id] << author_address.id - end - true + AuthorAddress.destroyed_author_address_ids << author_address.id end end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index df5fd10b6b..f31d5f87e5 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -82,7 +82,7 @@ class Firm < Company has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account", :order => "id" has_one :account_using_foreign_and_primary_keys, :foreign_key => "firm_name", :primary_key => "name", :class_name => "Account" has_one :deletable_account, :foreign_key => "firm_id", :class_name => "Account", :dependent => :delete - + has_one :account_limit_500_with_hash_conditions, :foreign_key => "firm_id", :class_name => "Account", :conditions => { :credit_limit => 500 } has_one :unautosaved_account, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false @@ -155,7 +155,7 @@ class VerySpecialClient < SpecialClient end class Account < ActiveRecord::Base - belongs_to :firm + belongs_to :firm, :class_name => 'Company' belongs_to :unautosaved_firm, :foreign_key => "firm_id", :class_name => "Firm", :autosave => false def self.destroyed_account_ids diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index e7a1e110d7..e35de3b9b0 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -99,3 +99,8 @@ class DeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' default_scope :conditions => { :name => 'Jamis' } end + +class PoorDeveloperCalledJamis < ActiveRecord::Base + self.table_name = 'developers' + default_scope :conditions => { :name => 'Jamis', :salary => 50000 } +end
\ No newline at end of file diff --git a/activeresource/Rakefile b/activeresource/Rakefile index 829752516f..175b379699 100644 --- a/activeresource/Rakefile +++ b/activeresource/Rakefile @@ -5,22 +5,6 @@ require 'rake/rdoctask' require 'rake/packagetask' require 'rake/gempackagetask' -require File.join(File.dirname(__FILE__), 'lib', 'active_resource', 'version') - -PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' -PKG_NAME = 'activeresource' -PKG_VERSION = ActiveResource::VERSION::STRING + PKG_BUILD -PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" - -RELEASE_NAME = "REL #{PKG_VERSION}" - -RUBY_FORGE_PROJECT = "activerecord" -RUBY_FORGE_USER = "webster132" - -PKG_FILES = FileList[ - "lib/**/*", "test/**/*", "[A-Z]*", "Rakefile" -].exclude(/\bCVS\b|~$/) - desc "Default Task" task :default => [ :test ] diff --git a/activeresource/activeresource.gemspec b/activeresource/activeresource.gemspec index 1722ebeb8e..3718bb4f2e 100644 --- a/activeresource/activeresource.gemspec +++ b/activeresource/activeresource.gemspec @@ -1,9 +1,11 @@ +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip + Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'activeresource' - s.version = '3.0.0.beta1' - s.summary = 'REST-model framework (part of Rails).' - s.description = 'REST-model framework (part of Rails).' + s.version = version + s.summary = 'REST modeling framework (part of Rails).' + s.description = 'REST on Rails. Wrap your RESTful web app with Ruby classes and work with them like Active Record models.' s.required_ruby_version = '>= 1.8.7' s.author = 'David Heinemeier Hansson' @@ -18,6 +20,6 @@ Gem::Specification.new do |s| s.extra_rdoc_files = %w( README ) s.rdoc_options.concat ['--main', 'README'] - s.add_dependency('activesupport', '= 3.0.0.beta1') - s.add_dependency('activemodel', '= 3.0.0.beta1') + s.add_dependency('activesupport', version) + s.add_dependency('activemodel', version) end diff --git a/activeresource/lib/active_resource/railtie.rb b/activeresource/lib/active_resource/railtie.rb index 7e35fdc0eb..27c88415f6 100644 --- a/activeresource/lib/active_resource/railtie.rb +++ b/activeresource/lib/active_resource/railtie.rb @@ -5,8 +5,8 @@ module ActiveResource class Railtie < Rails::Railtie railtie_name :active_resource - require "active_resource/railties/subscriber" - subscriber ActiveResource::Railties::Subscriber.new + require "active_resource/railties/log_subscriber" + log_subscriber ActiveResource::Railties::LogSubscriber.new initializer "active_resource.set_configs" do |app| app.config.active_resource.each do |k,v| diff --git a/activeresource/lib/active_resource/railties/subscriber.rb b/activeresource/lib/active_resource/railties/log_subscriber.rb index fb98061b71..86806a93d0 100644 --- a/activeresource/lib/active_resource/railties/subscriber.rb +++ b/activeresource/lib/active_resource/railties/log_subscriber.rb @@ -1,6 +1,6 @@ module ActiveResource module Railties - class Subscriber < Rails::Subscriber + class LogSubscriber < Rails::LogSubscriber def request(event) result = event.payload[:result] info "#{event.payload[:method].to_s.upcase} #{event.payload[:request_uri]}" diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb index 461fef5283..bb60954e05 100644 --- a/activeresource/lib/active_resource/version.rb +++ b/activeresource/lib/active_resource/version.rb @@ -2,8 +2,9 @@ module ActiveResource module VERSION #:nodoc: MAJOR = 3 MINOR = 0 - TINY = "0.beta1" + TINY = 0 + BUILD = "beta1" - STRING = [MAJOR, MINOR, TINY].join('.') + STRING = [MAJOR, MINOR, TINY, BUILD].join('.') end end diff --git a/activeresource/test/abstract_unit.rb b/activeresource/test/abstract_unit.rb index 1af535e811..fcb770d612 100644 --- a/activeresource/test/abstract_unit.rb +++ b/activeresource/test/abstract_unit.rb @@ -1,12 +1,14 @@ require File.expand_path('../../../load_paths', __FILE__) +lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) + require 'rubygems' require 'test/unit' require 'active_resource' require 'active_support' require 'active_support/test_case' -$:.unshift "#{File.dirname(__FILE__)}/../test" require 'setter_trap' require 'logger' diff --git a/activeresource/test/cases/subscriber_test.rb b/activeresource/test/cases/log_subscriber_test.rb index fb890e86cb..f0330e8f51 100644 --- a/activeresource/test/cases/subscriber_test.rb +++ b/activeresource/test/cases/log_subscriber_test.rb @@ -1,20 +1,22 @@ require "abstract_unit" require "fixtures/person" -require "rails/subscriber/test_helper" -require "active_resource/railties/subscriber" +require "rails/log_subscriber/test_helper" +require "active_resource/railties/log_subscriber" require "active_support/core_ext/hash/conversions" -class SubscriberTest < ActiveSupport::TestCase - include Rails::Subscriber::TestHelper - Rails::Subscriber.add(:active_resource, ActiveResource::Railties::Subscriber.new) +# TODO: This test should be part of Railties +class LogSubscriberTest < ActiveSupport::TestCase + include Rails::LogSubscriber::TestHelper def setup + super + @matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person') ActiveResource::HttpMock.respond_to do |mock| mock.get "/people/1.xml", {}, @matz end - super + Rails::LogSubscriber.add(:active_resource, ActiveResource::Railties::LogSubscriber.new) end def set_logger(logger) @@ -26,6 +28,6 @@ class SubscriberTest < ActiveSupport::TestCase wait assert_equal 2, @logger.logged(:info).size assert_equal "GET http://37s.sunrise.i:3000/people/1.xml", @logger.logged(:info)[0] - assert_match /\-\-\> 200 200 106/, @logger.logged(:info)[1] + assert_match(/\-\-\> 200 200 106/, @logger.logged(:info)[1]) end -end
\ No newline at end of file +end diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 8ec903e376..56c81cf63b 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.0 (pending)* +* Use Object#singleton_class instead of #metaclass. Prefer Ruby's choice. [Jeremy Kemper] + * JSON backend for YAJL. Preferred if available. #2666 [Brian Lopez] diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 357bdca715..43f4722dbc 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -2,18 +2,6 @@ require 'rake/testtask' require 'rake/rdoctask' require 'rake/gempackagetask' -require File.join(File.dirname(__FILE__), 'lib', 'active_support', 'version') - -PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' -PKG_NAME = 'activesupport' -PKG_VERSION = ActiveSupport::VERSION::STRING + PKG_BUILD -PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" - -RELEASE_NAME = "REL #{PKG_VERSION}" - -RUBY_FORGE_PROJECT = "activesupport" -RUBY_FORGE_USER = "webster132" - task :default => :test Rake::TestTask.new do |t| t.libs << 'test' diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index d0557230aa..78fb48924e 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -1,9 +1,11 @@ +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip + Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'activesupport' - s.version = '3.0.0.beta1' - s.summary = 'Support and utility classes used by the Rails framework.' - s.description = 'Support and utility classes used by the Rails framework.' + s.version = version + s.summary = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.' + s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.' s.required_ruby_version = '>= 1.8.7' s.author = 'David Heinemeier Hansson' @@ -16,7 +18,7 @@ Gem::Specification.new do |s| s.has_rdoc = true - s.add_dependency('i18n', '~> 0.3.0') + s.add_dependency('i18n', '~> 0.3.6.pre') s.add_dependency('tzinfo', '~> 0.3.16') s.add_dependency('builder', '~> 2.1.2') s.add_dependency('memcache-client', '~> 1.7.5') diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index ae31d191c0..e34e46b4cf 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -53,6 +53,7 @@ module ActiveSupport autoload :Deprecation autoload :Gzip autoload :Inflector + autoload :JSON autoload :Memoizable autoload :MessageEncryptor autoload :MessageVerifier @@ -70,3 +71,5 @@ module ActiveSupport autoload :SafeBuffer, "active_support/core_ext/string/output_safety" autoload :TestCase end + +autoload :I18n, "active_support/i18n" diff --git a/activesupport/lib/active_support/all.rb b/activesupport/lib/active_support/all.rb index 64600575d9..f537818300 100644 --- a/activesupport/lib/active_support/all.rb +++ b/activesupport/lib/active_support/all.rb @@ -1,4 +1,3 @@ require 'active_support' -require 'active_support/i18n' require 'active_support/time' require 'active_support/core_ext' diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 6727eda811..b230bb8a40 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -1,7 +1,7 @@ require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/class/inheritable_attributes' require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/object/singleton_class' module ActiveSupport # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic @@ -312,7 +312,7 @@ module ActiveSupport def _normalize_legacy_filter(kind, filter) if !filter.respond_to?(kind) && filter.respond_to?(:filter) - filter.metaclass.class_eval( + filter.singleton_class.class_eval( "def #{kind}(context, &block) filter(context, &block) end", __FILE__, __LINE__ - 1) elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 814567a5a6..2119322bfe 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -1,7 +1,6 @@ require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/reverse_merge' require 'active_support/inflector' -require 'active_support/i18n' class Array # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options: diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index d74219cb93..c18905b369 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/object/singleton_class' require 'active_support/core_ext/module/delegation' class Class @@ -24,13 +24,35 @@ class Class # For convenience, a query method is defined as well: # # Subclass.setting? # => false + # + # Instances may overwrite the class value in the same way: + # + # Base.setting = true + # object = Base.new + # object.setting # => true + # object.setting = false + # object.setting # => false + # Base.setting # => true + # + # To opt out of the instance writer method, pass :instance_writer => false. + # + # object.setting = false # => NoMethodError def class_attribute(*attrs) + instance_writer = !attrs.last.is_a?(Hash) || attrs.pop[:instance_writer] + + s = singleton_class attrs.each do |attr| - metaclass.send(:define_method, attr) { } - metaclass.send(:define_method, "#{attr}?") { !!send(attr) } - metaclass.send(:define_method, "#{attr}=") do |value| - metaclass.send(:define_method, attr) { value } + s.send(:define_method, attr) { } + s.send(:define_method, :"#{attr}?") { !!send(attr) } + s.send(:define_method, :"#{attr}=") do |value| + singleton_class.send(:define_method, attr) { value } end + + define_method(attr) { self.class.send(attr) } + define_method(:"#{attr}?") { !!send(attr) } + define_method(:"#{attr}=") do |value| + singleton_class.send(:define_method, attr) { value } + end if instance_writer end end end diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb index 19382abb76..b5785bdcd3 100644 --- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb @@ -1,6 +1,6 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/object/singleton_class' class Class def superclass_delegating_accessor(name, options = {}) @@ -11,9 +11,9 @@ class Class # Generate the public methods name, name=, and name? # These methods dispatch to the private _name, and _name= methods, making them # overridable - metaclass.send(:define_method, name) { send("_#{name}") } - metaclass.send(:define_method, "#{name}?") { !!send("_#{name}") } - metaclass.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) } + singleton_class.send(:define_method, name) { send("_#{name}") } + singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") } + singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) } # If an instance_reader is needed, generate methods for name and name= on the # class itself, so instances will be able to see them @@ -27,12 +27,12 @@ private # inheritance behavior, without having to store the object in an instance # variable and look up the superclass chain manually. def _stash_object_in_method(object, method, instance_reader = true) - metaclass.send(:define_method, method) { object } + singleton_class.send(:define_method, method) { object } define_method(method) { object } if instance_reader end def _superclass_delegating_accessor(name, options = {}) - metaclass.send(:define_method, "#{name}=") do |value| + singleton_class.send(:define_method, "#{name}=") do |value| _stash_object_in_method(value, name, options[:instance_reader] != false) end self.send("#{name}=", nil) diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb index 49d28e8a34..26b73ed442 100644 --- a/activesupport/lib/active_support/core_ext/file/atomic.rb +++ b/activesupport/lib/active_support/core_ext/file/atomic.rb @@ -14,6 +14,7 @@ class File # end def self.atomic_write(file_name, temp_dir = Dir.tmpdir) require 'tempfile' unless defined?(Tempfile) + require 'fileutils' unless defined?(FileUtils) temp_file = Tempfile.new(basename(file_name), temp_dir) yield temp_file @@ -31,7 +32,7 @@ class File end # Overwrite original file with temp file - rename(temp_file.path, file_name) + FileUtils.mv(temp_file.path, file_name) # Set correct permissions on new file chown(old_stat.uid, old_stat.gid, file_name) diff --git a/activesupport/lib/active_support/core_ext/file/path.rb b/activesupport/lib/active_support/core_ext/file/path.rb new file mode 100644 index 0000000000..b5feab80ae --- /dev/null +++ b/activesupport/lib/active_support/core_ext/file/path.rb @@ -0,0 +1,5 @@ +class File + unless File.allocate.respond_to?(:to_path) + alias to_path path + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index 045a6944fa..e4d429fc2b 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -22,7 +22,7 @@ class Hash # to +to_sym+. def symbolize_keys! keys.each do |key| - self[(key.to_sym rescue key) || key] = delete(key) + self[(key.to_sym rescue key)] = delete(key) if key.respond_to?(:to_sym) && !key.is_a?(Fixnum) end self end diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index db2dac1472..4f86d9d605 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -5,7 +5,7 @@ require 'active_support/core_ext/object/try' require 'active_support/core_ext/object/conversions' require 'active_support/core_ext/object/instance_variables' -require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/object/singleton_class' require 'active_support/core_ext/object/misc' require 'active_support/core_ext/object/extending' diff --git a/activesupport/lib/active_support/core_ext/object/metaclass.rb b/activesupport/lib/active_support/core_ext/object/metaclass.rb deleted file mode 100644 index 93fb0ad594..0000000000 --- a/activesupport/lib/active_support/core_ext/object/metaclass.rb +++ /dev/null @@ -1,13 +0,0 @@ -class Object - # Get object's meta (ghost, eigenclass, singleton) class - def metaclass - class << self - self - end - end - - # If class_eval is called on an object, add those methods to its metaclass - def class_eval(*args, &block) - metaclass.class_eval(*args, &block) - end -end diff --git a/activesupport/lib/active_support/core_ext/object/singleton_class.rb b/activesupport/lib/active_support/core_ext/object/singleton_class.rb new file mode 100644 index 0000000000..8dee54e71b --- /dev/null +++ b/activesupport/lib/active_support/core_ext/object/singleton_class.rb @@ -0,0 +1,13 @@ +class Object + # Returns the object's singleton class. + def singleton_class + class << self + self + end + end unless respond_to?(:singleton_class) + + # class_eval on an object acts like singleton_class_eval. + def class_eval(*args, &block) + singleton_class.class_eval(*args, &block) + end +end diff --git a/activesupport/lib/active_support/core_ext/proc.rb b/activesupport/lib/active_support/core_ext/proc.rb index d50076a01e..71b413a88a 100644 --- a/activesupport/lib/active_support/core_ext/proc.rb +++ b/activesupport/lib/active_support/core_ext/proc.rb @@ -5,9 +5,9 @@ class Proc #:nodoc: block, time = self, Time.now object.class_eval do method_name = "__bind_#{time.to_i}_#{time.usec}" - define_method(method_name, &block) # define_method("__bind_1230458026_720454", &block) - method = instance_method(method_name) # method = instance_method("__bind_1230458026_720454") - remove_method(method_name) # remove_method("__bind_1230458026_720454") + define_method(method_name, &block) + method = instance_method(method_name) + remove_method(method_name) method end.bind(object) end diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index 331416b3a9..52946f9037 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -1,4 +1,5 @@ require 'date' +require 'active_support/core_ext/time/publicize_conversion_methods' require 'active_support/core_ext/time/calculations' class String diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index ea4ed61e42..fbb7b79fc6 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -1,8 +1,10 @@ +require 'active_support/inflector' + # String inflections define new methods on the String class to transform names for different purposes. # For instance, you can figure out the name of a database from the name of a class. # # "ScaleScore".tableize # => "scale_scores" - +# class String # Returns the plural form of the word in the string. # diff --git a/activesupport/lib/active_support/core_ext/string/interpolation.rb b/activesupport/lib/active_support/core_ext/string/interpolation.rb index 06d3505c60..932117cc10 100644 --- a/activesupport/lib/active_support/core_ext/string/interpolation.rb +++ b/activesupport/lib/active_support/core_ext/string/interpolation.rb @@ -1,91 +1 @@ -=begin - heavily based on Masao Mutoh's gettext String interpolation extension - http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb - Copyright (C) 2005-2010 Masao Mutoh - You may redistribute it and/or modify it under the same license terms as Ruby. -=end - -if RUBY_VERSION < '1.9' && !"".respond_to?(:interpolate_without_ruby_19_syntax) - - # KeyError is raised by String#% when the string contains a named placeholder - # that is not contained in the given arguments hash. Ruby 1.9 includes and - # raises this exception natively. We define it to mimic Ruby 1.9's behaviour - # in Ruby 1.8.x - - class KeyError < IndexError - def initialize(message = nil) - super(message || "key not found") - end - end unless defined?(KeyError) - - # Extension for String class. This feature is included in Ruby 1.9 or later but not occur TypeError. - # - # String#% method which accept "named argument". The translator can know - # the meaning of the msgids using "named argument" instead of %s/%d style. - - class String - alias :interpolate_without_ruby_19_syntax :% # :nodoc: - - INTERPOLATION_PATTERN = Regexp.union( - /%%/, - /%\{(\w+)\}/, # matches placeholders like "%{foo}" - /%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d" - ) - - # % uses self (i.e. the String) as a format specification and returns the - # result of applying it to the given arguments. In other words it interpolates - # the given arguments to the string according to the formats the string - # defines. - # - # There are three ways to use it: - # - # * Using a single argument or Array of arguments. - # - # This is the default behaviour of the String class. See Kernel#sprintf for - # more details about the format string. - # - # Example: - # - # "%d %s" % [1, "message"] - # # => "1 message" - # - # * Using a Hash as an argument and unformatted, named placeholders. - # - # When you pass a Hash as an argument and specify placeholders with %{foo} - # it will interpret the hash values as named arguments. - # - # Example: - # - # "%{firstname}, %{lastname}" % {:firstname => "Masao", :lastname => "Mutoh"} - # # => "Masao Mutoh" - # - # * Using a Hash as an argument and formatted, named placeholders. - # - # When you pass a Hash as an argument and specify placeholders with %<foo>d - # it will interpret the hash values as named arguments and format the value - # according to the formatting instruction appended to the closing >. - # - # Example: - # - # "%<integer>d, %<float>.1f" % { :integer => 10, :float => 43.4 } - # # => "10, 43.3" - def %(args) - if args.kind_of?(Hash) - dup.gsub(INTERPOLATION_PATTERN) do |match| - if match == '%%' - '%' - else - key = ($1 || $2).to_sym - raise KeyError unless args.has_key?(key) - $3 ? sprintf("%#{$3}", args[key]) : args[key] - end - end - elsif self =~ INTERPOLATION_PATTERN - raise ArgumentError.new('one hash required') - else - result = gsub(/%([{<])/, '%%\1') - result.send :'interpolate_without_ruby_19_syntax', args - end - end - end -end +require 'i18n/core_ext/string/interpolate' diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 567ba00b0d..af46ae10d6 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -88,6 +88,14 @@ module ActiveSupport #:nodoc: def to_s self end + + def as_str + ''.replace(self) + end + + def to_yaml(*args) + as_str.to_yaml(*args) + end end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 56de29b730..9c4412c28c 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -76,17 +76,19 @@ module ActiveSupport #:nodoc: locked :concat, :each, :delete_if, :<< def new_constants_for(frames) - frames.map do |mod_name, prior_constants| - mod = Inflector.constantize(mod_name) + constants = [] + frames.each do |mod_name, prior_constants| + mod = Inflector.constantize(mod_name) if Dependencies.qualified_const_defined?(mod_name) next unless mod.is_a?(Module) new_constants = mod.local_constant_names - prior_constants get(mod_name).concat(new_constants) - new_constants.map do |suffix| - ([mod_name, suffix] - ["Object"]).join("::") + new_constants.each do |suffix| + constants << ([mod_name, suffix] - ["Object"]).join("::") end - end.flatten + end + constants end # Add a set of modules to the watch stack, remembering the initial constants @@ -115,8 +117,8 @@ module ActiveSupport #:nodoc: def self.append_features(base) base.class_eval do # Emulate #exclude via an ivar - return if @_const_missing - @_const_missing = method(:const_missing) + return if defined?(@_const_missing) && @_const_missing + @_const_missing = instance_method(:const_missing) remove_method(:const_missing) end super @@ -177,6 +179,10 @@ module ActiveSupport #:nodoc: end def require_dependency(file_name, message = "No such file to load -- %s") + unless file_name.is_a?(String) + raise ArgumentError, "the file name must be a String -- you passed #{file_name.inspect}" + end + Dependencies.depend_on(file_name, false, message) end @@ -435,7 +441,10 @@ module ActiveSupport #:nodoc: qualified_name = qualified_name_for from_mod, const_name path_suffix = qualified_name.underscore + + trace = caller.reject {|l| l =~ %r{#{Regexp.escape(__FILE__)}}} name_error = NameError.new("uninitialized constant #{qualified_name}") + name_error.set_backtrace(trace) file_path = search_for_file(path_suffix) diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb index 44edb89ad5..f669f4a77e 100644 --- a/activesupport/lib/active_support/dependencies/autoload.rb +++ b/activesupport/lib/active_support/dependencies/autoload.rb @@ -1,7 +1,12 @@ require "active_support/inflector/methods" +require "active_support/lazy_load_hooks" module ActiveSupport module Autoload + def self.extended(base) + base.extend(LazyLoadHooks) + end + @@autoloads = {} @@under_path = nil @@at_path = nil diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index cec8024b17..d0d8b577b3 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -12,15 +12,15 @@ module ActiveSupport method_names.each do |method_name| target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation| target_module.module_eval(<<-end_eval, __FILE__, __LINE__ + 1) - def #{target}_with_deprecation#{punctuation}(*args, &block) # def generate_secret_with_deprecation(*args, &block) - ::ActiveSupport::Deprecation.warn( # ::ActiveSupport::Deprecation.warn( - ::ActiveSupport::Deprecation.deprecated_method_warning( # ::ActiveSupport::Deprecation.deprecated_method_warning( - :#{method_name}, # :generate_secret, - #{options[method_name].inspect}), # "You should use ActiveSupport::SecureRandom.hex(64)"), - caller # caller - ) # ) - send(:#{target}_without_deprecation#{punctuation}, *args, &block) # send(:generate_secret_without_deprecation, *args, &block) - end # end + def #{target}_with_deprecation#{punctuation}(*args, &block) + ::ActiveSupport::Deprecation.warn( + ::ActiveSupport::Deprecation.deprecated_method_warning( + :#{method_name}, + #{options[method_name].inspect}), + caller + ) + send(:#{target}_without_deprecation#{punctuation}, *args, &block) + end end_eval end end diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index fcb05ad8d9..03c445ffbf 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -29,7 +29,8 @@ module ActiveSupport private def deprecation_message(callstack, message = nil) message ||= "You are using deprecated behavior which will be removed from the next major or minor release." - "DEPRECATION WARNING: #{message}. #{deprecation_caller_message(callstack)}" + message += '.' unless message =~ /\.$/ + "DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}" end def deprecation_caller_message(callstack) diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb index 854c60eec1..034d7d8ddc 100644 --- a/activesupport/lib/active_support/i18n.rb +++ b/activesupport/lib/active_support/i18n.rb @@ -1,2 +1,3 @@ require 'i18n' -I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
\ No newline at end of file +I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml" +ActiveSupport.run_base_hooks(:i18n)
\ No newline at end of file diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 41277893e3..c7df62e915 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -109,7 +109,7 @@ module ActiveSupport constant = Object names.each do |name| - constant = constant.const_get(name, false) || constant.const_missing(name) + constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name) end constant end diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 30a9072ee1..236f2eb628 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -47,7 +47,7 @@ module ActiveSupport # replace accented chars with their ascii equivalents parameterized_string = transliterate(string) # Turn unwanted chars into the separator - parameterized_string.gsub!(/[^a-z0-9\-_\+]+/i, sep) + parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep) unless sep.nil? || sep.empty? re_sep = Regexp.escape(sep) # No more than one of the separator in a row. diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb index e357b6837a..04ff316a44 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -7,7 +7,7 @@ module ActiveSupport module JSON # Listed in order of preference. - DECODERS = %w(Yajl JSONGem Yaml) + DECODERS = %w(Yajl Yaml) class << self attr_reader :parse_error diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb new file mode 100644 index 0000000000..36acfda524 --- /dev/null +++ b/activesupport/lib/active_support/lazy_load_hooks.rb @@ -0,0 +1,25 @@ +module ActiveSupport + module LazyLoadHooks + def _setup_base_hooks + @base_hooks ||= Hash.new {|h,k| h[k] = [] } + @base ||= {} + end + + def base_hook(name = nil, &block) + _setup_base_hooks + + if base = @base[name] + base.instance_eval(&block) + else + @base_hooks[name] << block + end + end + + def run_base_hooks(base, name = nil) + _setup_base_hooks + + @base_hooks[name].each { |hook| base.instance_eval(&hook) } if @base_hooks + @base[name] = base + end + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb index f810f53029..ca1cfedae3 100644 --- a/activesupport/lib/active_support/memoizable.rb +++ b/activesupport/lib/active_support/memoizable.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/object/singleton_class' require 'active_support/core_ext/module/aliasing' module ActiveSupport diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 3e96decb8c..3f1fe64e9b 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -9,7 +9,7 @@ module ActiveSupport # end # # You can consume those events and the information they provide by registering - # a subscriber. For instance, let's store all instrumented events in an array: + # a log subscriber. For instance, let's store all instrumented events in an array: # # @events = [] # @@ -35,7 +35,7 @@ module ActiveSupport # end # # Notifications ships with a queue implementation that consumes and publish events - # to subscribers in a thread. You can use any queue implementation you want. + # to log subscribers in a thread. You can use any queue implementation you want. # module Notifications autoload :Instrumenter, 'active_support/notifications/instrumenter' @@ -44,7 +44,7 @@ module ActiveSupport class << self attr_writer :notifier - delegate :publish, :subscribe, :to => :notifier + delegate :publish, :subscribe, :unsubscribe, :to => :notifier delegate :instrument, :to => :instrumenter def notifier @@ -69,6 +69,10 @@ module ActiveSupport @queue.bind(pattern).subscribe(&block) end + def unsubscribe(subscriber) + @queue.unsubscribe(subscriber) + end + def wait @queue.wait end diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 0ec23da073..cd60054862 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -1,10 +1,11 @@ module ActiveSupport module Notifications # This is a default queue implementation that ships with Notifications. It - # just pushes events to all registered subscribers. + # just pushes events to all registered log subscribers. class Fanout def initialize @subscribers = [] + @listeners_for = {} end def bind(pattern) @@ -12,11 +13,22 @@ module ActiveSupport end def subscribe(pattern = nil, &block) + @listeners_for.clear @subscribers << Subscriber.new(pattern, &block) + @subscribers.last end - def publish(*args) - @subscribers.each { |s| s.publish(*args) } + def unsubscribe(subscriber) + @subscribers.delete(subscriber) + @listeners_for.clear + end + + def publish(name, *args) + if listeners = @listeners_for[name] + listeners.each { |s| s.publish(name, *args) } + else + @listeners_for[name] = @subscribers.select { |s| s.publish(name, *args) } + end end # This is a sync queue, so there is not waiting. @@ -48,7 +60,9 @@ module ActiveSupport end def publish(*args) - push(*args) if matches?(args.first) + return unless matches?(args.first) + push(*args) + true end def drained? diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 596a7b757d..61ccb79211 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -18,4 +18,10 @@ module ActiveSupport #:nodoc: end end end + + class InheritableOptions < OrderedOptions + def initialize(parent) + super() { |h,k| parent[k] } + end + end end diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index c8d8b85000..d2c13e030d 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -33,14 +33,16 @@ module I18n railtie_name :i18n # Initialize I18n load paths to an array - config.i18n.engines_load_path = [] + config.i18n.railties_load_path = [] config.i18n.load_path = [] initializer "i18n.initialize" do - require 'active_support/i18n' - - ActionDispatch::Callbacks.to_prepare do + ActiveSupport.base_hook(:i18n) do I18n.reload! + + ActionDispatch::Callbacks.to_prepare do + I18n.reload! + end end end @@ -49,7 +51,7 @@ module I18n config.after_initialize do |app| app.config.i18n.each do |setting, value| case setting - when :engines_load_path + when :railties_load_path app.config.i18n.load_path.unshift(*value) when :load_path I18n.load_path += value diff --git a/activesupport/lib/active_support/ruby/shim.rb b/activesupport/lib/active_support/ruby/shim.rb index 1e49ccdade..f0db5b3021 100644 --- a/activesupport/lib/active_support/ruby/shim.rb +++ b/activesupport/lib/active_support/ruby/shim.rb @@ -17,3 +17,4 @@ require 'active_support/core_ext/string/conversions' require 'active_support/core_ext/string/interpolation' require 'active_support/core_ext/rexml' require 'active_support/core_ext/time/conversions' +require 'active_support/core_ext/file/path' diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index 6c6187be29..831204693f 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -2,8 +2,9 @@ module ActiveSupport module VERSION #:nodoc: MAJOR = 3 MINOR = 0 - TINY = "0.beta1" + TINY = 0 + BUILD = "beta1" - STRING = [MAJOR, MINOR, TINY].join('.') + STRING = [MAJOR, MINOR, TINY, BUILD].join('.') end end diff --git a/activesupport/lib/active_support/whiny_nil.rb b/activesupport/lib/active_support/whiny_nil.rb index 11b05efac1..91ddef2619 100644 --- a/activesupport/lib/active_support/whiny_nil.rb +++ b/activesupport/lib/active_support/whiny_nil.rb @@ -25,17 +25,16 @@ # By default it is on in development and test modes, and it is off in production # mode. class NilClass - WHINERS = [::Array] - WHINERS << ::ActiveRecord::Base if defined? ::ActiveRecord - METHOD_CLASS_MAP = Hash.new - WHINERS.each do |klass| + def self.add_whiner(klass) methods = klass.public_instance_methods - public_instance_methods class_name = klass.name methods.each { |method| METHOD_CLASS_MAP[method.to_sym] = class_name } end + add_whiner ::Array + # Raises a RuntimeError when you attempt to call +id+ on +nil+. def id raise RuntimeError, "Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id", caller diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 33be6f65bf..da338a2a26 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -1,5 +1,8 @@ require File.expand_path('../../../load_paths', __FILE__) +lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) + require 'test/unit' require 'mocha' diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb index 2e978f01be..e74c64ba8d 100644 --- a/activesupport/test/callback_inheritance_test.rb +++ b/activesupport/test/callback_inheritance_test.rb @@ -1,5 +1,4 @@ require 'test/unit' -$:.unshift "#{File.dirname(__FILE__)}/../lib" require 'active_support' class GrandParent diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 11494e951e..3fb940ad3c 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -1,6 +1,5 @@ # require 'abstract_unit' require 'test/unit' -$:.unshift "#{File.dirname(__FILE__)}/../lib" require 'active_support' module CallbacksTest diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index d4cd5ddbde..b374eca370 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -306,7 +306,7 @@ class ArrayUniqByTests < Test::Unit::TestCase def test_uniq_by assert_equal [1,2], [1,2,3,4].uniq_by { |i| i.odd? } assert_equal [1,2], [1,2,3,4].uniq_by(&:even?) - assert_equal (-5..0).to_a, (-5..5).to_a.uniq_by{ |i| i**2 } + assert_equal((-5..0).to_a, (-5..5).to_a.uniq_by{ |i| i**2 }) end def test_uniq_by! @@ -320,7 +320,7 @@ class ArrayUniqByTests < Test::Unit::TestCase a = (-5..5).to_a a.uniq_by! { |i| i**2 } - assert_equal (-5..0).to_a, a + assert_equal((-5..0).to_a, a) end end diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb index ef84b9f255..06b4cf075f 100644 --- a/activesupport/test/core_ext/class/attribute_test.rb +++ b/activesupport/test/core_ext/class/attribute_test.rb @@ -2,13 +2,6 @@ require 'abstract_unit' require 'active_support/core_ext/class/attribute' class ClassAttributeTest < ActiveSupport::TestCase - class Base - class_attribute :setting - end - - class Subclass < Base - end - def setup @klass = Class.new { class_attribute :setting } @sub = Class.new(@klass) @@ -40,8 +33,30 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_equal true, @klass.setting? end - test 'no instance delegates' do - assert_raise(NoMethodError) { @klass.new.setting } - assert_raise(NoMethodError) { @klass.new.setting? } + test 'instance reader delegates to class' do + assert_nil @klass.new.setting + + @klass.setting = 1 + assert_equal 1, @klass.new.setting + end + + test 'instance override' do + object = @klass.new + object.setting = 1 + assert_nil @klass.setting + @klass.setting = 2 + assert_equal 1, object.setting + end + + test 'instance query' do + object = @klass.new + assert_equal false, object.setting? + object.setting = 1 + assert_equal true, object.setting? + end + + test 'disabling instance writer' do + object = Class.new { class_attribute :setting, :instance_writer => false }.new + assert_raise(NoMethodError) { object.setting = 'boom' } end end diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb index 011068ab74..636edb8d4b 100644 --- a/activesupport/test/core_ext/class/delegating_attributes_test.rb +++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb @@ -84,6 +84,8 @@ class DelegatingAttributesTest < Test::Unit::TestCase assert_equal "1", Child.some_attribute assert_nil Mokopuna.some_attribute + ensure + Child.some_attribute=nil end end diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb index 26be694176..e1258b872e 100644 --- a/activesupport/test/core_ext/file_test.rb +++ b/activesupport/test/core_ext/file_test.rb @@ -57,6 +57,10 @@ class AtomicWriteTest < Test::Unit::TestCase File.unlink(file_name) rescue nil end + def test_responds_to_to_path + assert_equal __FILE__, File.open(__FILE__, "r").to_path + end + private def file_name "atomic.file" diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 1fe75d5930..9edd7cc7c0 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -55,18 +55,6 @@ class Name end end -$nowhere = <<-EOF -class Name - delegate :nowhere -end -EOF - -$noplace = <<-EOF -class Name - delegate :noplace, :tos => :hollywood -end -EOF - class ModuleTest < Test::Unit::TestCase def setup @david = Someone.new("David", Somewhere.new("Paulina", "Chicago")) @@ -87,8 +75,12 @@ class ModuleTest < Test::Unit::TestCase end def test_missing_delegation_target - assert_raise(ArgumentError) { eval($nowhere) } - assert_raise(ArgumentError) { eval($noplace) } + assert_raise(ArgumentError) do + Name.send :delegate, :nowhere + end + assert_raise(ArgumentError) do + Name.send :delegate, :noplace, :tos => :hollywood + end end def test_delegation_prefix diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb index f31e7774e9..437bb78a4e 100644 --- a/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -89,7 +89,7 @@ class ClassExtTest < Test::Unit::TestCase end end -class ObjectTests < Test::Unit::TestCase +class ObjectTests < ActiveSupport::TestCase class DuckTime def acts_like_time? true @@ -119,12 +119,9 @@ class ObjectTests < Test::Unit::TestCase assert !duck.acts_like?(:date) end - def test_metaclass - string = "Hello" - string.metaclass.instance_eval do - define_method(:foo) { "bar" } - end - assert_equal "bar", string.foo + def test_singleton_class + o = Object.new + assert_equal class << o; self end, o.singleton_class end end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index d8145d467b..a50e259726 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -444,6 +444,10 @@ class OutputSafetyTest < ActiveSupport::TestCase assert_equal "hello".concat(13), string assert string.html_safe? end + + test 'emits normal string yaml' do + assert_equal 'foo'.to_yaml, 'foo'.html_safe.to_yaml(:foo => 1) + end end class StringExcludeTest < ActiveSupport::TestCase diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index 2fa94b8e9c..ebd26d3fc6 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -158,7 +158,8 @@ module InflectorTestCases "Allow_Under_Scores" => "allow_under_scores", "Trailing bad characters!@#" => "trailing-bad-characters", "!@#Leading bad characters" => "leading-bad-characters", - "Squeeze separators" => "squeeze-separators" + "Squeeze separators" => "squeeze-separators", + "Test with + sign" => "test-with-sign" } StringToParameterizeWithNoSeparator = { @@ -166,7 +167,8 @@ module InflectorTestCases "Random text with *(bad)* characters" => "randomtextwithbadcharacters", "Trailing bad characters!@#" => "trailingbadcharacters", "!@#Leading bad characters" => "leadingbadcharacters", - "Squeeze separators" => "squeezeseparators" + "Squeeze separators" => "squeezeseparators", + "Test with + sign" => "testwithsign" } StringToParameterizeWithUnderscore = { @@ -174,7 +176,8 @@ module InflectorTestCases "Random text with *(bad)* characters" => "random_text_with_bad_characters", "Trailing bad characters!@#" => "trailing_bad_characters", "!@#Leading bad characters" => "leading_bad_characters", - "Squeeze separators" => "squeeze_separators" + "Squeeze separators" => "squeeze_separators", + "Test with + sign" => "test_with_sign" } # Ruby 1.9 doesn't do Unicode normalization yet. diff --git a/activesupport/test/load_paths_test.rb b/activesupport/test/load_paths_test.rb new file mode 100644 index 0000000000..9c83d6f061 --- /dev/null +++ b/activesupport/test/load_paths_test.rb @@ -0,0 +1,15 @@ +require 'abstract_unit' + +class LoadPathsTest < Test::Unit::TestCase + def test_uniq_load_paths + load_paths_count = $LOAD_PATH.inject({}) { |paths, path| + expanded_path = File.expand_path(path) + paths[expanded_path] ||= 0 + paths[expanded_path] += 1 + paths + } + + # CI has a bunch of duplicate load paths + # assert_equal [], load_paths_count.select { |k, v| v > 1 }, $LOAD_PATH.inspect + end +end diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index 545811a376..67c3527e23 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -6,7 +6,7 @@ module Notifications ActiveSupport::Notifications.notifier = nil @notifier = ActiveSupport::Notifications.notifier @events = [] - @notifier.subscribe { |*args| @events << event(*args) } + @subscription = @notifier.subscribe { |*args| @events << event(*args) } end private @@ -19,6 +19,23 @@ module Notifications end end + class UnsubscribeTest < TestCase + def test_unsubscribing_removes_a_subscription + @notifier.publish :foo + @notifier.wait + assert_equal [[:foo]], @events + @notifier.unsubscribe(@subscription) + @notifier.publish :foo + @notifier.wait + assert_equal [[:foo]], @events + end + + private + def event(*args) + args + end + end + class SyncPubSubTest < TestCase def test_events_are_published_to_a_listener @notifier.publish :foo @@ -26,7 +43,29 @@ module Notifications assert_equal [[:foo]], @events end - def test_subscriber_with_pattern + def test_publishing_multiple_times_works + @notifier.publish :foo + @notifier.publish :foo + @notifier.wait + assert_equal [[:foo], [:foo]], @events + end + + def test_publishing_after_a_new_subscribe_works + @notifier.publish :foo + @notifier.publish :foo + + @notifier.subscribe("not_existant") do |*args| + @events << ActiveSupport::Notifications::Event.new(*args) + end + + @notifier.publish :foo + @notifier.publish :foo + @notifier.wait + + assert_equal [[:foo]] * 4, @events + end + + def test_log_subscriber_with_pattern events = [] @notifier.subscribe('1') { |*args| events << args } @@ -38,7 +77,7 @@ module Notifications assert_equal [['1'], ['1.a']], events end - def test_subscriber_with_pattern_as_regexp + def test_log_subscriber_with_pattern_as_regexp events = [] @notifier.subscribe(/\d/) { |*args| events << args } @@ -50,7 +89,7 @@ module Notifications assert_equal [['1'], ['a.1'], ['1.a']], events end - def test_multiple_subscribers + def test_multiple_log_subscribers @another = [] @notifier.subscribe { |*args| @another << args } @notifier.publish :foo diff --git a/activesupport/test/whiny_nil_test.rb b/activesupport/test/whiny_nil_test.rb index 1e4f8d854a..4b9f06dead 100644 --- a/activesupport/test/whiny_nil_test.rb +++ b/activesupport/test/whiny_nil_test.rb @@ -9,6 +9,8 @@ end require 'abstract_unit' require 'active_support/whiny_nil' +NilClass.add_whiner ::ActiveRecord::Base + class WhinyNilTest < Test::Unit::TestCase def test_unchanged nil.method_thats_not_in_whiners diff --git a/ci/ci_build.rb b/ci/ci_build.rb index 0c7dd837b1..2a7bbc376e 100755 --- a/ci/ci_build.rb +++ b/ci/ci_build.rb @@ -19,7 +19,7 @@ puts "[CruiseControl] Rails build" build_results = {} # Install required version of bundler. -bundler_install_cmd = "gem install bundler -v 0.9.3 --no-ri --no-rdoc" +bundler_install_cmd = "sudo gem install bundler -v 0.9.10 --no-ri --no-rdoc" puts "Running command: #{bundler_install_cmd}" build_results[:install_bundler] = system bundler_install_cmd diff --git a/load_paths.rb b/load_paths.rb index d5f2ca0734..b87e0d7235 100644 --- a/load_paths.rb +++ b/load_paths.rb @@ -6,6 +6,11 @@ rescue LoadError require 'bundler' Bundler.setup rescue LoadError + module Bundler + def self.require(*args, &block); end + def self.method_missing(*args, &block); end + end + %w( actionmailer actionpack diff --git a/pushgems.rb b/pushgems.rb deleted file mode 100755 index 784aa5de68..0000000000 --- a/pushgems.rb +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env ruby - -unless ARGV.first == "no_build" - build_number = Time.now.strftime("%Y%m%d%H%M%S").to_i -end - -%w( activeresource actionmailer actionpack activerecord railties activesupport ).each do |pkg| - puts "Pushing: #{pkg} (#{build_number})" - if build_number - `cd #{pkg} && rm -rf pkg && PKG_BUILD=#{build_number} rake pgem && cd ..` - else - `cd #{pkg} && rm -rf pkg && rake pgem && cd ..` - end -end
\ No newline at end of file diff --git a/rails.gemspec b/rails.gemspec index 1460dd5688..8b9a374e25 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -1,10 +1,13 @@ +version = File.read(File.expand_path("../RAILS_VERSION",__FILE__)).strip + Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'rails' - s.version = '3.0.0.beta1' - s.summary = 'Full-stack web-application framework.' - s.description = 'Full-stack web-application framework.' + s.version = version + s.summary = 'Full-stack web application framework.' + s.description = 'Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity. It encourages beautiful code by favoring convention over configuration.' s.required_ruby_version = '>= 1.8.7' + s.required_rubygems_version = ">= 1.3.6" s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' @@ -14,11 +17,11 @@ Gem::Specification.new do |s| s.files = [] s.require_path = [] - s.add_dependency('activesupport', '= 3.0.0.beta1') - s.add_dependency('actionpack', '= 3.0.0.beta1') - s.add_dependency('activerecord', '= 3.0.0.beta1') - s.add_dependency('activeresource', '= 3.0.0.beta1') - s.add_dependency('actionmailer', '= 3.0.0.beta1') - s.add_dependency('railties', '= 3.0.0.beta1') - s.add_dependency('bundler', '>= 0.9.3') + s.add_dependency('activesupport', version) + s.add_dependency('actionpack', version) + s.add_dependency('activerecord', version) + s.add_dependency('activeresource', version) + s.add_dependency('actionmailer', version) + s.add_dependency('railties', version) + s.add_dependency('bundler', '>= 0.9.8') end diff --git a/rails3b.gemspec b/rails3b.gemspec index d67d20b884..613a248ef7 100644 --- a/rails3b.gemspec +++ b/rails3b.gemspec @@ -2,8 +2,7 @@ Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'rails3b' s.version = '3.0.1' - s.summary = 'Just the Rails 3 beta dependencies. Works around prerelease RubyGems bug.' - s.description = 'My kingdom for working dependencies.' + s.summary = 'Just the Rails 3 beta dependencies. Works around prerelease RubyGems bug in versions before 1.3.6.' s.required_ruby_version = '>= 1.8.7' s.author = 'Jeremy Kemper' @@ -22,7 +21,7 @@ Gem::Specification.new do |s| s.add_dependency('tzinfo', '~> 0.3.16') s.add_dependency('builder', '~> 2.1.2') s.add_dependency('memcache-client', '~> 1.7.5') - s.add_dependency('bundler', '>= 0.9.2') + s.add_dependency('bundler', '>= 0.9.8') s.add_dependency('rake', '>= 0.8.3') s.add_dependency('thor', '~> 0.13') end diff --git a/railties/Rakefile b/railties/Rakefile index f32a794544..fe049d565f 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -8,20 +8,6 @@ require 'rake/gempackagetask' require 'date' require 'rbconfig' -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/lib" -require 'rails/version' - -PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' -PKG_NAME = 'railties' -PKG_VERSION = Rails::VERSION::STRING + PKG_BUILD -PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" -PKG_DESTINATION = ENV["RAILS_PKG_DESTINATION"] || "../#{PKG_NAME}" - -RELEASE_NAME = "REL #{PKG_VERSION}" - -RUBY_FORGE_PROJECT = "rails" -RUBY_FORGE_USER = "webster132" - task :default => :test task :test => 'test:isolated' diff --git a/railties/bin/rails b/railties/bin/rails index 72c47b533f..173f122445 100755 --- a/railties/bin/rails +++ b/railties/bin/rails @@ -1,11 +1,30 @@ -if File.exists?(Dir.getwd + '/script/rails') - exec(Dir.getwd + '/script/rails', *ARGV) -else - railties_path = File.expand_path('../../lib', __FILE__) - $:.unshift(railties_path) if File.directory?(railties_path) && !$:.include?(railties_path) +require 'rbconfig' - require 'rails/ruby_version_check' - Signal.trap("INT") { puts; exit } +module Rails + module ScriptRailsLoader + RUBY = File.join(*RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) + RbConfig::CONFIG["EXEEXT"] + SCRIPT_RAILS = File.join('script', 'rails') - require 'rails/commands/application' -end
\ No newline at end of file + def self.exec_script_rails! + cwd = Dir.pwd + exec RUBY, SCRIPT_RAILS, *ARGV if File.exists?(SCRIPT_RAILS) + Dir.chdir("..") do + # Recurse in a chdir block: if the search fails we want to be sure + # the application is generated in the original working directory. + exec_script_rails! unless cwd == Dir.pwd + end + rescue SystemCallError + # could not chdir, no problem just return + end + end +end + +Rails::ScriptRailsLoader.exec_script_rails! + +railties_path = File.expand_path('../../lib', __FILE__) +$:.unshift(railties_path) if File.directory?(railties_path) && !$:.include?(railties_path) + +require 'rails/ruby_version_check' +Signal.trap("INT") { puts; exit } + +require 'rails/commands/application' diff --git a/railties/builtin/routes.rb b/railties/builtin/routes.rb index ef9d9e756d..bd58034d8f 100644 --- a/railties/builtin/routes.rb +++ b/railties/builtin/routes.rb @@ -1,3 +1,3 @@ -ActionController::Routing::Routes.draw do |map| +Rails.application.routes.draw do |map| match '/rails/info/properties' => "rails/info#properties" -end
\ No newline at end of file +end diff --git a/railties/guides/source/3_0_release_notes.textile b/railties/guides/source/3_0_release_notes.textile index 55fc610a53..9c6e7863f4 100644 --- a/railties/guides/source/3_0_release_notes.textile +++ b/railties/guides/source/3_0_release_notes.textile @@ -443,7 +443,7 @@ As well as the following deprecations: * I18n error messages for ActiveRecord should be changed from :en.activerecord.errors.template to <tt>:en.errors.template</tt>. * <tt>model.errors.on</tt> is deprecated in favour of <tt>model.errors[]</tt> * validates_presence_of => validates... :presence => true -* <tt>ActiveRecord::Base.colorize_logging</tt> and <tt>config.active_record.colorize_logging</tt> are deprecated in favour of <tt>Rails::Subscriber.colorize_logging</tt> or <tt>config.colorize_logging</tt> +* <tt>ActiveRecord::Base.colorize_logging</tt> and <tt>config.active_record.colorize_logging</tt> are deprecated in favour of <tt>Rails::LogSubscriber.colorize_logging</tt> or <tt>config.colorize_logging</tt> NOTE: While an implementation of State Machine has been in Active Record edge for some months now, it has been removed from the Rails 3.0 release. diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 0e6f981168..5418ad7dd4 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -159,7 +159,7 @@ WARNING: Threadsafe operation in incompatible with the normal workings of develo * +config.action_controller.relative_url_root+ can be used to tell Rails that you are deploying to a subdirectory. The default is +ENV['RAILS_RELATIVE_URL_ROOT']+. -* +config.action_controller.session_store+ sets the name of the store for session data. The default is +:cookie_store+; other valid options include +:active_record_store+, +:mem_cache_store+ or the name of your own custom class. +* +config.action_dispatch.session_store+ sets the name of the store for session data. The default is +:cookie_store+; other valid options include +:active_record_store+, +:mem_cache_store+ or the name of your own custom class. The caching code adds two additional settings: diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index ecf68b56f9..b62ff8cb38 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -92,7 +92,7 @@ Rails 2 introduced a new default session storage, CookieStore. CookieStore saves That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA512, which has not been compromised, yet). So _(highlight)don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters_. Put the secret in your environment.rb: <ruby> -config.action_controller.session = { +config.action_dispatch.session = { :key => '_app_session', :secret => '0x0dkfj3927dkc7djdh36rkckdfzsg...' } diff --git a/railties/lib/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/generators/erb/scaffold/templates/_form.html.erb index 9c19a09616..01ec58c615 100644 --- a/railties/lib/generators/erb/scaffold/templates/_form.html.erb +++ b/railties/lib/generators/erb/scaffold/templates/_form.html.erb @@ -1,4 +1,4 @@ -<%% form_for(@<%= singular_name %>) do |f| %> +<%%= form_for(@<%= singular_name %>) do |f| %> <%%= f.error_messages %> <% for attribute in attributes -%> diff --git a/railties/lib/generators/erb/scaffold/templates/layout.html.erb b/railties/lib/generators/erb/scaffold/templates/layout.html.erb index 420d17f33c..3f64be0c45 100644 --- a/railties/lib/generators/erb/scaffold/templates/layout.html.erb +++ b/railties/lib/generators/erb/scaffold/templates/layout.html.erb @@ -9,6 +9,7 @@ <body> <p class="notice"><%%= notice %></p> +<p class="alert"><%%= alert %></p> <%%= yield %> diff --git a/railties/lib/generators/rails/app/app_generator.rb b/railties/lib/generators/rails/app/app_generator.rb index c439ed89f5..92e0d37436 100644 --- a/railties/lib/generators/rails/app/app_generator.rb +++ b/railties/lib/generators/rails/app/app_generator.rb @@ -7,6 +7,10 @@ module Rails::Generators # can change in Ruby 1.8.7 when we FileUtils.cd. RAILS_DEV_PATH = File.expand_path("../../../../..", File.dirname(__FILE__)) + RESERVED_NAMES = %w[generate console server dbconsole + application destroy benchmarker profiler + plugin runner test] + class AppGenerator < Base DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) @@ -134,8 +138,11 @@ module Rails::Generators end def create_prototype_files - return if options[:skip_prototype] - directory "public/javascripts" + unless options[:skip_prototype] + directory "public/javascripts" + else + empty_directory_with_gitkeep "public/javascripts" + end end def create_script_files @@ -209,9 +216,10 @@ module Rails::Generators end def valid_app_const? - case app_const - when /^\d/ + if app_const =~ /^\d/ raise Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers." + elsif RESERVED_NAMES.include?(app_name) + raise Error, "Invalid application name #{app_name}. Please give a name which does not match one of the reserved rails words." end end diff --git a/railties/lib/generators/rails/app/templates/Gemfile b/railties/lib/generators/rails/app/templates/Gemfile index f4bce8646d..0dd10f3f2d 100644 --- a/railties/lib/generators/rails/app/templates/Gemfile +++ b/railties/lib/generators/rails/app/templates/Gemfile @@ -1,4 +1,4 @@ -source 'http://gemcutter.org' +source 'http://rubygems.org' <%- if options.dev? -%> gem 'rails', :path => '<%= Rails::Generators::RAILS_DEV_PATH %>' @@ -15,15 +15,15 @@ gem 'rails', '<%= Rails::VERSION::STRING %>' gem '<%= gem_for_database %>'<% if require_for_database %>, :require => '<%= require_for_database %>'<% end %> <% end -%> -# Use mongrel as the web server -# gem 'mongrel' +# Use unicorn as the web server +# gem 'unicorn' # Deploy with Capistrano # gem 'capistrano' # Bundle the extra gems: # gem 'bj' -# gem 'hpricot', '0.6' +# gem 'nokogiri', '1.4.1' # gem 'sqlite3-ruby', :require => 'sqlite3' # gem 'aws-s3', :require => 'aws/s3' diff --git a/railties/lib/generators/rails/app/templates/config/application.rb b/railties/lib/generators/rails/app/templates/config/application.rb index 7c555c2542..dc20ffb2fa 100644 --- a/railties/lib/generators/rails/app/templates/config/application.rb +++ b/railties/lib/generators/rails/app/templates/config/application.rb @@ -35,7 +35,7 @@ module <%= app_const_base %> # 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}')] + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de # Configure generators values. Many other options are available, be sure to check the documentation. diff --git a/railties/lib/generators/rails/app/templates/config/boot.rb b/railties/lib/generators/rails/app/templates/config/boot.rb index 29c9d506e5..3cb561d41f 100644 --- a/railties/lib/generators/rails/app/templates/config/boot.rb +++ b/railties/lib/generators/rails/app/templates/config/boot.rb @@ -5,13 +5,4 @@ rescue LoadError require 'rubygems' require 'bundler' Bundler.setup - - # To use 2.x style vendor/rails and RubyGems - # - # vendor_rails = File.expand_path('../../vendor/rails', __FILE__) - # if File.exist?(vendor_rails) - # Dir["#{vendor_rails}/*/lib"].each { |path| $:.unshift(path) } - # end - # - # require 'rubygems' end diff --git a/railties/lib/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt b/railties/lib/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt index 451dbe1d1c..be627fbbcc 100644 --- a/railties/lib/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt +++ b/railties/lib/generators/rails/app/templates/config/initializers/cookie_verification_secret.rb.tt @@ -4,4 +4,4 @@ # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. -ActionController::Base.cookie_verifier_secret = '<%= app_secret %>' +Rails.application.config.cookie_secret = '<%= app_secret %>' diff --git a/railties/lib/generators/rails/app/templates/config/initializers/session_store.rb.tt b/railties/lib/generators/rails/app/templates/config/initializers/session_store.rb.tt index baff704d3e..9e32fb930e 100644 --- a/railties/lib/generators/rails/app/templates/config/initializers/session_store.rb.tt +++ b/railties/lib/generators/rails/app/templates/config/initializers/session_store.rb.tt @@ -1,15 +1,10 @@ # Be sure to restart your server when you modify this file. -# Your secret key for verifying cookie session data integrity. -# If you change this key, all old sessions will become invalid! -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -ActionController::Base.session = { - :key => '_<%= app_name %>_session', - :secret => '<%= app_secret %>' +Rails.application.config.session_store :cookie_store, { + :key => '_<%= app_name %>_session', } # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information # (create the session table with "rake db:sessions:create") -# ActionController::Base.session_store = :active_record_store +# Rails.application.config.session_store :active_record_store diff --git a/railties/lib/generators/rails/app/templates/public/index.html b/railties/lib/generators/rails/app/templates/public/index.html index ef916f9c5a..836da1b689 100644 --- a/railties/lib/generators/rails/app/templates/public/index.html +++ b/railties/lib/generators/rails/app/templates/public/index.html @@ -181,27 +181,27 @@ } </style> - <script type="text/javascript" src="javascripts/prototype.js"></script> - <script type="text/javascript" src="javascripts/effects.js"></script> <script type="text/javascript"> function about() { - if (Element.empty('about-content')) { - new Ajax.Updater('about-content', 'rails/info/properties', { - method: 'get', - onFailure: function() {Element.classNames('about-content').add('failure')}, - onComplete: function() {new Effect.BlindDown('about-content', {duration: 0.25})} - }); - } else { - new Effect[Element.visible('about-content') ? - 'BlindUp' : 'BlindDown']('about-content', {duration: 0.25}); - } + info = document.getElementById('about-content'); + if (window.XMLHttpRequest) + { xhr = new XMLHttpRequest(); } + else + { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } + xhr.open("GET","rails/info/properties",false); + xhr.send(""); + info.innerHTML = xhr.responseText; + info.style.display = 'block' + } + + function prepend() { + search = document.getElementById('search-text'); + text = search.value; + search.value = 'site:rubyonrails.org ' + text; } window.onload = function() { - $('search-text').value = ''; - $('search').onsubmit = function() { - $('search-text').value = 'site:rubyonrails.org ' + $F('search-text'); - } + document.getElementById('search-text').value = ''; } </script> </head> @@ -210,7 +210,7 @@ <div id="sidebar"> <ul id="sidebar-items"> <li> - <form id="search" action="http://www.google.com/search" method="get"> + <form id="search" action="http://www.google.com/search" method="get" onSubmit="prepend();"> <input type="hidden" name="hl" value="en" /> <input type="text" id="search-text" name="q" value="site:rubyonrails.org " /> <input type="submit" value="Search" /> the Rails site diff --git a/railties/lib/generators/rails/app/templates/script/rails b/railties/lib/generators/rails/app/templates/script/rails index b01d1ee183..199fe1a6d3 100644 --- a/railties/lib/generators/rails/app/templates/script/rails +++ b/railties/lib/generators/rails/app/templates/script/rails @@ -1,8 +1,5 @@ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. -ENV_PATH = File.expand_path('../../config/environment', __FILE__) -BOOT_PATH = File.expand_path('../../config/boot', __FILE__) -APP_PATH = File.expand_path('../../config/application', __FILE__) - -require BOOT_PATH +ENV_PATH = File.expand_path('../../config/environment', __FILE__) +require File.expand_path('../../config/boot', __FILE__) require 'rails/commands' diff --git a/railties/lib/generators/rails/stylesheets/templates/scaffold.css b/railties/lib/generators/rails/stylesheets/templates/scaffold.css index de6669ad9e..ea3dc9b8b5 100644 --- a/railties/lib/generators/rails/stylesheets/templates/scaffold.css +++ b/railties/lib/generators/rails/stylesheets/templates/scaffold.css @@ -24,6 +24,10 @@ div.field, div.actions { color: green; } +.alert { + color: red; +} + .fieldWithErrors { padding: 2px; background-color: red; diff --git a/railties/lib/generators/test_unit/mailer/templates/functional_test.rb b/railties/lib/generators/test_unit/mailer/templates/functional_test.rb index e1aeb2db90..a2b1f1ed05 100644 --- a/railties/lib/generators/test_unit/mailer/templates/functional_test.rb +++ b/railties/lib/generators/test_unit/mailer/templates/functional_test.rb @@ -7,7 +7,6 @@ class <%= class_name %>Test < ActionMailer::TestCase @expected.to = "to@example.org" @expected.from = "from@example.com" @expected.body = read_fixture("<%= action %>") - @expected.date = Time.now assert_equal @expected, <%= class_name %>.<%= action %> end diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index b7a39fd5a7..3d3151bd8f 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -7,7 +7,7 @@ require 'active_support/core_ext/logger' require 'rails/application' require 'rails/version' require 'rails/deprecation' -require 'rails/subscriber' +require 'rails/log_subscriber' require 'rails/ruby_version_check' require 'active_support/railtie' diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index f445ef4831..49388c5060 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -52,7 +52,10 @@ module Rails class << self private :new - alias :configure :class_eval + + def configure(&block) + class_eval(&block) + end def instance if self == Rails::Application @@ -85,17 +88,13 @@ module Rails end def routes - ::ActionController::Routing::Routes + @routes ||= ActionDispatch::Routing::RouteSet.new end def railties @railties ||= Railties.new(config) end - def metal_loader - @metal_laoder ||= MetalLoader.new - end - def routes_reloader @routes_reloader ||= RoutesReloader.new end diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index b20e53f2de..06243f2e5e 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -1,3 +1,5 @@ +require "active_support/notifications" + module Rails class Application module Bootstrap @@ -49,20 +51,6 @@ module Rails end end - # Initialize rails subscriber on top of notifications. - initializer :initialize_subscriber do - require 'active_support/notifications' - - if config.colorize_logging == false - Rails::Subscriber.colorize_logging = false - config.generators.colorize_logging = false - end - - ActiveSupport::Notifications.subscribe do |*args| - Rails::Subscriber.dispatch(args) - end - end - initializer :set_clear_dependencies_hook do unless config.cache_classes ActionDispatch::Callbacks.after do diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index d6ad045294..d3a0ecb243 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -5,7 +5,7 @@ module Rails class Configuration < ::Rails::Engine::Configuration include ::Rails::Configuration::Deprecated - attr_accessor :allow_concurrency, :cache_classes, :cache_store, :colorize_logging, + attr_accessor :allow_concurrency, :cache_classes, :cache_store, :consider_all_requests_local, :dependency_loading, :filter_parameters, :log_level, :logger, :metals, :plugins, :preload_frameworks, :reload_engines, :reload_plugins, @@ -14,7 +14,6 @@ module Rails def initialize(*) super @allow_concurrency = false - @colorize_logging = true @filter_parameters = [] @dependency_loading = true @serve_static_assets = true @@ -22,6 +21,10 @@ module Rails @consider_all_requests_local = true end + def middleware + @@default_middleware_stack ||= default_middleware + end + def paths @paths ||= begin paths = super @@ -81,6 +84,16 @@ module Rails def log_level @log_level ||= Rails.env.production? ? :info : :debug end + + def colorize_logging + @colorize_logging + end + + def colorize_logging=(val) + @colorize_logging = val + Rails::LogSubscriber.colorize_logging = val + self.generators.colorize_logging = val + end end end end
\ No newline at end of file diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index b722679ec2..cb38d5a5db 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -31,7 +31,6 @@ module Rails app end - # Fires the user-supplied after_initialize block (config.after_initialize) initializer :after_initialize do config.after_initialize_blocks.each do |block| block.call(self) diff --git a/railties/lib/rails/application/metal_loader.rb b/railties/lib/rails/application/metal_loader.rb index c0f2e4f948..2a43fa7892 100644 --- a/railties/lib/rails/application/metal_loader.rb +++ b/railties/lib/rails/application/metal_loader.rb @@ -34,7 +34,7 @@ module Rails Dir.glob("#{path}/**/*.rb").sort.each do |metal_path| metal = metal_path.sub(matcher, '\1').to_sym next unless list.include?(metal) || list.include?(:all) - require_dependency metal + require_dependency metal.to_s metals << metal end end diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb index fde6211c5d..a5154f4bba 100644 --- a/railties/lib/rails/application/routes_reloader.rb +++ b/railties/lib/rails/application/routes_reloader.rb @@ -27,7 +27,7 @@ module Rails routes.clear! paths.each { |path| load(path) } - routes.finalize! + ActionController.base_hook { routes.finalize! } nil ensure diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 6972e25b29..d4558be00f 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -30,19 +30,17 @@ when 'g', 'generate' require 'rails/commands/generate' when 'c', 'console' require 'rails/commands/console' - require APP_PATH + require ENV_PATH Rails::Console.start(Rails::Application) when 's', 'server' require 'rails/commands/server' - # Initialize the server first, so environment options are set server = Rails::Server.new - require APP_PATH - + require ENV_PATH Dir.chdir(Rails::Application.root) server.start when 'db', 'dbconsole' require 'rails/commands/dbconsole' - require APP_PATH + require ENV_PATH Rails::DBConsole.start(Rails::Application) when 'application' @@ -57,7 +55,7 @@ when 'profiler' require ENV_PATH require 'rails/commands/performance/profiler' when 'plugin' - require APP_PATH + require ENV_PATH require 'rails/commands/plugin' when 'runner' require 'rails/commands/runner' diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 910b26f886..73ae9bcb16 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -4,92 +4,6 @@ require 'rails/rack' module Rails module Configuration - # Holds coonfiguration shared between Railtie, Engine and Application. - module Shared - def middleware - @@default_middleware_stack ||= ActionDispatch::MiddlewareStack.new.tap do |middleware| - middleware.use('::ActionDispatch::Static', lambda { Rails.public_path }, :if => lambda { Rails.application.config.serve_static_assets }) - middleware.use('::Rack::Lock', :if => lambda { !Rails.application.config.allow_concurrency }) - middleware.use('::Rack::Runtime') - middleware.use('::Rails::Rack::Logger') - middleware.use('::ActionDispatch::ShowExceptions', lambda { Rails.application.config.consider_all_requests_local }) - middleware.use('::ActionDispatch::Callbacks', lambda { !Rails.application.config.cache_classes }) - middleware.use('::ActionDispatch::Cookies') - middleware.use(lambda { ActionController::Base.session_store }, lambda { ActionController::Base.session_options }) - middleware.use('::ActionDispatch::Flash', :if => lambda { ActionController::Base.session_store }) - middleware.use(lambda { Rails.application.metal_loader.build_middleware(Rails.application.config.metals) }, :if => lambda { Rails.application.metal_loader.metals.any? }) - middleware.use('ActionDispatch::ParamsParser') - middleware.use('::Rack::MethodOverride') - middleware.use('::ActionDispatch::Head') - end - end - - # Holds generators configuration: - # - # config.generators do |g| - # g.orm :datamapper, :migration => true - # g.template_engine :haml - # g.test_framework :rspec - # end - # - # If you want to disable color in console, do: - # - # config.generators.colorize_logging = false - # - def generators - @@generators ||= Rails::Configuration::Generators.new - if block_given? - yield @@generators - else - @@generators - end - end - - def after_initialize_blocks - @@after_initialize_blocks ||= [] - end - - def after_initialize(&blk) - after_initialize_blocks << blk if blk - end - - def to_prepare_blocks - @@to_prepare_blocks ||= [] - end - - def to_prepare(&blk) - to_prepare_blocks << blk if blk - end - - def respond_to?(name) - super || name.to_s =~ config_key_regexp - end - - private - - def method_missing(name, *args, &blk) - if name.to_s =~ config_key_regexp - return $2 == '=' ? options[$1] = args.first : options[$1] - end - super - end - - def config_key_regexp - bits = config_keys.map { |n| Regexp.escape(n.to_s) }.join('|') - /^(#{bits})(?:=)?$/ - end - - def config_keys - (Railtie.railtie_names + Engine.engine_names).map { |n| n.to_s }.uniq - end - - def options - @@options ||= Hash.new { |h,k| h[k] = ActiveSupport::OrderedOptions.new } - end - end - - # Generators configuration which uses method missing to wrap it in a nifty DSL. - # It also allows you to set generators fallbacks and aliases. class Generators #:nodoc: attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging @@ -123,7 +37,6 @@ module Rails end end - # Holds configs deprecated in 3.0. Will be removed on 3.1. module Deprecated def frameworks(*args) raise "config.frameworks in no longer supported. See the generated " \ diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb index 7e8fd027e6..4a7701081b 100644 --- a/railties/lib/rails/console/app.rb +++ b/railties/lib/rails/console/app.rb @@ -17,8 +17,8 @@ end # create a new session. If a block is given, the new session will be yielded # to the block before being returned. def new_session - app = ActionController::Dispatcher.new - session = ActionController::Integration::Session.new(app) + app = Rails.application + session = ActionDispatch::Integration::Session.new(app) yield session if block_given? session end @@ -26,7 +26,8 @@ end # reloads the environment def reload!(print=true) puts "Reloading..." if print - ActionDispatch::Callbacks.new(lambda {}, false) + # This triggers the to_prepare callbacks + ActionDispatch::Callbacks.new(Proc.new {}, false).call({}) true end diff --git a/railties/lib/rails/console/helpers.rb b/railties/lib/rails/console/helpers.rb index 039db667c4..212fc6125a 100644 --- a/railties/lib/rails/console/helpers.rb +++ b/railties/lib/rails/console/helpers.rb @@ -2,4 +2,6 @@ def helper @helper ||= ApplicationController.helpers end -@controller = ApplicationController.new +def controller + @controller ||= ApplicationController.new +end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 226d1aaa88..7ee0f31359 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -1,5 +1,6 @@ -require 'active_support/core_ext/module/delegation' require 'rails/railtie' +require 'active_support/core_ext/module/delegation' +require 'pathname' module Rails # Rails::Engine allows you to wrap a specific Rails application and share it accross @@ -98,8 +99,9 @@ module Rails def inherited(base) unless abstract_railtie?(base) base.called_from = begin - call_stack = caller.map { |p| p.split(':').first } - File.dirname(call_stack.detect { |p| p !~ %r[railties[\w\-]*/lib/rails|rack[\w\-]*/lib/rack] }) + # Remove the line number from backtraces making sure we don't leave anything behind + call_stack = caller.map { |p| p.split(':')[0..-2].join(':') } + File.dirname(call_stack.detect { |p| p !~ %r[railties[\w\-\.]*/lib/rails|rack[\w\-\.]*/lib/rack] }) end end @@ -122,10 +124,10 @@ module Rails end end - delegate :middleware, :paths, :root, :to => :config + delegate :middleware, :paths, :metal_loader, :root, :to => :config def load_tasks - super + super config.paths.lib.tasks.to_a.sort.each { |ext| load(ext) } end @@ -159,10 +161,11 @@ module Rails end end + # DEPRECATED: Remove in 3.1 initializer :add_routing_namespaces do |app| paths.app.controllers.to_a.each do |load_path| load_path = File.expand_path(load_path) - Dir["#{load_path}/*/*_controller.rb"].collect do |path| + Dir["#{load_path}/*/**/*_controller.rb"].collect do |path| namespace = File.dirname(path).sub(/#{load_path}\/?/, '') app.routes.controller_namespaces << namespace unless namespace.empty? end @@ -172,13 +175,13 @@ module Rails # I18n load paths are a special case since the ones added # later have higher priority. initializer :add_locales do - config.i18n.engines_load_path.concat(paths.config.locales.to_a) + config.i18n.railties_load_path.concat(paths.config.locales.to_a) end initializer :add_view_paths do views = paths.app.views.to_a - ActionController::Base.view_paths.unshift(*views) if defined?(ActionController) - ActionMailer::Base.view_paths.unshift(*views) if defined?(ActionMailer) + ActionController.base_hook { prepend_view_path(views) } if defined?(ActionController) + ActionMailer.base_hook { prepend_view_path(views) } if defined?(ActionMailer) end initializer :add_metals do |app| @@ -214,4 +217,4 @@ module Rails app.config.reload_engines end end -end
\ No newline at end of file +end diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 93b882f874..b8f1f1009c 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -7,6 +7,7 @@ module Rails attr_writer :eager_load_paths, :load_once_paths, :load_paths def initialize(root=nil) + super() @root = root end @@ -17,8 +18,9 @@ module Rails paths.app.controllers "app/controllers", :eager_load => true paths.app.helpers "app/helpers", :eager_load => true paths.app.models "app/models", :eager_load => true - paths.app.metals "app/metal" - paths.app.views "app/views" + paths.app.mailers "app/mailers", :eager_load => true + paths.app.metals "app/metal", :eager_load => true + paths.app.views "app/views", :eager_load => true paths.lib "lib", :load_path => true paths.lib.tasks "lib/tasks", :glob => "**/*.rake" paths.lib.templates "lib/templates" @@ -26,6 +28,9 @@ module Rails paths.config.initializers "config/initializers", :glob => "**/*.rb" paths.config.locales "config/locales", :glob => "*.{rb,yml}" paths.config.routes "config/routes.rb" + paths.public "public" + paths.public.javascripts "public/javascripts" + paths.public.stylesheets "public/stylesheets" paths end end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index c01018aab2..3c902ce0d4 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -3,17 +3,23 @@ $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.inc require 'active_support' require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/object/metaclass' +require 'active_support/core_ext/object/singleton_class' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/hash/deep_merge' require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/string/inflections' require 'rails/generators/base' -require 'rails/generators/named_base' module Rails module Generators + autoload :Actions, 'rails/generators/actions' + autoload :ActiveModel, 'rails/generators/active_model' + autoload :Migration, 'rails/generators/migration' + autoload :NamedBase, 'rails/generators/named_base' + autoload :ResourceHelpers, 'rails/generators/resource_helpers' + autoload :TestCase, 'rails/generators/test_case' + DEFAULT_ALIASES = { :rails => { :actions => '-a', @@ -291,4 +297,4 @@ end # If the application was already defined, configure generators, # otherwise you have to configure it by hand. -Rails::Generators.configure! if Rails.respond_to?(:application) && Rails.application
\ No newline at end of file +Rails::Generators.configure! if Rails.respond_to?(:application) && Rails.application diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index d41da773c6..7dec4d446a 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -89,12 +89,12 @@ module Rails # # ==== Example # - # source "http://gems.github.com/" + # add_source "http://gems.github.com/" def add_source(source, options={}) log :source, source in_root do - prepend_file "Gemfile", "source #{source.inspect}", :verbose => false + prepend_file "Gemfile", "source #{source.inspect}\n", :verbose => false end end @@ -280,6 +280,16 @@ module Rails end end + # Reads the given file at the source root and prints it in the console. + # + # === Example + # + # readme "README" + # + def readme(path) + say File.read(find_in_source_paths(path)) + end + protected # Define log for backwards compatibility. If just one argument is sent, diff --git a/railties/lib/rails/subscriber.rb b/railties/lib/rails/log_subscriber.rb index 9bf22f27a4..42697d2e32 100644 --- a/railties/lib/rails/subscriber.rb +++ b/railties/lib/rails/log_subscriber.rb @@ -2,15 +2,15 @@ require 'active_support/core_ext/class/inheritable_attributes' require 'active_support/notifications' module Rails - # Rails::Subscriber is an object set to consume ActiveSupport::Notifications - # on initialization with the sole purpose of logging. The subscriber dispatches - # notifications to a registered object based on it's given namespace. + # Rails::LogSubscriber is an object set to consume ActiveSupport::Notifications + # on initialization with solely purpose of logging. The log subscriber dispatches + # notifications to a regirested object based on its given namespace. # - # An example would be an Active Record subscriber responsible for logging queries: + # An example would be ActiveRecord log subscriber responsible for logging queries: # # module ActiveRecord # class Railtie - # class Subscriber < Rails::Subscriber + # class LogSubscriber < Rails::LogSubscriber # def sql(event) # "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}" # end @@ -18,21 +18,21 @@ module Rails # end # end # - # Which would be registed as: + # It's finally registed as: # - # Rails::Subscriber.add :active_record, ActiveRecord::Railtie::Subscriber.new + # Rails::LogSubscriber.add :active_record, ActiveRecord::Railtie::LogSubscriber.new # - # So whenever an +active_record.sql+ notification arrives to Rails::Subscriber, + # So whenever a "active_record.sql" notification arrive to Rails::LogSubscriber, # it will properly dispatch the event (ActiveSupport::Notifications::Event) to # the sql method. # - # This avoids spanning several subscribers just for logging purposes - # (which slows down the main thread). It also provides a centralized + # This is useful because it avoids spanning several log subscribers just for logging + # purposes(which slows down the main thread). Besides of providing a centralized # facility on top of Rails.logger. - # - # Subscriber also has some helpers to deal with logging and automatically flushes - # all logs when the request finishes. - class Subscriber + # + # Log subscriber also has some helpers to deal with logging and automatically flushes + # all logs when the request finishes (via action_dispatch.callback notification). + class LogSubscriber mattr_accessor :colorize_logging self.colorize_logging = true @@ -50,30 +50,29 @@ module Rails CYAN = "\e[36m" WHITE = "\e[37m" - def self.add(namespace, subscriber) - subscribers[namespace.to_sym] = subscriber - end - - def self.subscribers - @subscribers ||= {} - end + def self.add(namespace, log_subscriber, notifier = ActiveSupport::Notifications) + log_subscribers << log_subscriber - def self.dispatch(args) - namespace, name = args[0].split(".") - subscriber = subscribers[namespace.to_sym] + log_subscriber.public_methods(false).each do |event| + notifier.subscribe("#{namespace}.#{event}") do |*args| + next if log_subscriber.logger.nil? - if subscriber.respond_to?(name) && subscriber.logger - begin - subscriber.send(name, ActiveSupport::Notifications::Event.new(*args)) - rescue Exception => e - Rails.logger.error "Could not log #{args[0].inspect} event. #{e.class}: #{e.message}" + begin + log_subscriber.send(event, ActiveSupport::Notifications::Event.new(*args)) + rescue Exception => e + Rails.logger.error "Could not log #{args[0].inspect} event. #{e.class}: #{e.message}" + end end end end - # Flush all subscribers' logger. + def self.log_subscribers + @log_subscribers ||= [] + end + + # Flush all log_subscribers' logger. def self.flush_all! - loggers = subscribers.values.map(&:logger) + loggers = log_subscribers.map(&:logger) loggers.uniq! loggers.each { |l| l.flush if l.respond_to?(:flush) } end @@ -88,6 +87,7 @@ module Rails %w(info debug warn error fatal unknown).each do |level| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{level}(*args, &block) + return unless logger logger.#{level}(*args, &block) end METHOD @@ -97,6 +97,7 @@ module Rails # option is set to true, it also adds bold to the string. This is based # on Highline implementation and it automatically appends CLEAR to the end # of the returned String. + # def color(text, color, bold=false) return text unless colorize_logging color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol) diff --git a/railties/lib/rails/subscriber/test_helper.rb b/railties/lib/rails/log_subscriber/test_helper.rb index 39b4117372..02f5079462 100644 --- a/railties/lib/rails/subscriber/test_helper.rb +++ b/railties/lib/rails/log_subscriber/test_helper.rb @@ -1,13 +1,13 @@ -require 'rails/subscriber' +require 'rails/log_subscriber' module Rails - class Subscriber - # Provides some helpers to deal with testing subscribers by setting up + class LogSubscriber + # Provides some helpers to deal with testing log subscribers by setting up # notifications. Take for instance ActiveRecord subscriber tests: # - # class SyncSubscriberTest < ActiveSupport::TestCase - # include Rails::Subscriber::TestHelper - # Rails::Subscriber.add(:active_record, ActiveRecord::Railties::Subscriber.new) + # class SyncLogSubscriberTest < ActiveSupport::TestCase + # include Rails::LogSubscriber::TestHelper + # Rails::LogSubscriber.add(:active_record, ActiveRecord::Railties::LogSubscriber.new) # # def test_basic_query_logging # Developer.all @@ -17,18 +17,18 @@ module Rails # assert_match /SELECT \* FROM "developers"/, @logger.logged(:debug).last # end # - # class SyncSubscriberTest < ActiveSupport::TestCase - # include Rails::Subscriber::SyncTestHelper - # include SubscriberTest + # class SyncLogSubscriberTest < ActiveSupport::TestCase + # include Rails::LogSubscriber::SyncTestHelper + # include LogSubscriberTest # end # - # class AsyncSubscriberTest < ActiveSupport::TestCase - # include Rails::Subscriber::AsyncTestHelper - # include SubscriberTest + # class AsyncLogSubscriberTest < ActiveSupport::TestCase + # include Rails::LogSubscriber::AsyncTestHelper + # include LogSubscriberTest # end # end # - # All you need to do is to ensure that your subscriber is added to Rails::Subscriber, + # All you need to do is to ensure that your log subscriber is added to Rails::Subscriber, # as in the second line of the code above. The test helpers is reponsible for setting # up the queue, subscriptions and turning colors in logs off. # @@ -42,8 +42,7 @@ module Rails @logger = MockLogger.new @notifier = ActiveSupport::Notifications::Notifier.new(queue) - Rails::Subscriber.colorize_logging = false - @notifier.subscribe { |*args| Rails::Subscriber.dispatch(args) } + Rails::LogSubscriber.colorize_logging = false set_logger(@logger) ActiveSupport::Notifications.notifier = @notifier @@ -80,7 +79,7 @@ module Rails @notifier.wait end - # Overwrite if you use another logger in your subscriber: + # Overwrite if you use another logger in your log subscriber: # # def logger # ActiveRecord::Base.logger = @logger diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index 2a746e0fa6..0997be1b6f 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -1,4 +1,5 @@ require 'rails/engine' +require 'active_support/core_ext/array/conversions' module Rails # Rails::Plugin is nothing more than a Rails::Engine, but since it's loaded too late @@ -40,12 +41,14 @@ module Rails def load_tasks super - extra_tasks = Dir["#{root}/{tasks,rails/tasks}/**/*.rake"] + load_deprecated_tasks + end - unless extra_tasks.empty? - ActiveSupport::Deprecation.warn "Having rake tasks in PLUGIN_PATH/tasks or " << - "PLUGIN_PATH/rails/tasks is deprecated. Use to PLUGIN_PATH/lib/tasks instead" - extra_tasks.sort.each { |ext| load(ext) } + def load_deprecated_tasks + tasks = Dir["#{root}/{tasks,rails/tasks}/**/*.rake"].sort + if tasks.any? + ActiveSupport::Deprecation.warn "Rake tasks in #{tasks.to_sentence} are deprecated. Use lib/tasks instead" + tasks.each { |ext| load(ext) } end end @@ -59,9 +62,14 @@ module Rails end initializer :load_init_rb, :before => :load_application_initializers do |app| - file = Dir["#{root}/{rails/init,init}.rb"].first - config = app.config - eval(File.read(file), binding, file) if file && File.file?(file) + files = %w(rails/init.rb init.rb).map { |path| File.expand_path path, root } + if initrb = files.find { |path| File.file? path } + if initrb == files.first + ActiveSupport::Deprecation.warn "Use toplevel init.rb; rails/init.rb is deprecated: #{initrb}" + end + config = app.config + eval(File.read(initrb), binding, initrb) + end end initializer :sanity_check_railties_collision do diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb index de21fb4f10..dd8b342f59 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -1,9 +1,9 @@ -require 'rails/subscriber' +require 'rails/log_subscriber' module Rails module Rack # Log the request started and flush all loggers after it. - class Logger < Rails::Subscriber + class Logger < Rails::LogSubscriber def initialize(app) @app = app end @@ -19,14 +19,14 @@ module Rails def before_dispatch(env) request = ActionDispatch::Request.new(env) - path = request.request_uri.inspect rescue "unknown" + path = request.fullpath.inspect rescue "unknown" info "\n\nStarted #{request.method.to_s.upcase} #{path} " << "for #{request.remote_ip} at #{Time.now.to_s(:db)}" end def after_dispatch(env) - Rails::Subscriber.flush_all! + Rails::LogSubscriber.flush_all! end end diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index b817f6b71e..96a07844e5 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -1,5 +1,6 @@ require 'rails/initializable' require 'rails/configuration' +require 'active_support/inflector' module Rails # Railtie is the core of the Rails Framework and provides several hooks to extend @@ -193,17 +194,16 @@ module Rails end def railtie_name(railtie_name = nil) - @railtie_name ||= name.demodulize.underscore @railtie_name = railtie_name if railtie_name - @railtie_name + @railtie_name ||= default_name end def railtie_names subclasses.map { |p| p.railtie_name } end - def subscriber(subscriber) - Rails::Subscriber.add(railtie_name, subscriber) + def log_subscriber(log_subscriber) + Rails::LogSubscriber.add(railtie_name, log_subscriber) end def rake_tasks(&blk) @@ -223,6 +223,10 @@ module Rails def abstract_railtie?(base) ABSTRACT_RAILTIES.include?(base.name) end + + def default_name + ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name)) + end end def rake_tasks diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index bfb43f7041..828ccec3d0 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -3,7 +3,123 @@ require 'rails/configuration' module Rails class Railtie class Configuration - include Rails::Configuration::Shared + attr_accessor :cookie_secret + + def initialize + @session_store = :cookie_store + @session_options = {} + end + + # Holds generators configuration: + # + # config.generators do |g| + # g.orm :datamapper, :migration => true + # g.template_engine :haml + # g.test_framework :rspec + # end + # + # If you want to disable color in console, do: + # + # config.generators.colorize_logging = false + # + def generators + @@generators ||= Rails::Configuration::Generators.new + if block_given? + yield @@generators + else + @@generators + end + end + + def after_initialize_blocks + @@after_initialize_blocks ||= [] + end + + def after_initialize(&blk) + after_initialize_blocks << blk if blk + end + + def to_prepare_blocks + @@to_prepare_blocks ||= [] + end + + def to_prepare(&blk) + to_prepare_blocks << blk if blk + end + + def respond_to?(name) + super || name.to_s =~ config_key_regexp + end + + def metal_loader + @metal_loader ||= Rails::Application::MetalLoader.new + end + + def session_store(*args) + if args.empty? + case @session_store + when :disabled + nil + when :active_record_store + ActiveRecord::SessionStore + when Symbol + ActionDispatch::Session.const_get(@session_store.to_s.camelize) + else + @session_store + end + else + @session_store = args.shift + @session_options = args.shift || {} + end + end + + private + + def method_missing(name, *args, &blk) + if name.to_s =~ config_key_regexp + return $2 == '=' ? options[$1] = args.first : options[$1] + end + super + end + + def session_options + return @session_options unless @session_store == :cookie_store + @session_options.merge(:secret => @cookie_secret) + end + + def config_key_regexp + bits = config_keys.map { |n| Regexp.escape(n.to_s) }.join('|') + /^(#{bits})(?:=)?$/ + end + + def config_keys + (Railtie.railtie_names + Engine.engine_names).map { |n| n.to_s }.uniq + end + + def options + @@options ||= Hash.new { |h,k| h[k] = ActiveSupport::OrderedOptions.new } + end + + def default_middleware + require 'action_dispatch' + ActionDispatch::MiddlewareStack.new.tap do |middleware| + middleware.use('::ActionDispatch::Static', lambda { Rails.public_path }, :if => lambda { serve_static_assets }) + middleware.use('::Rack::Lock', :if => lambda { !allow_concurrency }) + middleware.use('::Rack::Runtime') + middleware.use('::Rails::Rack::Logger') + middleware.use('::ActionDispatch::ShowExceptions', lambda { consider_all_requests_local }) + middleware.use("::ActionDispatch::RemoteIp", lambda { action_dispatch.ip_spoofing_check }, lambda { action_dispatch.trusted_proxies }) + middleware.use('::Rack::Sendfile', lambda { action_dispatch.x_sendfile_header }) + middleware.use('::ActionDispatch::Callbacks', lambda { !cache_classes }) + middleware.use('::ActionDispatch::Cookies') + middleware.use(lambda { session_store }, lambda { session_options }) + middleware.use('::ActionDispatch::Flash', :if => lambda { session_store }) + middleware.use(lambda { metal_loader.build_middleware(metals) }, :if => lambda { metal_loader.metals.any? }) + middleware.use('ActionDispatch::ParamsParser') + middleware.use('::Rack::MethodOverride') + middleware.use('::ActionDispatch::Head') + end + end end end end
\ No newline at end of file diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index f7cc6ff4be..abf9b33ae5 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -11,44 +11,54 @@ namespace :doc do rdoc.rdoc_files.include('lib/**/*.rb') } - desc 'Generate documentation for the Rails framework. Specify path with PATH="/path/to/rails"' - Rake::RDocTask.new("rails") { |rdoc| - path = ENV['RAILS_PATH'] || 'vendor/gems/gems' - version = '-3.0.0.beta1' unless ENV['RAILS_PATH'] - rdoc.rdoc_dir = 'doc/api' - rdoc.template = "#{ENV['template']}.rb" if ENV['template'] - rdoc.title = "Rails Framework Documentation" - rdoc.options << '--line-numbers' << '--inline-source' - rdoc.rdoc_files.include('README') - - %w(README CHANGELOG lib/action_mailer/base.rb).each do |file| - rdoc.rdoc_files.include("#{path}/actionmailer#{version}/#{file}") + desc 'Generate documentation for the Rails framework. Specify path with RAILS_PATH="/path/to/rails"' + path = ENV['RAILS_PATH'] + unless path && File.directory?(path) + task :rails do + if path + $stderr.puts "Skipping doc:rails, missing Rails directory at #{path}" + else + $stderr.puts "Skipping doc:rails, RAILS_PATH environment variable is not set" + end end + else + Rake::RDocTask.new("rails") { |rdoc| + version = "-#{Rails::VERSION::STRING}" unless ENV['RAILS_PATH'] + rdoc.rdoc_dir = 'doc/api' + rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + rdoc.title = "Rails Framework Documentation" + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + + %w(README CHANGELOG lib/action_mailer/base.rb).each do |file| + rdoc.rdoc_files.include("#{path}/actionmailer#{version}/#{file}") + end - %w(README CHANGELOG lib/action_controller/**/*.rb lib/action_view/**/*.rb).each do |file| - rdoc.rdoc_files.include("#{path}/actionpack#{version}/#{file}") - end + %w(README CHANGELOG lib/action_controller/**/*.rb lib/action_view/**/*.rb).each do |file| + rdoc.rdoc_files.include("#{path}/actionpack#{version}/#{file}") + end - %w(README CHANGELOG lib/active_model/**/*.rb).each do |file| - rdoc.rdoc_files.include("#{path}/activemodel#{version}/#{file}") - end + %w(README CHANGELOG lib/active_model/**/*.rb).each do |file| + rdoc.rdoc_files.include("#{path}/activemodel#{version}/#{file}") + end - %w(README CHANGELOG lib/active_record/**/*.rb).each do |file| - rdoc.rdoc_files.include("#{path}/activerecord#{version}/#{file}") - end + %w(README CHANGELOG lib/active_record/**/*.rb).each do |file| + rdoc.rdoc_files.include("#{path}/activerecord#{version}/#{file}") + end - %w(README CHANGELOG lib/active_resource.rb lib/active_resource/*).each do |file| - rdoc.rdoc_files.include("#{path}/activeresource#{version}/#{file}") - end + %w(README CHANGELOG lib/active_resource.rb lib/active_resource/*).each do |file| + rdoc.rdoc_files.include("#{path}/activeresource#{version}/#{file}") + end - %w(README CHANGELOG lib/active_support/**/*.rb).each do |file| - rdoc.rdoc_files.include("#{path}/activesupport#{version}/#{file}") - end + %w(README CHANGELOG lib/active_support/**/*.rb).each do |file| + rdoc.rdoc_files.include("#{path}/activesupport#{version}/#{file}") + end - %w(README CHANGELOG MIT-LICENSE lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file| - rdoc.rdoc_files.include("#{path}/railties#{version}/#{file}") - end - } + %w(README CHANGELOG MIT-LICENSE lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file| + rdoc.rdoc_files.include("#{path}/railties#{version}/#{file}") + end + } + end plugins = FileList['vendor/plugins/**'].collect { |plugin| File.basename(plugin) } diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 65d3c48f2d..dbe2ac54ed 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -32,12 +32,18 @@ namespace :rails do namespace :update do def invoke_from_app_generator(method) - require 'rails/generators' - require 'generators/rails/app/app_generator' + app_generator.invoke(method) + end - generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true }, - :destination_root => Rails.root - generator.invoke(method) + def app_generator + @app_generator ||= begin + require 'rails/generators' + require 'generators/rails/app/app_generator' + gen = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true }, + :destination_root => Rails.root + gen.send(:valid_app_const?) + gen + end end desc "Update config/boot.rb from your current rails install" diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index ac0f440896..42e01d5e51 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -1,9 +1,9 @@ desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.' task :routes => :environment do Rails::Application.reload_routes! - all_routes = ENV['CONTROLLER'] ? ActionController::Routing::Routes.routes.select { |route| route.defaults[:controller] == ENV['CONTROLLER'] } : ActionController::Routing::Routes.routes + all_routes = ENV['CONTROLLER'] ? Rails.application.routes.routes.select { |route| route.defaults[:controller] == ENV['CONTROLLER'] } : Rails.application.routes.routes routes = all_routes.collect do |route| - name = ActionController::Routing::Routes.named_routes.routes.index(route).to_s + name = Rails.application.routes.named_routes.routes.index(route).to_s reqs = route.requirements.empty? ? "" : route.requirements.inspect {:name => name, :verb => route.verb.to_s, :path => route.path, :reqs => reqs} end diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index f9aa018cab..2ed5353755 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -1,6 +1,6 @@ # Make double-sure the RAILS_ENV is set to test, # so fixtures are loaded to the right database -exit("Abort testing: Your Rails environment is not running in test mode!") unless Rails.env.test? +abort("Abort testing: Your Rails environment is not running in test mode!") unless Rails.env.test? require 'test/unit' require 'active_support/core_ext/kernel/requires' @@ -24,6 +24,16 @@ if defined?(ActiveRecord) end end +class ActionController::TestCase + setup do + @router = Rails.application.routes + end +end + +class ActionDispatch::IntegrationTest + include Rails.application.routes.url_helpers +end + begin require_library_or_gem 'ruby-debug' Debugger.start diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index d0c7cb45db..1dd8fa0ec7 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -2,8 +2,9 @@ module Rails module VERSION #:nodoc: MAJOR = 3 MINOR = 0 - TINY = "0.beta1" + TINY = 0 + BUILD = "beta1" - STRING = [MAJOR, MINOR, TINY].join('.') + STRING = [MAJOR, MINOR, TINY, BUILD].join('.') end end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 65d2dde89f..b9d2739539 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -1,9 +1,11 @@ +version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip + Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = 'railties' - s.version = '3.0.0.beta1' - s.summary = 'Controls boot-up, rake tasks and generators for the Rails framework.' - s.description = 'Controls boot-up, rake tasks and generators for the Rails framework.' + s.version = version + s.summary = 'Tools for creating, working with, and running Rails applications.' + s.description = 'Rails internals: application bootup, plugins, generators, and rake tasks.' s.required_ruby_version = '>= 1.8.7' s.author = 'David Heinemeier Hansson' @@ -21,7 +23,7 @@ Gem::Specification.new do |s| s.has_rdoc = false s.add_dependency('rake', '>= 0.8.3') - s.add_dependency('thor', '~> 0.13') - s.add_dependency('activesupport', '= 3.0.0.beta1') - s.add_dependency('actionpack', '= 3.0.0.beta1') + s.add_dependency('thor', '~> 0.13.4') + s.add_dependency('activesupport', version) + s.add_dependency('actionpack', version) end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 56f45582c8..54cd751f4e 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -171,5 +171,62 @@ module ApplicationTests get "/" assert $prepared end + + test "config.action_dispatch.x_sendfile_header defaults to X-Sendfile" do + require "rails" + require "action_controller/railtie" + + class MyApp < Rails::Application + config.cookie_secret = "3b7cd727ee24e8444053437c36cc66c4" + config.session_store :cookie_store, :key => "_myapp_session" + end + + MyApp.initialize! + + class ::OmgController < ActionController::Base + def index + send_file __FILE__ + end + end + + MyApp.routes.draw do + match "/" => "omg#index" + end + + require 'rack/test' + extend Rack::Test::Methods + + get "/" + assert_equal File.expand_path(__FILE__), last_response.headers["X-Sendfile"] + end + + test "config.action_dispatch.x_sendfile_header is sent to Rack::Sendfile" do + require "rails" + require "action_controller/railtie" + + class MyApp < Rails::Application + config.cookie_secret = "3b7cd727ee24e8444053437c36cc66c4" + config.session_store :cookie_store, :key => "_myapp_session" + config.action_dispatch.x_sendfile_header = 'X-Lighttpd-Send-File' + end + + MyApp.initialize! + + class ::OmgController < ActionController::Base + def index + send_file __FILE__ + end + end + + MyApp.routes.draw do + match "/" => "omg#index" + end + + require 'rack/test' + extend Rack::Test::Methods + + get "/" + assert_equal File.expand_path(__FILE__), last_response.headers["X-Lighttpd-Send-File"] + end end end diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index 22ab60f4a0..8ff69f0208 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -6,7 +6,9 @@ class ConsoleTest < Test::Unit::TestCase def setup build_app boot_rails + end + def load_environment # Load steps taken from rails/commands/console.rb require "#{rails_root}/config/environment" require 'rails/console/app' @@ -14,18 +16,21 @@ class ConsoleTest < Test::Unit::TestCase end def test_app_method_should_return_integration_session + load_environment console_session = app assert_not_nil console_session assert_instance_of ActionController::Integration::Session, console_session end def test_new_session_should_return_integration_session + load_environment session = new_session assert_not_nil session assert_instance_of ActionController::Integration::Session, session end def test_reload_should_fire_preparation_callbacks + load_environment a = b = c = nil # TODO: These should be defined on the initializer @@ -34,16 +39,37 @@ class ConsoleTest < Test::Unit::TestCase ActionDispatch::Callbacks.to_prepare { c = 3 } # Hide Reloading... output - silence_stream(STDOUT) do - reload! - end + silence_stream(STDOUT) { reload! } assert_equal 1, a assert_equal 2, b assert_equal 3, c end + def test_reload_should_reload_constants + app_file "app/models/user.rb", <<-MODEL + class User + attr_accessor :name + end + MODEL + + load_environment + assert User.new.respond_to?(:name) + assert !User.new.respond_to?(:age) + + app_file "app/models/user.rb", <<-MODEL + class User + attr_accessor :name, :age + end + MODEL + + assert !User.new.respond_to?(:age) + silence_stream(STDOUT) { reload! } + assert User.new.respond_to?(:age) + end + def test_access_to_helpers + load_environment assert_not_nil helper assert_instance_of ActionView::Base, helper assert_equal 'Once upon a time in a world...', diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 1e7b9c9997..8e57022e5b 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -32,6 +32,17 @@ module ApplicationTests ActionMailer::Base.view_paths.include?(File.expand_path("app/views", app_path)) end + test "allows me to configure default url options for ActionMailer" do + app_file "config/environments/development.rb", <<-RUBY + Rails::Application.configure do + config.action_mailer.default_url_options = { :host => "test.rails" } + end + RUBY + + require "#{app_path}/config/environment" + assert "test.rails", ActionMailer::Base.default_url_options[:host] + end + # AS test "if there's no config.active_support.bare, all of ActiveSupport is required" do use_frameworks [] @@ -54,7 +65,7 @@ module ApplicationTests test "database middleware doesn't initialize when session store is not active_record" do add_to_config <<-RUBY config.root = "#{app_path}" - config.action_controller.session_store = :cookie_store + config.session_store :cookie_store, { :key => "blahblahblah" } RUBY require "#{app_path}/config/environment" @@ -62,7 +73,7 @@ module ApplicationTests end test "database middleware initializes when session store is active record" do - add_to_config "config.action_controller.session_store = :active_record_store" + add_to_config "config.session_store :active_record_store" require "#{app_path}/config/environment" @@ -80,7 +91,7 @@ module ApplicationTests test "database middleware doesn't initialize when activerecord is not in frameworks" do use_frameworks [] require "#{app_path}/config/environment" - assert_nil defined?(ActiveRecord) + assert_nil defined?(ActiveRecord::Base) end end end diff --git a/railties/test/application/initializers/initializers_test.rb b/railties/test/application/initializers/initializers_test.rb index 0c3de7ce33..2e6a707175 100644 --- a/railties/test/application/initializers/initializers_test.rb +++ b/railties/test/application/initializers/initializers_test.rb @@ -51,5 +51,31 @@ module ApplicationTests assert $activerecord_configurations assert $activerecord_configurations['development'] end + + test "after_initialize happens after to_prepare in development" do + $order = [] + add_to_config <<-RUBY + config.cache_classes = false + config.after_initialize { $order << :after_initialize } + config.to_prepare { $order << :to_prepare } + RUBY + + require "#{app_path}/config/environment" + assert [:to_prepare, :after_initialize], $order + end + + test "after_initialize happens after to_prepare in production" do + $order = [] + add_to_config <<-RUBY + config.cache_classes = true + config.after_initialize { $order << :after_initialize } + config.to_prepare { $order << :to_prepare } + RUBY + + require "#{app_path}/config/application" + Rails.env.replace "production" + require "#{app_path}/config/environment" + assert [:to_prepare, :after_initialize], $order + end end end diff --git a/railties/test/application/initializers/notifications_test.rb b/railties/test/application/initializers/notifications_test.rb index 061bb34c19..b99cf5bb4f 100644 --- a/railties/test/application/initializers/notifications_test.rb +++ b/railties/test/application/initializers/notifications_test.rb @@ -28,7 +28,7 @@ module ApplicationTests ActiveSupport::Notifications.notifier.wait end - test "rails subscribers are added" do + test "rails log_subscribers are added" do add_to_config <<-RUBY config.colorize_logging = false RUBY diff --git a/railties/test/application/metal_test.rb b/railties/test/application/metal_test.rb index 225bede117..1ec62282c8 100644 --- a/railties/test/application/metal_test.rb +++ b/railties/test/application/metal_test.rb @@ -28,7 +28,7 @@ module ApplicationTests end RUBY - get "/" + get "/not/slash" assert_equal 200, last_response.status assert_equal "FooMetal", last_response.body end @@ -50,7 +50,7 @@ module ApplicationTests end RUBY - get "/" + get "/not/slash" assert_equal 200, last_response.status assert_equal "Metal B", last_response.body end diff --git a/railties/test/application/middleware_stack_defaults_test.rb b/railties/test/application/middleware_stack_defaults_test.rb new file mode 100644 index 0000000000..284f7e2e5b --- /dev/null +++ b/railties/test/application/middleware_stack_defaults_test.rb @@ -0,0 +1,54 @@ +require 'isolation/abstract_unit' + +class MiddlewareStackDefaultsTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation + + def setup + boot_rails + require "rails" + require "action_controller/railtie" + + Object.const_set(:MyApplication, Class.new(Rails::Application)) + MyApplication.class_eval do + config.cookie_secret = "3b7cd727ee24e8444053437c36cc66c4" + config.session_store :cookie_store, :key => "_myapp_session" + end + end + + def remote_ip(env = {}) + remote_ip = nil + env = Rack::MockRequest.env_for("/").merge(env).merge('action_dispatch.show_exceptions' => false) + + endpoint = Proc.new do |e| + remote_ip = ActionDispatch::Request.new(e).remote_ip + [200, {}, ["Hello"]] + end + + out = MyApplication.middleware.build(endpoint).call(env) + remote_ip + end + + test "remote_ip works" do + assert_equal "1.1.1.1", remote_ip("REMOTE_ADDR" => "1.1.1.1") + end + + test "checks IP spoofing by default" do + assert_raises(ActionDispatch::RemoteIp::IpSpoofAttackError) do + remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2") + end + end + + test "can disable IP spoofing check" do + MyApplication.config.action_dispatch.ip_spoofing_check = false + + assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_equal "1.1.1.2", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2") + end + end + + test "the user can set trusted proxies" do + MyApplication.config.action_dispatch.trusted_proxies = /^4\.2\.42\.42$/ + + assert_equal "1.1.1.1", remote_ip("REMOTE_ADDR" => "4.2.42.42,1.1.1.1") + end +end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index ce9cd510a3..9a359d20b1 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -19,6 +19,8 @@ module ApplicationTests "Rack::Runtime", "Rails::Rack::Logger", "ActionDispatch::ShowExceptions", + "ActionDispatch::RemoteIp", + "Rack::Sendfile", "ActionDispatch::Callbacks", "ActionDispatch::Cookies", "ActionDispatch::Session::CookieStore", diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index ac0aa27c64..511b8b629a 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -11,8 +11,8 @@ module ApplicationTests app_file "config/environments/development.rb", "" add_to_config <<-RUBY config.root = "#{app_path}" - config.after_initialize do - ActionController::Base.session_store = nil + config.after_initialize do |app| + app.config.session_store nil end RUBY use_frameworks [:action_controller, :action_view, :action_mailer, :active_record] @@ -56,9 +56,10 @@ module ApplicationTests end test "booting up Rails yields a list of paths that are eager" do - assert @paths.app.eager_load? - assert @paths.app.controllers.eager_load? - assert @paths.app.helpers.eager_load? + eager_load = @paths.eager_load + assert eager_load.include?(root("app/controllers")) + assert eager_load.include?(root("app/helpers")) + assert eager_load.include?(root("app/models")) end test "environments has a glob equal to the current environment" do diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index b93e349a46..dcac1a87d9 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -120,7 +120,8 @@ module ApplicationTests app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do |map| - match ':controller(/:action)' + match 'admin/foo', :to => 'admin/foo#index' + match 'foo', :to => 'foo#index' end RUBY diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb new file mode 100644 index 0000000000..04f5454e09 --- /dev/null +++ b/railties/test/application/url_generation_test.rb @@ -0,0 +1,43 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class UrlGenerationTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation + + def app + Rails.application + end + + test "it works" do + boot_rails + require "rails" + require "action_controller/railtie" + + class MyApp < Rails::Application + config.cookie_secret = "3b7cd727ee24e8444053437c36cc66c4" + config.session_store :cookie_store, :key => "_myapp_session" + end + + MyApp.initialize! + + class ::ApplicationController < ActionController::Base + end + + class ::OmgController < ::ApplicationController + def index + render :text => omg_path + end + end + + MyApp.routes.draw do + match "/" => "omg#index", :as => :omg + end + + require 'rack/test' + extend Rack::Test::Methods + + get "/" + assert_equal "/", last_response.body + end + end +end diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 5929db6318..3585e6e7c0 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -210,6 +210,12 @@ class ActionsTest < Rails::Generators::TestCase assert_file 'config/routes.rb', /#{Regexp.escape(route_command)}/ end + def test_readme + run_generator + Rails::Generators::AppGenerator.expects(:source_root).returns(destination_root) + assert_match(/Welcome to Rails/, action(:readme, "README")) + end + protected def action(*args, &block) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 0a746b200f..412034029e 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -50,6 +50,11 @@ class AppGeneratorTest < Rails::Generators::TestCase ).each{ |path| assert_file path } end + def test_name_collision_raises_an_error + content = capture(:stderr){ run_generator [File.join(destination_root, "generate")] } + assert_equal "Invalid application name generate. Please give a name which does not match one of the reserved rails words.\n", content + end + def test_invalid_database_option_raises_an_error content = capture(:stderr){ run_generator([destination_root, "-d", "unknown"]) } assert_match /Invalid value for \-\-database option/, content @@ -102,6 +107,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_prototype_and_test_unit_are_skipped_if_required run_generator [destination_root, "--skip-prototype", "--skip-testunit"] assert_no_file "public/javascripts/prototype.js" + assert_file "public/javascripts" assert_no_file "test" end diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 33cc27bd84..dd17f8f756 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -145,16 +145,17 @@ class GeneratorsTest < Rails::Generators::TestCase end def test_developer_options_are_overwriten_by_user_options - Rails::Generators.options[:new_generator] = { :generate => false } + Rails::Generators.options[:with_options] = { :generate => false } - klass = Class.new(Rails::Generators::Base) do - def self.name() 'NewGenerator' end - class_option :generate, :default => true - end + self.class.class_eval <<-end_eval + class WithOptionsGenerator < Rails::Generators::Base + class_option :generate, :default => true + end + end_eval - assert_equal false, klass.class_options[:generate].default + assert_equal false, WithOptionsGenerator.class_options[:generate].default ensure - Rails::Generators.subclasses.delete(klass) + Rails::Generators.subclasses.delete(WithOptionsGenerator) end def test_load_generators_from_railties diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 364dbd8e55..8f2f15b49e 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -100,7 +100,7 @@ module TestHelpers end end - add_to_config 'config.action_controller.session = { :key => "_myapp_session", :secret => "bac838a849c1d5c4de2e6a50af826079" }' + add_to_config 'config.cookie_secret = "3b7cd727ee24e8444053437c36cc66c4"; config.session_store :cookie_store, :key => "_myapp_session"' end class Bukkit diff --git a/railties/test/log_subscriber_test.rb b/railties/test/log_subscriber_test.rb new file mode 100644 index 0000000000..49288cfaa8 --- /dev/null +++ b/railties/test/log_subscriber_test.rb @@ -0,0 +1,123 @@ +require 'abstract_unit' +require 'rails/log_subscriber/test_helper' + +class MyLogSubscriber < Rails::LogSubscriber + attr_reader :event + + def some_event(event) + @event = event + info event.name + end + + def foo(event) + debug "debug" + info "info" + warn "warn" + end + + def bar(event) + info "#{color("cool", :red)}, #{color("isn't it?", :blue, true)}" + end + + def puke(event) + raise "puke" + end +end + +class SyncLogSubscriberTest < ActiveSupport::TestCase + include Rails::LogSubscriber::TestHelper + + def setup + super + @log_subscriber = MyLogSubscriber.new + end + + def teardown + super + Rails::LogSubscriber.log_subscribers.clear + end + + def instrument(*args, &block) + ActiveSupport::Notifications.instrument(*args, &block) + end + + def test_proxies_method_to_rails_logger + @log_subscriber.foo(nil) + assert_equal %w(debug), @logger.logged(:debug) + assert_equal %w(info), @logger.logged(:info) + assert_equal %w(warn), @logger.logged(:warn) + end + + def test_set_color_for_messages + Rails::LogSubscriber.colorize_logging = true + @log_subscriber.bar(nil) + assert_equal "\e[31mcool\e[0m, \e[1m\e[34misn't it?\e[0m", @logger.logged(:info).last + end + + def test_does_not_set_color_if_colorize_logging_is_set_to_false + @log_subscriber.bar(nil) + assert_equal "cool, isn't it?", @logger.logged(:info).last + end + + def test_event_is_sent_to_the_registered_class + Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber + instrument "my_log_subscriber.some_event" + wait + assert_equal %w(my_log_subscriber.some_event), @logger.logged(:info) + end + + def test_event_is_an_active_support_notifications_event + Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber + instrument "my_log_subscriber.some_event" + wait + assert_kind_of ActiveSupport::Notifications::Event, @log_subscriber.event + end + + def test_does_not_send_the_event_if_it_doesnt_match_the_class + Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber + instrument "my_log_subscriber.unknown_event" + wait + # If we get here, it means that NoMethodError was not raised. + end + + def test_does_not_send_the_event_if_logger_is_nil + Rails.logger = nil + @log_subscriber.expects(:some_event).never + Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber + instrument "my_log_subscriber.some_event" + wait + end + + def test_does_not_fail_with_non_namespaced_events + Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber + instrument "whatever" + wait + end + + def test_flushes_loggers + Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber + Rails::LogSubscriber.flush_all! + assert_equal 1, @logger.flush_count + end + + def test_flushes_the_same_logger_just_once + Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber + Rails::LogSubscriber.add :another, @log_subscriber + Rails::LogSubscriber.flush_all! + wait + assert_equal 1, @logger.flush_count + end + + def test_logging_does_not_die_on_failures + Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber + instrument "my_log_subscriber.puke" + instrument "my_log_subscriber.some_event" + wait + + assert_equal 1, @logger.logged(:info).size + assert_equal 'my_log_subscriber.some_event', @logger.logged(:info).last + + assert_equal 1, @logger.logged(:error).size + assert_equal 'Could not log "my_log_subscriber.puke" event. RuntimeError: puke', @logger.logged(:error).last + end +end
\ No newline at end of file diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index 4163fb2c6d..d904d7b461 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -14,10 +14,13 @@ class InfoControllerTest < ActionController::TestCase tests Rails::InfoController def setup - ActionController::Routing::Routes.draw do |map| - match ':controller/:action' + Rails.application.routes.draw do |map| + match '/rails/info/properties' => "rails/info#properties" end @controller.stubs(:consider_all_requests_local? => false, :local_request? => true) + @router = Rails.application.routes + + Rails::InfoController.send(:include, @router.url_helpers) end test "info controller does not allow remote requests" do diff --git a/railties/test/railties/plugin_test.rb b/railties/test/railties/plugin_test.rb index 09b859dcdd..0f5f29468c 100644 --- a/railties/test/railties/plugin_test.rb +++ b/railties/test/railties/plugin_test.rb @@ -94,6 +94,15 @@ module RailtiesTest assert rescued, "Expected boot rails to fail" end + test "loads deprecated rails/init.rb" do + @plugin.write "rails/init.rb", <<-RUBY + $loaded = true + RUBY + + boot_rails + assert $loaded + end + test "deprecated tasks are also loaded" do $executed = false @plugin.write "tasks/foo.rake", <<-RUBY diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb index b723e08281..9fefb285b4 100644 --- a/railties/test/railties/railtie_test.rb +++ b/railties/test/railties/railtie_test.rb @@ -51,12 +51,12 @@ module RailtiesTest assert_equal "bar", Bar.config.foo.bar end - test "railtie can add subscribers" do + test "railtie can add log subscribers" do begin - class Foo < Rails::Railtie ; subscriber(Rails::Subscriber.new) ; end - assert_kind_of Rails::Subscriber, Rails::Subscriber.subscribers[:foo] + class Foo < Rails::Railtie ; log_subscriber(Rails::LogSubscriber.new) ; end + assert_kind_of Rails::LogSubscriber, Rails::LogSubscriber.log_subscribers[0] ensure - Rails::Subscriber.subscribers.clear + Rails::LogSubscriber.log_subscribers.clear end end diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index d51a0d153c..83d25be5db 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -133,7 +133,7 @@ module RailtiesTest end end - ActionController::Routing::Routes.draw do + Rails.application.routes.draw do match "/sprokkit", :to => Sprokkit end RUBY @@ -170,7 +170,7 @@ module RailtiesTest RUBY @plugin.write "config/routes.rb", <<-RUBY - ActionController::Routing::Routes.draw do |map| + Rails.application.routes.draw do |map| match 'foo', :to => 'bar#index' match 'bar', :to => 'bar#index' end @@ -254,22 +254,24 @@ YAML require 'rack/test' extend Rack::Test::Methods - get "/" + get "/not/slash" assert_equal 200, last_response.status assert_equal "FooMetal", last_response.body end def test_namespaced_controllers_with_namespaced_routes @plugin.write "config/routes.rb", <<-RUBY - ActionController::Routing::Routes.draw do + Rails.application.routes.draw do namespace :admin do - match "index", :to => "admin/foo#index" + namespace :foo do + match "bar", :to => "admin/foo/bar#index" + end end end RUBY - @plugin.write "app/controllers/admin/foo_controller.rb", <<-RUBY - class Admin::FooController < ApplicationController + @plugin.write "app/controllers/admin/foo/bar_controller.rb", <<-RUBY + class Admin::Foo::BarController < ApplicationController def index render :text => "Rendered from namespace" end @@ -280,7 +282,7 @@ YAML require 'rack/test' extend Rack::Test::Methods - get "/admin/index" + get "/admin/foo/bar" assert_equal 200, last_response.status assert_equal "Rendered from namespace", last_response.body end @@ -312,4 +314,4 @@ YAML boot_rails end end -end
\ No newline at end of file +end diff --git a/railties/test/subscriber_test.rb b/railties/test/subscriber_test.rb deleted file mode 100644 index f6c895093f..0000000000 --- a/railties/test/subscriber_test.rb +++ /dev/null @@ -1,119 +0,0 @@ -require 'abstract_unit' -require 'rails/subscriber/test_helper' - -class MySubscriber < Rails::Subscriber - attr_reader :event - - def some_event(event) - @event = event - info event.name - end - - def foo(event) - debug "debug" - info "info" - warn "warn" - end - - def bar(event) - info "#{color("cool", :red)}, #{color("isn't it?", :blue, true)}" - end - - def puke(event) - raise "puke" - end -end - -class SyncSubscriberTest < ActiveSupport::TestCase - include Rails::Subscriber::TestHelper - - def setup - super - @subscriber = MySubscriber.new - Rails::Subscriber.instance_variable_set(:@log_tailer, nil) - end - - def teardown - super - Rails::Subscriber.subscribers.clear - Rails::Subscriber.instance_variable_set(:@log_tailer, nil) - end - - def instrument(*args, &block) - ActiveSupport::Notifications.instrument(*args, &block) - end - - def test_proxies_method_to_rails_logger - @subscriber.foo(nil) - assert_equal %w(debug), @logger.logged(:debug) - assert_equal %w(info), @logger.logged(:info) - assert_equal %w(warn), @logger.logged(:warn) - end - - def test_set_color_for_messages - Rails::Subscriber.colorize_logging = true - @subscriber.bar(nil) - assert_equal "\e[31mcool\e[0m, \e[1m\e[34misn't it?\e[0m", @logger.logged(:info).last - end - - def test_does_not_set_color_if_colorize_logging_is_set_to_false - @subscriber.bar(nil) - assert_equal "cool, isn't it?", @logger.logged(:info).last - end - - def test_event_is_sent_to_the_registered_class - Rails::Subscriber.add :my_subscriber, @subscriber - instrument "my_subscriber.some_event" - wait - assert_equal %w(my_subscriber.some_event), @logger.logged(:info) - end - - def test_event_is_an_active_support_notifications_event - Rails::Subscriber.add :my_subscriber, @subscriber - instrument "my_subscriber.some_event" - wait - assert_kind_of ActiveSupport::Notifications::Event, @subscriber.event - end - - def test_does_not_send_the_event_if_it_doesnt_match_the_class - Rails::Subscriber.add :my_subscriber, @subscriber - instrument "my_subscriber.unknown_event" - wait - # If we get here, it means that NoMethodError was raised. - end - - def test_does_not_send_the_event_if_logger_is_nil - Rails.logger = nil - @subscriber.expects(:some_event).never - Rails::Subscriber.add :my_subscriber, @subscriber - instrument "my_subscriber.some_event" - wait - end - - def test_flushes_loggers - Rails::Subscriber.add :my_subscriber, @subscriber - Rails::Subscriber.flush_all! - assert_equal 1, @logger.flush_count - end - - def test_flushes_the_same_logger_just_once - Rails::Subscriber.add :my_subscriber, @subscriber - Rails::Subscriber.add :another, @subscriber - Rails::Subscriber.flush_all! - wait - assert_equal 1, @logger.flush_count - end - - def test_logging_does_not_die_on_failures - Rails::Subscriber.add :my_subscriber, @subscriber - instrument "my_subscriber.puke" - instrument "my_subscriber.some_event" - wait - - assert_equal 1, @logger.logged(:info).size - assert_equal 'my_subscriber.some_event', @logger.logged(:info).last - - assert_equal 1, @logger.logged(:error).size - assert_equal 'Could not log "my_subscriber.puke" event. RuntimeError: puke', @logger.logged(:error).last - end -end
\ No newline at end of file diff --git a/release.rb b/release.rb deleted file mode 100755 index e8e6c01c35..0000000000 --- a/release.rb +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env ruby - -VERSION = ARGV.first -PACKAGES = %w(activesupport activerecord actionpack actionmailer activeresource) - -# Copy source -`mkdir release` -(PACKAGES + %w(railties)).each do |p| - `cp -R #{p} release/#{p}` -end - -# Create Rails packages -`cd release/railties && rake template=jamis package` - -# Upload documentation -`cd release/rails/doc/api && scp -r * davidhh@wrath.rubyonrails.com:public_html/api` - -# Upload packages -(PACKAGES + %w(railties)).each do |p| - `cd release/#{p} && echo "Releasing #{p}" && rake release` -end - -# Upload rails tgz/zip -`rubyforge add_release rails rails 'REL #{VERSION}' release/rails-#{VERSION}.tgz` -`rubyforge add_release rails rails 'REL #{VERSION}' release/rails-#{VERSION}.zip`
\ No newline at end of file diff --git a/version.rb b/version.rb new file mode 100644 index 0000000000..1dd8fa0ec7 --- /dev/null +++ b/version.rb @@ -0,0 +1,10 @@ +module Rails + module VERSION #:nodoc: + MAJOR = 3 + MINOR = 0 + TINY = 0 + BUILD = "beta1" + + STRING = [MAJOR, MINOR, TINY, BUILD].join('.') + end +end |